mirror of
https://github.com/wakatime/sublime-wakatime.git
synced 2023-08-10 21:13:02 +03:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
253728545c | |||
49d9b1d7dc | |||
8574abe012 | |||
6b6f60d8e8 | |||
986e592d1e | |||
6ec3b171e1 | |||
bcfb9862af | |||
85cf9f4eb5 | |||
d2a996e845 | |||
c863bde54a | |||
e19f85f081 | |||
7b854d4041 |
49
HISTORY.rst
49
HISTORY.rst
@ -3,6 +3,55 @@ History
|
||||
-------
|
||||
|
||||
|
||||
2.0.10 (2014-08-29)
|
||||
++++++++++++++++++
|
||||
|
||||
- upgrade external wakatime package to v2.0.8
|
||||
- supress output from svn command
|
||||
|
||||
|
||||
2.0.9 (2014-08-27)
|
||||
++++++++++++++++++
|
||||
|
||||
- upgrade external wakatime package to v2.0.7
|
||||
- fix support for subversion projects on Mac OS X
|
||||
|
||||
|
||||
2.0.8 (2014-08-07)
|
||||
++++++++++++++++++
|
||||
|
||||
- upgrade external wakatime package to v2.0.6
|
||||
- fix unicode bug by encoding json POST data
|
||||
|
||||
|
||||
2.0.7 (2014-07-25)
|
||||
++++++++++++++++++
|
||||
|
||||
- upgrade external wakatime package to v2.0.5
|
||||
- option in .wakatime.cfg to obfuscate file names
|
||||
|
||||
|
||||
2.0.6 (2014-07-25)
|
||||
++++++++++++++++++
|
||||
|
||||
- upgrade external wakatime package to v2.0.4
|
||||
- use unique logger namespace to prevent collisions in shared plugin environments
|
||||
|
||||
|
||||
2.0.5 (2014-06-18)
|
||||
++++++++++++++++++
|
||||
|
||||
- upgrade external wakatime package to v2.0.3
|
||||
- use project name from sublime-project file when no revision control project found
|
||||
|
||||
|
||||
2.0.4 (2014-06-09)
|
||||
++++++++++++++++++
|
||||
|
||||
- upgrade external wakatime package to v2.0.2
|
||||
- disable offline logging when Python not compiled with sqlite3 module
|
||||
|
||||
|
||||
2.0.3 (2014-05-26)
|
||||
++++++++++++++++++
|
||||
|
||||
|
36
WakaTime.py
36
WakaTime.py
@ -6,7 +6,7 @@ License: BSD, see LICENSE for more details.
|
||||
Website: https://wakatime.com/
|
||||
==========================================================="""
|
||||
|
||||
__version__ = '2.0.4'
|
||||
__version__ = '2.0.10'
|
||||
|
||||
import sublime
|
||||
import sublime_plugin
|
||||
@ -18,7 +18,7 @@ import sys
|
||||
import time
|
||||
import threading
|
||||
import uuid
|
||||
from os.path import expanduser, dirname, realpath, isfile, join, exists
|
||||
from os.path import expanduser, dirname, basename, realpath, isfile, join, exists
|
||||
|
||||
|
||||
# globals
|
||||
@ -53,7 +53,9 @@ if HAS_SSL:
|
||||
|
||||
def prompt_api_key():
|
||||
global SETTINGS
|
||||
if not SETTINGS.get('api_key'):
|
||||
if SETTINGS.get('api_key'):
|
||||
return True
|
||||
else:
|
||||
def got_key(text):
|
||||
if text:
|
||||
SETTINGS.set('api_key', str(text))
|
||||
@ -86,12 +88,23 @@ def enough_time_passed(now, last_time):
|
||||
return False
|
||||
|
||||
|
||||
def find_project_name_from_folders(folders):
|
||||
for folder in folders:
|
||||
for file_name in os.listdir(folder):
|
||||
if file_name.endswith('.sublime-project'):
|
||||
return file_name.replace('.sublime-project', '', 1)
|
||||
return None
|
||||
|
||||
|
||||
def handle_action(view, is_write=False):
|
||||
global LOCK, LAST_ACTION
|
||||
with LOCK:
|
||||
target_file = view.file_name()
|
||||
if target_file:
|
||||
thread = SendActionThread(target_file, is_write=is_write)
|
||||
project = view.window().project_file_name() if hasattr(view.window(), 'project_file_name') else None
|
||||
if project:
|
||||
project = basename(project).replace('.sublime-project', '', 1)
|
||||
thread = SendActionThread(target_file, is_write=is_write, project=project, folders=view.window().folders())
|
||||
thread.start()
|
||||
LAST_ACTION = {
|
||||
'file': target_file,
|
||||
@ -102,10 +115,12 @@ def handle_action(view, is_write=False):
|
||||
|
||||
class SendActionThread(threading.Thread):
|
||||
|
||||
def __init__(self, target_file, is_write=False, force=False):
|
||||
def __init__(self, target_file, is_write=False, project=None, folders=None, force=False):
|
||||
threading.Thread.__init__(self)
|
||||
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', '')
|
||||
@ -132,13 +147,19 @@ class SendActionThread(threading.Thread):
|
||||
]
|
||||
if self.is_write:
|
||||
cmd.append('--write')
|
||||
if self.project:
|
||||
cmd.extend(['--project', self.project])
|
||||
elif self.folders:
|
||||
project_name = find_project_name_from_folders(self.folders)
|
||||
if project_name:
|
||||
cmd.extend(['--project', project_name])
|
||||
for pattern in self.ignore:
|
||||
cmd.extend(['--ignore', pattern])
|
||||
if self.debug:
|
||||
cmd.append('--verbose')
|
||||
if HAS_SSL:
|
||||
if self.debug:
|
||||
print(cmd)
|
||||
print('[WakaTime] %s' % ' '.join(cmd))
|
||||
code = wakatime.main(cmd)
|
||||
if code != 0:
|
||||
print('[WakaTime] Error: Response code %d from wakatime package.' % code)
|
||||
@ -147,7 +168,7 @@ class SendActionThread(threading.Thread):
|
||||
if python:
|
||||
cmd.insert(0, python)
|
||||
if self.debug:
|
||||
print(cmd)
|
||||
print('[WakaTime] %s' % ' '.join(cmd))
|
||||
if platform.system() == 'Windows':
|
||||
Popen(cmd, shell=False)
|
||||
else:
|
||||
@ -159,6 +180,7 @@ class SendActionThread(threading.Thread):
|
||||
|
||||
def plugin_loaded():
|
||||
global SETTINGS
|
||||
print('[WakaTime] Initializing WakaTime plugin v%s' % __version__)
|
||||
SETTINGS = sublime.load_settings(SETTINGS_FILE)
|
||||
after_loaded()
|
||||
|
||||
|
1
packages/wakatime/.gitignore
vendored
1
packages/wakatime/.gitignore
vendored
@ -36,3 +36,4 @@ nosetests.xml
|
||||
|
||||
virtualenv
|
||||
venv
|
||||
.DS_Store
|
||||
|
@ -3,6 +3,49 @@ History
|
||||
-------
|
||||
|
||||
|
||||
2.0.8 (2014-08-29)
|
||||
++++++++++++++++++
|
||||
|
||||
- supress output from svn command
|
||||
|
||||
|
||||
2.0.7 (2014-08-27)
|
||||
++++++++++++++++++
|
||||
|
||||
- find svn binary location from common install directories
|
||||
|
||||
|
||||
2.0.6 (2014-08-07)
|
||||
++++++++++++++++++
|
||||
|
||||
- encode json data as str when passing to urllib
|
||||
|
||||
|
||||
2.0.5 (2014-07-25)
|
||||
++++++++++++++++++
|
||||
|
||||
- option in .wakatime.cfg to obfuscate file names
|
||||
|
||||
|
||||
2.0.4 (2014-07-25)
|
||||
++++++++++++++++++
|
||||
|
||||
- use unique logger namespace to prevent collisions in shared plugin environments
|
||||
|
||||
|
||||
2.0.3 (2014-06-18)
|
||||
++++++++++++++++++
|
||||
|
||||
- use project from command line arg when no revision control project is found
|
||||
|
||||
|
||||
2.0.2 (2014-06-09)
|
||||
++++++++++++++++++
|
||||
|
||||
- include python3.2 compatible versions of simplejson, pytz, and tzlocal
|
||||
- disable offline logging when Python was not compiled with sqlite3 module
|
||||
|
||||
|
||||
2.0.1 (2014-05-26)
|
||||
++++++++++++++++++
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
from __future__ import print_function
|
||||
|
||||
__title__ = 'wakatime'
|
||||
__version__ = '2.0.2'
|
||||
__version__ = '2.0.8'
|
||||
__author__ = 'Alan Hamlett'
|
||||
__license__ = 'BSD'
|
||||
__copyright__ = 'Copyright 2014 Alan Hamlett'
|
||||
@ -52,7 +52,12 @@ except:
|
||||
from .packages import tzlocal3
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
|
||||
class FileAction(argparse.Action):
|
||||
@ -122,7 +127,7 @@ def parseConfigFile(configFile):
|
||||
return None
|
||||
except IOError:
|
||||
if not os.path.isfile(configFile):
|
||||
print('Error: Could not read from config file ~/.wakatime.conf')
|
||||
print('Error: Could not read from config file ~/.wakatime.cfg')
|
||||
return configs
|
||||
|
||||
|
||||
@ -161,6 +166,9 @@ def parseArguments(argv):
|
||||
parser.add_argument('--disableoffline', dest='offline',
|
||||
action='store_false',
|
||||
help='disables offline time logging instead of queuing logged time')
|
||||
parser.add_argument('--hidefilenames', dest='hidefilenames',
|
||||
action='store_true',
|
||||
help='obfuscate file names; will not send file names to api')
|
||||
parser.add_argument('--ignore', dest='ignore', action='append',
|
||||
help='filename patterns to ignore; POSIX regex syntax; can be used more than once')
|
||||
parser.add_argument('--logfile', dest='logfile',
|
||||
@ -203,6 +211,8 @@ def parseArguments(argv):
|
||||
pass
|
||||
if args.offline and configs.has_option('settings', 'offline'):
|
||||
args.offline = configs.getboolean('settings', 'offline')
|
||||
if not args.hidefilenames and configs.has_option('settings', 'hidefilenames'):
|
||||
args.hidefilenames = configs.getboolean('settings', 'hidefilenames')
|
||||
if not args.verbose and configs.has_option('settings', 'verbose'):
|
||||
args.verbose = configs.getboolean('settings', 'verbose')
|
||||
if not args.verbose and configs.has_option('settings', 'debug'):
|
||||
@ -221,7 +231,10 @@ def should_ignore(fileName, patterns):
|
||||
if compiled.search(fileName):
|
||||
return pattern
|
||||
except re.error as ex:
|
||||
log.warning('Regex error (%s) for ignore pattern: %s' % (str(ex), pattern))
|
||||
log.warning(unicode('Regex error ({msg}) for ignore pattern: {pattern}').format(
|
||||
msg=str(ex),
|
||||
pattern=pattern,
|
||||
))
|
||||
except TypeError:
|
||||
pass
|
||||
return False
|
||||
@ -230,21 +243,34 @@ def should_ignore(fileName, patterns):
|
||||
def get_user_agent(plugin):
|
||||
ver = sys.version_info
|
||||
python_version = '%d.%d.%d.%s.%d' % (ver[0], ver[1], ver[2], ver[3], ver[4])
|
||||
user_agent = 'wakatime/%s (%s) Python%s' % (__version__,
|
||||
platform.platform(), python_version)
|
||||
user_agent = unicode('wakatime/{ver} ({platform}) Python{py_ver}').format(
|
||||
ver=__version__,
|
||||
platform=platform.platform(),
|
||||
py_ver=python_version,
|
||||
)
|
||||
if plugin:
|
||||
user_agent = user_agent+' '+plugin
|
||||
user_agent = unicode('{user_agent} {plugin}').format(
|
||||
user_agent=user_agent,
|
||||
plugin=plugin,
|
||||
)
|
||||
return user_agent
|
||||
|
||||
|
||||
def send_action(project=None, branch=None, stats=None, key=None, targetFile=None,
|
||||
timestamp=None, isWrite=None, plugin=None, offline=None, **kwargs):
|
||||
timestamp=None, isWrite=None, plugin=None, offline=None,
|
||||
hidefilenames=None, **kwargs):
|
||||
url = 'https://wakatime.com/api/v1/actions'
|
||||
log.debug('Sending action to api at %s' % url)
|
||||
log.debug('Sending heartbeat to api at %s' % url)
|
||||
data = {
|
||||
'time': timestamp,
|
||||
'file': targetFile,
|
||||
}
|
||||
if hidefilenames and targetFile is not None:
|
||||
data['file'] = data['file'].rsplit('/', 1)[-1].rsplit('\\', 1)[-1]
|
||||
if len(data['file'].strip('.').split('.', 1)) > 1:
|
||||
data['file'] = unicode('HIDDEN.{ext}').format(ext=data['file'].strip('.').rsplit('.', 1)[-1])
|
||||
else:
|
||||
data['file'] = unicode('HIDDEN')
|
||||
if stats.get('lines'):
|
||||
data['lines'] = stats['lines']
|
||||
if stats.get('language'):
|
||||
@ -261,13 +287,13 @@ def send_action(project=None, branch=None, stats=None, key=None, targetFile=None
|
||||
request = Request(url=url, data=str.encode(json.dumps(data)))
|
||||
request.add_header('User-Agent', get_user_agent(plugin))
|
||||
request.add_header('Content-Type', 'application/json')
|
||||
auth = 'Basic %s' % bytes.decode(base64.b64encode(str.encode(key)))
|
||||
auth = unicode('Basic {key}').format(key=bytes.decode(base64.b64encode(str.encode(key))))
|
||||
request.add_header('Authorization', auth)
|
||||
|
||||
# add Olson timezone to request
|
||||
tz = tzlocal.get_localzone()
|
||||
if tz:
|
||||
request.add_header('TimeZone', str(tz.zone))
|
||||
request.add_header('TimeZone', unicode(tz.zone))
|
||||
|
||||
# log time to api
|
||||
response = None
|
||||
@ -276,7 +302,7 @@ def send_action(project=None, branch=None, stats=None, key=None, targetFile=None
|
||||
except HTTPError as exc:
|
||||
exception_data = {
|
||||
'response_code': exc.getcode(),
|
||||
sys.exc_info()[0].__name__: str(sys.exc_info()[1]),
|
||||
sys.exc_info()[0].__name__: unicode(sys.exc_info()[1]),
|
||||
}
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data['traceback'] = traceback.format_exc()
|
||||
@ -289,7 +315,7 @@ def send_action(project=None, branch=None, stats=None, key=None, targetFile=None
|
||||
log.error(exception_data)
|
||||
except:
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: str(sys.exc_info()[1]),
|
||||
sys.exc_info()[0].__name__: unicode(sys.exc_info()[1]),
|
||||
}
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data['traceback'] = traceback.format_exc()
|
||||
@ -339,7 +365,9 @@ def main(argv=None):
|
||||
|
||||
ignore = should_ignore(args.targetFile, args.ignore)
|
||||
if ignore is not False:
|
||||
log.debug('File ignored because matches pattern: %s' % ignore)
|
||||
log.debug(unicode('File ignored because matches pattern: {pattern}').format(
|
||||
pattern=ignore,
|
||||
))
|
||||
return 0
|
||||
|
||||
if os.path.isfile(args.targetFile):
|
||||
@ -348,10 +376,10 @@ def main(argv=None):
|
||||
|
||||
project = find_project(args.targetFile, configs=configs)
|
||||
branch = None
|
||||
project_name = None
|
||||
project_name = args.project_name
|
||||
if project:
|
||||
branch = project.branch()
|
||||
project_name = args.project_name or project.name()
|
||||
project_name = project.name()
|
||||
|
||||
if send_action(
|
||||
project=project_name,
|
||||
@ -364,9 +392,16 @@ def main(argv=None):
|
||||
action = queue.pop()
|
||||
if action is None:
|
||||
break
|
||||
if not send_action(project=action['project'], targetFile=action['file'], timestamp=action['time'],
|
||||
branch=action['branch'], stats={'language': action['language'], 'lines': action['lines']},
|
||||
key=args.key, isWrite=action['is_write'], plugin=action['plugin'], offline=args.offline):
|
||||
sent = send_action(project=action['project'],
|
||||
targetFile=action['file'],
|
||||
timestamp=action['time'],
|
||||
branch=action['branch'],
|
||||
stats={'language': action['language'], 'lines': action['lines']},
|
||||
key=args.key, isWrite=action['is_write'],
|
||||
plugin=action['plugin'],
|
||||
offline=args.offline,
|
||||
hidefilenames=args.hidefilenames)
|
||||
if not sent:
|
||||
break
|
||||
return 0 # success
|
||||
|
||||
|
@ -73,7 +73,7 @@ def set_log_level(logger, args):
|
||||
|
||||
|
||||
def setup_logging(args, version):
|
||||
logger = logging.getLogger()
|
||||
logger = logging.getLogger('WakaTime')
|
||||
set_log_level(logger, args)
|
||||
if len(logger.handlers) > 0:
|
||||
formatter = JsonFormatter(datefmt='%Y/%m/%d %H:%M:%S %z')
|
||||
|
@ -19,7 +19,7 @@ from .projects.subversion import Subversion
|
||||
from .projects.wakatime import WakaTime
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
# List of plugin classes to find a project for the current file path.
|
||||
|
@ -13,7 +13,7 @@ import logging
|
||||
import os
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
class BaseProject(object):
|
||||
|
@ -15,7 +15,7 @@ import os
|
||||
from .base import BaseProject
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
# str is unicode in Python3
|
||||
|
@ -15,7 +15,7 @@ import os
|
||||
from .base import BaseProject
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
# str is unicode in Python3
|
||||
|
@ -26,7 +26,7 @@ import os
|
||||
from .base import BaseProject
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
# str is unicode in Python3
|
||||
|
@ -21,7 +21,7 @@ except ImportError:
|
||||
from ..packages.ordereddict import OrderedDict
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
# str is unicode in Python3
|
||||
@ -32,6 +32,7 @@ except NameError:
|
||||
|
||||
|
||||
class Subversion(BaseProject):
|
||||
binary_location = None
|
||||
|
||||
def process(self):
|
||||
return self._find_project_base(self.path)
|
||||
@ -44,13 +45,32 @@ class Subversion(BaseProject):
|
||||
unicode(os.path.basename(self.base))
|
||||
return None
|
||||
|
||||
def _find_binary(self):
|
||||
if self.binary_location:
|
||||
return self.binary_location
|
||||
locations = [
|
||||
'svn',
|
||||
'/usr/bin/svn',
|
||||
'/usr/local/bin/svn',
|
||||
]
|
||||
for location in locations:
|
||||
with open(os.devnull, 'wb') as DEVNULL:
|
||||
try:
|
||||
Popen([location, '--version'], stdout=DEVNULL, stderr=DEVNULL)
|
||||
self.binary_location = location
|
||||
return location
|
||||
except:
|
||||
pass
|
||||
self.binary_location = 'svn'
|
||||
return 'svn'
|
||||
|
||||
def _get_info(self, path):
|
||||
info = OrderedDict()
|
||||
stdout = None
|
||||
try:
|
||||
os.environ['LANG'] = 'en_US'
|
||||
stdout, stderr = Popen([
|
||||
'svn', 'info', os.path.realpath(path)
|
||||
self._find_binary(), 'info', os.path.realpath(path)
|
||||
], stdout=PIPE, stderr=PIPE).communicate()
|
||||
except OSError:
|
||||
pass
|
||||
|
@ -17,7 +17,7 @@ import os
|
||||
from .base import BaseProject
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
# str is unicode in Python3
|
||||
|
@ -22,7 +22,7 @@ except ImportError:
|
||||
HAS_SQL = False
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
class Queue(object):
|
||||
|
@ -20,7 +20,12 @@ else:
|
||||
from pygments.lexers import guess_lexer_for_filename
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
|
||||
# force file name extensions to be recognized as a certain language
|
||||
@ -54,7 +59,7 @@ def guess_language(file_name):
|
||||
except:
|
||||
pass
|
||||
if lexer:
|
||||
return translate_language(str(lexer.name))
|
||||
return translate_language(unicode(lexer.name))
|
||||
else:
|
||||
return None
|
||||
|
||||
|
Reference in New Issue
Block a user