mirror of
https://github.com/wakatime/sublime-wakatime.git
synced 2023-08-10 21:13:02 +03:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
66fddc07b9 | |||
e56a07e909 | |||
64ea40b3f5 | |||
17fd6ef8e1 | |||
e5e399dfbe | |||
bcf037e8a4 | |||
7e678a38bd | |||
533aaac313 | |||
7f4f70cc85 | |||
4adb8a8796 | |||
48e1993b24 | |||
8a3375bb23 | |||
8bd54a7427 |
34
HISTORY.rst
34
HISTORY.rst
@ -3,6 +3,40 @@ History
|
||||
-------
|
||||
|
||||
|
||||
2.0.3 (2014-05-26)
|
||||
++++++++++++++++++
|
||||
|
||||
- upgrade external wakatime package to v2.0.1
|
||||
- fix bug in queue preventing completed tasks from being purged
|
||||
|
||||
|
||||
2.0.2 (2014-05-26)
|
||||
++++++++++++++++++
|
||||
|
||||
- disable syncing offline time until bug fixed
|
||||
|
||||
|
||||
2.0.1 (2014-05-25)
|
||||
++++++++++++++++++
|
||||
|
||||
- upgrade external wakatime package to v2.0.0
|
||||
- offline time logging using sqlite3 to queue editor events
|
||||
|
||||
|
||||
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)
|
||||
++++++++++++++++++
|
||||
|
||||
|
29
LICENSE
Normal file
29
LICENSE
Normal file
@ -0,0 +1,29 @@
|
||||
Copyright (c) 2014 Alan Hamlett https://wakatime.com
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* 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.
|
||||
|
||||
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
||||
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -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://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
|
||||
------------
|
||||
|
||||

|
||||

