updated wakatime.py package. using new usage logic for better actions accuracy.

This commit is contained in:
Alan Hamlett 2013-07-10 00:14:44 -07:00
parent eb1e3f72db
commit a9e0bdb3fe
9 changed files with 180 additions and 83 deletions

View File

@ -1,4 +1,4 @@
sublime-wakatime 0.1.0
sublime-wakatime
================
automatic time tracking for Sublime Text 2 & 3 using https://wakati.me

View File

@ -27,15 +27,13 @@ class CustomEncoder(json.JSONEncoder):
class JsonFormatter(logging.Formatter):
def __init__(self, timestamp, endtime, isWrite, targetFile, version,
plugin, datefmt=None):
def setup(self, timestamp, endtime, isWrite, targetFile, version, plugin):
self.timestamp = timestamp
self.endtime = endtime
self.isWrite = isWrite
self.targetFile = targetFile
self.version = version
self.plugin = plugin
super(JsonFormatter, self).__init__(datefmt=datefmt)
def format(self, record):
data = OrderedDict([
@ -66,14 +64,14 @@ def setup_logging(args, version):
if not logfile:
logfile = '~/.wakatime.log'
handler = logging.FileHandler(os.path.expanduser(logfile))
formatter = JsonFormatter(
formatter = JsonFormatter(datefmt='%Y-%m-%dT%H:%M:%SZ')
formatter.setup(
timestamp=args.timestamp,
endtime=args.endtime,
isWrite=args.isWrite,
targetFile=args.targetFile,
version=version,
plugin=args.plugin,
datefmt='%Y-%m-%dT%H:%M:%SZ',
)
handler.setFormatter(formatter)
logger = logging.getLogger()

View File

@ -30,6 +30,6 @@ PLUGINS = [
def find_project(path):
for plugin in PLUGINS:
project = plugin(path)
if project.config:
if project.process():
return project
return BaseProject(path)

View File

@ -16,32 +16,39 @@ import os
log = logging.getLogger(__name__)
class BaseProject():
class BaseProject(object):
""" Parent project class only
used when no valid project can
be found for the current path.
"""
def __init__(self, path):
self.path = path
self.config = self.findConfig(path)
def name(self):
base = self.base()
if base:
return os.path.basename(base)
return None
def type(self):
""" Returns None if this is the base class.
Returns the type of project if this is a
valid project.
"""
type = self.__class__.__name__.lower()
if type == 'baseproject':
type = None
return type
def base(self):
if self.config:
return os.path.dirname(self.config)
def process(self):
""" Processes self.path into a project and
returns True if project is valid, otherwise
returns False.
"""
return False
def name(self):
""" Returns the project's name.
"""
return None
def tags(self):
tags = []
return tags
def findConfig(self, path):
return ''
""" Returns an array of tag strings for the
path and/or project.
"""
return []

View File

@ -11,13 +11,10 @@
import logging
import os
from subprocess import Popen, PIPE
from .base import BaseProject
try:
from collections import OrderedDict
except ImportError:
from ..packages.ordereddict import OrderedDict
from ..packages.ordereddict import OrderedDict
log = logging.getLogger(__name__)
@ -25,21 +22,54 @@ log = logging.getLogger(__name__)
class Git(BaseProject):
def base(self):
def process(self):
self.config = self._find_config(self.path)
if self.config:
return os.path.dirname(os.path.dirname(self.config))
return True
return False
def name(self):
base = self._project_base()
if base:
return os.path.basename(base)
return None
def tags(self):
tags = []
if self.config:
sections = self.parseConfig()
base = self._project_base()
if base:
tags.append(base)
sections = self._parse_config()
for section in sections:
if section.split(' ', 1)[0] == 'remote' and 'url' in sections[section]:
tags.append(sections[section]['url'])
branch = self._current_branch()
if branch is not None:
tags.append(branch)
return tags
def findConfig(self, path):
def _project_base(self):
if self.config:
return os.path.dirname(os.path.dirname(self.config))
return None
def _current_branch(self):
stdout = None
try:
stdout, stderr = Popen([
'git', 'branch', '--no-color', '--list'
], stdout=PIPE, cwd=self._project_base()).communicate()
except OSError:
pass
if stdout:
for line in stdout.splitlines():
line = line.split(' ', 1)
if line[0] == '*':
return line[1]
return None
def _find_config(self, path):
path = os.path.realpath(path)
if os.path.isfile(path):
path = os.path.split(path)[0]
@ -48,9 +78,9 @@ class Git(BaseProject):
split_path = os.path.split(path)
if split_path[1] == '':
return None
return self.findConfig(split_path[0])
return self._find_config(split_path[0])
def parseConfig(self):
def _parse_config(self):
sections = {}
try:
f = open(self.config, 'r')

View File

@ -20,9 +20,11 @@ log = logging.getLogger(__name__)
class Mercurial(BaseProject):
def base(self):
return super(Mercurial, self).base()
def process(self):
return False
def name(self):
return None
def tags(self):
tags = []
return tags
return []

View File

@ -11,8 +11,10 @@
import logging
import os
from subprocess import Popen, PIPE
from .base import BaseProject
from ..packages.ordereddict import OrderedDict
log = logging.getLogger(__name__)
@ -20,9 +22,42 @@ log = logging.getLogger(__name__)
class Subversion(BaseProject):
def base(self):
return super(Subversion, self).base()
def process(self):
self.info = self._get_info()
if 'Repository Root' in self.info:
return True
return False
def name(self):
return self.info['Repository Root'].split('/')[-1]
def _get_info(self):
info = OrderedDict()
stdout = None
try:
stdout, stderr = Popen([
'svn', 'info', os.path.realpath(self.path)
], stdout=PIPE).communicate()
except OSError:
pass
else:
if stdout:
interesting = [
'Repository Root',
'Repository UUID',
'URL',
]
for line in stdout.splitlines():
line = line.split(': ', 1)
if line[0] in interesting:
info[line[0]] = line[1]
return info
def tags(self):
tags = []
for key in self.info:
if key == 'Repository UUID':
tags.append(self.info[key])
if key == 'URL':
tags.append(os.path.dirname(self.info[key]))
return tags

View File

@ -12,7 +12,7 @@
from __future__ import print_function
__title__ = 'wakatime'
__version__ = '0.1.1'
__version__ = '0.1.2'
__author__ = 'Alan Hamlett'
__license__ = 'BSD'
__copyright__ = 'Copyright 2013 Alan Hamlett'
@ -97,10 +97,6 @@ def parseArguments():
args.key = default_key
else:
parser.error('Missing api key')
if args.endtime and args.endtime < args.timestamp:
tmp = args.timestamp
args.timestamp = args.endtime
args.endtime = tmp
return args

View File

@ -16,15 +16,16 @@ import sublime
import sublime_plugin
# Prompt user if no activity for this many minutes
AWAY_MINUTES = 5
# globals
AWAY_MINUTES = 10
ACTION_FREQUENCY = 5
PLUGIN_DIR = dirname(realpath(__file__))
API_CLIENT = '%s/packages/wakatime/wakatime.py' % PLUGIN_DIR
LAST_ACTION = 0
LAST_USAGE = 0
LAST_FILE = None
# To be backwards compatible, rename config file
if isfile(expanduser('~/.wakatime')):
call([
@ -35,7 +36,7 @@ if isfile(expanduser('~/.wakatime')):
def api(targetFile, timestamp, isWrite=False, endtime=None):
global LAST_ACTION, LAST_FILE
global LAST_ACTION, LAST_USAGE, LAST_FILE
if not targetFile:
targetFile = LAST_FILE
if targetFile:
@ -43,6 +44,7 @@ def api(targetFile, timestamp, isWrite=False, endtime=None):
'--file', targetFile,
'--time', str('%f' % timestamp),
'--plugin', 'sublime-wakatime/%s' % __version__,
#'--verbose',
]
if isWrite:
cmd.append('--write')
@ -53,53 +55,80 @@ def api(targetFile, timestamp, isWrite=False, endtime=None):
if endtime and endtime > LAST_ACTION:
LAST_ACTION = endtime
LAST_FILE = targetFile
LAST_USAGE = LAST_ACTION
def away(now):
if LAST_ACTION == 0:
return False
duration = now - LAST_ACTION
if duration > AWAY_MINUTES * 60:
duration = int(duration)
units = 'seconds'
if duration > 59:
duration = int(duration / 60.0)
units = 'minutes'
if duration > 59:
duration = int(duration / 60.0)
units = 'hours'
if duration > 24:
duration = int(duration / 24.0)
units = 'days'
return sublime\
.ok_cancel_dialog("You were away %d %s. Add time to current file?"\
% (duration, units), 'Yes, log this time')
duration = now - LAST_USAGE
units = 'seconds'
if duration > 59:
duration = int(duration / 60)
units = 'minutes'
if duration > 59:
duration = int(duration / 60)
units = 'hours'
if duration > 24:
duration = int(duration / 24)
units = 'days'
return sublime\
.ok_cancel_dialog("You were away %d %s. Add time to current file?"\
% (duration, units), 'Yes, log this time')
def enough_time_passed(now):
return (now - LAST_ACTION >= 299)
if now - LAST_ACTION > ACTION_FREQUENCY * 60:
return True
return False
def should_prompt_user(now):
if not LAST_USAGE:
return False
duration = now - LAST_USAGE
if duration > AWAY_MINUTES * 60:
return True
return False
def handle_write_action(view):
now = time.time()
targetFile = view.file_name()
if enough_time_passed(now) or targetFile != LAST_FILE:
if should_prompt_user(now):
if away(now):
api(targetFile, now, endtime=LAST_ACTION, isWrite=True)
else:
api(targetFile, now, isWrite=True)
else:
api(targetFile, now, endtime=LAST_ACTION, isWrite=True)
else:
api(targetFile, now, isWrite=True)
def handle_normal_action(view):
global LAST_USAGE
now = time.time()
targetFile = view.file_name()
if enough_time_passed(now) or targetFile != LAST_FILE:
if should_prompt_user(now):
if away(now):
api(targetFile, now, endtime=LAST_ACTION)
else:
api(targetFile, now)
else:
api(targetFile, now, endtime=LAST_ACTION)
else:
LAST_USAGE = now
class WakatimeListener(sublime_plugin.EventListener):
def on_post_save(self, view):
api(view.file_name(), time.time(), isWrite=True)
handle_write_action(view)
def on_activated(self, view):
now = time.time()
targetFile = view.file_name()
if enough_time_passed(now) or targetFile != LAST_FILE:
if away(now):
api(targetFile, LAST_ACTION, endtime=now)
else:
api(targetFile, now)
handle_normal_action(view)
def on_selection_modified(self, view):
now = time.time()
targetFile = view.file_name()
if enough_time_passed(now) or targetFile != LAST_FILE:
if away(now):
api(targetFile, LAST_ACTION, endtime=now)
else:
api(targetFile, now)
handle_normal_action(view)