rana-cli/wakatime/__init__.py

272 lines
8.6 KiB
Python
Raw Normal View History

2013-07-06 07:51:09 +00:00
# -*- coding: utf-8 -*-
"""
wakatime
~~~~~~~~
Action event appender for Wakati.Me, auto time tracking for text editors.
2013-07-06 07:51:09 +00:00
:copyright: (c) 2013 Alan Hamlett.
:license: BSD, see LICENSE for more details.
"""
from __future__ import print_function
__title__ = 'wakatime'
2013-11-13 18:43:42 +00:00
__version__ = '0.4.10'
2013-07-06 07:51:09 +00:00
__author__ = 'Alan Hamlett'
__license__ = 'BSD'
__copyright__ = 'Copyright 2013 Alan Hamlett'
import base64
import logging
import os
import platform
import re
import sys
import time
import traceback
from ConfigParser import RawConfigParser
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages'))
2013-07-06 07:51:09 +00:00
from .log import setup_logging
from .project import find_project
from .stats import get_file_stats
2013-09-07 05:46:52 +00:00
from .packages import argparse
from .packages import simplejson as json
2013-10-01 04:55:19 +00:00
from .packages import tzlocal
try:
from urllib2 import HTTPError, Request, urlopen
except ImportError:
from urllib.error import HTTPError
from urllib.request import Request, urlopen
2013-07-06 07:51:09 +00:00
log = logging.getLogger(__name__)
class FileAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
values = os.path.realpath(values)
setattr(namespace, self.dest, values)
def checkUpdateConfigFile(configFile):
"""Checks if the config has a header section, if not add it for ConfigParser"""
with open(configFile) as fh:
configData = fh.read()
if not configData.strip().startswith('[settings]'):
configData = "[settings]\n" + configData.strip()
with open(configFile, 'w') as fh:
fh.write(configData)
2013-07-06 07:51:09 +00:00
def parseConfigFile(configFile):
if not configFile:
configFile = os.path.join(os.path.expanduser('~'), '.wakatime.conf')
checkUpdateConfigFile(configFile)
# define default config values
defaults = {
'settings' : {
'api_key': None,
'ignore': [],
'verbose': False
},
}
if not os.path.isfile(configFile):
return configs
try:
with open(configFile) as fh:
configs = RawConfigParser()
setConfigDefaults(configs, defaults)
configs.readfp(fh)
except IOError:
print('Error: Could not read from config file ~/.wakatime.conf')
return configs
def setConfigDefaults(config, defaults):
for section, values in defaults.iteritems():
if not config.has_section(section):
config.add_section(section)
for key, value in values.iteritems():
config.set(section, key, value)
def parseArguments(argv):
try:
sys.argv
except AttributeError:
sys.argv = argv
2013-07-06 07:51:09 +00:00
parser = argparse.ArgumentParser(
description='Wakati.Me event api appender')
parser.add_argument('--file', dest='targetFile', metavar='file',
action=FileAction, required=True,
help='absolute path to file for current action')
2013-07-06 07:51:09 +00:00
parser.add_argument('--time', dest='timestamp', metavar='time',
type=float,
help='optional floating-point unix epoch timestamp; '+
'uses current time by default')
parser.add_argument('--write', dest='isWrite',
action='store_true',
help='note action was triggered from writing to a file')
2013-07-06 07:51:09 +00:00
parser.add_argument('--plugin', dest='plugin',
help='optional text editor plugin name and version '+
'for User-Agent header')
parser.add_argument('--key', dest='key',
help='your wakati.me api key; uses api_key from '+
'~/.wakatime.conf by default')
parser.add_argument('--ignore', dest='ignore', action='append',
help='filename patterns to ignore; POSIX regex syntax; can be used more than once')
2013-07-06 07:51:09 +00:00
parser.add_argument('--logfile', dest='logfile',
help='defaults to ~/.wakatime.log')
parser.add_argument('--config', dest='config',
help='defaults to ~/.wakatime.conf')
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__)
args = parser.parse_args(args=argv[1:])
2013-07-06 07:51:09 +00:00
if not args.timestamp:
args.timestamp = time.time()
# set arguments from config file
configs = parseConfigFile(args.config)
2013-07-06 07:51:09 +00:00
if not args.key:
default_key = configs.get('settings', 'api_key')
2013-07-06 07:51:09 +00:00
if default_key:
args.key = default_key
else:
parser.error('Missing api key')
for pattern in configs.get('settings', 'ignore'):
if not args.ignore:
args.ignore = []
args.ignore.append(pattern)
if not args.verbose and configs.has_option('settings', 'verbose'):
args.verbose = configs.getboolean('settings', 'verbose')
if not args.logfile and configs.has_option('settings', 'logfile'):
args.logfile = configs.get('settings', 'logfile')
2013-07-06 07:51:09 +00:00
return args
def should_ignore(fileName, patterns):
try:
for pattern in patterns:
try:
compiled = re.compile(pattern, re.IGNORECASE)
if compiled.search(fileName):
return pattern
except re.error as ex:
log.warning('Regex error (%s) for ignore pattern: %s' % (str(ex), pattern))
except TypeError:
pass
return False
2013-07-06 07:51:09 +00:00
def get_user_agent(plugin):
2013-07-24 03:31:36 +00:00
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)
2013-07-06 07:51:09 +00:00
if plugin:
user_agent = user_agent+' '+plugin
2013-07-06 07:51:09 +00:00
return user_agent
def send_action(project=None, branch=None, stats={}, key=None, targetFile=None,
2013-09-22 21:56:30 +00:00
timestamp=None, isWrite=None, plugin=None, **kwargs):
2013-07-07 23:33:11 +00:00
url = 'https://www.wakati.me/api/v1/actions'
log.debug('Sending action to api at %s' % url)
2013-07-06 07:51:09 +00:00
data = {
'time': timestamp,
2013-07-06 07:51:09 +00:00
'file': targetFile,
}
if stats.get('lines'):
data['lines'] = stats['lines']
if stats.get('language'):
data['language'] = stats['language']
2013-07-06 07:51:09 +00:00
if isWrite:
data['is_write'] = isWrite
if project:
data['project'] = project
2013-08-15 07:09:27 +00:00
if branch:
data['branch'] = branch
2013-07-06 07:51:09 +00:00
log.debug(data)
# setup api request
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')
2013-07-30 06:04:43 +00:00
auth = 'Basic %s' % bytes.decode(base64.b64encode(str.encode(key)))
request.add_header('Authorization', auth)
2013-10-01 04:55:19 +00:00
# add Olson timezone to request
tz = tzlocal.get_localzone()
if tz:
request.add_header('TimeZone', str(tz.zone))
# log time to api
response = None
2013-07-06 07:51:09 +00:00
try:
response = urlopen(request)
except HTTPError as exc:
exception_data = {
'response_code': exc.getcode(),
sys.exc_info()[0].__name__: str(sys.exc_info()[1]),
}
if log.isEnabledFor(logging.DEBUG):
exception_data['traceback'] = traceback.format_exc()
log.error(exception_data)
except:
exception_data = {
2013-07-06 07:51:09 +00:00
sys.exc_info()[0].__name__: str(sys.exc_info()[1]),
}
if log.isEnabledFor(logging.DEBUG):
exception_data['traceback'] = traceback.format_exc()
log.error(exception_data)
2013-07-06 07:51:09 +00:00
else:
if response.getcode() == 201:
log.debug({
'response_code': response.getcode(),
})
return True
log.error({
'response_code': response.getcode(),
'response_content': response.read(),
2013-07-06 07:51:09 +00:00
})
return False
def main(argv=None):
if not argv:
argv = sys.argv
args = parseArguments(argv)
2013-07-06 07:51:09 +00:00
setup_logging(args, __version__)
ignore = should_ignore(args.targetFile, args.ignore)
if ignore is not False:
log.debug('File ignored because matches pattern: %s' % ignore)
return 0
2013-07-06 07:51:09 +00:00
if os.path.isfile(args.targetFile):
2013-08-15 07:09:27 +00:00
branch = None
name = None
stats = get_file_stats(args.targetFile)
project = find_project(args.targetFile, args.config)
if project:
2013-08-15 07:09:27 +00:00
branch = project.branch()
name = project.name()
if send_action(
project=name,
branch=branch,
stats=stats,
**vars(args)
):
2013-07-07 23:33:11 +00:00
return 0
2013-07-06 07:51:09 +00:00
return 102
else:
log.debug('File does not exist; ignoring this action.')
2013-07-06 07:51:09 +00:00
return 101