Compare commits

...

33 Commits
1.4.7 ... 1.6.5

Author SHA1 Message Date
4adb8a8796 v1.6.5 2014-03-05 13:55:08 -08:00
48e1993b24 upgrade external wakatime package to v1.0.1 2014-03-05 13:51:29 -08:00
8a3375bb23 v1.6.4 2014-02-05 01:03:05 -08:00
8bd54a7427 upgrade wakatime package to v1.0.0 for mercurial support 2014-02-05 01:01:47 -08:00
fcbbf05933 v1.6.3 2014-01-15 16:46:32 -08:00
9733087094 upgrade wakatime package to v0.5.3 2014-01-15 16:45:07 -08:00
da4e02199a v1.6.2 2014-01-14 05:14:25 -08:00
09a16dea1e upgrade wakatime package to v0.5.2 2014-01-14 05:11:00 -08:00
4c7adf0943 v1.6.1 2013-12-13 16:38:52 +01:00
216a8eaa0a upgrade common wakatime package to v0.5.1 2013-12-13 16:37:54 +01:00
81f838489d v1.6.0 2013-12-13 16:11:46 +01:00
d6228b8dce use [WakaTime] namespace for all console messages 2013-12-13 16:08:34 +01:00
7a2c2b9750 fix variable definition bug 2013-12-13 15:41:45 +01:00
d9cc911595 upgrade common wakatime package to v0.5.0 2013-12-13 15:35:49 +01:00
805e2fe222 v1.5.2 2013-12-03 02:41:54 +01:00
bbcb39b2cf fix #18 by using a non-localized datetime in log 2013-12-03 02:38:30 +01:00
9f9b97c69f v1.5.1 2013-12-02 09:17:35 +01:00
908ff98613 decode file names with filesystem encoding, then encode as utf-8 before encoding with simplejson 2013-12-02 09:16:21 +01:00
37f74b4b56 v1.5.0 2013-11-28 12:18:44 +01:00
a1c0d7e489 prevent sending timestamp when saving same file multiple times. increase send frequency from 5 minutes to 2 minutes. 2013-11-28 12:16:43 +01:00
3127f264b4 v1.4.12 2013-11-21 01:11:40 -08:00
049bc57019 fix #17 by removing non-utf8 characters 2013-11-21 01:09:37 -08:00
03ec38bb67 v1.4.11 2013-11-13 18:07:41 -08:00
4fc1a55ff7 set project name with .wakatime-project file. upgrade wakatime package to 0.4.10. 2013-11-13 18:06:55 -08:00
f60815b813 upgrade wakatime package to recognize .markdown file extension 2013-11-03 11:36:43 -08:00
ca47c2308d v1.4.10 2013-10-31 17:20:12 -07:00
146a959747 recognize jinja2 file extensions as HTML 2013-10-31 17:19:17 -07:00
906184cd88 v1.4.9 2013-10-28 18:12:12 -07:00
a13e11d24d handle case where ignore patterns not defined 2013-10-28 18:09:51 -07:00
d34432217f v1.4.8 2013-10-27 21:31:34 -07:00
f2e8f85198 fix syntax in default sublime-settings file 2013-10-27 21:31:15 -07:00
05b08b6ab2 new sublime-setting ingore for ignoring files by regular expressions 2013-10-27 21:30:10 -07:00
685d242c60 upgrade wakatime package to v0.4.9. adds new ignore config to .wakatime.conf to ignore files based on regex patterns. 2013-10-27 21:07:42 -07:00
20 changed files with 599 additions and 182 deletions

View File