|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
""" ==========================================================
|
||||
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>
|
||||
License: BSD, see LICENSE for more details.
|
||||
Website: https://wakatime.com/
|
||||
==========================================================="""
|
||||
|
||||
__version__ = '1.6.3'
|
||||
__version__ = '2.0.3'
|
||||
|
||||
import sublime
|
||||
import sublime_plugin
|
||||
|
@ -3,7 +3,7 @@
|
||||
// This settings file will be overwritten when upgrading.
|
||||
|
||||
{
|
||||
// Your api key from https://www.wakati.me/#apikey
|
||||
// Your api key from https://wakatime.com/#apikey
|
||||
// Set this in your User specific WakaTime.sublime-settings file.
|
||||
"api_key": "",
|
||||
|
||||
|
@ -3,6 +3,36 @@ History
|
||||
-------
|
||||
|
||||
|
||||
2.0.1 (2014-05-26)
|
||||
++++++++++++++++++
|
||||
|
||||
- fix bug in queue preventing actions with NULL values from being purged
|
||||
|
||||
|
||||
2.0.0 (2014-05-25)
|
||||
++++++++++++++++++
|
||||
|
||||
- offline time logging using sqlite3 to queue editor events
|
||||
|
||||
|
||||
1.0.2 (2014-05-06)
|
||||
++++++++++++++++++
|
||||
|
||||
- ability to set project from command line argument
|
||||
|
||||
|
||||
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)
|
||||
++++++++++++++++++
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
WakaTime
|
||||
========
|
||||
|
||||
Fully automatic time tracking for your text editor.
|
||||
Fully automatic time tracking for programmers.
|
||||
|
||||
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.
|
||||
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 or your text editor's plugin asks you to install the wakatime-cli interface.
|
||||
|
||||
Go to http://wakatime.com to install the plugin for your text editor.
|
||||
|
||||
@ -11,4 +11,10 @@ Go to http://wakatime.com to install the plugin for your text editor.
|
||||
Installation
|
||||
------------
|
||||
|
||||
https://www.wakati.me/help/plugins/installing-plugins
|
||||
pip install wakatime
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
https://wakatime.com/
|
||||
|
@ -11,13 +11,10 @@ setup(
|
||||
name='wakatime',
|
||||
version=VERSION,
|
||||
license='BSD 3 Clause',
|
||||
description=' '.join([
|
||||
'Action event appender for Wakati.Me, a time',
|
||||
'tracking api for text editors.',
|
||||
]),
|
||||
description='Interface to the WakaTime api.',
|
||||
long_description=open('README.rst').read(),
|
||||
author='Alan Hamlett',
|
||||
author_email='alan.hamlett@gmail.com',
|
||||
author_email='alan@wakatime.com',
|
||||
url='https://github.com/wakatime/wakatime',
|
||||
packages=packages,
|
||||
package_dir={'wakatime': 'wakatime'},
|
||||
@ -28,7 +25,7 @@ setup(
|
||||
'console_scripts': ['wakatime = wakatime.__init__:main'],
|
||||
},
|
||||
classifiers=(
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
|
@ -13,10 +13,10 @@
|
||||
from __future__ import print_function
|
||||
|
||||
__title__ = 'wakatime'
|
||||
__version__ = '0.5.3'
|
||||
__version__ = '2.0.1'
|
||||
__author__ = 'Alan Hamlett'
|
||||
__license__ = 'BSD'
|
||||
__copyright__ = 'Copyright 2013 Alan Hamlett'
|
||||
__copyright__ = 'Copyright 2014 Alan Hamlett'
|
||||
|
||||
|
||||
import base64
|
||||
@ -39,6 +39,7 @@ except ImportError:
|
||||
|
||||
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'))
|
||||
from .queue import Queue
|
||||
from .log import setup_logging
|
||||
from .project import find_project
|
||||
from .stats import get_file_stats
|
||||
@ -148,9 +149,14 @@ def parseArguments(argv):
|
||||
parser.add_argument('--plugin', dest='plugin',
|
||||
help='optional text editor plugin name and version '+
|
||||
'for User-Agent header')
|
||||
parser.add_argument('--project', dest='project_name',
|
||||
help='optional project name; will auto-discover by default')
|
||||
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('--disableoffline', dest='offline',
|
||||
action='store_false',
|
||||
help='disables offline time logging instead of queuing logged time')
|
||||
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',
|
||||
@ -191,6 +197,8 @@ def parseArguments(argv):
|
||||
args.ignore.append(pattern)
|
||||
except TypeError:
|
||||
pass
|
||||
if args.offline and configs.has_option('settings', 'offline'):
|
||||
args.offline = configs.getboolean('settings', 'offline')
|
||||
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'):
|
||||
@ -225,9 +233,9 @@ def get_user_agent(plugin):
|
||||
return user_agent
|
||||
|
||||
|
||||
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'
|
||||
def send_action(project=None, branch=None, stats=None, key=None, targetFile=None,
|
||||
timestamp=None, isWrite=None, plugin=None, offline=None, **kwargs):
|
||||
url = 'https://wakatime.com/api/v1/actions'
|
||||
log.debug('Sending action to api at %s' % url)
|
||||
data = {
|
||||
'time': timestamp,
|
||||
@ -268,24 +276,45 @@ def send_action(project=None, branch=None, stats={}, key=None, targetFile=None,
|
||||
}
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data['traceback'] = traceback.format_exc()
|
||||
log.error(exception_data)
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, plugin)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.warn(exception_data)
|
||||
else:
|
||||
log.error(exception_data)
|
||||
except:
|
||||
exception_data = {
|
||||
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)
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, plugin)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.warn(exception_data)
|
||||
else:
|
||||
log.error(exception_data)
|
||||
else:
|
||||
if response.getcode() == 201:
|
||||
log.debug({
|
||||
'response_code': response.getcode(),
|
||||
})
|
||||
return True
|
||||
log.error({
|
||||
'response_code': response.getcode(),
|
||||
'response_content': response.read(),
|
||||
})
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, plugin)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.warn({
|
||||
'response_code': response.getcode(),
|
||||
'response_content': response.read(),
|
||||
})
|
||||
else:
|
||||
log.error({
|
||||
'response_code': response.getcode(),
|
||||
'response_content': response.read(),
|
||||
})
|
||||
return False
|
||||
|
||||
|
||||
@ -313,7 +342,7 @@ def main(argv=None):
|
||||
project_name = None
|
||||
if project:
|
||||
branch = project.branch()
|
||||
project_name = project.name()
|
||||
project_name = args.project_name or project.name()
|
||||
|
||||
if send_action(
|
||||
project=project_name,
|
||||
@ -321,6 +350,15 @@ def main(argv=None):
|
||||
stats=stats,
|
||||
**vars(args)
|
||||
):
|
||||
queue = Queue()
|
||||
while True:
|
||||
action = queue.pop()
|
||||
if action is None:
|
||||
break
|
||||
if not send_action(project=action['project'], targetFile=action['file'], timestamp=action['time'],
|
||||
branch=action['branch'], stats={'language': action['language'], 'lines': action['lines']},
|
||||
key=args.key, isWrite=action['is_write'], plugin=action['plugin'], offline=args.offline):
|
||||
break
|
||||
return 0 # success
|
||||
|
||||
return 102 # api error
|
||||
|
@ -13,10 +13,6 @@ import logging
|
||||
import os
|
||||
|
||||
from .base import BaseProject
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ..packages.ordereddict import OrderedDict
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -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])
|
||||
|
114
packages/wakatime/wakatime/queue.py
Normal file
114
packages/wakatime/wakatime/queue.py
Normal file
@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.queue
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Queue for offline time logging.
|
||||
http://wakatime.com
|
||||
|
||||
:copyright: (c) 2014 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
import traceback
|
||||
from time import sleep
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Queue(object):
|
||||
DB_FILE = os.path.join(os.path.expanduser('~'), '.wakatime.db')
|
||||
|
||||
def connect(self):
|
||||
conn = sqlite3.connect(self.DB_FILE)
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS action (
|
||||
file text,
|
||||
time real,
|
||||
project text,
|
||||
language text,
|
||||
lines integer,
|
||||
branch text,
|
||||
is_write integer,
|
||||
plugin text)
|
||||
''')
|
||||
return (conn, c)
|
||||
|
||||
|
||||
def push(self, data, plugin):
|
||||
try:
|
||||
conn, c = self.connect()
|
||||
action = {
|
||||
'file': data.get('file'),
|
||||
'time': data.get('time'),
|
||||
'project': data.get('project'),
|
||||
'language': data.get('language'),
|
||||
'lines': data.get('lines'),
|
||||
'branch': data.get('branch'),
|
||||
'is_write': 1 if data.get('is_write') else 0,
|
||||
'plugin': plugin,
|
||||
}
|
||||
c.execute('INSERT INTO action VALUES (:file,:time,:project,:language,:lines,:branch,:is_write,:plugin)', action)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except sqlite3.Error, e:
|
||||
log.error(str(e))
|
||||
|
||||
|
||||
def pop(self):
|
||||
tries = 3
|
||||
wait = 0.1
|
||||
action = None
|
||||
try:
|
||||
conn, c = self.connect()
|
||||
except sqlite3.Error, e:
|
||||
log.debug(traceback.format_exc())
|
||||
return None
|
||||
loop = True
|
||||
while loop and tries > -1:
|
||||
try:
|
||||
c.execute('BEGIN IMMEDIATE')
|
||||
c.execute('SELECT * FROM action LIMIT 1')
|
||||
row = c.fetchone()
|
||||
if row is not None:
|
||||
values = []
|
||||
clauses = []
|
||||
index = 0
|
||||
for row_name in ['file', 'time', 'project', 'language', 'lines', 'branch', 'is_write']:
|
||||
if row[index] is not None:
|
||||
clauses.append('{0}=?'.format(row_name))
|
||||
values.append(row[index])
|
||||
else:
|
||||
clauses.append('{0} IS NULL'.format(row_name))
|
||||
index += 1
|
||||
if len(values) > 0:
|
||||
c.execute('DELETE FROM action WHERE {0}'.format(u' AND '.join(clauses)), values)
|
||||
else:
|
||||
c.execute('DELETE FROM action WHERE {0}'.format(u' AND '.join(clauses)))
|
||||
conn.commit()
|
||||
if row is not None:
|
||||
action = {
|
||||
'file': row[0],
|
||||
'time': row[1],
|
||||
'project': row[2],
|
||||
'language': row[3],
|
||||
'lines': row[4],
|
||||
'branch': row[5],
|
||||
'is_write': True if row[6] is 1 else False,
|
||||
'plugin': row[7],
|
||||
}
|
||||
loop = False
|
||||
except sqlite3.Error, e:
|
||||
log.debug(traceback.format_exc())
|
||||
sleep(wait)
|
||||
tries -= 1
|
||||
try:
|
||||
conn.close()
|
||||
except sqlite3.Error, e:
|
||||
log.debug(traceback.format_exc())
|
||||
return action
|
Reference in New Issue
Block a user