initial commit

This commit is contained in:
Alan Hamlett 2013-07-02 02:21:13 -07:00
parent b7fe36f882
commit 960fe601e1
3 changed files with 278 additions and 0 deletions

View File

@ -2,3 +2,30 @@ sublime-wakatime
================ ================
automatic time tracking for Sublime Text 2 automatic time tracking for Sublime Text 2
Installation
------------
1) Get an api key from:
https://wakati.me
2) Run this shell command replacing KEY with your api key:
echo "api_key=KEY" >> ~/.wakatime
3) Install the plugin in Sublime's Packages directory.
4) Use Sublime and your time will automatically be tracked for you.
Visit https://wakati.me to view your time spent in each file.
Screen Shots
------------
![Project Overview](https://www.wakati.me/static/img/ScreenShots/Screenshot%20from%202013-06-26%2001:12:59.png)
![Files in a Project](https://www.wakati.me/static/img/ScreenShots/Screenshot%20from%202013-06-26%2001:13:13.png)
![Changing Date Range](https://www.wakati.me/static/img/ScreenShots/Screenshot%20from%202013-06-26%2001:13:53.png)

185
libs/wakatime.py Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env python
import os
import sys
import argparse
import platform
import urllib2
import json
import base64
import uuid
import time
import re
from collections import OrderedDict
from httplib import BadStatusLine, IncompleteRead
from urllib2 import HTTPError, URLError
import logging as log
# Config
version = '0.1.0'
user_agent = 'sublime-wakatime/%s (%s)' % (version, platform.platform())
def project_from_path(path):
project = git_project(path)
if project:
return project
return None
def tags_from_path(path):
tags = []
if os.path.exists(path):
tags.extend(git_tags(path))
tags.extend(mercurial_tags(path))
return list(set(tags))
def git_project(path):
config_file = find_git_config(path)
if config_file:
folder = os.path.split(os.path.split(os.path.split(config_file)[0])[0])[1]
if folder:
return folder
return None
def find_git_config(path):
path = os.path.realpath(path)
if os.path.isfile(path):
path = os.path.split(path)[0]
if os.path.isfile(os.path.join(path, '.git', 'config')):
return os.path.join(path, '.git', 'config')
split_path = os.path.split(path)
if split_path[1] == '':
return None
return find_git_config(split_path[0])
def parse_git_config(config):
sections = OrderedDict()
try:
f = open(config, 'r')
except IOError as e:
log.exception("Exception:")
else:
with f:
section = None
for line in f.readlines():
line = line.lstrip()
if len(line) > 0 and line[0] == '[':
section = line[1:].split(']', 1)[0]
temp = section.split(' ', 1)
section = temp[0].lower()
if len(temp) > 1:
section = ' '.join([section, temp[1]])
sections[section] = OrderedDict()
else:
try:
(setting, value) = line.split('=', 1)
except ValueError:
setting = line.split('#', 1)[0].split(';', 1)[0]
value = 'true'
setting = setting.strip().lower()
value = value.split('#', 1)[0].split(';', 1)[0].strip()
sections[section][setting] = value
f.close()
return sections
def git_tags(path):
tags = []
config_file = find_git_config(path)
if config_file:
sections = parse_git_config(config_file)
for section in sections:
if section.split(' ', 1)[0] == 'remote' and 'url' in sections[section]:
tags.append(sections[section]['url'])
return tags
def mercurial_tags(path):
tags = []
return tags
def svn_tags(path):
tags = []
return tags
def log_action(**kwargs):
kwargs['User-Agent'] = user_agent
log.info(json.dumps(kwargs))
def send_action(key, instance, action, task, timestamp, project, tags):
url = 'https://www.wakati.me/api/v1/actions'
data = {
'type': action,
'task': os.path.realpath(task),
'time': timestamp,
'instance_id': instance,
'project': project,
'tags': tags,
}
request = urllib2.Request(url=url, data=json.dumps(data))
request.add_header('User-Agent', user_agent)
request.add_header('Content-Type', 'application/json')
request.add_header('Authorization', 'Basic %s' % base64.b64encode(key))
log_action(**data)
response = None
try:
response = urllib2.urlopen(request)
except HTTPError as ex:
log.error("%s:\ndata=%s\nresponse=%s" % (ex.getcode(), json.dumps(data), ex.read()))
if log.getLogger().isEnabledFor(log.DEBUG):
log.exception("Exception for %s:\n%s" % (data['time'], json.dumps(data)))
except (URLError, IncompleteRead, BadStatusLine) as ex:
log.error("%s:\ndata=%s\nmessage=%s" % (ex.__class__.__name__, json.dumps(data), ex))
if log.getLogger().isEnabledFor(log.DEBUG):
log.exception("Exception for %s:\n%s" % (data['time'], json.dumps(data)))
if response:
log.debug('response_code=%s response_content=%s' % (response.getcode(), response.read()))
if response and (response.getcode() == 200 or response.getcode() == 201):
return True
return False
def parse_args(argv):
parser = argparse.ArgumentParser(description='Log time to the wakati.me api')
parser.add_argument('--key', dest='key', required=True,
help='your wakati.me api key')
parser.add_argument('--action', dest='action', required=True,
choices=['open_file', 'ping', 'close_file', 'write_file', 'open_editor', 'quit_editor', 'minimize_editor', 'maximize_editor', 'start', 'stop'])
parser.add_argument('--task', dest='task', required=True,
help='path to file or named task')
parser.add_argument('--instance', dest='instance', required=True,
help='the UUID4 representing the current editor')
parser.add_argument('--time', dest='timestamp', metavar='time', type=float,
help='optional floating-point timestamp in seconds')
parser.add_argument('--verbose', dest='verbose', action='store_true',
help='turns on debug messages in logfile')
parser.add_argument('--version', action='version', version=version)
return parser.parse_args(argv)
def main(argv):
args = parse_args(argv)
level = log.INFO
if args.verbose:
level = log.DEBUG
del args.verbose
if not args.timestamp:
args.timestamp = time.time()
log.basicConfig(filename=os.path.expanduser('~/.wakatime.log'), format='%(asctime)s vim-wakatime/'+version+' %(levelname)s %(message)s', datefmt='%Y-%m-%dT%H:%M:%SZ', level=level)
if os.path.isfile(os.path.realpath(args.task)):
tags = tags_from_path(args.task)
project = project_from_path(args.task)
send_action(project=project, tags=tags, **vars(args))
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

66
sublime-wakatime.py Normal file
View File

@ -0,0 +1,66 @@
""" ======================================================
File: sublime-wakatime.py
Description: Automatic time tracking for Sublime Text 2.
Maintainer: Wakati.Me <support@wakatime.com>
Version: 0.1.0
======================================================="""
import time
import uuid
from os.path import expanduser, dirname, realpath
from subprocess import call, Popen
import sublime
import sublime_plugin
# Create logfile if does not exist
call(['touch', '~/.wakatime.log'])
PLUGIN_DIR = dirname(realpath(__file__))
API_CLIENT = '%s/libs/wakatime.py' % PLUGIN_DIR
INSTANCE_ID = str(uuid.uuid4())
def get_api_key():
api_key = None
try:
cf = open(expanduser('~/.wakatime'))
for line in cf:
line = line.split('=', 1)
if line[0] == 'api_key':
api_key = line[1]
cf.close()
except IOError:
pass
return api_key
def api(action, task, timestamp):
if task:
api_key = get_api_key()
if api_key:
cmd = ['python', API_CLIENT,
'--key', api_key,
'--instance', INSTANCE_ID,
'--action', action,
'--task', task,
'--time', str('%f' % timestamp)]
Popen(cmd)
class WakatimeListener(sublime_plugin.EventListener):
def on_post_save(self, view):
api('write_file', view.file_name(), time.time())
def on_activated(self, view):
api('open_file', view.file_name(), time.time())
def on_deactivated(self, view):
api('close_file', view.file_name(), time.time())
if get_api_key() is None:
sublime.error_message('Missing your Wakati.Me api key')