diff --git a/plugin/packages/wakatime/__about__.py b/plugin/packages/wakatime/__about__.py index 9c2c562..6cbcb20 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', '3') +__version_info__ = ('4', '1', '4') __version__ = '.'.join(__version_info__) __author__ = 'Alan Hamlett' __author_email__ = 'alan@wakatime.com' diff --git a/plugin/packages/wakatime/__init__.py b/plugin/packages/wakatime/__init__.py index e6793b3..adaf85b 100644 --- a/plugin/packages/wakatime/__init__.py +++ b/plugin/packages/wakatime/__init__.py @@ -14,4 +14,4 @@ __all__ = ['main'] -from .base import main +from .main import execute diff --git a/plugin/packages/wakatime/cli.py b/plugin/packages/wakatime/cli.py index 6144906..9952f3f 100644 --- a/plugin/packages/wakatime/cli.py +++ b/plugin/packages/wakatime/cli.py @@ -32,4 +32,4 @@ except (TypeError, ImportError): if __name__ == '__main__': - sys.exit(wakatime.main(sys.argv[1:])) + sys.exit(wakatime.execute(sys.argv[1:])) diff --git a/plugin/packages/wakatime/logger.py b/plugin/packages/wakatime/logger.py index 6ac8ea0..b0732b2 100644 --- a/plugin/packages/wakatime/logger.py +++ b/plugin/packages/wakatime/logger.py @@ -40,11 +40,11 @@ class CustomEncoder(json.JSONEncoder): class JsonFormatter(logging.Formatter): - def setup(self, timestamp, isWrite, targetFile, version, plugin, verbose, + def setup(self, timestamp, isWrite, entity, version, plugin, verbose, warnings=False): self.timestamp = timestamp self.isWrite = isWrite - self.targetFile = targetFile + self.entity = entity self.version = version self.plugin = plugin self.verbose = verbose @@ -61,7 +61,7 @@ class JsonFormatter(logging.Formatter): data['caller'] = record.pathname data['lineno'] = record.lineno data['isWrite'] = self.isWrite - data['file'] = self.targetFile + data['file'] = self.entity if not self.isWrite: del data['isWrite'] data['level'] = record.levelname @@ -89,7 +89,7 @@ def setup_logging(args, version): formatter.setup( timestamp=args.timestamp, isWrite=args.isWrite, - targetFile=args.targetFile, + entity=args.entity, version=version, plugin=args.plugin, verbose=args.verbose, @@ -104,7 +104,7 @@ def setup_logging(args, version): formatter.setup( timestamp=args.timestamp, isWrite=args.isWrite, - targetFile=args.targetFile, + entity=args.entity, version=version, plugin=args.plugin, verbose=args.verbose, @@ -116,7 +116,7 @@ def setup_logging(args, version): warnings_formatter.setup( timestamp=args.timestamp, isWrite=args.isWrite, - targetFile=args.targetFile, + entity=args.entity, version=version, plugin=args.plugin, verbose=args.verbose, diff --git a/plugin/packages/wakatime/base.py b/plugin/packages/wakatime/main.py similarity index 88% rename from plugin/packages/wakatime/base.py rename to plugin/packages/wakatime/main.py index 4452bb2..be2c7bc 100644 --- a/plugin/packages/wakatime/base.py +++ b/plugin/packages/wakatime/main.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ - wakatime.base + wakatime.main ~~~~~~~~~~~~~ wakatime module entry point. @@ -53,7 +53,11 @@ log = logging.getLogger('WakaTime') class FileAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): - values = os.path.realpath(values) + try: + if os.path.isfile(values): + values = os.path.realpath(values) + except: + pass setattr(namespace, self.dest, values) @@ -88,9 +92,12 @@ def parseArguments(): # define supported command line arguments parser = argparse.ArgumentParser( description='Common interface for the WakaTime api.') - parser.add_argument('--file', dest='targetFile', metavar='file', - action=FileAction, required=True, - help='absolute path to file for current heartbeat') + 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') + 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') @@ -109,9 +116,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('--notfile', dest='notfile', action='store_true', - help='when set, will accept any value for the file. for example, '+ - 'a domain name or other item you want to log time towards.') + parser.add_argument('--entitytype', dest='entity_type', + help='entity type for this heartbeat. can be one of "file", '+ + '"url", "domain", or "app"; defaults to file.') parser.add_argument('--proxy', dest='proxy', help='optional https proxy url; for example: '+ 'https://user:pass@localhost:8080') @@ -168,6 +175,13 @@ 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 + else: + parser.error('argument --entity is required') if not args.exclude: args.exclude = [] if configs.has_option('settings', 'ignore'): @@ -211,13 +225,13 @@ def parseArguments(): return args, configs -def should_exclude(fileName, include, exclude): - if fileName is not None and fileName.strip() != '': +def should_exclude(entity, include, exclude): + if entity is not None and entity.strip() != '': try: for pattern in include: try: compiled = re.compile(pattern, re.IGNORECASE) - if compiled.search(fileName): + if compiled.search(entity): return False except re.error as ex: log.warning(u('Regex error ({msg}) for include pattern: {pattern}').format( @@ -230,7 +244,7 @@ def should_exclude(fileName, include, exclude): for pattern in exclude: try: compiled = re.compile(pattern, re.IGNORECASE) - if compiled.search(fileName): + if compiled.search(entity): return pattern except re.error as ex: log.warning(u('Regex error ({msg}) for exclude pattern: {pattern}').format( @@ -262,8 +276,8 @@ def get_user_agent(plugin): return user_agent -def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, targetFile=None, - timestamp=None, isWrite=None, plugin=None, offline=None, notfile=False, +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): """Sends heartbeat as POST request to WakaTime api server. """ @@ -273,10 +287,10 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, log.debug('Sending heartbeat to api at %s' % api_url) data = { 'time': timestamp, - 'entity': targetFile, - 'type': 'file', + 'entity': entity, + 'type': entity_type, } - if hidefilenames and targetFile is not None and not notfile: + if hidefilenames and entity is not None and entity_type == 'file': extension = u(os.path.splitext(data['entity'])[1]) data['entity'] = u('HIDDEN{0}').format(extension) if stats.get('lines'): @@ -379,7 +393,7 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, return False -def main(argv): +def execute(argv): sys.argv = ['wakatime'] + argv args, configs = parseArguments() @@ -388,21 +402,22 @@ def main(argv): setup_logging(args, __version__) - exclude = should_exclude(args.targetFile, args.include, args.exclude) + exclude = should_exclude(args.entity, args.include, args.exclude) if exclude is not False: - log.debug(u('File not logged because matches exclude pattern: {pattern}').format( + log.debug(u('Skipping because matches exclude pattern: {pattern}').format( pattern=u(exclude), )) return 0 - if os.path.isfile(args.targetFile) or args.notfile: + if args.entity_type != 'file' or os.path.isfile(args.entity): - stats = get_file_stats(args.targetFile, notfile=args.notfile, + stats = get_file_stats(args.entity, entity_type=args.entity_type, lineno=args.lineno, cursorpos=args.cursorpos) - project, branch = None, None - if not args.notfile: - project, branch = get_project_info(configs=configs, args=args) + project = args.project or args.alternate_project + branch = None + if args.entity_type == 'file': + project, branch = get_project_info(configs, args) kwargs = vars(args) kwargs['project'] = project @@ -418,7 +433,7 @@ def main(argv): break sent = send_heartbeat( project=heartbeat['project'], - targetFile=heartbeat['file'], + entity=heartbeat['entity'], timestamp=heartbeat['time'], branch=heartbeat['branch'], hostname=kwargs['hostname'], @@ -428,7 +443,7 @@ def main(argv): plugin=heartbeat['plugin'], offline=args.offline, hidefilenames=args.hidefilenames, - notfile=args.notfile, + entity_type=heartbeat['type'], proxy=args.proxy, api_url=args.api_url, ) diff --git a/plugin/packages/wakatime/offlinequeue.py b/plugin/packages/wakatime/offlinequeue.py index 3ef3d30..09efe41 100644 --- a/plugin/packages/wakatime/offlinequeue.py +++ b/plugin/packages/wakatime/offlinequeue.py @@ -28,13 +28,15 @@ log = logging.getLogger('WakaTime') class Queue(object): - DB_FILE = os.path.join(os.path.expanduser('~'), '.wakatime.db') + db_file = os.path.join(os.path.expanduser('~'), '.wakatime.db') + table_name = 'heartbeat_1' def connect(self): - conn = sqlite3.connect(self.DB_FILE) + conn = sqlite3.connect(self.db_file) c = conn.cursor() - c.execute('''CREATE TABLE IF NOT EXISTS heartbeat ( - file text, + c.execute('''CREATE TABLE IF NOT EXISTS {0} ( + entity text, + type text, time real, project text, branch text, @@ -42,7 +44,7 @@ class Queue(object): stats text, misc text, plugin text) - ''') + '''.format(self.table_name)) return (conn, c) @@ -52,7 +54,8 @@ class Queue(object): try: conn, c = self.connect() heartbeat = { - 'file': u(data.get('entity')), + 'entity': u(data.get('entity')), + 'type': u(data.get('type')), 'time': data.get('time'), 'project': u(data.get('project')), 'branch': u(data.get('branch')), @@ -61,7 +64,7 @@ class Queue(object): 'misc': u(misc), 'plugin': u(plugin), } - c.execute('INSERT INTO heartbeat VALUES (:file,:time,:project,:branch,:is_write,:stats,:misc,:plugin)', heartbeat) + c.execute('INSERT INTO {0} VALUES (:entity,:type,:time,:project,:branch,:is_write,:stats,:misc,:plugin)'.format(self.table_name), heartbeat) conn.commit() conn.close() except sqlite3.Error: @@ -83,13 +86,13 @@ class Queue(object): while loop and tries > -1: try: c.execute('BEGIN IMMEDIATE') - c.execute('SELECT * FROM heartbeat LIMIT 1') + c.execute('SELECT * FROM {0} LIMIT 1'.format(self.table_name)) row = c.fetchone() if row is not None: values = [] clauses = [] index = 0 - for row_name in ['file', 'time', 'project', 'branch', 'is_write']: + for row_name in ['entity', 'type', 'time', 'project', 'branch', 'is_write']: if row[index] is not None: clauses.append('{0}=?'.format(row_name)) values.append(row[index]) @@ -97,20 +100,21 @@ class Queue(object): clauses.append('{0} IS NULL'.format(row_name)) index += 1 if len(values) > 0: - c.execute('DELETE FROM heartbeat WHERE {0}'.format(' AND '.join(clauses)), values) + c.execute('DELETE FROM {0} WHERE {1}'.format(self.table_name, ' AND '.join(clauses)), values) else: - c.execute('DELETE FROM heartbeat WHERE {0}'.format(' AND '.join(clauses))) + c.execute('DELETE FROM {0} WHERE {1}'.format(self.table_name, ' AND '.join(clauses))) conn.commit() if row is not None: heartbeat = { - 'file': row[0], - 'time': row[1], - 'project': row[2], - 'branch': row[3], - 'is_write': True if row[4] is 1 else False, - 'stats': row[5], - 'misc': row[6], - 'plugin': row[7], + 'entity': row[0], + 'type': row[1], + 'time': row[2], + 'project': row[3], + 'branch': row[4], + 'is_write': True if row[5] is 1 else False, + 'stats': row[6], + 'misc': row[7], + 'plugin': row[8], } loop = False except sqlite3.Error: diff --git a/plugin/packages/wakatime/project.py b/plugin/packages/wakatime/project.py index bb8b503..f3299fe 100644 --- a/plugin/packages/wakatime/project.py +++ b/plugin/packages/wakatime/project.py @@ -33,7 +33,7 @@ REV_CONTROL_PLUGINS = [ ] -def get_project_info(configs=None, args=None): +def get_project_info(configs, args): """Find the current project and branch. First looks for a .wakatime-project file. Second, uses the --project arg. @@ -50,9 +50,9 @@ def get_project_info(configs=None, args=None): plugin_name = plugin_cls.__name__.lower() plugin_configs = get_configs_for_plugin(plugin_name, configs) - project = plugin_cls(args.targetFile, configs=plugin_configs) + project = plugin_cls(args.entity, configs=plugin_configs) if project.process(): - project_name = project.name() + project_name = project_name or project.name() branch_name = project.branch() break @@ -66,7 +66,7 @@ def get_project_info(configs=None, args=None): plugin_name = plugin_cls.__name__.lower() plugin_configs = get_configs_for_plugin(plugin_name, configs) - project = plugin_cls(args.targetFile, configs=plugin_configs) + project = plugin_cls(args.entity, configs=plugin_configs) if project.process(): project_name = project_name or project.name() branch_name = branch_name or project.branch() diff --git a/plugin/packages/wakatime/stats.py b/plugin/packages/wakatime/stats.py index db424c0..e02a0eb 100644 --- a/plugin/packages/wakatime/stats.py +++ b/plugin/packages/wakatime/stats.py @@ -153,8 +153,8 @@ def number_lines_in_file(file_name): return lines -def get_file_stats(file_name, notfile=False, lineno=None, cursorpos=None): - if notfile: +def get_file_stats(file_name, entity_type='file', lineno=None, cursorpos=None): + if entity_type != 'file': stats = { 'language': None, 'dependencies': [],