Compare commits

...

17 Commits
6.0.7 ... 7.0.3

Author SHA1 Message Date
09754849be v7.0.3 2016-05-16 16:09:32 +02:00
25ad48a97a upgrade wakatime-cli to v6.0.2 2016-05-16 16:08:58 +02:00
3b2520afa9 use common resources folder location 2016-05-12 12:07:24 +02:00
77c2041ad3 rename activity callback 2016-04-30 00:01:25 +02:00
8af3b53937 v7.0.2 2016-04-29 16:57:54 +02:00
5ef2e6954e prevent implicit decoding from string format 2016-04-29 16:56:48 +02:00
ca94272de5 v7.0.1 2016-04-29 01:13:50 +02:00
f19a448d95 upgrade wakatime-cli to v6.0.1 2016-04-29 01:13:01 +02:00
e178765412 update menu screenshot 2016-04-29 00:51:38 +02:00
6a7de84b9c v7.0.0 2016-04-29 00:21:51 +02:00
48810f2977 queue heartbeats and send to wakatime-cli after 4 seconds 2016-04-29 00:18:38 +02:00
260eedb31d upgrade wakatime-cli to v6.0.0 2016-04-29 00:04:46 +02:00
02e2bfcad2 nest settings menu under Package Settings 2016-04-28 19:37:24 +02:00
f14ece63f3 v6.0.8 2016-04-19 00:27:52 +02:00
cb7f786ec8 upgrade wakatime-cli to v5.0.0 2016-04-19 00:26:28 +02:00
ab8711d0b1 update screenshot 2016-03-21 01:46:22 +01:00
2354be358c update product screenshot 2016-03-21 01:23:44 +01:00
1983 changed files with 4714 additions and 5171 deletions

View File

@ -3,6 +3,52 @@ History
-------
7.0.3 (2016-05-16)
++++++++++++++++++
- Upgrade wakatime-cli to v6.0.2.
- Prevent popup on Mac when xcode-tools is not installed.
7.0.2 (2016-04-29)
++++++++++++++++++
- Prevent implicit unicode decoding from string format when logging output
from Python version check.
7.0.1 (2016-04-28)
++++++++++++++++++
- Upgrade wakatime-cli to v6.0.1.
- Fix bug which prevented plugin from being sent with extra heartbeats.
7.0.0 (2016-04-28)
++++++++++++++++++
- Queue heartbeats and send to wakatime-cli after 4 seconds.
- Nest settings menu under Package Settings.
- Upgrade wakatime-cli to v6.0.0.
- Increase default network timeout to 60 seconds when sending heartbeats to
the api.
- New --extra-heartbeats command line argument for sending a JSON array of
extra queued heartbeats to STDIN.
- Change --entitytype command line argument to --entity-type.
- No longer allowing --entity-type of url.
- Support passing an alternate language to cli to be used when a language can
not be guessed from the code file.
6.0.8 (2016-04-18)
++++++++++++++++++
- Upgrade wakatime-cli to v5.0.0.
- Support regex patterns in projectmap config section for renaming projects.
- Upgrade pytz to v2016.3.
- Upgrade tzlocal to v1.2.2.
6.0.7 (2016-03-11)
++++++++++++++++++

View File

