added version 0.0.1
This commit is contained in:
parent
3da94756aa
commit
a902a2b948
18 changed files with 3075 additions and 4 deletions
10
HISTORY.rst
Normal file
10
HISTORY.rst
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
History
|
||||
-------
|
||||
|
||||
|
||||
0.0.1 (2013-07-05)
|
||||
++++++++++++++++++
|
||||
|
||||
- Birth
|
||||
|
29
LICENSE
Normal file
29
LICENSE
Normal file
|
@ -0,0 +1,29 @@
|
|||
Copyright (c) 2013 Alan Hamlett https://wakati.me
|
||||
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 Wakati.Me, 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.
|
2
MANIFEST.in
Normal file
2
MANIFEST.in
Normal file
|
@ -0,0 +1,2 @@
|
|||
include README.rst LICENSE HISTORY.rst
|
||||
recursive-include wakatime *.py
|
|
@ -1,4 +0,0 @@
|
|||
wakatime
|
||||
========
|
||||
|
||||
Command line event appender for Wakati.Me
|
12
README.rst
Normal file
12
README.rst
Normal file
|
@ -0,0 +1,12 @@
|
|||
Wakati.Me
|
||||
=========
|
||||
|
||||
Wakati.Me is a time tracking api for text editors. This is the command line
|
||||
event appender for the Wakati.Me api. You shouldn't need to directly use
|
||||
this outside of a text editor plugin.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
https://www.wakati.me/help/plugins/installing-plugins
|
37
setup.py
Normal file
37
setup.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from setuptools import setup
|
||||
|
||||
from wakatime.wakatime import __version__ as VERSION
|
||||
|
||||
|
||||
packages = [
|
||||
'wakatime',
|
||||
]
|
||||
|
||||
setup(
|
||||
name='wakatime',
|
||||
version=VERSION,
|
||||
license='BSD 3 Clause',
|
||||
description=(
|
||||
'Event appender for Wakati.Me, a time',
|
||||
'tracking api for text editors.',
|
||||
),
|
||||
author='Alan Hamlett',
|
||||
author_email='alan.hamlett@gmail.com',
|
||||
url='https://github.com/wakatime/wakatime',
|
||||
packages=packages,
|
||||
package_dir={'wakatime': 'wakatime'},
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
entry_points={
|
||||
'console_scripts': ['wakatime = wakatime.wakatime:main'],
|
||||
},
|
||||
classifiers=(
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Natural Language :: English',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Text Editors',
|
||||
),
|
||||
)
|
0
wakatime/__init__.py
Normal file
0
wakatime/__init__.py
Normal file
85
wakatime/log.py
Normal file
85
wakatime/log.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.log
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Provides the configured logger for writing JSON to the log file.
|
||||
|
||||
:copyright: (c) 2013 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from .packages.ordereddict import OrderedDict
|
||||
|
||||
|
||||
class CustomEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
return super(CustomEncoder, self).default(obj)
|
||||
|
||||
|
||||
class JsonFormatter(logging.Formatter):
|
||||
|
||||
def __init__(self, timestamp, endtime, isWrite, targetFile, version,
|
||||
plugin, datefmt=None):
|
||||
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([
|
||||
('now', self.formatTime(record, self.datefmt)),
|
||||
('version', self.version),
|
||||
('plugin', self.plugin),
|
||||
('timestamp', self.timestamp),
|
||||
('endtime', self.endtime),
|
||||
('isWrite', self.isWrite),
|
||||
('file', self.targetFile),
|
||||
('level', record.levelname),
|
||||
('message', record.msg),
|
||||
])
|
||||
if not self.endtime:
|
||||
del data['endtime']
|
||||
if not self.plugin:
|
||||
del data['plugin']
|
||||
if not self.isWrite:
|
||||
del data['isWrite']
|
||||
return CustomEncoder().encode(data)
|
||||
|
||||
def formatException(self, exc_info):
|
||||
return exec_info[2].format_exc()
|
||||
|
||||
|
||||
def setup_logging(args, version):
|
||||
logfile = args.logfile
|
||||
if not logfile:
|
||||
logfile = '~/.wakatime.log'
|
||||
handler = logging.FileHandler(os.path.expanduser(logfile))
|
||||
formatter = JsonFormatter(
|
||||
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()
|
||||
logger.addHandler(handler)
|
||||
level = logging.INFO
|
||||
if args.verbose:
|
||||
level = logging.DEBUG
|
||||
logger.setLevel(level)
|
||||
return logger
|
0
wakatime/packages/__init__.py
Normal file
0
wakatime/packages/__init__.py
Normal file
2362
wakatime/packages/argparse.py
Normal file
2362
wakatime/packages/argparse.py
Normal file
File diff suppressed because it is too large
Load diff
127
wakatime/packages/ordereddict.py
Normal file
127
wakatime/packages/ordereddict.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
# Copyright (c) 2009 Raymond Hettinger
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from UserDict import DictMixin
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
try:
|
||||
self.__end
|
||||
except AttributeError:
|
||||
self.clear()
|
||||
self.update(*args, **kwds)
|
||||
|
||||
def clear(self):
|
||||
self.__end = end = []
|
||||
end += [None, end, end] # sentinel node for doubly linked list
|
||||
self.__map = {} # key --> [key, prev, next]
|
||||
dict.clear(self)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key not in self:
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
curr[2] = end[1] = self.__map[key] = [key, curr, end]
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key)
|
||||
key, prev, next = self.__map.pop(key)
|
||||
prev[2] = next
|
||||
next[1] = prev
|
||||
|
||||
def __iter__(self):
|
||||
end = self.__end
|
||||
curr = end[2]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[2]
|
||||
|
||||
def __reversed__(self):
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[1]
|
||||
|
||||
def popitem(self, last=True):
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
if last:
|
||||
key = reversed(self).next()
|
||||
else:
|
||||
key = iter(self).next()
|
||||
value = self.pop(key)
|
||||
return key, value
|
||||
|
||||
def __reduce__(self):
|
||||
items = [[k, self[k]] for k in self]
|
||||
tmp = self.__map, self.__end
|
||||
del self.__map, self.__end
|
||||
inst_dict = vars(self).copy()
|
||||
self.__map, self.__end = tmp
|
||||
if inst_dict:
|
||||
return (self.__class__, (items,), inst_dict)
|
||||
return self.__class__, (items,)
|
||||
|
||||
def keys(self):
|
||||
return list(self)
|
||||
|
||||
setdefault = DictMixin.setdefault
|
||||
update = DictMixin.update
|
||||
pop = DictMixin.pop
|
||||
values = DictMixin.values
|
||||
items = DictMixin.items
|
||||
iterkeys = DictMixin.iterkeys
|
||||
itervalues = DictMixin.itervalues
|
||||
iteritems = DictMixin.iteritems
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, value=None):
|
||||
d = cls()
|
||||
for key in iterable:
|
||||
d[key] = value
|
||||
return d
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, OrderedDict):
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
for p, q in zip(self.items(), other.items()):
|
||||
if p != q:
|
||||
return False
|
||||
return True
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
35
wakatime/project.py
Normal file
35
wakatime/project.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.project
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Returns a project for the given file.
|
||||
|
||||
:copyright: (c) 2013 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from .projects.base import BaseProject
|
||||
from .projects.git import Git
|
||||
from .projects.mercurial import Mercurial
|
||||
from .projects.subversion import Subversion
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PLUGINS = [
|
||||
Git,
|
||||
Mercurial,
|
||||
Subversion,
|
||||
]
|
||||
|
||||
|
||||
def find_project(path):
|
||||
for plugin in PLUGINS:
|
||||
project = plugin(path)
|
||||
if project.config:
|
||||
return project
|
||||
return BaseProject(path)
|
0
wakatime/projects/__init__.py
Normal file
0
wakatime/projects/__init__.py
Normal file
47
wakatime/projects/base.py
Normal file
47
wakatime/projects/base.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.projects.base
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Base project for use when no other project can be found.
|
||||
|
||||
:copyright: (c) 2013 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseProject():
|
||||
|
||||
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):
|
||||
type = self.__class__.__name__.lower()
|
||||
if type == 'baseproject':
|
||||
type = None
|
||||
return type
|
||||
|
||||
def base(self):
|
||||
if self.config:
|
||||
return os.path.dirname(self.config)
|
||||
return None
|
||||
|
||||
def tags(self):
|
||||
tags = []
|
||||
return tags
|
||||
|
||||
def findConfig(self, path):
|
||||
return ''
|
81
wakatime/projects/git.py
Normal file
81
wakatime/projects/git.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.projects.git
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Information about the git project for a given file.
|
||||
|
||||
:copyright: (c) 2013 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from .base import BaseProject
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ..packages.ordereddict import OrderedDict
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Git(BaseProject):
|
||||
|
||||
def base(self):
|
||||
if self.config:
|
||||
return os.path.dirname(os.path.dirname(self.config))
|
||||
return None
|
||||
|
||||
def tags(self):
|
||||
tags = []
|
||||
if self.config:
|
||||
sections = self.parseConfig()
|
||||
for section in sections:
|
||||
if section.split(' ', 1)[0] == 'remote' and 'url' in sections[section]:
|
||||
tags.append(sections[section]['url'])
|
||||
return tags
|
||||
|
||||
def findConfig(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, '.git', 'config')):
|
||||
return os.path.join(path, '.git', 'config')
|
||||
split_path = os.path.split(path)
|
||||
if split_path[1] == '':
|
||||
return None
|
||||
return self.findConfig(split_path[0])
|
||||
|
||||
def parseConfig(self):
|
||||
sections = {}
|
||||
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] = {}
|
||||
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
|
28
wakatime/projects/mercurial.py
Normal file
28
wakatime/projects/mercurial.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.projects.mercurial
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Information about the mercurial project for a given file.
|
||||
|
||||
:copyright: (c) 2013 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from .base import BaseProject
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Mercurial(BaseProject):
|
||||
|
||||
def base(self):
|
||||
return super(Mercurial, self).base()
|
||||
|
||||
def tags(self):
|
||||
tags = []
|
||||
return tags
|
28
wakatime/projects/subversion.py
Normal file
28
wakatime/projects/subversion.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.projects.subversion
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Information about the svn project for a given file.
|
||||
|
||||
:copyright: (c) 2013 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from .base import BaseProject
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Subversion(BaseProject):
|
||||
|
||||
def base(self):
|
||||
return super(Subversion, self).base()
|
||||
|
||||
def tags(self):
|
||||
tags = []
|
||||
return tags
|
192
wakatime/wakatime.py
Normal file
192
wakatime/wakatime.py
Normal file
|
@ -0,0 +1,192 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime
|
||||
~~~~~~~~
|
||||
|
||||
Event appender for Wakati.Me, a time tracking api for programmers.
|
||||
|
||||
:copyright: (c) 2013 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
__title__ = 'wakatime'
|
||||
__version__ = '0.0.1'
|
||||
__author__ = 'Alan Hamlett'
|
||||
__license__ = 'BSD'
|
||||
__copyright__ = 'Copyright 2013 Alan Hamlett'
|
||||
|
||||
|
||||
# allow running script directly
|
||||
if __name__ == '__main__' and __package__ is None:
|
||||
import os, sys
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, parent_dir)
|
||||
import wakatime
|
||||
__package__ = 'wakatime'
|
||||
del os, sys
|
||||
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import urllib2
|
||||
|
||||
from .log import setup_logging
|
||||
from .project import find_project
|
||||
|
||||
try:
|
||||
import argparse
|
||||
except ImportError:
|
||||
from .packages import argparse
|
||||
|
||||
|
||||
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 parseArguments():
|
||||
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 event')
|
||||
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('--endtime', dest='endtime',
|
||||
help='optional end timestamp turning this event into '+
|
||||
'a duration; if a non-duration event occurs within a '+
|
||||
'duration, the duration is ignored')
|
||||
parser.add_argument('--write', dest='isWrite',
|
||||
action='store_true',
|
||||
help='note event was triggered from writing to a file')
|
||||
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('--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()
|
||||
if not args.timestamp:
|
||||
args.timestamp = time.time()
|
||||
if not args.key:
|
||||
default_key = get_api_key(args.config)
|
||||
if default_key:
|
||||
args.key = default_key
|
||||
else:
|
||||
parser.error('Missing api key')
|
||||
return args
|
||||
|
||||
|
||||
def get_api_key(configFile):
|
||||
if not configFile:
|
||||
configFile = '~/.wakatime.conf'
|
||||
api_key = None
|
||||
try:
|
||||
cf = open(os.path.expanduser('~/.wakatime'))
|
||||
for line in cf:
|
||||
line = line.split('=', 1)
|
||||
if line[0] == 'api_key':
|
||||
api_key = line[1].strip()
|
||||
cf.close()
|
||||
except IOError:
|
||||
log.error('Could not read from config file.')
|
||||
return api_key
|
||||
|
||||
|
||||
def get_user_agent(plugin):
|
||||
user_agent = 'wakatime/%s %s' % (__version__, platform.platform())
|
||||
if plugin:
|
||||
user_agent = plugin+' '+user_agent
|
||||
return user_agent
|
||||
|
||||
|
||||
def send_action(project_name=None, tags=None, key=None, targetFile=None,
|
||||
timestamp=None, endtime=None, isWrite=None, plugin=None, **kwargs):
|
||||
url = 'https://www.wakati.me/api/v1/events'
|
||||
log.debug('Sending event to api at %s' % url)
|
||||
data = {
|
||||
'timestamp': timestamp,
|
||||
'file': targetFile,
|
||||
}
|
||||
if endtime:
|
||||
data['endtime'] = endtime
|
||||
if isWrite:
|
||||
data['isWrite'] = isWrite
|
||||
if project_name:
|
||||
data['project'] = project_name
|
||||
if tags:
|
||||
data['tags'] = tags
|
||||
log.debug(data)
|
||||
request = urllib2.Request(url=url, data=json.dumps(data))
|
||||
user_agent = get_user_agent(plugin)
|
||||
request.add_header('User-Agent', user_agent)
|
||||
request.add_header('Content-Type', 'application/json')
|
||||
request.add_header('Authorization', 'Basic %s' % base64.b64encode(key))
|
||||
response = None
|
||||
try:
|
||||
response = urllib2.urlopen(request)
|
||||
except urllib2.HTTPError as exc:
|
||||
data = {
|
||||
'response_code': exc.getcode(),
|
||||
'response_content': exc.read(),
|
||||
sys.exc_info()[0].__name__: str(sys.exc_info()[1]),
|
||||
}
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
data['traceback'] = traceback.format_exc()
|
||||
log.error(data)
|
||||
except:
|
||||
data = {
|
||||
sys.exc_info()[0].__name__: str(sys.exc_info()[1]),
|
||||
}
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
data['traceback'] = traceback.format_exc()
|
||||
log.error(data)
|
||||
else:
|
||||
log.debug({
|
||||
'response_code': response.getcode(),
|
||||
'response_content': response.read(),
|
||||
})
|
||||
if response.getcode() >= 200 and response.getcode() < 300:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
args = parseArguments()
|
||||
setup_logging(args, __version__)
|
||||
if os.path.isfile(args.targetFile):
|
||||
project = find_project(args.targetFile)
|
||||
tags = project.tags()
|
||||
if send_action(project=project, tags=tags, **vars(args)):
|
||||
return 0
|
||||
return 102
|
||||
else:
|
||||
log.debug('File does not exist; ignoring this event.')
|
||||
return 101
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
Loading…
Reference in a new issue