@ -3,6 +3,94 @@ History
-------
1.6.5 (2014-03-05)
++++++++++++++++++
- upgrade external wakatime package to v1.0.1
- use new domain wakatime.com
1.6.4 (2014-02-05)
++++++++++++++++++
- upgrade external wakatime package to v1.0.0
- support for mercurial revision control
1.6.3 (2014-01-15)
++++++++++++++++++
- upgrade common wakatime package to v0.5.3
1.6.2 (2014-01-14)
++++++++++++++++++
- upgrade common wakatime package to v0.5.2
1.6.1 (2013-12-13)
++++++++++++++++++
- upgrade common wakatime package to v0.5.1
- second line in .wakatime-project now sets branch name
1.6.0 (2013-12-13)
++++++++++++++++++
- upgrade common wakatime package to v0.5.0
1.5.2 (2013-12-03)
++++++++++++++++++
- use non-localized datetime in log
1.5.1 (2013-12-02)
++++++++++++++++++
- decode file names with filesystem encoding, then encode as utf-8 for logging
1.5.0 (2013-11-28)
++++++++++++++++++
- increase "ping" frequency from every 5 minutes to every 2 minutes
- prevent sending multiple api requests when saving the same file
1.4.12 (2013-11-21)
+++++++++++++++++++
- handle UnicodeDecodeError exceptions when json encoding log messages
1.4.11 (2013-11-13)
+++++++++++++++++++
- placing .wakatime-project file in a folder will read the project's name from that file
1.4.10 (2013-10-31)
++++++++++++++++++
- recognize jinja2 file extensions as HTML
1.4.9 (2013-10-28)
++++++++++++++++++
- handle case where ignore patterns not defined
1.4.8 (2013-10-27)
++++++++++++++++++
- new setting to ignore files that match a regular expression pattern
1.4.7 (2013-10-26)
++++++++++++++++++

View File