@ -6,24 +6,37 @@
"children":
[
{
"caption": "WakaTime",
"mnemonic": "W",
"id": "wakatime-settings",
"caption": "Package Settings",
"mnemonic": "P",
"id": "package-settings",
"children":
[
{
"command": "open_file", "args":
{
"file": "${packages}/WakaTime/WakaTime.sublime-settings"
},
"caption": "Settings Default"
},
{
"command": "open_file", "args":
{
"file": "${packages}/User/WakaTime.sublime-settings"
},
"caption": "Settings User"
"caption": "WakaTime",
"mnemonic": "W",
"id": "wakatime-settings",
"children":
[
{
"command": "open_file", "args":
{
"file": "${packages}/WakaTime/WakaTime.sublime-settings"
},
"caption": "Settings Default"
},
{
"command": "open_file", "args":
{
"file": "${packages}/User/WakaTime.sublime-settings"
},
"caption": "Settings User"
},
{
"command": "wakatime_dashboard",
"args": {},
"caption": "WakaTime Dashboard"
}
]
}
]
}

View File

@ -27,7 +27,7 @@ Installation
Screen Shots
------------
![Project Overview](https://wakatime.com/static/img/ScreenShots/ScreenShot-2014-10-29.png)
![Project Overview](https://wakatime.com/static/img/ScreenShots/Screen-Shot-2016-03-21.png)
Unresponsive Plugin Warning
@ -47,7 +47,7 @@ Troubleshooting
First, turn on debug mode in your `WakaTime.sublime-settings` file.
![sublime user settings](https://wakatime.com/static/img/ScreenShots/sublime-wakatime-settings-menu.png)
![sublime user settings](https://wakatime.com/static/img/ScreenShots/sublime-wakatime-settings-menu.png?v=2)
Add the line: `"debug": true`

View File

@ -7,12 +7,13 @@ Website: https://wakatime.com/
==========================================================="""
__version__ = '6.0.7'
__version__ = '7.0.3'
import sublime
import sublime_plugin
import json
import os
import platform
import re
@ -31,6 +32,10 @@ except ImportError:
import winreg # py3
except ImportError:
winreg = None
try:
import Queue as queue # py2
except ImportError:
import queue # py3
is_py2 = (sys.version_info[0] == 2)
@ -41,7 +46,6 @@ if is_py2:
if text is None:
return None
try:
text = str(text)
return text.decode('utf-8')
except:
try:
@ -89,8 +93,8 @@ LAST_HEARTBEAT = {
'file': None,
'is_write': False,
}
LOCK = threading.RLock()
PYTHON_LOCATION = None
HEARTBEATS = queue.Queue()
# Log Levels
@ -108,6 +112,20 @@ except ImportError:
pass
def set_timeout(callback, seconds):
"""Runs the callback after the given seconds delay.
If this is Sublime Text 3, runs the callback on an alternate thread. If this
is Sublime Text 2, runs the callback in the main thread.
"""
milliseconds = int(seconds * 1000)
try:
sublime.set_timeout_async(callback, milliseconds)
except AttributeError:
sublime.set_timeout(callback, milliseconds)
def log(lvl, message, *args, **kwargs):
try:
if lvl == DEBUG and not SETTINGS.get('debug'):
@ -119,10 +137,34 @@ def log(lvl, message, *args, **kwargs):
msg = message.format(**kwargs)
print('[WakaTime] [{lvl}] {msg}'.format(lvl=lvl, msg=msg))
except RuntimeError:
sublime.set_timeout(lambda: log(lvl, message, *args, **kwargs), 0)
set_timeout(lambda: log(lvl, message, *args, **kwargs), 0)
def createConfigFile():
def resources_folder():
if platform.system() == 'Windows':
return os.path.join(os.getenv('APPDATA'), 'WakaTime')
else:
return os.path.join(os.path.expanduser('~'), '.wakatime')
def update_status_bar(status):
"""Updates the status bar."""
try:
if SETTINGS.get('status_bar_message'):
msg = datetime.now().strftime(SETTINGS.get('status_bar_message_fmt'))
if '{status}' in msg:
msg = msg.format(status=status)
active_window = sublime.active_window()
if active_window:
for view in active_window.views():
view.set_status('wakatime', msg)
except RuntimeError:
set_timeout(lambda: update_status_bar(status), 0)
def create_config_file():
"""Creates the .wakatime.cfg INI file in $HOME directory, if it does
not already exist.
"""
@ -143,7 +185,7 @@ def createConfigFile():
def prompt_api_key():
global SETTINGS
createConfigFile()
create_config_file()
default_key = ''
try:
@ -176,7 +218,7 @@ def python_binary():
# look for python in PATH and common install locations
paths = [
os.path.join(os.path.expanduser('~'), '.wakatime', 'python'),
os.path.join(resources_folder(), 'python'),
None,
'/',
'/usr/local/bin/',
@ -282,7 +324,7 @@ def find_python_in_folder(folder, headless=True):
if not retcode and pattern.search(output):
return path
except:
log(DEBUG, u('Python Version Output: {0}').format(u(sys.exc_info()[1])))
log(DEBUG, u(sys.exc_info()[1]))
if headless:
path = find_python_in_folder(folder, headless=False)
@ -304,10 +346,10 @@ def obfuscate_apikey(command_list):
return cmd
def enough_time_passed(now, last_heartbeat, is_write):
if now - last_heartbeat['time'] > HEARTBEAT_FREQUENCY * 60:
def enough_time_passed(now, is_write):
if now - LAST_HEARTBEAT['time'] > HEARTBEAT_FREQUENCY * 60:
return True
if is_write and now - last_heartbeat['time'] > 2:
if is_write and now - LAST_HEARTBEAT['time'] > 2:
return True
return False
@ -351,106 +393,176 @@ def is_view_active(view):
return False
def handle_heartbeat(view, is_write=False):
def handle_activity(view, is_write=False):
window = view.window()
if window is not None:
target_file = view.file_name()
project = window.project_data() if hasattr(window, 'project_data') else None
folders = window.folders()
thread = SendHeartbeatThread(target_file, view, is_write=is_write, project=project, folders=folders)
thread.start()
entity = view.file_name()
if entity:
timestamp = time.time()
last_file = LAST_HEARTBEAT['file']
if entity != last_file or enough_time_passed(timestamp, is_write):
project = window.project_data() if hasattr(window, 'project_data') else None
folders = window.folders()
append_heartbeat(entity, timestamp, is_write, view, project, folders)
class SendHeartbeatThread(threading.Thread):
def append_heartbeat(entity, timestamp, is_write, view, project, folders):
global LAST_HEARTBEAT
# add this heartbeat to queue
heartbeat = {
'entity': entity,
'timestamp': timestamp,
'is_write': is_write,
'cursorpos': view.sel()[0].begin() if view.sel() else None,
'project': project,
'folders': folders,
}
HEARTBEATS.put_nowait(heartbeat)
# make this heartbeat the LAST_HEARTBEAT
LAST_HEARTBEAT = {
'file': entity,
'time': timestamp,
'is_write': is_write,
}
# process the queue of heartbeats in the future
seconds = 4
set_timeout(process_queue, seconds)
def process_queue():
try:
heartbeat = HEARTBEATS.get_nowait()
except queue.Empty:
return
has_extra_heartbeats = False
extra_heartbeats = []
try:
while True:
extra_heartbeats.append(HEARTBEATS.get_nowait())
has_extra_heartbeats = True
except queue.Empty:
pass
thread = SendHeartbeatsThread(heartbeat)
if has_extra_heartbeats:
thread.add_extra_heartbeats(extra_heartbeats)
thread.start()
class SendHeartbeatsThread(threading.Thread):
"""Non-blocking thread for sending heartbeats to api.
"""
def __init__(self, target_file, view, is_write=False, project=None, folders=None, force=False):
def __init__(self, heartbeat):
threading.Thread.__init__(self)
self.lock = LOCK
self.target_file = target_file
self.is_write = is_write
self.project = project
self.folders = folders
self.force = force
self.debug = SETTINGS.get('debug')
self.api_key = SETTINGS.get('api_key', '')
self.ignore = SETTINGS.get('ignore', [])
self.last_heartbeat = LAST_HEARTBEAT.copy()
self.cursorpos = view.sel()[0].begin() if view.sel() else None
self.view = view
self.heartbeat = heartbeat
self.has_extra_heartbeats = False
def add_extra_heartbeats(self, extra_heartbeats):
self.has_extra_heartbeats = True
self.extra_heartbeats = extra_heartbeats
def run(self):
with self.lock:
if self.target_file:
self.timestamp = time.time()
if self.force or self.target_file != self.last_heartbeat['file'] or enough_time_passed(self.timestamp, self.last_heartbeat, self.is_write):
self.send_heartbeat()
"""Running in background thread."""
def send_heartbeat(self):
if not self.api_key:
log(ERROR, 'missing api key.')
return
ua = 'sublime/%d sublime-wakatime/%s' % (ST_VERSION, __version__)
cmd = [
API_CLIENT,
'--file', self.target_file,
'--time', str('%f' % self.timestamp),
'--plugin', ua,
'--key', str(bytes.decode(self.api_key.encode('utf8'))),
]
if self.is_write:
cmd.append('--write')
if self.project and self.project.get('name'):
cmd.extend(['--alternate-project', self.project.get('name')])
elif self.folders:
project_name = find_project_from_folders(self.folders, self.target_file)
self.send_heartbeats()
def build_heartbeat(self, entity=None, timestamp=None, is_write=None,
cursorpos=None, project=None, folders=None):
"""Returns a dict for passing to wakatime-cli as arguments."""
heartbeat = {
'entity': entity,
'timestamp': timestamp,
'is_write': is_write,
}
if project and project.get('name'):
heartbeat['alternate_project'] = project.get('name')
elif folders:
project_name = find_project_from_folders(folders, entity)
if project_name:
cmd.extend(['--alternate-project', project_name])
if self.cursorpos is not None:
cmd.extend(['--cursorpos', '{0}'.format(self.cursorpos)])
for pattern in self.ignore:
cmd.extend(['--ignore', pattern])
if self.debug:
cmd.append('--verbose')
heartbeat['alternate_project'] = project_name
if cursorpos is not None:
heartbeat['cursorpos'] = '{0}'.format(cursorpos)
return heartbeat
def send_heartbeats(self):
if python_binary():
cmd.insert(0, python_binary())
heartbeat = self.build_heartbeat(**self.heartbeat)
ua = 'sublime/%d sublime-wakatime/%s' % (ST_VERSION, __version__)
cmd = [
python_binary(),
API_CLIENT,
'--entity', heartbeat['entity'],
'--time', str('%f' % heartbeat['timestamp']),
'--plugin', ua,
]
if self.api_key:
cmd.extend(['--key', str(bytes.decode(self.api_key.encode('utf8')))])
if heartbeat['is_write']:
cmd.append('--write')
if heartbeat.get('alternate_project'):
cmd.extend(['--alternate-project', heartbeat['alternate_project']])
if heartbeat.get('cursorpos') is not None:
cmd.extend(['--cursorpos', heartbeat['cursorpos']])
for pattern in self.ignore:
cmd.extend(['--ignore', pattern])
if self.debug:
cmd.append('--verbose')
if self.has_extra_heartbeats:
cmd.append('--extra-heartbeats')
stdin = PIPE
extra_heartbeats = [self.build_heartbeat(**x) for x in self.extra_heartbeats]
extra_heartbeats = json.dumps(extra_heartbeats)
else:
extra_heartbeats = None
stdin = None
log(DEBUG, ' '.join(obfuscate_apikey(cmd)))
try:
if not self.debug:
Popen(cmd)
process = Popen(cmd, stdin=stdin, stdout=PIPE, stderr=STDOUT)
inp = None
if self.has_extra_heartbeats:
inp = "{0}\n".format(extra_heartbeats)
inp = inp.encode('utf-8')
output, err = process.communicate(input=inp)
output = u(output)
retcode = process.poll()
if (not retcode or retcode == 102) and not output:
self.sent()
else:
process = Popen(cmd, stdout=PIPE, stderr=STDOUT)
output, err = process.communicate()
output = u(output)
retcode = process.poll()
if (not retcode or retcode == 102) and not output:
self.sent()
if retcode:
log(DEBUG if retcode == 102 else ERROR, 'wakatime-core exited with status: {0}'.format(retcode))
if output:
log(ERROR, u('wakatime-core output: {0}').format(output))
update_status_bar('Error')
if retcode:
log(DEBUG if retcode == 102 else ERROR, 'wakatime-core exited with status: {0}'.format(retcode))
if output:
log(ERROR, u('wakatime-core output: {0}').format(output))
except:
log(ERROR, u(sys.exc_info()[1]))
update_status_bar('Error')
else:
log(ERROR, 'Unable to find python binary.')
update_status_bar('Error')
def sent(self):
sublime.set_timeout(self.set_status_bar, 0)
sublime.set_timeout(self.set_last_heartbeat, 0)
update_status_bar('OK')
def set_status_bar(self):
if SETTINGS.get('status_bar_message'):
self.view.set_status('wakatime', datetime.now().strftime(SETTINGS.get('status_bar_message_fmt')))
def set_last_heartbeat(self):
global LAST_HEARTBEAT
LAST_HEARTBEAT = {
'file': self.target_file,
'time': self.timestamp,
'is_write': self.is_write,
}
def download_python():
thread = DownloadPython()
thread.start()
class DownloadPython(threading.Thread):
@ -467,10 +579,10 @@ class DownloadPython(threading.Thread):
arch=arch,
)
if not os.path.exists(os.path.join(os.path.expanduser('~'), '.wakatime')):
os.makedirs(os.path.join(os.path.expanduser('~'), '.wakatime'))
if not os.path.exists(resources_folder()):
os.makedirs(resources_folder())
zip_file = os.path.join(os.path.expanduser('~'), '.wakatime', 'python.zip')
zip_file = os.path.join(resources_folder(), 'python.zip')
try:
urllib.urlretrieve(url, zip_file)
except AttributeError:
@ -478,7 +590,7 @@ class DownloadPython(threading.Thread):
log(INFO, 'Extracting Python...')
with ZipFile(zip_file) as zf:
path = os.path.join(os.path.expanduser('~'), '.wakatime', 'python')
path = os.path.join(resources_folder(), 'python')
zf.extractall(path)
try:
@ -491,15 +603,15 @@ class DownloadPython(threading.Thread):
def plugin_loaded():
global SETTINGS
log(INFO, 'Initializing WakaTime plugin v%s' % __version__)
SETTINGS = sublime.load_settings(SETTINGS_FILE)
log(INFO, 'Initializing WakaTime plugin v%s' % __version__)
update_status_bar('Initializing')
if not python_binary():
log(WARNING, 'Python binary not found.')
if platform.system() == 'Windows':
thread = DownloadPython()
thread.start()
set_timeout(download_python, 0)
else:
sublime.error_message("Unable to find Python binary!\nWakaTime needs Python to work correctly.\n\nGo to https://www.python.org/downloads")
return
@ -509,7 +621,7 @@ def plugin_loaded():
def after_loaded():
if not prompt_api_key():
sublime.set_timeout(after_loaded, 500)
set_timeout(after_loaded, 0.5)
# need to call plugin_loaded because only ST3 will auto-call it
@ -520,15 +632,15 @@ if ST_VERSION < 3000:
class WakatimeListener(sublime_plugin.EventListener):
def on_post_save(self, view):
handle_heartbeat(view, is_write=True)
handle_activity(view, is_write=True)
def on_selection_modified(self, view):
if is_view_active(view):
handle_heartbeat(view)
handle_activity(view)
def on_modified(self, view):
if is_view_active(view):
handle_heartbeat(view)
handle_activity(view)
class WakatimeDashboardCommand(sublime_plugin.ApplicationCommand):

View File

@ -19,5 +19,5 @@
"status_bar_message": true,
// Status bar message format.
"status_bar_message_fmt": "WakaTime active %I:%M %p"
"status_bar_message_fmt": "WakaTime {status} %I:%M %p"
}

View File

@ -1,7 +1,7 @@
__title__ = 'wakatime'
__description__ = 'Common interface to the WakaTime api.'
__url__ = 'https://github.com/wakatime/wakatime'
__version_info__ = ('4', '1', '13')
__version_info__ = ('6', '0', '2')
__version__ = '.'.join(__version_info__)
__author__ = 'Alan Hamlett'
__author_email__ = 'alan@wakatime.com'

View File

@ -15,3 +15,4 @@ API_ERROR = 102
CONFIG_FILE_PARSE_ERROR = 103
AUTH_ERROR = 104
UNKNOWN_ERROR = 105
MALFORMED_HEARTBEAT_ERROR = 106

View File

@ -0,0 +1,80 @@
{
"ActionScript": "ActionScript",
"ApacheConf": "ApacheConf",
"AppleScript": "AppleScript",
"ASP": "ASP",
"Assembly": "Assembly",
"Awk": "Awk",
"Bash": "Bash",
"Basic": "Basic",
"BrightScript": "BrightScript",
"C": "C",
"C#": "C#",
"C++": "C++",
"Clojure": "Clojure",
"Cocoa": "Cocoa",
"CoffeeScript": "CoffeeScript",
"ColdFusion": "ColdFusion",
"Common Lisp": "Common Lisp",
"CSHTML": "CSHTML",
"CSS": "CSS",
"Dart": "Dart",
"Delphi": "Delphi",
"Elixir": "Elixir",
"Elm": "Elm",
"Emacs Lisp": "Emacs Lisp",
"Erlang": "Erlang",
"F#": "F#",
"Fortran": "Fortran",
"Go": "Go",
"Gous": "Gosu",
"Groovy": "Groovy",
"Haml": "Haml",
"HaXe": "HaXe",
"Haskell": "Haskell",
"HTML": "HTML",
"INI": "INI",
"Jade": "Jade",
"Java": "Java",
"JavaScript": "JavaScript",
"JSON": "JSON",
"JSX": "JSX",
"Kotlin": "Kotlin",
"LESS": "LESS",
"Lua": "Lua",
"Markdown": "Markdown",
"Matlab": "Matlab",
"Mustache": "Mustache",
"OCaml": "OCaml",
"Objective-C": "Objective-C",
"Objective-C++": "Objective-C++",
"Objective-J": "Objective-J",
"Perl": "Perl",
"PHP": "PHP",
"PowerShell": "PowerShell",
"Prolog": "Prolog",
"Puppet": "Puppet",
"Python": "Python",
"R": "R",
"reStructuredText": "reStructuredText",
"Ruby": "Ruby",
"Rust": "Rust",
"Sass": "Sass",
"Scala": "Scala",
"Scheme": "Scheme",
"SCSS": "SCSS",
"Shell": "Shell",
"Slim": "Slim",
"Smalltalk": "Smalltalk",
"SQL": "SQL",
"Swift": "Swift",
"Text": "Text",
"Turing": "Turing",
"Twig": "Twig",
"TypeScript": "TypeScript",
"VB.net": "VB.net",
"VimL": "VimL",
"XAML": "XAML",
"XML": "XML",
"YAML": "YAML"
}

View File

@ -0,0 +1,531 @@
{
"a2ps": null,
"a65": "Assembly",
"aap": null,
"abap": null,
"abaqus": null,
"abc": null,
"abel": null,
"acedb": null,
"ada": null,
"aflex": null,
"ahdl": null,
"alsaconf": null,
"amiga": null,
"aml": null,
"ampl": null,
"ant": null,
"antlr": null,
"apache": null,
"apachestyle": null,
"arch": null,
"art": null,
"asm": "Assembly",
"asm68k": "Assembly",
"asmh8300": "Assembly",
"asn": null,
"aspperl": null,
"aspvbs": null,
"asterisk": null,
"asteriskvm": null,
"atlas": null,
"autohotkey": null,
"autoit": null,
"automake": null,
"ave": null,
"awk": null,
"ayacc": null,
"b": null,
"baan": null,
"basic": "Basic",
"bc": null,
"bdf": null,
"bib": null,
"bindzone": null,
"blank": null,
"bst": null,
"btm": null,
"bzr": null,
"c": "C",
"cabal": null,
"calendar": null,
"catalog": null,
"cdl": null,
"cdrdaoconf": null,
"cdrtoc": null,
"cf": null,
"cfg": null,
"ch": null,
"chaiscript": null,
"change": null,
"changelog": null,
"chaskell": null,
"cheetah": null,
"chill": null,
"chordpro": null,
"cl": null,
"clean": null,
"clipper": null,
"cmake": null,
"cmusrc": null,
"cobol": null,
"coco": null,
"conaryrecipe": null,
"conf": null,
"config": null,
"context": null,
"cpp": "C++",
"crm": null,
"crontab": "Crontab",
"cs": "C#",
"csc": null,
"csh": null,
"csp": null,
"css": null,
"cterm": null,
"ctrlh": null,
"cucumber": null,
"cuda": null,
"cupl": null,
"cuplsim": null,
"cvs": null,
"cvsrc": null,
"cweb": null,
"cynlib": null,
"cynpp": null,
"d": null,
"datascript": null,
"dcd": null,
"dcl": null,
"debchangelog": null,
"debcontrol": null,
"debsources": null,
"def": null,
"denyhosts": null,
"desc": null,
"desktop": null,
"dictconf": null,
"dictdconf": null,
"diff": null,
"dircolors": null,
"diva": null,
"django": null,
"dns": null,
"docbk": null,
"docbksgml": null,
"docbkxml": null,
"dosbatch": null,
"dosini": null,
"dot": null,
"doxygen": null,
"dracula": null,
"dsl": null,
"dtd": null,
"dtml": null,
"dtrace": null,
"dylan": null,
"dylanintr": null,
"dylanlid": null,
"ecd": null,
"edif": null,
"eiffel": null,
"elf": null,
"elinks": null,
"elmfilt": null,
"erlang": null,
"eruby": null,
"esmtprc": null,
"esqlc": null,
"esterel": null,
"eterm": null,
"eviews": null,
"exim": null,
"expect": null,
"exports": null,
"fan": null,
"fasm": null,
"fdcc": null,
"fetchmail": null,
"fgl": null,
"flexwiki": null,
"focexec": null,
"form": null,
"forth": null,
"fortran": null,
"foxpro": null,
"framescript": null,
"freebasic": null,
"fstab": null,
"fvwm": null,
"fvwm2m4": null,
"gdb": null,
"gdmo": null,
"gedcom": null,
"git": null,
"gitcommit": null,
"gitconfig": null,
"gitrebase": null,
"gitsendemail": null,
"gkrellmrc": null,
"gnuplot": null,
"gp": null,
"gpg": null,
"grads": null,
"gretl": null,
"groff": null,
"groovy": null,
"group": null,
"grub": null,
"gsp": null,
"gtkrc": null,
"haml": "Haml",
"hamster": null,
"haskell": "Haskell",
"haste": null,
"hastepreproc": null,
"hb": null,
"help": null,
"hercules": null,
"hex": null,
"hog": null,
"hostconf": null,
"html": "HTML",
"htmlcheetah": "HTML",
"htmldjango": "HTML",
"htmlm4": "HTML",
"htmlos": null,
"ia64": null,
"ibasic": null,
"icemenu": null,
"icon": null,
"idl": null,
"idlang": null,
"indent": null,
"inform": null,
"initex": null,
"initng": null,
"inittab": null,
"ipfilter": null,
"ishd": null,
"iss": null,
"ist": null,
"jal": null,
"jam": null,
"jargon": null,
"java": "Java",
"javacc": null,
"javascript": "JavaScript",
"jess": null,
"jgraph": null,
"jproperties": null,
"jsp": null,
"kconfig": null,
"kix": null,
"kscript": null,
"kwt": null,
"lace": null,
"latte": null,
"ld": null,
"ldapconf": null,
"ldif": null,
"lex": null,
"lftp": null,
"lhaskell": "Haskell",
"libao": null,
"lifelines": null,
"lilo": null,
"limits": null,
"liquid": null,
"lisp": null,
"lite": null,
"litestep": null,
"loginaccess": null,
"logindefs": null,
"logtalk": null,
"lotos": null,
"lout": null,
"lpc": null,
"lprolog": null,
"lscript": null,
"lsl": null,
"lss": null,
"lua": null,
"lynx": null,
"m4": null,
"mail": null,
"mailaliases": null,
"mailcap": null,
"make": null,
"man": null,
"manconf": null,
"manual": null,
"maple": null,
"markdown": "Markdown",
"masm": null,
"mason": null,
"master": null,
"matlab": null,
"maxima": null,
"mel": null,
"messages": null,
"mf": null,
"mgl": null,
"mgp": null,
"mib": null,
"mma": null,
"mmix": null,
"mmp": null,
"modconf": null,
"model": null,
"modsim3": null,
"modula2": null,
"modula3": null,
"monk": null,
"moo": null,
"mp": null,
"mplayerconf": null,
"mrxvtrc": null,
"msidl": null,
"msmessages": null,
"msql": null,
"mupad": null,
"mush": null,
"muttrc": null,
"mysql": null,
"named": null,
"nanorc": null,
"nasm": null,
"nastran": null,
"natural": null,
"ncf": null,
"netrc": null,
"netrw": null,
"nosyntax": null,
"nqc": null,
"nroff": null,
"nsis": null,
"obj": null,
"objc": "Objective-C",
"objcpp": "Objective-C++",
"ocaml": "OCaml",
"occam": null,
"omnimark": null,
"openroad": null,
"opl": null,
"ora": null,
"pamconf": null,
"papp": null,
"pascal": null,
"passwd": null,
"pcap": null,
"pccts": null,
"pdf": null,
"perl": "Perl",
"perl6": "Perl",
"pf": null,
"pfmain": null,
"php": "PHP",
"phtml": "PHP",
"pic": null,
"pike": null,
"pilrc": null,
"pine": null,
"pinfo": null,
"plaintex": null,
"plm": null,
"plp": null,
"plsql": null,
"po": null,
"pod": null,
"postscr": null,
"pov": null,
"povini": null,
"ppd": null,
"ppwiz": null,
"prescribe": null,
"privoxy": null,
"procmail": null,
"progress": null,
"prolog": "Prolog",
"promela": null,
"protocols": null,
"psf": null,
"ptcap": null,
"purifylog": null,
"pyrex": null,
"python": "Python",
"qf": null,
"quake": null,
"r": "R",
"racc": null,
"radiance": null,
"ratpoison": null,
"rc": null,
"rcs": null,
"rcslog": null,
"readline": null,
"rebol": null,
"registry": null,
"remind": null,
"resolv": null,
"reva": null,
"rexx": null,
"rhelp": null,
"rib": null,
"rnc": null,
"rnoweb": null,
"robots": null,
"rpcgen": null,
"rpl": null,
"rst": null,
"rtf": null,
"ruby": "Ruby",
"samba": null,
"sas": null,
"sass": "Sass",
"sather": null,
"scheme": "Scheme",
"scilab": null,
"screen": null,
"scss": "SCSS",
"sd": null,
"sdc": null,
"sdl": null,
"sed": null,
"sendpr": null,
"sensors": null,
"services": null,
"setserial": null,
"sgml": null,
"sgmldecl": null,
"sgmllnx": null,
"sh": null,
"sicad": null,
"sieve": null,
"simula": null,
"sinda": null,
"sindacmp": null,
"sindaout": null,
"sisu": null,
"skill": "SKILL",
"sl": null,
"slang": null,
"slice": null,
"slpconf": null,
"slpreg": null,
"slpspi": null,
"slrnrc": null,
"slrnsc": null,
"sm": null,
"smarty": null,
"smcl": null,
"smil": null,
"smith": null,
"sml": null,
"snnsnet": null,
"snnspat": null,
"snnsres": null,
"snobol4": null,
"spec": null,
"specman": null,
"spice": null,
"splint": null,
"spup": null,
"spyce": null,
"sql": null,
"sqlanywhere": null,
"sqlforms": null,
"sqlinformix": null,
"sqlj": null,
"sqloracle": null,
"sqr": null,
"squid": null,
"sshconfig": null,
"sshdconfig": null,
"st": null,
"stata": null,
"stp": null,
"strace": null,
"sudoers": null,
"svg": null,
"svn": null,
"syncolor": null,
"synload": null,
"syntax": null,
"sysctl": null,
"tads": null,
"tags": null,
"tak": null,
"takcmp": null,
"takout": null,
"tar": null,
"taskdata": null,
"taskedit": null,
"tasm": null,
"tcl": null,
"tcsh": null,
"terminfo": null,
"tex": null,
"texinfo": null,
"texmf": null,
"tf": null,
"tidy": null,
"tilde": null,
"tli": null,
"tpp": null,
"trasys": null,
"trustees": null,
"tsalt": null,
"tsscl": null,
"tssgm": null,
"tssop": null,
"uc": null,
"udevconf": null,
"udevperm": null,
"udevrules": null,
"uil": null,
"updatedb": null,
"valgrind": null,
"vb": "VB.net",
"vera": null,
"verilog": null,
"verilogams": null,
"vgrindefs": null,
"vhdl": null,
"vim": "VimL",
"viminfo": null,
"virata": null,
"vmasm": null,
"voscm": null,
"vrml": null,
"vsejcl": null,
"wdiff": null,
"web": null,
"webmacro": null,
"wget": null,
"winbatch": null,
"wml": null,
"wsh": null,
"wsml": null,
"wvdial": null,
"xbl": null,
"xdefaults": null,
"xf86conf": null,
"xhtml": "HTML",
"xinetd": null,
"xkb": null,
"xmath": null,
"xml": "XML",
"xmodmap": null,
"xpm": null,
"xpm2": null,
"xquery": null,
"xs": null,
"xsd": null,
"xslt": null,
"xxd": null,
"yacc": null,
"yaml": "YAML",
"z8a": null,
"zsh": null
}

View File

@ -41,10 +41,10 @@ class CustomEncoder(json.JSONEncoder):
class JsonFormatter(logging.Formatter):
def setup(self, timestamp, isWrite, entity, version, plugin, verbose,
def setup(self, timestamp, is_write, entity, version, plugin, verbose,
warnings=False):
self.timestamp = timestamp
self.isWrite = isWrite
self.is_write = is_write
self.entity = entity
self.version = version
self.plugin = plugin
@ -61,10 +61,10 @@ class JsonFormatter(logging.Formatter):
if self.verbose:
data['caller'] = record.pathname
data['lineno'] = record.lineno
data['isWrite'] = self.isWrite
data['is_write'] = self.is_write
data['file'] = self.entity
if not self.isWrite:
del data['isWrite']
if not self.is_write:
del data['is_write']
data['level'] = record.levelname
data['message'] = record.getMessage() if self.warnings else record.msg
if not self.plugin:
@ -103,7 +103,7 @@ def setup_logging(args, version):
formatter = JsonFormatter(datefmt='%Y/%m/%d %H:%M:%S %z')
formatter.setup(
timestamp=args.timestamp,
isWrite=args.isWrite,
is_write=args.is_write,
entity=args.entity,
version=version,
plugin=args.plugin,
@ -118,7 +118,7 @@ def setup_logging(args, version):
warnings_formatter = JsonFormatter(datefmt='%Y/%m/%d %H:%M:%S %z')
warnings_formatter.setup(
timestamp=args.timestamp,
isWrite=args.isWrite,
is_write=args.is_write,
entity=args.entity,
version=version,
plugin=args.plugin,

View File

@ -37,6 +37,7 @@ from .constants import (
CONFIG_FILE_PARSE_ERROR,
SUCCESS,
UNKNOWN_ERROR,
MALFORMED_HEARTBEAT_ERROR,
)
from .logger import setup_logging
from .offlinequeue import Queue
@ -50,10 +51,7 @@ try:
from .packages import simplejson as json # pragma: nocover
except (ImportError, SyntaxError): # pragma: nocover
import json
try:
from .packages import tzlocal
except: # pragma: nocover
from .packages import tzlocal3 as tzlocal
from .packages import tzlocal
log = logging.getLogger('WakaTime')
@ -104,13 +102,13 @@ def parseArguments():
parser.add_argument('--entity', dest='entity', metavar='FILE',
action=FileAction,
help='absolute path to file for the heartbeat; can also be a '+
'url, domain, or app when --entitytype is not file')
'url, domain, or app when --entity-type is not file')
parser.add_argument('--file', dest='file', action=FileAction,
help=argparse.SUPPRESS)
parser.add_argument('--key', dest='key',
help='your wakatime api key; uses api_key from '+
'~/.wakatime.conf by default')
parser.add_argument('--write', dest='isWrite',
'~/.wakatime.cfg by default')
parser.add_argument('--write', dest='is_write',
action='store_true',
help='when set, tells api this heartbeat was triggered from '+
'writing to a file')
@ -125,9 +123,9 @@ def parseArguments():
help='optional line number; current line being edited')
parser.add_argument('--cursorpos', dest='cursorpos',
help='optional cursor position in the current file')
parser.add_argument('--entitytype', dest='entity_type',
parser.add_argument('--entity-type', dest='entity_type',
help='entity type for this heartbeat. can be one of "file", '+
'"url", "domain", or "app"; defaults to file.')
'"domain", or "app"; defaults to file.')
parser.add_argument('--proxy', dest='proxy',
help='optional https proxy url; for example: '+
'https://user:pass@localhost:8080')
@ -136,6 +134,9 @@ def parseArguments():
parser.add_argument('--alternate-project', dest='alternate_project',
help='optional alternate project name; auto-discovered project '+
'takes priority')
parser.add_argument('--alternate-language', dest='alternate_language',
help='optional alternate language name; auto-detected language'+
'takes priority')
parser.add_argument('--hostname', dest='hostname', help='hostname of '+
'current machine.')
parser.add_argument('--disableoffline', dest='offline',
@ -153,14 +154,18 @@ def parseArguments():
'POSIX regex syntax; can be used more than once')
parser.add_argument('--ignore', dest='ignore', action='append',
help=argparse.SUPPRESS)
parser.add_argument('--extra-heartbeats', dest='extra_heartbeats',
action='store_true',
help='reads extra heartbeats from STDIN as a JSON array until EOF')
parser.add_argument('--logfile', dest='logfile',
help='defaults to ~/.wakatime.log')
parser.add_argument('--apiurl', dest='api_url',
help='heartbeats api url; for debugging with a local server')
parser.add_argument('--timeout', dest='timeout', type=int,
help='number of seconds to wait when sending heartbeats to api')
help='number of seconds to wait when sending heartbeats to api; '+
'defaults to 60 seconds')
parser.add_argument('--config', dest='config',
help='defaults to ~/.wakatime.conf')
help='defaults to ~/.wakatime.cfg')
parser.add_argument('--verbose', dest='verbose', action='store_true',
help='turns on debug messages in log file')
parser.add_argument('--version', action='version', version=__version__)
@ -188,8 +193,6 @@ def parseArguments():
args.key = default_key
else:
parser.error('Missing api key')
if not args.entity_type:
args.entity_type = 'file'
if not args.entity:
if args.file:
args.entity = args.file
@ -295,7 +298,7 @@ def get_user_agent(plugin):
def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
entity=None, timestamp=None, isWrite=None, plugin=None,
entity=None, timestamp=None, is_write=None, plugin=None,
offline=None, entity_type='file', hidefilenames=None,
proxy=None, api_url=None, timeout=None, **kwargs):
"""Sends heartbeat as POST request to WakaTime api server.
@ -307,7 +310,7 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
if not api_url:
api_url = 'https://api.wakatime.com/api/v1/heartbeats'
if not timeout:
timeout = 30
timeout = 60
log.debug('Sending heartbeat to api at %s' % api_url)
data = {
'time': timestamp,
@ -327,8 +330,8 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
data['lineno'] = stats['lineno']
if stats.get('cursorpos'):
data['cursorpos'] = stats['cursorpos']
if isWrite:
data['is_write'] = isWrite
if is_write:
data['is_write'] = is_write
if project:
data['project'] = project
if branch:
@ -435,7 +438,7 @@ def sync_offline_heartbeats(args, hostname):
hostname=hostname,
stats=json.loads(heartbeat['stats']),
key=args.key,
isWrite=heartbeat['is_write'],
is_write=heartbeat['is_write'],
plugin=heartbeat['plugin'],
offline=args.offline,
hidefilenames=args.hidefilenames,
@ -451,6 +454,50 @@ def sync_offline_heartbeats(args, hostname):
return SUCCESS
def process_heartbeat(args, configs, hostname, heartbeat):
exclude = should_exclude(heartbeat['entity'], args.include, args.exclude)
if exclude is not False:
log.debug(u('Skipping because matches exclude pattern: {pattern}').format(
pattern=u(exclude),
))
return SUCCESS
if heartbeat.get('entity_type') not in ['file', 'domain', 'app']:
heartbeat['entity_type'] = 'file'
if heartbeat['entity_type'] != 'file' or os.path.isfile(heartbeat['entity']):
stats = get_file_stats(heartbeat['entity'],
entity_type=heartbeat['entity_type'],
lineno=heartbeat.get('lineno'),
cursorpos=heartbeat.get('cursorpos'),
plugin=args.plugin,
alternate_language=heartbeat.get('alternate_language'))
project = heartbeat.get('project') or heartbeat.get('alternate_project')
branch = None
if heartbeat['entity_type'] == 'file':
project, branch = get_project_info(configs, heartbeat)
heartbeat['project'] = project
heartbeat['branch'] = branch
heartbeat['stats'] = stats
heartbeat['hostname'] = hostname
heartbeat['timeout'] = args.timeout
heartbeat['key'] = args.key
heartbeat['plugin'] = args.plugin
heartbeat['offline'] = args.offline
heartbeat['hidefilenames'] = args.hidefilenames
heartbeat['proxy'] = args.proxy
heartbeat['api_url'] = args.api_url
return send_heartbeat(**heartbeat)
else:
log.debug('File does not exist; ignoring this heartbeat.')
return SUCCESS
def execute(argv=None):
if argv:
sys.argv = ['wakatime'] + argv
@ -462,42 +509,25 @@ def execute(argv=None):
setup_logging(args, __version__)
try:
exclude = should_exclude(args.entity, args.include, args.exclude)
if exclude is not False:
log.debug(u('Skipping because matches exclude pattern: {pattern}').format(
pattern=u(exclude),
))
return SUCCESS
if args.entity_type != 'file' or os.path.isfile(args.entity):
hostname = args.hostname or socket.gethostname()
stats = get_file_stats(args.entity,
entity_type=args.entity_type,
lineno=args.lineno,
cursorpos=args.cursorpos)
heartbeat = vars(args)
retval = process_heartbeat(args, configs, hostname, heartbeat)
project = args.project or args.alternate_project
branch = None
if args.entity_type == 'file':
project, branch = get_project_info(configs, args)
if args.extra_heartbeats:
try:
for heartbeat in json.loads(sys.stdin.readline()):
retval = process_heartbeat(args, configs, hostname, heartbeat)
except json.JSONDecodeError:
retval = MALFORMED_HEARTBEAT_ERROR
kwargs = vars(args)
kwargs['project'] = project
kwargs['branch'] = branch
kwargs['stats'] = stats
hostname = args.hostname or socket.gethostname()
kwargs['hostname'] = hostname
kwargs['timeout'] = args.timeout
if retval == SUCCESS:
retval = sync_offline_heartbeats(args, hostname)
status = send_heartbeat(**kwargs)
if status == SUCCESS:
return sync_offline_heartbeats(args, hostname)
else:
return status
return retval
else:
log.debug('File does not exist; ignoring this heartbeat.')
return SUCCESS
except:
log.traceback()
print(traceback.format_exc())
return UNKNOWN_ERROR

View File

@ -0,0 +1,14 @@
import os
import sys
from ..compat import is_py2
if is_py2:
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'py2'))
else:
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'py3'))
import tzlocal
from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
from pygments.modeline import get_filetype_from_buffer
from pygments.util import ClassNotFound

Some files were not shown because too many files have changed in this diff Show More