From 3f65a3ab0cbef7d77fa1e648b9c02fad99bbb9b2 Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Tue, 29 Sep 2015 03:10:32 -0700 Subject: [PATCH] upgrade wakatime cli to v4.1.8 --- plugin/packages/wakatime/__about__.py | 2 +- .../{languages => dependencies}/__init__.py | 47 +++++---- .../packages/wakatime/dependencies/c_cpp.py | 68 +++++++++++++ .../{languages => dependencies}/data.py | 4 +- .../{languages => dependencies}/dotnet.py | 7 +- plugin/packages/wakatime/dependencies/jvm.py | 96 +++++++++++++++++++ .../{languages => dependencies}/php.py | 10 +- .../{languages => dependencies}/python.py | 61 +++--------- .../{languages => dependencies}/templates.py | 8 +- .../{languages => dependencies}/unknown.py | 2 +- plugin/packages/wakatime/exceptions.py | 14 +++ plugin/packages/wakatime/languages/c_cpp.py | 37 ------- plugin/packages/wakatime/languages/jvm.py | 36 ------- plugin/packages/wakatime/logger.py | 28 ++---- plugin/packages/wakatime/main.py | 31 ++++-- plugin/packages/wakatime/offlinequeue.py | 15 +-- plugin/packages/wakatime/projects/base.py | 8 +- .../packages/wakatime/projects/mercurial.py | 6 +- .../packages/wakatime/projects/subversion.py | 6 +- plugin/packages/wakatime/session_cache.py | 14 +-- plugin/packages/wakatime/stats.py | 17 ++-- 21 files changed, 296 insertions(+), 221 deletions(-) rename plugin/packages/wakatime/{languages => dependencies}/__init__.py (74%) create mode 100644 plugin/packages/wakatime/dependencies/c_cpp.py rename plugin/packages/wakatime/{languages => dependencies}/data.py (94%) rename plugin/packages/wakatime/{languages => dependencies}/dotnet.py (80%) create mode 100644 plugin/packages/wakatime/dependencies/jvm.py rename plugin/packages/wakatime/{languages => dependencies}/php.py (90%) rename plugin/packages/wakatime/{languages => dependencies}/python.py (54%) rename plugin/packages/wakatime/{languages => dependencies}/templates.py (95%) rename plugin/packages/wakatime/{languages => dependencies}/unknown.py (96%) create mode 100644 plugin/packages/wakatime/exceptions.py delete mode 100644 plugin/packages/wakatime/languages/c_cpp.py delete mode 100644 plugin/packages/wakatime/languages/jvm.py diff --git a/plugin/packages/wakatime/__about__.py b/plugin/packages/wakatime/__about__.py index 83a7115..aa701ee 100644 --- a/plugin/packages/wakatime/__about__.py +++ b/plugin/packages/wakatime/__about__.py @@ -1,7 +1,7 @@ __title__ = 'wakatime' __description__ = 'Common interface to the WakaTime api.' __url__ = 'https://github.com/wakatime/wakatime' -__version_info__ = ('4', '1', '6') +__version_info__ = ('4', '1', '8') __version__ = '.'.join(__version_info__) __author__ = 'Alan Hamlett' __author_email__ = 'alan@wakatime.com' diff --git a/plugin/packages/wakatime/languages/__init__.py b/plugin/packages/wakatime/dependencies/__init__.py similarity index 74% rename from plugin/packages/wakatime/languages/__init__.py rename to plugin/packages/wakatime/dependencies/__init__.py index 1982778..b02e70c 100644 --- a/plugin/packages/wakatime/languages/__init__.py +++ b/plugin/packages/wakatime/dependencies/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ - wakatime.languages - ~~~~~~~~~~~~~~~~~~ + wakatime.dependencies + ~~~~~~~~~~~~~~~~~~~~~ Parse dependencies from a source code file. @@ -10,10 +10,12 @@ """ import logging +import re import sys import traceback from ..compat import u, open, import_module +from ..exceptions import NotYetImplemented log = logging.getLogger('WakaTime') @@ -24,26 +26,28 @@ class TokenParser(object): language, inherit from this class and implement the :meth:`parse` method to return a list of dependency strings. """ - source_file = None - lexer = None - dependencies = [] - tokens = [] + exclude = [] def __init__(self, source_file, lexer=None): + self._tokens = None + self.dependencies = [] self.source_file = source_file self.lexer = lexer + self.exclude = [re.compile(x, re.IGNORECASE) for x in self.exclude] + + @property + def tokens(self): + if self._tokens is None: + self._tokens = self._extract_tokens() + return self._tokens def parse(self, tokens=[]): """ Should return a list of dependencies. """ - if not tokens and not self.tokens: - self.tokens = self._extract_tokens() - raise Exception('Not yet implemented.') + raise NotYetImplemented() def append(self, dep, truncate=False, separator=None, truncate_to=None, strip_whitespace=True): - if dep == 'as': - print('***************** as') self._save_dependency( dep, truncate=truncate, @@ -52,6 +56,9 @@ class TokenParser(object): strip_whitespace=strip_whitespace, ) + def partial(self, token): + return u(token).split('.')[-1] + def _extract_tokens(self): if self.lexer: try: @@ -73,13 +80,21 @@ class TokenParser(object): separator = u('.') separator = u(separator) dep = dep.split(separator) - if truncate_to is None or truncate_to < 0 or truncate_to > len(dep) - 1: - truncate_to = len(dep) - 1 - dep = dep[0] if len(dep) == 1 else separator.join(dep[0:truncate_to]) + if truncate_to is None or truncate_to < 1: + truncate_to = 1 + if truncate_to > len(dep): + truncate_to = len(dep) + dep = dep[0] if len(dep) == 1 else separator.join(dep[:truncate_to]) if strip_whitespace: dep = dep.strip() - if dep: - self.dependencies.append(dep) + if dep and (not separator or not dep.startswith(separator)): + should_exclude = False + for compiled in self.exclude: + if compiled.search(dep): + should_exclude = True + break + if not should_exclude: + self.dependencies.append(dep) class DependencyParser(object): diff --git a/plugin/packages/wakatime/dependencies/c_cpp.py b/plugin/packages/wakatime/dependencies/c_cpp.py new file mode 100644 index 0000000..ec62be0 --- /dev/null +++ b/plugin/packages/wakatime/dependencies/c_cpp.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +""" + wakatime.languages.c_cpp + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Parse dependencies from C++ code. + + :copyright: (c) 2014 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + +from . import TokenParser + + +class CppParser(TokenParser): + exclude = [ + r'^stdio\.h$', + r'^stdlib\.h$', + r'^string\.h$', + r'^time\.h$', + ] + + def parse(self): + for index, token, content in self.tokens: + self._process_token(token, content) + return self.dependencies + + def _process_token(self, token, content): + if self.partial(token) == 'Preproc': + self._process_preproc(token, content) + else: + self._process_other(token, content) + + def _process_preproc(self, token, content): + if content.strip().startswith('include ') or content.strip().startswith("include\t"): + content = content.replace('include', '', 1).strip().strip('"').strip('<').strip('>').strip() + self.append(content) + + def _process_other(self, token, content): + pass + + +class CParser(TokenParser): + exclude = [ + r'^stdio\.h$', + r'^stdlib\.h$', + r'^string\.h$', + r'^time\.h$', + ] + + def parse(self): + for index, token, content in self.tokens: + self._process_token(token, content) + return self.dependencies + + def _process_token(self, token, content): + if self.partial(token) == 'Preproc': + self._process_preproc(token, content) + else: + self._process_other(token, content) + + def _process_preproc(self, token, content): + if content.strip().startswith('include ') or content.strip().startswith("include\t"): + content = content.replace('include', '', 1).strip().strip('"').strip('<').strip('>').strip() + self.append(content) + + def _process_other(self, token, content): + pass diff --git a/plugin/packages/wakatime/languages/data.py b/plugin/packages/wakatime/dependencies/data.py similarity index 94% rename from plugin/packages/wakatime/languages/data.py rename to plugin/packages/wakatime/dependencies/data.py index 89cd790..389a1b6 100644 --- a/plugin/packages/wakatime/languages/data.py +++ b/plugin/packages/wakatime/dependencies/data.py @@ -26,10 +26,8 @@ class JsonParser(TokenParser): state = None level = 0 - def parse(self, tokens=[]): + def parse(self): self._process_file_name(os.path.basename(self.source_file)) - if not tokens and not self.tokens: - self.tokens = self._extract_tokens() for index, token, content in self.tokens: self._process_token(token, content) return self.dependencies diff --git a/plugin/packages/wakatime/languages/dotnet.py b/plugin/packages/wakatime/dependencies/dotnet.py similarity index 80% rename from plugin/packages/wakatime/languages/dotnet.py rename to plugin/packages/wakatime/dependencies/dotnet.py index 6178a40..c6456fb 100644 --- a/plugin/packages/wakatime/languages/dotnet.py +++ b/plugin/packages/wakatime/dependencies/dotnet.py @@ -10,20 +10,17 @@ """ from . import TokenParser -from ..compat import u class CSharpParser(TokenParser): - def parse(self, tokens=[]): - if not tokens and not self.tokens: - self.tokens = self._extract_tokens() + def parse(self): for index, token, content in self.tokens: self._process_token(token, content) return self.dependencies def _process_token(self, token, content): - if u(token).split('.')[-1] == 'Namespace': + if self.partial(token) == 'Namespace': self._process_namespace(token, content) else: self._process_other(token, content) diff --git a/plugin/packages/wakatime/dependencies/jvm.py b/plugin/packages/wakatime/dependencies/jvm.py new file mode 100644 index 0000000..3af3fcb --- /dev/null +++ b/plugin/packages/wakatime/dependencies/jvm.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +""" + wakatime.languages.java + ~~~~~~~~~~~~~~~~~~~~~~~ + + Parse dependencies from Java code. + + :copyright: (c) 2014 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + +from . import TokenParser +from ..compat import u + + +class JavaParser(TokenParser): + exclude = [ + r'^java\.', + r'^javax\.', + r'^import$', + r'^package$', + r'^namespace$', + r'^static$', + ] + state = None + buffer = u('') + + def parse(self): + for index, token, content in self.tokens: + self._process_token(token, content) + return self.dependencies + + def _process_token(self, token, content): + if self.partial(token) == 'Namespace': + self._process_namespace(token, content) + if self.partial(token) == 'Name': + self._process_name(token, content) + elif self.partial(token) == 'Attribute': + self._process_attribute(token, content) + elif self.partial(token) == 'Operator': + self._process_operator(token, content) + else: + self._process_other(token, content) + + def _process_namespace(self, token, content): + if u(content) == u('import'): + self.state = 'import' + + elif self.state == 'import': + keywords = [ + u('package'), + u('namespace'), + u('static'), + ] + if u(content) in keywords: + return + self.buffer = u('{0}{1}').format(self.buffer, u(content)) + + elif self.state == 'import-finished': + content = content.split(u('.')) + + if len(content) == 1: + self.append(content[0]) + + elif len(content) > 1: + if len(content[0]) == 3: + content = content[1:] + if content[-1] == u('*'): + content = content[:len(content) - 1] + + if len(content) == 1: + self.append(content[0]) + elif len(content) > 1: + self.append(u('.').join(content[:2])) + + self.state = None + + def _process_name(self, token, content): + if self.state == 'import': + self.buffer = u('{0}{1}').format(self.buffer, u(content)) + + def _process_attribute(self, token, content): + if self.state == 'import': + self.buffer = u('{0}{1}').format(self.buffer, u(content)) + + def _process_operator(self, token, content): + if u(content) == u(';'): + self.state = 'import-finished' + self._process_namespace(token, self.buffer) + self.state = None + self.buffer = u('') + elif self.state == 'import': + self.buffer = u('{0}{1}').format(self.buffer, u(content)) + + def _process_other(self, token, content): + pass diff --git a/plugin/packages/wakatime/languages/php.py b/plugin/packages/wakatime/dependencies/php.py similarity index 90% rename from plugin/packages/wakatime/languages/php.py rename to plugin/packages/wakatime/dependencies/php.py index b807f9d..728c75d 100644 --- a/plugin/packages/wakatime/languages/php.py +++ b/plugin/packages/wakatime/dependencies/php.py @@ -17,15 +17,13 @@ class PhpParser(TokenParser): state = None parens = 0 - def parse(self, tokens=[]): - if not tokens and not self.tokens: - self.tokens = self._extract_tokens() + def parse(self): for index, token, content in self.tokens: self._process_token(token, content) return self.dependencies def _process_token(self, token, content): - if u(token).split('.')[-1] == 'Keyword': + if self.partial(token) == 'Keyword': self._process_keyword(token, content) elif u(token) == 'Token.Literal.String.Single' or u(token) == 'Token.Literal.String.Double': self._process_literal_string(token, content) @@ -33,9 +31,9 @@ class PhpParser(TokenParser): self._process_name(token, content) elif u(token) == 'Token.Name.Function': self._process_function(token, content) - elif u(token).split('.')[-1] == 'Punctuation': + elif self.partial(token) == 'Punctuation': self._process_punctuation(token, content) - elif u(token).split('.')[-1] == 'Text': + elif self.partial(token) == 'Text': self._process_text(token, content) else: self._process_other(token, content) diff --git a/plugin/packages/wakatime/languages/python.py b/plugin/packages/wakatime/dependencies/python.py similarity index 54% rename from plugin/packages/wakatime/languages/python.py rename to plugin/packages/wakatime/dependencies/python.py index ea61279..65e9e47 100644 --- a/plugin/packages/wakatime/languages/python.py +++ b/plugin/packages/wakatime/dependencies/python.py @@ -10,33 +10,30 @@ """ from . import TokenParser -from ..compat import u class PythonParser(TokenParser): state = None parens = 0 nonpackage = False + exclude = [ + r'^os$', + r'^sys\.', + ] - def parse(self, tokens=[]): - if not tokens and not self.tokens: - self.tokens = self._extract_tokens() + def parse(self): for index, token, content in self.tokens: self._process_token(token, content) return self.dependencies def _process_token(self, token, content): - if u(token).split('.')[-1] == 'Namespace': + if self.partial(token) == 'Namespace': self._process_namespace(token, content) - elif u(token).split('.')[-1] == 'Name': - self._process_name(token, content) - elif u(token).split('.')[-1] == 'Word': - self._process_word(token, content) - elif u(token).split('.')[-1] == 'Operator': + elif self.partial(token) == 'Operator': self._process_operator(token, content) - elif u(token).split('.')[-1] == 'Punctuation': + elif self.partial(token) == 'Punctuation': self._process_punctuation(token, content) - elif u(token).split('.')[-1] == 'Text': + elif self.partial(token) == 'Text': self._process_text(token, content) else: self._process_other(token, content) @@ -50,38 +47,6 @@ class PythonParser(TokenParser): else: self._process_import(token, content) - def _process_name(self, token, content): - if self.state is not None: - if self.nonpackage: - self.nonpackage = False - else: - if self.state == 'from': - self.append(content, truncate=True, truncate_to=0) - if self.state == 'from-2' and content != 'import': - self.append(content, truncate=True, truncate_to=0) - elif self.state == 'import': - self.append(content, truncate=True, truncate_to=0) - elif self.state == 'import-2': - self.append(content, truncate=True, truncate_to=0) - else: - self.state = None - - def _process_word(self, token, content): - if self.state is not None: - if self.nonpackage: - self.nonpackage = False - else: - if self.state == 'from': - self.append(content, truncate=True, truncate_to=0) - if self.state == 'from-2' and content != 'import': - self.append(content, truncate=True, truncate_to=0) - elif self.state == 'import': - self.append(content, truncate=True, truncate_to=0) - elif self.state == 'import-2': - self.append(content, truncate=True, truncate_to=0) - else: - self.state = None - def _process_operator(self, token, content): if self.state is not None: if content == '.': @@ -106,15 +71,15 @@ class PythonParser(TokenParser): def _process_import(self, token, content): if not self.nonpackage: if self.state == 'from': - self.append(content, truncate=True, truncate_to=0) + self.append(content, truncate=True, truncate_to=1) self.state = 'from-2' elif self.state == 'from-2' and content != 'import': - self.append(content, truncate=True, truncate_to=0) + self.append(content, truncate=True, truncate_to=1) elif self.state == 'import': - self.append(content, truncate=True, truncate_to=0) + self.append(content, truncate=True, truncate_to=1) self.state = 'import-2' elif self.state == 'import-2': - self.append(content, truncate=True, truncate_to=0) + self.append(content, truncate=True, truncate_to=1) else: self.state = None self.nonpackage = False diff --git a/plugin/packages/wakatime/languages/templates.py b/plugin/packages/wakatime/dependencies/templates.py similarity index 95% rename from plugin/packages/wakatime/languages/templates.py rename to plugin/packages/wakatime/dependencies/templates.py index 69e5267..1430881 100644 --- a/plugin/packages/wakatime/languages/templates.py +++ b/plugin/packages/wakatime/dependencies/templates.py @@ -71,9 +71,7 @@ KEYWORDS = [ class LassoJavascriptParser(TokenParser): - def parse(self, tokens=[]): - if not tokens and not self.tokens: - self.tokens = self._extract_tokens() + def parse(self): for index, token, content in self.tokens: self._process_token(token, content) return self.dependencies @@ -99,9 +97,7 @@ class HtmlDjangoParser(TokenParser): current_attr = None current_attr_value = None - def parse(self, tokens=[]): - if not tokens and not self.tokens: - self.tokens = self._extract_tokens() + def parse(self): for index, token, content in self.tokens: self._process_token(token, content) return self.dependencies diff --git a/plugin/packages/wakatime/languages/unknown.py b/plugin/packages/wakatime/dependencies/unknown.py similarity index 96% rename from plugin/packages/wakatime/languages/unknown.py rename to plugin/packages/wakatime/dependencies/unknown.py index bfd1cb0..5d269eb 100644 --- a/plugin/packages/wakatime/languages/unknown.py +++ b/plugin/packages/wakatime/dependencies/unknown.py @@ -22,7 +22,7 @@ FILES = { class UnknownParser(TokenParser): - def parse(self, tokens=[]): + def parse(self): self._process_file_name(os.path.basename(self.source_file)) return self.dependencies diff --git a/plugin/packages/wakatime/exceptions.py b/plugin/packages/wakatime/exceptions.py new file mode 100644 index 0000000..e99ba90 --- /dev/null +++ b/plugin/packages/wakatime/exceptions.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" + wakatime.exceptions + ~~~~~~~~~~~~~~~~~~~ + + Custom exceptions. + + :copyright: (c) 2015 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + + +class NotYetImplemented(Exception): + """This method needs to be implemented.""" diff --git a/plugin/packages/wakatime/languages/c_cpp.py b/plugin/packages/wakatime/languages/c_cpp.py deleted file mode 100644 index 57a9af4..0000000 --- a/plugin/packages/wakatime/languages/c_cpp.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wakatime.languages.c_cpp - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Parse dependencies from C++ code. - - :copyright: (c) 2014 Alan Hamlett. - :license: BSD, see LICENSE for more details. -""" - -from . import TokenParser -from ..compat import u - - -class CppParser(TokenParser): - - def parse(self, tokens=[]): - if not tokens and not self.tokens: - self.tokens = self._extract_tokens() - for index, token, content in self.tokens: - self._process_token(token, content) - return self.dependencies - - def _process_token(self, token, content): - if u(token).split('.')[-1] == 'Preproc': - self._process_preproc(token, content) - else: - self._process_other(token, content) - - def _process_preproc(self, token, content): - if content.strip().startswith('include ') or content.strip().startswith("include\t"): - content = content.replace('include', '', 1).strip() - self.append(content) - - def _process_other(self, token, content): - pass diff --git a/plugin/packages/wakatime/languages/jvm.py b/plugin/packages/wakatime/languages/jvm.py deleted file mode 100644 index afbe0db..0000000 --- a/plugin/packages/wakatime/languages/jvm.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wakatime.languages.java - ~~~~~~~~~~~~~~~~~~~~~~~ - - Parse dependencies from Java code. - - :copyright: (c) 2014 Alan Hamlett. - :license: BSD, see LICENSE for more details. -""" - -from . import TokenParser -from ..compat import u - - -class JavaParser(TokenParser): - - def parse(self, tokens=[]): - if not tokens and not self.tokens: - self.tokens = self._extract_tokens() - for index, token, content in self.tokens: - self._process_token(token, content) - return self.dependencies - - def _process_token(self, token, content): - if u(token).split('.')[-1] == 'Namespace': - self._process_namespace(token, content) - else: - self._process_other(token, content) - - def _process_namespace(self, token, content): - if content != 'import' and content != 'package' and content != 'namespace': - self.append(content, truncate=True) - - def _process_other(self, token, content): - pass diff --git a/plugin/packages/wakatime/logger.py b/plugin/packages/wakatime/logger.py index d315e3b..19f8581 100644 --- a/plugin/packages/wakatime/logger.py +++ b/plugin/packages/wakatime/logger.py @@ -16,8 +16,8 @@ import sys from .compat import u try: from collections import OrderedDict # pragma: nocover -except ImportError: - from .packages.ordereddict import OrderedDict # pragma: nocover +except ImportError: # pragma: nocover + from .packages.ordereddict import OrderedDict try: from .packages import simplejson as json # pragma: nocover except (ImportError, SyntaxError): # pragma: nocover @@ -27,12 +27,12 @@ except (ImportError, SyntaxError): # pragma: nocover class CustomEncoder(json.JSONEncoder): def default(self, obj): - if isinstance(obj, bytes): - obj = bytes.decode(obj) + if isinstance(obj, bytes): # pragma: nocover + obj = u(obj) return json.dumps(obj) - try: + try: # pragma: nocover encoded = super(CustomEncoder, self).default(obj) - except UnicodeDecodeError: + except UnicodeDecodeError: # pragma: nocover obj = u(obj) encoded = super(CustomEncoder, self).default(obj) return encoded @@ -83,19 +83,9 @@ def set_log_level(logger, args): def setup_logging(args, version): logger = logging.getLogger('WakaTime') + for handler in logger.handlers: + logger.removeHandler(handler) set_log_level(logger, args) - if len(logger.handlers) > 0: - formatter = JsonFormatter(datefmt='%Y/%m/%d %H:%M:%S %z') - formatter.setup( - timestamp=args.timestamp, - isWrite=args.isWrite, - entity=args.entity, - version=version, - plugin=args.plugin, - verbose=args.verbose, - ) - logger.handlers[0].setFormatter(formatter) - return logger logfile = args.logfile if not logfile: logfile = '~/.wakatime.log' @@ -127,7 +117,7 @@ def setup_logging(args, version): logging.getLogger('py.warnings').addHandler(warnings_handler) try: logging.captureWarnings(True) - except AttributeError: + except AttributeError: # pragma: nocover pass # Python >= 2.7 is needed to capture warnings return logger diff --git a/plugin/packages/wakatime/main.py b/plugin/packages/wakatime/main.py index 551929d..306ad77 100644 --- a/plugin/packages/wakatime/main.py +++ b/plugin/packages/wakatime/main.py @@ -39,12 +39,12 @@ from .session_cache import SessionCache from .stats import get_file_stats try: from .packages import simplejson as json # pragma: nocover -except (ImportError, SyntaxError): - import json # pragma: nocover +except (ImportError, SyntaxError): # pragma: nocover + import json try: - from .packages import tzlocal # pragma: nocover + from .packages import tzlocal except: # pragma: nocover - from .packages import tzlocal3 as tzlocal # pragma: nocover + from .packages import tzlocal3 as tzlocal log = logging.getLogger('WakaTime') @@ -56,7 +56,7 @@ class FileAction(argparse.Action): try: if os.path.isfile(values): values = os.path.realpath(values) - except: + except: # pragma: nocover pass setattr(namespace, self.dest, values) @@ -146,6 +146,8 @@ def parseArguments(): 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') parser.add_argument('--config', dest='config', help='defaults to ~/.wakatime.conf') parser.add_argument('--verbose', dest='verbose', action='store_true', @@ -189,14 +191,14 @@ def parseArguments(): for pattern in configs.get('settings', 'ignore').split("\n"): if pattern.strip() != '': args.exclude.append(pattern) - except TypeError: + except TypeError: # pragma: nocover pass if configs.has_option('settings', 'exclude'): try: for pattern in configs.get('settings', 'exclude').split("\n"): if pattern.strip() != '': args.exclude.append(pattern) - except TypeError: + except TypeError: # pragma: nocover pass if not args.include: args.include = [] @@ -205,7 +207,7 @@ def parseArguments(): for pattern in configs.get('settings', 'include').split("\n"): if pattern.strip() != '': args.include.append(pattern) - except TypeError: + except TypeError: # pragma: nocover pass if args.offline and configs.has_option('settings', 'offline'): args.offline = configs.getboolean('settings', 'offline') @@ -221,6 +223,11 @@ def parseArguments(): args.logfile = configs.get('settings', 'logfile') if not args.api_url and configs.has_option('settings', 'api_url'): args.api_url = configs.get('settings', 'api_url') + if not args.timeout and configs.has_option('settings', 'timeout'): + try: + args.timeout = int(configs.get('settings', 'timeout')) + except ValueError: + print(traceback.format_exc()) return args, configs @@ -278,12 +285,14 @@ 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, offline=None, entity_type='file', - hidefilenames=None, proxy=None, api_url=None, **kwargs): + hidefilenames=None, proxy=None, api_url=None, timeout=None, **kwargs): """Sends heartbeat as POST request to WakaTime api server. """ if not api_url: api_url = 'https://wakatime.com/api/v1/heartbeats' + if not timeout: + timeout = 30 log.debug('Sending heartbeat to api at %s' % api_url) data = { 'time': timestamp, @@ -342,7 +351,7 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, response = None try: response = session.post(api_url, data=request_body, headers=headers, - proxies=proxies) + proxies=proxies, timeout=timeout) except RequestException: exception_data = { sys.exc_info()[0].__name__: u(sys.exc_info()[1]), @@ -425,6 +434,7 @@ def execute(argv=None): kwargs['branch'] = branch kwargs['stats'] = stats kwargs['hostname'] = args.hostname or socket.gethostname() + kwargs['timeout'] = args.timeout if send_heartbeat(**kwargs): queue = Queue() @@ -447,6 +457,7 @@ def execute(argv=None): entity_type=heartbeat['type'], proxy=args.proxy, api_url=args.api_url, + timeout=args.timeout, ) if not sent: break diff --git a/plugin/packages/wakatime/offlinequeue.py b/plugin/packages/wakatime/offlinequeue.py index 8870e26..9455e2b 100644 --- a/plugin/packages/wakatime/offlinequeue.py +++ b/plugin/packages/wakatime/offlinequeue.py @@ -31,8 +31,11 @@ class Queue(object): db_file = os.path.join(os.path.expanduser('~'), '.wakatime.db') table_name = 'heartbeat_1' + def get_db_file(self): + return self.db_file + def connect(self): - conn = sqlite3.connect(self.db_file) + conn = sqlite3.connect(self.get_db_file()) c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS {0} ( entity text, @@ -47,9 +50,8 @@ class Queue(object): '''.format(self.table_name)) return (conn, c) - def push(self, data, stats, plugin, misc=None): - if not HAS_SQL: + if not HAS_SQL: # pragma: nocover return try: conn, c = self.connect() @@ -70,9 +72,8 @@ class Queue(object): except sqlite3.Error: log.error(traceback.format_exc()) - def pop(self): - if not HAS_SQL: + if not HAS_SQL: # pragma: nocover return None tries = 3 wait = 0.1 @@ -96,12 +97,12 @@ class Queue(object): if row[index] is not None: clauses.append('{0}=?'.format(row_name)) values.append(row[index]) - else: + else: # pragma: nocover clauses.append('{0} IS NULL'.format(row_name)) index += 1 if len(values) > 0: c.execute('DELETE FROM {0} WHERE {1}'.format(self.table_name, ' AND '.join(clauses)), values) - else: + else: # pragma: nocover c.execute('DELETE FROM {0} WHERE {1}'.format(self.table_name, ' AND '.join(clauses))) conn.commit() if row is not None: diff --git a/plugin/packages/wakatime/projects/base.py b/plugin/packages/wakatime/projects/base.py index 882b035..4269d39 100644 --- a/plugin/packages/wakatime/projects/base.py +++ b/plugin/packages/wakatime/projects/base.py @@ -11,6 +11,8 @@ import logging +from ..exceptions import NotYetImplemented + log = logging.getLogger('WakaTime') @@ -30,14 +32,14 @@ class BaseProject(object): returns True if project is valid, otherwise returns False. """ - return False # pragma: nocover + raise NotYetImplemented() def name(self): """ Returns the project's name. """ - return None + raise NotYetImplemented() def branch(self): """ Returns the current branch. """ - return None # pragma: nocover + raise NotYetImplemented() diff --git a/plugin/packages/wakatime/projects/mercurial.py b/plugin/packages/wakatime/projects/mercurial.py index 11e15fa..7cb3e5e 100644 --- a/plugin/packages/wakatime/projects/mercurial.py +++ b/plugin/packages/wakatime/projects/mercurial.py @@ -29,7 +29,7 @@ class Mercurial(BaseProject): def name(self): if self.configDir: return u(os.path.basename(os.path.dirname(self.configDir))) - return None + return None # pragma: nocover def branch(self): if self.configDir: @@ -37,13 +37,13 @@ class Mercurial(BaseProject): try: with open(branch_file, 'r', encoding='utf-8') as fh: return u(fh.readline().strip().rsplit('/', 1)[-1]) - except UnicodeDecodeError: + except UnicodeDecodeError: # pragma: nocover try: with open(branch_file, 'r', encoding=sys.getfilesystemencoding()) as fh: return u(fh.readline().strip().rsplit('/', 1)[-1]) except: log.exception("Exception:") - except IOError: + except IOError: # pragma: nocover log.exception("Exception:") return u('default') diff --git a/plugin/packages/wakatime/projects/subversion.py b/plugin/packages/wakatime/projects/subversion.py index 1232f23..33617ad 100644 --- a/plugin/packages/wakatime/projects/subversion.py +++ b/plugin/packages/wakatime/projects/subversion.py @@ -18,7 +18,7 @@ from .base import BaseProject from ..compat import u, open try: from collections import OrderedDict -except ImportError: +except ImportError: # pragma: nocover from ..packages.ordereddict import OrderedDict # pragma: nocover @@ -33,12 +33,12 @@ class Subversion(BaseProject): def name(self): if 'Repository Root' not in self.info: - return None + return None # pragma: nocover return u(self.info['Repository Root'].split('/')[-1].split('\\')[-1]) def branch(self): if 'URL' not in self.info: - return None + return None # pragma: nocover return u(self.info['URL'].split('/')[-1].split('\\')[-1]) def _find_binary(self): diff --git a/plugin/packages/wakatime/session_cache.py b/plugin/packages/wakatime/session_cache.py index a2c0f83..ba519e9 100644 --- a/plugin/packages/wakatime/session_cache.py +++ b/plugin/packages/wakatime/session_cache.py @@ -46,8 +46,8 @@ class SessionCache(object): """Saves a requests.Session object for the next heartbeat process. """ - if not HAS_SQL: - return # pragma: nocover + if not HAS_SQL: # pragma: nocover + return try: conn, c = self.connect() c.execute('DELETE FROM session') @@ -67,14 +67,14 @@ class SessionCache(object): Gets Session from sqlite3 cache or creates a new Session. """ - if not HAS_SQL: + if not HAS_SQL: # pragma: nocover return requests.session() try: conn, c = self.connect() except: log.error(traceback.format_exc()) - return requests.session() # pragma: nocover + return requests.session() session = None try: @@ -83,12 +83,12 @@ class SessionCache(object): row = c.fetchone() if row is not None: session = pickle.loads(row[0]) - except: + except: # pragma: nocover log.error(traceback.format_exc()) try: conn.close() - except: + except: # pragma: nocover log.error(traceback.format_exc()) return session if session is not None else requests.session() @@ -98,7 +98,7 @@ class SessionCache(object): """Clears all cached Session objects. """ - if not HAS_SQL: + if not HAS_SQL: # pragma: nocover return try: conn, c = self.connect() diff --git a/plugin/packages/wakatime/stats.py b/plugin/packages/wakatime/stats.py index d409740..07b9bb7 100644 --- a/plugin/packages/wakatime/stats.py +++ b/plugin/packages/wakatime/stats.py @@ -14,11 +14,11 @@ import os import sys from .compat import u, open -from .languages import DependencyParser +from .dependencies import DependencyParser -if sys.version_info[0] == 2: +if sys.version_info[0] == 2: # pragma: nocover sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages', 'pygments_py2')) -else: +else: # pragma: nocover sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages', 'pygments_py3')) from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename from pygments.modeline import get_filetype_from_buffer @@ -35,11 +35,8 @@ def guess_language(file_name): """ language = get_language_from_extension(file_name) - if language: - return language, None - lexer = smart_guess_lexer(file_name) - if lexer: + if language is None and lexer is not None: language = u(lexer.name) return language, lexer @@ -78,7 +75,7 @@ def guess_lexer_using_filename(file_name, text): try: lexer = guess_lexer_for_filename(file_name, text) - except: + except: # pragma: nocover pass if lexer is not None: @@ -148,7 +145,7 @@ def number_lines_in_file(file_name): with open(file_name, 'r', encoding='utf-8') as fh: for line in fh: lines += 1 - except: + except: # pragma: nocover try: with open(file_name, 'r', encoding=sys.getfilesystemencoding()) as fh: for line in fh: @@ -189,7 +186,7 @@ def get_file_contents(file_name): try: with open(file_name, 'r', encoding='utf-8') as fh: text = fh.read(512000) - except: + except: # pragma: nocover try: with open(file_name, 'r', encoding=sys.getfilesystemencoding()) as fh: text = fh.read(512000)