@ -8,7 +8,7 @@ Installation
Heads Up! For Sublime Text 2 on Windows & Linux, WakaTime depends on [Python](http://www.python.org/getit/) being installed to work correctly.
1. Get an api key from: https://wakati.me
1. Get an api key from: https://wakatime.com/#apikey
2. Using [Sublime Package Control](http://wbond.net/sublime_packages/package_control):
@ -18,16 +18,16 @@ Heads Up! For Sublime Text 2 on Windows & Linux, WakaTime depends on [Python](ht
c) Type `wakatime`, then press `enter` with the `WakaTime` plugin selected.
3. You will see a prompt at the bottom asking for your [api key](https://www.wakati.me/#apikey). Enter your api key, then press `enter`.
3. You will see a prompt at the bottom asking for your [api key](https://www.wakatime.com/#apikey). Enter your api key, then press `enter`.
4. Use Sublime and your time will automatically be tracked for you.
5. Visit https://wakati.me to see your logged time.
5. Visit https://wakatime.com to see your logged time.
6. Consider installing [BIND9](https://help.ubuntu.com/community/BIND9ServerHowto#Caching_Server_configuration) to cache your repeated DNS requests: `sudo apt-get install bind9`
Screen Shots
------------
![Project Overview](https://www.wakati.me/static/img/ScreenShots/Screen Shot 2013-10-26 at 5.04.01 PM.png)
![Project Overview](https://www.wakatime.com/static/img/ScreenShots/Screen Shot 2013-10-26 at 5.04.01 PM.png)

View File

@ -1,11 +1,11 @@
""" ==========================================================
File: WakaTime.py
Description: Automatic time tracking for Sublime Text 2 and 3.
Maintainer: WakaTi.me <support@wakatime.com>
Website: https://www.wakati.me/
Maintainer: WakaTime <support@wakatime.com>
Website: https://www.wakatime.com/
==========================================================="""
__version__ = '1.4.7'
__version__ = '1.6.5'
import sublime
import sublime_plugin
@ -21,14 +21,17 @@ from os.path import expanduser, dirname, realpath, isfile, join, exists
# globals
ACTION_FREQUENCY = 5
ACTION_FREQUENCY = 2
ST_VERSION = int(sublime.version())
PLUGIN_DIR = dirname(realpath(__file__))
API_CLIENT = '%s/packages/wakatime/wakatime-cli.py' % PLUGIN_DIR
SETTINGS_FILE = 'WakaTime.sublime-settings'
SETTINGS = {}
LAST_ACTION = 0
LAST_FILE = None
LAST_ACTION = {
'time': 0,
'file': None,
'is_write': False,
}
HAS_SSL = False
LOCK = threading.RLock()
@ -47,31 +50,6 @@ if HAS_SSL:
import wakatime
def setup_settings_file():
""" Convert ~/.wakatime.conf to WakaTime.sublime-settings
"""
global SETTINGS
# To be backwards compatible, rename config file
SETTINGS = sublime.load_settings(SETTINGS_FILE)
api_key = SETTINGS.get('api_key', '')
if not api_key:
api_key = ''
try:
with open(join(expanduser('~'), '.wakatime.conf')) as old_file:
for line in old_file:
line = line.split('=', 1)
if line[0] == 'api_key':
api_key = str(line[1].strip())
try:
os.remove(join(expanduser('~'), '.wakatime.conf'))
except:
pass
except IOError:
pass
SETTINGS.set('api_key', api_key)
sublime.save_settings(SETTINGS_FILE)
def prompt_api_key():
global SETTINGS
if not SETTINGS.get('api_key'):
@ -81,10 +59,10 @@ def prompt_api_key():
sublime.save_settings(SETTINGS_FILE)
window = sublime.active_window()
if window:
window.show_input_panel('Enter your WakaTime api key:', '', got_key, None, None)
window.show_input_panel('[WakaTime] Enter your wakatime.com api key:', '', got_key, None, None)
return True
else:
print('Error: Could not prompt for api key because no window found.')
print('[WakaTime] Error: Could not prompt for api key because no window found.')
return False
@ -101,63 +79,60 @@ def python_binary():
return 'python'
def enough_time_passed(now):
if now - LAST_ACTION > ACTION_FREQUENCY * 60:
def enough_time_passed(now, last_time):
if now - last_time > ACTION_FREQUENCY * 60:
return True
return False
def handle_write_action(view):
global LOCK, LAST_FILE, LAST_ACTION
def handle_action(view, is_write=False):
global LOCK, LAST_ACTION
with LOCK:
targetFile = view.file_name()
thread = SendActionThread(targetFile, isWrite=True)
thread.start()
LAST_FILE = targetFile
LAST_ACTION = time.time()
def handle_normal_action(view):
global LOCK, LAST_FILE, LAST_ACTION
with LOCK:
targetFile = view.file_name()
thread = SendActionThread(targetFile)
thread.start()
LAST_FILE = targetFile
LAST_ACTION = time.time()
target_file = view.file_name()
if target_file:
thread = SendActionThread(target_file, is_write=is_write)
thread.start()
LAST_ACTION = {
'file': target_file,
'time': time.time(),
'is_write': is_write,
}
class SendActionThread(threading.Thread):
def __init__(self, targetFile, isWrite=False, force=False):
def __init__(self, target_file, is_write=False, force=False):
threading.Thread.__init__(self)
self.targetFile = targetFile
self.isWrite = isWrite
self.target_file = target_file
self.is_write = is_write
self.force = force
self.debug = SETTINGS.get('debug')
self.api_key = SETTINGS.get('api_key', '')
self.last_file = LAST_FILE
self.ignore = SETTINGS.get('ignore', [])
self.last_action = LAST_ACTION
def run(self):
if self.targetFile:
if self.target_file:
self.timestamp = time.time()
if self.force or self.isWrite or self.targetFile != self.last_file or enough_time_passed(self.timestamp):
if self.force or (self.is_write and not self.last_action['is_write']) or self.target_file != self.last_action['file'] or enough_time_passed(self.timestamp, self.last_action['time']):
self.send()
def send(self):
if not self.api_key:
print('missing api key')
print('[WakaTime] Error: missing api key.')
return
ua = 'sublime/%d sublime-wakatime/%s' % (ST_VERSION, __version__)
cmd = [
API_CLIENT,
'--file', self.targetFile,
'--file', self.target_file,
'--time', str('%f' % self.timestamp),
'--plugin', ua,
'--key', str(bytes.decode(self.api_key.encode('utf8'))),
]
if self.isWrite:
if self.is_write:
cmd.append('--write')
for pattern in self.ignore:
cmd.extend(['--ignore', pattern])
if self.debug:
cmd.append('--verbose')
if HAS_SSL:
@ -165,7 +140,7 @@ class SendActionThread(threading.Thread):
print(cmd)
code = wakatime.main(cmd)
if code != 0:
print('Error: Response code %d from wakatime package' % code)
print('[WakaTime] Error: Response code %d from wakatime package.' % code)
else:
python = python_binary()
if python:
@ -178,11 +153,12 @@ class SendActionThread(threading.Thread):
with open(join(expanduser('~'), '.wakatime.log'), 'a') as stderr:
Popen(cmd, stderr=stderr)
else:
print('Error: Unable to find python binary.')
print('[WakaTime] Error: Unable to find python binary.')
def plugin_loaded():
setup_settings_file()
global SETTINGS
SETTINGS = sublime.load_settings(SETTINGS_FILE)
after_loaded()
@ -199,10 +175,10 @@ if ST_VERSION < 3000:
class WakatimeListener(sublime_plugin.EventListener):
def on_post_save(self, view):
handle_write_action(view)
handle_action(view, is_write=True)
def on_activated(self, view):
handle_normal_action(view)
handle_action(view)
def on_modified(self, view):
handle_normal_action(view)
handle_action(view)

View File

@ -3,10 +3,14 @@
// This settings file will be overwritten when upgrading.
{
// Your api key from https://www.wakati.me/#apikey
// Your api key from https://www.wakatime.com/#apikey
// Set this in your User specific WakaTime.sublime-settings file.
"api_key": "",
// Ignore files; Files (including absolute paths) that match one of these
// POSIX regular expressions will not be logged.
"ignore": ["^/tmp/", "^/etc/", "^/var/"],
// Debug mode. Set to true for verbose logging. Defaults to false.
"debug": false
}

15
packages/wakatime/AUTHORS Normal file
View File

@ -0,0 +1,15 @@
WakaTime is written and maintained by Alan Hamlett and
various contributors:
Development Lead
----------------
- Alan Hamlett <alan.hamlett@gmail.com>
Patches and Suggestions
-----------------------
- 3onyc <3onyc@x3tech.com>
- userid <xixico@ymail.com>

View File

@ -3,6 +3,56 @@ History
-------
1.0.1 (2014-03-05)
++++++++++++++++++
- use new domain name wakatime.com
1.0.0 (2014-02-05)
++++++++++++++++++
- detect project name and branch name from mercurial revision control
0.5.3 (2014-01-15)
++++++++++++++++++
- bug fix for unicode in Python3
0.5.2 (2014-01-14)
++++++++++++++++++
- minor bug fix for Subversion on non-English systems
0.5.1 (2013-12-13)
++++++++++++++++++
- second line in .wakatime-project file now sets branch name
0.5.0 (2013-12-13)
++++++++++++++++++
- Convert ~/.wakatime.conf to ~/.wakatime.cfg and use configparser format
- new [projectmap] section in cfg file for naming projects based on folders
0.4.10 (2013-11-13)
+++++++++++++++++++
- Placing .wakatime-project file in a folder will read the project's name from that file
0.4.9 (2013-10-27)
++++++++++++++++++
- New config for ignoring files from regular expressions
- Parse more options from config file (verbose, logfile, ignore)
0.4.8 (2013-10-13)
++++++++++++++++++

View File

@ -1,4 +1,4 @@
Copyright (c) 2013 Alan Hamlett https://wakati.me
Copyright (c) 2013 Alan Hamlett https://wakatime.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -12,7 +12,7 @@ modification, are permitted provided that the following conditions are met:
in the documentation and/or other materials provided
with the distribution.
* Neither the names of Wakatime or Wakati.Me, nor the names of its
* Neither the names of Wakatime or WakaTime, nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

View File

@ -1,12 +1,14 @@
WakaTime
========
Automatic time tracking for your text editor. This is the command line
event appender for the WakaTime api. You shouldn't need to directly
use this outside of a text editor plugin.
Fully automatic time tracking for your text editor.
This is the common interface for the WakaTime api. You shouldn't need to directly use this package unless you are creating a new plugin.
Go to http://wakatime.com to install the plugin for your text editor.
Installation
------------
https://www.wakati.me/help/plugins/installing-plugins
https://www.wakatime.com/help/plugins/installing-plugins

View File

@ -12,7 +12,7 @@ setup(
version=VERSION,
license='BSD 3 Clause',
description=' '.join([
'Action event appender for Wakati.Me, a time',
'Action event appender for WakaTime, a time',
'tracking api for text editors.',
]),
long_description=open('README.rst').read(),

View File

@ -1,10 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
wakatime-cli
~~~~~~~~~~~~
Action event appender for Wakati.Me, auto time tracking for text editors.
Command-line entry point.
:copyright: (c) 2013 Alan Hamlett.
:license: BSD, see LICENSE for more details.

View File

@ -3,7 +3,8 @@
wakatime
~~~~~~~~
Action event appender for Wakati.Me, auto time tracking for text editors.
Common interface to the WakaTime api.
http://wakatime.com
:copyright: (c) 2013 Alan Hamlett.
:license: BSD, see LICENSE for more details.
@ -12,7 +13,7 @@
from __future__ import print_function
__title__ = 'wakatime'
__version__ = '0.4.8'
__version__ = '1.0.1'
__author__ = 'Alan Hamlett'
__license__ = 'BSD'
__copyright__ = 'Copyright 2013 Alan Hamlett'
@ -26,6 +27,15 @@ import re
import sys
import time
import traceback
try:
import ConfigParser as configparser
except ImportError:
import configparser
try:
from urllib2 import HTTPError, Request, urlopen
except ImportError:
from urllib.error import HTTPError
from urllib.request import Request, urlopen
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'))
@ -35,11 +45,6 @@ from .stats import get_file_stats
from .packages import argparse
from .packages import simplejson as json
from .packages import tzlocal
try:
from urllib2 import HTTPError, Request, urlopen
except ImportError:
from urllib.error import HTTPError
from urllib.request import Request, urlopen
log = logging.getLogger(__name__)
@ -52,13 +57,84 @@ class FileAction(argparse.Action):
setattr(namespace, self.dest, values)
def upgradeConfigFile(configFile):
"""For backwards-compatibility, upgrade the existing config file
to work with configparser and rename from .wakatime.conf to .wakatime.cfg.
"""
if os.path.isfile(configFile):
# if upgraded cfg file already exists, don't overwrite it
return
oldConfig = os.path.join(os.path.expanduser('~'), '.wakatime.conf')
try:
configs = {
'ignore': [],
}
with open(oldConfig) as fh:
for line in fh.readlines():
line = line.split('=', 1)
if len(line) == 2 and line[0].strip() and line[1].strip():
if line[0].strip() == 'ignore':
configs['ignore'].append(line[1].strip())
else:
configs[line[0].strip()] = line[1].strip()
with open(configFile, 'w') as fh:
fh.write("[settings]\n")
for name, value in configs.items():
if isinstance(value, list):
fh.write("%s=\n" % name)
for item in value:
fh.write(" %s\n" % item)
else:
fh.write("%s = %s\n" % (name, value))
os.remove(oldConfig)
except IOError:
pass
def parseConfigFile(configFile):
"""Returns a configparser.SafeConfigParser instance with configs
read from the config file. Default location of the config file is
at ~/.wakatime.cfg.
"""
if not configFile:
configFile = os.path.join(os.path.expanduser('~'), '.wakatime.cfg')
upgradeConfigFile(configFile)
configs = configparser.SafeConfigParser()
try:
with open(configFile) as fh:
try:
configs.readfp(fh)
except configparser.Error:
print(traceback.format_exc())
return None
except IOError:
if not os.path.isfile(configFile):
print('Error: Could not read from config file ~/.wakatime.conf')
return configs
def parseArguments(argv):
"""Parse command line arguments and configs from ~/.wakatime.cfg.
Command line arguments take precedence over config file settings.
Returns instances of ArgumentParser and SafeConfigParser.
"""
try:
sys.argv
except AttributeError:
sys.argv = argv
# define supported command line arguments
parser = argparse.ArgumentParser(
description='Wakati.Me event api appender')
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 action')
@ -73,8 +149,10 @@ def parseArguments(argv):
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 '+
help='your wakatime 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')
parser.add_argument('--logfile', dest='logfile',
help='defaults to ~/.wakatime.log')
parser.add_argument('--config', dest='config',
@ -82,32 +160,59 @@ def parseArguments(argv):
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__)
# parse command line arguments
args = parser.parse_args(args=argv[1:])
# use current unix epoch timestamp by default
if not args.timestamp:
args.timestamp = time.time()
# parse ~/.wakatime.cfg file
configs = parseConfigFile(args.config)
if configs is None:
return args, configs
# update args from configs
if not args.key:
default_key = get_api_key(args.config)
default_key = None
if configs.has_option('settings', 'api_key'):
default_key = configs.get('settings', 'api_key')
if default_key:
args.key = default_key
else:
parser.error('Missing api key')
return args
if not args.ignore:
args.ignore = []
if configs.has_option('settings', 'ignore'):
try:
for pattern in configs.get('settings', 'ignore').split("\n"):
if pattern.strip() != '':
args.ignore.append(pattern)
except TypeError:
pass
if not args.verbose and configs.has_option('settings', 'verbose'):
args.verbose = configs.getboolean('settings', 'verbose')
if not args.verbose and configs.has_option('settings', 'debug'):
args.verbose = configs.getboolean('settings', 'debug')
if not args.logfile and configs.has_option('settings', 'logfile'):
args.logfile = configs.get('settings', 'logfile')
return args, configs
def get_api_key(configFile):
if not configFile:
configFile = os.path.join(os.path.expanduser('~'), '.wakatime.conf')
api_key = None
def should_ignore(fileName, patterns):
try:
cf = open(configFile)
for line in cf:
line = line.split('=', 1)
if line[0] == 'api_key':
api_key = line[1].strip()
cf.close()
except IOError:
print('Error: Could not read from config file.')
return api_key
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
def get_user_agent(plugin):
@ -122,7 +227,7 @@ def get_user_agent(plugin):
def send_action(project=None, branch=None, stats={}, key=None, targetFile=None,
timestamp=None, isWrite=None, plugin=None, **kwargs):
url = 'https://www.wakati.me/api/v1/actions'
url = 'https://www.wakatime.com/api/v1/actions'
log.debug('Sending action to api at %s' % url)
data = {
'time': timestamp,
@ -187,25 +292,39 @@ def send_action(project=None, branch=None, stats={}, key=None, targetFile=None,
def main(argv=None):
if not argv:
argv = sys.argv
args = parseArguments(argv)
args, configs = parseArguments(argv)
if configs is None:
return 103 # config file parsing error
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
if os.path.isfile(args.targetFile):
branch = None
name = None
stats = get_file_stats(args.targetFile)
project = find_project(args.targetFile)
project = find_project(args.targetFile, configs=configs)
branch = None
project_name = None
if project:
branch = project.branch()
name = project.name()
project_name = project.name()
if send_action(
project=name,
project=project_name,
branch=branch,
stats=stats,
**vars(args)
):
return 0
return 102
return 0 # success
return 102 # api error
else:
log.debug('File does not exist; ignoring this action.')
return 101
return 0

View File

@ -11,6 +11,7 @@
import logging
import os
import sys
from .packages import simplejson as json
try:
@ -25,7 +26,13 @@ class CustomEncoder(json.JSONEncoder):
if isinstance(obj, bytes):
obj = bytes.decode(obj)
return json.dumps(obj)
return super(CustomEncoder, self).default(obj)
try:
encoded = super(CustomEncoder, self).default(obj)
except UnicodeDecodeError:
encoding = sys.getfilesystemencoding()
obj = obj.decode(encoding, 'ignore').encode('utf-8')
encoded = super(CustomEncoder, self).default(obj)
return encoded
class JsonFormatter(logging.Formatter):
@ -69,7 +76,7 @@ def setup_logging(args, version):
logger = logging.getLogger()
set_log_level(logger, args)
if len(logger.handlers) > 0:
formatter = JsonFormatter(datefmt='%a %b %d %H:%M:%S %Z %Y')
formatter = JsonFormatter(datefmt='%Y/%m/%d %H:%M:%S %z')
formatter.setup(
timestamp=args.timestamp,
isWrite=args.isWrite,
@ -83,7 +90,7 @@ def setup_logging(args, version):
if not logfile:
logfile = '~/.wakatime.log'
handler = logging.FileHandler(os.path.expanduser(logfile))
formatter = JsonFormatter(datefmt='%a %b %d %H:%M:%S %Z %Y')
formatter = JsonFormatter(datefmt='%Y/%m/%d %H:%M:%S %z')
formatter.setup(
timestamp=args.timestamp,
isWrite=args.isWrite,

View File

@ -14,21 +14,32 @@ import os
from .projects.git import Git
from .projects.mercurial import Mercurial
from .projects.projectmap import ProjectMap
from .projects.subversion import Subversion
from .projects.wakatime import WakaTime
log = logging.getLogger(__name__)
# List of plugin classes to find a project for the current file path.
# Project plugins will be processed with priority in the order below.
PLUGINS = [
WakaTime,
ProjectMap,
Git,
Mercurial,
Subversion,
]
def find_project(path):
def find_project(path, configs=None):
for plugin in PLUGINS:
project = plugin(path)
plugin_name = plugin.__name__.lower()
plugin_configs = None
if configs and configs.has_section(plugin_name):
plugin_configs = dict(configs.items(plugin_name))
project = plugin(path, configs=plugin_configs)
if project.process():
return project
return None

View File

@ -22,18 +22,19 @@ class BaseProject(object):
be found for the current path.
"""
def __init__(self, path):
def __init__(self, path, configs=None):
self.path = path
self._configs = configs
def type(self):
def project_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
project_type = self.__class__.__name__.lower()
if project_type == 'baseproject':
project_type = None
return project_type
def process(self):
""" Processes self.path into a project and

View File

@ -13,47 +13,47 @@ import logging
import os
from .base import BaseProject
try:
from collections import OrderedDict
except ImportError:
from ..packages.ordereddict import OrderedDict
log = logging.getLogger(__name__)
# str is unicode in Python3
try:
unicode
except NameError:
unicode = str
class Git(BaseProject):
def process(self):
self.config = self._find_config(self.path)
if self.config:
return True
return False
self.configFile = self._find_git_config_file(self.path)
return self.configFile is not None
def name(self):
base = self._project_base()
if base:
return os.path.basename(base)
return unicode(os.path.basename(base))
return None
def branch(self):
branch = None
base = self._project_base()
if base:
head = os.path.join(self._project_base(), '.git', 'HEAD')
try:
with open(head) as f:
branch = f.readline().strip().rsplit('/', 1)[-1]
with open(head) as fh:
return unicode(fh.readline().strip().rsplit('/', 1)[-1])
except IOError:
pass
return branch
def _project_base(self):
if self.config:
return os.path.dirname(os.path.dirname(self.config))
return None
def _find_config(self, path):
def _project_base(self):
if self.configFile:
return os.path.dirname(os.path.dirname(self.configFile))
return None
def _find_git_config_file(self, path):
path = os.path.realpath(path)
if os.path.isfile(path):
path = os.path.split(path)[0]
@ -62,34 +62,4 @@ class Git(BaseProject):
split_path = os.path.split(path)
if split_path[1] == '':
return None
return self._find_config(split_path[0])
def _parse_config(self):
sections = {}
try:
f = open(self.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] = {}
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
return self._find_git_config_file(split_path[0])

View File

@ -18,13 +18,41 @@ from .base import BaseProject
log = logging.getLogger(__name__)
# str is unicode in Python3
try:
unicode
except NameError:
unicode = str
class Mercurial(BaseProject):
def process(self):
return False
self.configDir = self._find_hg_config_dir(self.path)
return self.configDir is not None
def name(self):
if self.configDir:
return unicode(os.path.basename(os.path.dirname(self.configDir)))
return None
def branch(self):
return None
if self.configDir:
branch_file = os.path.join(self.configDir, 'branch')
try:
with open(branch_file) as fh:
return unicode(fh.readline().strip().rsplit('/', 1)[-1])
except IOError:
pass
return unicode('default')
def _find_hg_config_dir(self, path):
path = os.path.realpath(path)
if os.path.isfile(path):
path = os.path.split(path)[0]
if os.path.isdir(os.path.join(path, '.hg')):
return os.path.join(path, '.hg')
split_path = os.path.split(path)
if split_path[1] == '':
return None
return self._find_hg_config_dir(split_path[0])

View File

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
"""
wakatime.projects.projectmap
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use the ~/.wakatime.cfg file to set custom project names by
recursively matching folder paths.
Project maps go under the [projectmap] config section.
For example:
[projectmap]
/home/user/projects/foo = new project name
/home/user/projects/bar = project2
Will result in file `/home/user/projects/foo/src/main.c` to have
project name `new project name`.
:copyright: (c) 2013 Alan Hamlett.
:license: BSD, see LICENSE for more details.
"""
import logging
import os
from .base import BaseProject
log = logging.getLogger(__name__)
# str is unicode in Python3
try:
unicode
except NameError:
unicode = str
class ProjectMap(BaseProject):
def process(self):
if not self._configs:
return False
self.project = self._find_project(self.path)
return self.project is not None
def _find_project(self, path):
path = os.path.realpath(path)
if os.path.isfile(path):
path = os.path.split(path)[0]
if self._configs.get(path.lower()):
return self._configs.get(path.lower())
if self._configs.get('%s/' % path.lower()):
return self._configs.get('%s/' % path.lower())
if self._configs.get('%s\\' % path.lower()):
return self._configs.get('%s\\' % path.lower())
split_path = os.path.split(path)
if split_path[1] == '':
return None
return self._find_project(split_path[0])
def branch(self):
return None
def name(self):
if self.project:
return unicode(self.project)
return None

View File

@ -24,24 +24,31 @@ except ImportError:
log = logging.getLogger(__name__)
# str is unicode in Python3
try:
unicode
except NameError:
unicode = str
class Subversion(BaseProject):
def process(self):
return self._find_project_base(self.path)
def name(self):
return self.info['Repository Root'].split('/')[-1]
return unicode(self.info['Repository Root'].split('/')[-1])
def branch(self):
branch = None
if self.base:
branch = os.path.basename(self.base)
return branch
unicode(os.path.basename(self.base))
return None
def _get_info(self, path):
info = OrderedDict()
stdout = None
try:
os.environ['LANG'] = 'en_US'
stdout, stderr = Popen([
'svn', 'info', os.path.realpath(path)
], stdout=PIPE, stderr=PIPE).communicate()

View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""
wakatime.projects.wakatime
~~~~~~~~~~~~~~~~~~~~~~~~~~
Information from a .wakatime-project file about the project for
a given file. First line of .wakatime-project sets the project
name. Second line sets the current branch name.
:copyright: (c) 2013 Alan Hamlett.
:license: BSD, see LICENSE for more details.
"""
import logging
import os
from .base import BaseProject
log = logging.getLogger(__name__)
# str is unicode in Python3
try:
unicode
except NameError:
unicode = str
class WakaTime(BaseProject):
def process(self):
self.config = self._find_config(self.path)
self._project_name = None
self._project_branch = None
if self.config:
try:
with open(self.config) as fh:
self._project_name = unicode(fh.readline().strip())
self._project_branch = unicode(fh.readline().strip())
except IOError as e:
log.exception("Exception:")
return True
return False
def name(self):
return self._project_name
def branch(self):
return self._project_branch
def _find_config(self, 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, '.wakatime-project')):
return os.path.join(path, '.wakatime-project')
split_path = os.path.split(path)
if split_path[1] == '':
return None
return self._find_config(split_path[0])

View File

@ -25,7 +25,10 @@ log = logging.getLogger(__name__)
# force file name extensions to be recognized as a certain language
EXTENSIONS = {
'j2': 'HTML',
'markdown': 'Markdown',
'md': 'Markdown',
'twig': 'Twig',
}
TRANSLATIONS = {
'CSS+Genshi Text': 'CSS',
@ -35,6 +38,7 @@ TRANSLATIONS = {
'JavaScript+Genshi Text': 'JavaScript',
'JavaScript+Lasso': 'JavaScript',
'Perl6': 'Perl',
'RHTML': 'HTML',
}