upgrade wakatime cli to v4.0.4
This commit is contained in:
parent
307029c37a
commit
440e33b8b7
1087 changed files with 21913 additions and 587 deletions
26
WakaTime.py
26
WakaTime.py
|
@ -24,7 +24,7 @@ from os.path import expanduser, dirname, basename, realpath, join
|
||||||
ACTION_FREQUENCY = 2
|
ACTION_FREQUENCY = 2
|
||||||
ST_VERSION = int(sublime.version())
|
ST_VERSION = int(sublime.version())
|
||||||
PLUGIN_DIR = dirname(realpath(__file__))
|
PLUGIN_DIR = dirname(realpath(__file__))
|
||||||
API_CLIENT = '%s/packages/wakatime/wakatime-cli.py' % PLUGIN_DIR
|
API_CLIENT = '%s/packages/wakatime/cli.py' % PLUGIN_DIR
|
||||||
SETTINGS_FILE = 'WakaTime.sublime-settings'
|
SETTINGS_FILE = 'WakaTime.sublime-settings'
|
||||||
SETTINGS = {}
|
SETTINGS = {}
|
||||||
LAST_ACTION = {
|
LAST_ACTION = {
|
||||||
|
@ -37,9 +37,7 @@ LOCK = threading.RLock()
|
||||||
PYTHON_LOCATION = None
|
PYTHON_LOCATION = None
|
||||||
|
|
||||||
# add wakatime package to path
|
# add wakatime package to path
|
||||||
sys.path.insert(0, join(PLUGIN_DIR, 'packages', 'wakatime'))
|
sys.path.insert(0, join(PLUGIN_DIR, 'packages'))
|
||||||
|
|
||||||
from wakatime import parseConfigFile
|
|
||||||
|
|
||||||
# check if we have SSL support
|
# check if we have SSL support
|
||||||
try:
|
try:
|
||||||
|
@ -80,10 +78,14 @@ def prompt_api_key():
|
||||||
createConfigFile()
|
createConfigFile()
|
||||||
|
|
||||||
default_key = ''
|
default_key = ''
|
||||||
configs = parseConfigFile()
|
try:
|
||||||
if configs is not None:
|
from wakatime.base import parseConfigFile
|
||||||
if configs.has_option('settings', 'api_key'):
|
configs = parseConfigFile()
|
||||||
default_key = configs.get('settings', 'api_key')
|
if configs is not None:
|
||||||
|
if configs.has_option('settings', 'api_key'):
|
||||||
|
default_key = configs.get('settings', 'api_key')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if SETTINGS.get('api_key'):
|
if SETTINGS.get('api_key'):
|
||||||
return True
|
return True
|
||||||
|
@ -208,8 +210,8 @@ class SendActionThread(threading.Thread):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
cmd.append('--verbose')
|
cmd.append('--verbose')
|
||||||
if HAS_SSL:
|
if HAS_SSL:
|
||||||
if self.debug:
|
#if self.debug:
|
||||||
print('[WakaTime] %s' % ' '.join(cmd))
|
print('[WakaTime] %s' % ' '.join(cmd))
|
||||||
code = wakatime.main(cmd)
|
code = wakatime.main(cmd)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
print('[WakaTime] Error: Response code %d from wakatime package.' % code)
|
print('[WakaTime] Error: Response code %d from wakatime package.' % code)
|
||||||
|
@ -217,8 +219,8 @@ class SendActionThread(threading.Thread):
|
||||||
python = python_binary()
|
python = python_binary()
|
||||||
if python:
|
if python:
|
||||||
cmd.insert(0, python)
|
cmd.insert(0, python)
|
||||||
if self.debug:
|
#if self.debug:
|
||||||
print('[WakaTime] %s' % ' '.join(cmd))
|
print('[WakaTime] %s %s' % (python, ' '.join(cmd)))
|
||||||
if platform.system() == 'Windows':
|
if platform.system() == 'Windows':
|
||||||
Popen(cmd, shell=False)
|
Popen(cmd, shell=False)
|
||||||
else:
|
else:
|
||||||
|
|
39
packages/wakatime/.gitignore
vendored
39
packages/wakatime/.gitignore
vendored
|
@ -1,39 +0,0 @@
|
||||||
*.py[cod]
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Packages
|
|
||||||
*.egg
|
|
||||||
*.egg-info
|
|
||||||
dist
|
|
||||||
build
|
|
||||||
eggs
|
|
||||||
parts
|
|
||||||
bin
|
|
||||||
var
|
|
||||||
sdist
|
|
||||||
develop-eggs
|
|
||||||
.installed.cfg
|
|
||||||
lib
|
|
||||||
lib64
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
.coverage
|
|
||||||
.tox
|
|
||||||
nosetests.xml
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
|
|
||||||
# Mr Developer
|
|
||||||
.mr.developer.cfg
|
|
||||||
.project
|
|
||||||
.pydevproject
|
|
||||||
|
|
||||||
virtualenv
|
|
||||||
venv
|
|
||||||
.DS_Store
|
|
|
@ -1,15 +0,0 @@
|
||||||
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>
|
|
|
@ -1,304 +0,0 @@
|
||||||
|
|
||||||
History
|
|
||||||
-------
|
|
||||||
|
|
||||||
|
|
||||||
3.0.5 (2015-01-13)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- ignore errors from malformed markup (too many closing tags)
|
|
||||||
|
|
||||||
|
|
||||||
3.0.4 (2015-01-06)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- remove unused dependency, which is missing in some python environments
|
|
||||||
|
|
||||||
|
|
||||||
3.0.3 (2014-12-25)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- detect JavaScript frameworks from script tags in Html template files
|
|
||||||
|
|
||||||
|
|
||||||
3.0.2 (2014-12-25)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- detect frameworks from JavaScript and JSON files
|
|
||||||
|
|
||||||
|
|
||||||
3.0.1 (2014-12-23)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- handle unknown language when parsing dependencies
|
|
||||||
|
|
||||||
|
|
||||||
3.0.0 (2014-12-23)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- detect libraries and frameworks for C++, Java, .NET, PHP, and Python files
|
|
||||||
|
|
||||||
|
|
||||||
2.1.11 (2014-12-22)
|
|
||||||
+++++++++++++++++++
|
|
||||||
|
|
||||||
- fix offline logging when response from api is None
|
|
||||||
|
|
||||||
|
|
||||||
2.1.10 (2014-12-15)
|
|
||||||
+++++++++++++++++++
|
|
||||||
|
|
||||||
- prevent queuing offline heartbeats which will never be valid (400 errors)
|
|
||||||
|
|
||||||
|
|
||||||
2.1.9 (2014-12-05)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- fix bug preventing offline heartbeats from being purged after uploaded
|
|
||||||
|
|
||||||
|
|
||||||
2.1.8 (2014-12-04)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- fix UnicodeDecodeError when building user agent string
|
|
||||||
- handle case where response is None
|
|
||||||
|
|
||||||
|
|
||||||
2.1.7 (2014-11-30)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- upgrade pygments to v2.0.1
|
|
||||||
- always log an error when api key is incorrect
|
|
||||||
|
|
||||||
|
|
||||||
2.1.6 (2014-11-18)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- fix list index error when detecting subversion project
|
|
||||||
|
|
||||||
|
|
||||||
2.1.5 (2014-11-17)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- catch exceptions when getting current machine time zone
|
|
||||||
|
|
||||||
|
|
||||||
2.1.4 (2014-11-12)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- when Python was not compiled with https support, log an error to the log file
|
|
||||||
|
|
||||||
|
|
||||||
2.1.3 (2014-11-10)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- correctly detect branch name for subversion projects
|
|
||||||
|
|
||||||
|
|
||||||
2.1.2 (2014-10-07)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- still log heartbeat when something goes wrong while reading num lines in file
|
|
||||||
|
|
||||||
|
|
||||||
2.1.1 (2014-09-30)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- fix bug where binary file opened as utf-8
|
|
||||||
|
|
||||||
|
|
||||||
2.1.0 (2014-09-30)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- python3 compatibility changes
|
|
||||||
|
|
||||||
|
|
||||||
2.0.8 (2014-08-29)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- supress output from svn command
|
|
||||||
|
|
||||||
|
|
||||||
2.0.7 (2014-08-27)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- find svn binary location from common install directories
|
|
||||||
|
|
||||||
|
|
||||||
2.0.6 (2014-08-07)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- encode json data as str when passing to urllib
|
|
||||||
|
|
||||||
|
|
||||||
2.0.5 (2014-07-25)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- option in .wakatime.cfg to obfuscate file names
|
|
||||||
|
|
||||||
|
|
||||||
2.0.4 (2014-07-25)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- use unique logger namespace to prevent collisions in shared plugin environments
|
|
||||||
|
|
||||||
|
|
||||||
2.0.3 (2014-06-18)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- use project from command line arg when no revision control project is found
|
|
||||||
|
|
||||||
|
|
||||||
2.0.2 (2014-06-09)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- include python3.2 compatible versions of simplejson, pytz, and tzlocal
|
|
||||||
- disable offline logging when Python was not compiled with sqlite3 module
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Read git HEAD file to find current branch instead of running git command line
|
|
||||||
|
|
||||||
|
|
||||||
0.4.7 (2013-09-30)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Sending local olson timezone string in api request
|
|
||||||
|
|
||||||
|
|
||||||
0.4.6 (2013-09-22)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Sending total lines in file and language name to api
|
|
||||||
|
|
||||||
|
|
||||||
0.4.5 (2013-09-07)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Fixed relative import error by adding packages directory to sys path
|
|
||||||
|
|
||||||
|
|
||||||
0.4.4 (2013-09-06)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Using urllib2 again because of intermittent problems sending json with requests library
|
|
||||||
|
|
||||||
|
|
||||||
0.4.3 (2013-09-04)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Encoding json as utf-8 before making request
|
|
||||||
|
|
||||||
|
|
||||||
0.4.2 (2013-09-04)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Using requests package v1.2.3 from pypi
|
|
||||||
|
|
||||||
|
|
||||||
0.4.1 (2013-08-25)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Fix bug causing requests library to omit POST content
|
|
||||||
|
|
||||||
|
|
||||||
0.4.0 (2013-08-15)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Sending single branch instead of multiple tags
|
|
||||||
|
|
||||||
|
|
||||||
0.3.1 (2013-08-08)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Using requests module instead of urllib2 to verify SSL certs
|
|
||||||
|
|
||||||
|
|
||||||
0.3.0 (2013-08-08)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Allow importing directly from Python plugins
|
|
||||||
|
|
||||||
|
|
||||||
0.1.1 (2013-07-07)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Refactored
|
|
||||||
- Simplified action events schema
|
|
||||||
|
|
||||||
|
|
||||||
0.0.1 (2013-07-05)
|
|
||||||
++++++++++++++++++
|
|
||||||
|
|
||||||
- Birth
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
BSD 3-Clause License
|
|
||||||
|
|
||||||
Copyright (c) 2014 by the respective authors (see AUTHORS file).
|
|
||||||
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, 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.
|
|
|
@ -1,2 +0,0 @@
|
||||||
include README.rst LICENSE HISTORY.rst
|
|
||||||
recursive-include wakatime *.py
|
|
|
@ -1,20 +0,0 @@
|
||||||
WakaTime
|
|
||||||
========
|
|
||||||
|
|
||||||
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 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.
|
|
||||||
|
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
pip install wakatime
|
|
||||||
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
https://wakatime.com/
|
|
9
packages/wakatime/__about__.py
Normal file
9
packages/wakatime/__about__.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
__title__ = 'wakatime'
|
||||||
|
__description__ = 'Common interface to the WakaTime api.'
|
||||||
|
__url__ = 'https://github.com/wakatime/wakatime'
|
||||||
|
__version_info__ = ('4', '0', '4')
|
||||||
|
__version__ = '.'.join(__version_info__)
|
||||||
|
__author__ = 'Alan Hamlett'
|
||||||
|
__author_email__ = 'alan@wakatime.com'
|
||||||
|
__license__ = 'BSD'
|
||||||
|
__copyright__ = 'Copyright 2014 Alan Hamlett'
|
17
packages/wakatime/__init__.py
Normal file
17
packages/wakatime/__init__.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
wakatime
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Common interface to the WakaTime api.
|
||||||
|
http://wakatime.com
|
||||||
|
|
||||||
|
:copyright: (c) 2013 Alan Hamlett.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['main']
|
||||||
|
|
||||||
|
|
||||||
|
from .base import main
|
|
@ -1,10 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
wakatime
|
wakatime.base
|
||||||
~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
Common interface to the WakaTime api.
|
wakatime module entry point.
|
||||||
http://wakatime.com
|
|
||||||
|
|
||||||
:copyright: (c) 2013 Alan Hamlett.
|
:copyright: (c) 2013 Alan Hamlett.
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
|
@ -12,13 +11,6 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
__title__ = 'wakatime'
|
|
||||||
__version__ = '3.0.5'
|
|
||||||
__author__ = 'Alan Hamlett'
|
|
||||||
__license__ = 'BSD'
|
|
||||||
__copyright__ = 'Copyright 2014 Alan Hamlett'
|
|
||||||
|
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -31,26 +23,24 @@ try:
|
||||||
import ConfigParser as configparser
|
import ConfigParser as configparser
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import configparser
|
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.dirname(os.path.abspath(__file__)))
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages'))
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages'))
|
||||||
|
|
||||||
|
from .__about__ import __version__
|
||||||
from .compat import u, open, is_py3
|
from .compat import u, open, is_py3
|
||||||
from .queue import Queue
|
from .offlinequeue import Queue
|
||||||
from .log import setup_logging
|
from .log import setup_logging
|
||||||
from .project import find_project
|
from .project import find_project
|
||||||
from .stats import get_file_stats
|
from .stats import get_file_stats
|
||||||
from .packages import argparse
|
from .packages import argparse
|
||||||
from .packages import simplejson as json
|
from .packages import simplejson as json
|
||||||
|
from .packages import requests
|
||||||
|
from .packages.requests.exceptions import RequestException
|
||||||
try:
|
try:
|
||||||
from .packages import tzlocal
|
from .packages import tzlocal
|
||||||
except:
|
except:
|
||||||
from .packages import tzlocal3
|
from .packages import tzlocal3 as tzlocal
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger('WakaTime')
|
log = logging.getLogger('WakaTime')
|
||||||
|
@ -143,29 +133,43 @@ def parseArguments(argv):
|
||||||
parser.add_argument('--file', dest='targetFile', metavar='file',
|
parser.add_argument('--file', dest='targetFile', metavar='file',
|
||||||
action=FileAction, required=True,
|
action=FileAction, required=True,
|
||||||
help='absolute path to file for current heartbeat')
|
help='absolute path to file for current heartbeat')
|
||||||
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('--write', dest='isWrite',
|
|
||||||
action='store_true',
|
|
||||||
help='note heartbeat 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('--project', dest='project_name',
|
|
||||||
help='optional project name; will auto-discover by default')
|
|
||||||
parser.add_argument('--key', dest='key',
|
parser.add_argument('--key', dest='key',
|
||||||
help='your wakatime api key; uses api_key from '+
|
help='your wakatime api key; uses api_key from '+
|
||||||
'~/.wakatime.conf by default')
|
'~/.wakatime.conf by default')
|
||||||
|
parser.add_argument('--write', dest='isWrite',
|
||||||
|
action='store_true',
|
||||||
|
help='when set, tells api this heartbeat 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('--time', dest='timestamp', metavar='time',
|
||||||
|
type=float,
|
||||||
|
help='optional floating-point unix epoch timestamp; '+
|
||||||
|
'uses current time by default')
|
||||||
|
parser.add_argument('--notfile', dest='notfile', action='store_true',
|
||||||
|
help='when set, will accept any value for the file. for example, '+
|
||||||
|
'a domain name or other item you want to log time towards.')
|
||||||
|
parser.add_argument('--proxy', dest='proxy',
|
||||||
|
help='optional https proxy url; for example: '+
|
||||||
|
'https://user:pass@localhost:8080')
|
||||||
|
parser.add_argument('--project', dest='project_name',
|
||||||
|
help='optional project name; will auto-discover by default')
|
||||||
parser.add_argument('--disableoffline', dest='offline',
|
parser.add_argument('--disableoffline', dest='offline',
|
||||||
action='store_false',
|
action='store_false',
|
||||||
help='disables offline time logging instead of queuing logged time')
|
help='disables offline time logging instead of queuing logged time')
|
||||||
parser.add_argument('--hidefilenames', dest='hidefilenames',
|
parser.add_argument('--hidefilenames', dest='hidefilenames',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='obfuscate file names; will not send file names to api')
|
help='obfuscate file names; will not send file names to api')
|
||||||
|
parser.add_argument('--exclude', dest='exclude', action='append',
|
||||||
|
help='filename patterns to exclude from logging; POSIX regex '+
|
||||||
|
'syntax; can be used more than once')
|
||||||
|
parser.add_argument('--include', dest='include', action='append',
|
||||||
|
help='filename patterns to log; when used in combination with '+
|
||||||
|
'--exclude, files matching include will still be logged; '+
|
||||||
|
'POSIX regex syntax; can be used more than once')
|
||||||
parser.add_argument('--ignore', dest='ignore', action='append',
|
parser.add_argument('--ignore', dest='ignore', action='append',
|
||||||
help='filename patterns to ignore; POSIX regex syntax; can be used more than once')
|
help=argparse.SUPPRESS)
|
||||||
parser.add_argument('--logfile', dest='logfile',
|
parser.add_argument('--logfile', dest='logfile',
|
||||||
help='defaults to ~/.wakatime.log')
|
help='defaults to ~/.wakatime.log')
|
||||||
parser.add_argument('--config', dest='config',
|
parser.add_argument('--config', dest='config',
|
||||||
|
@ -191,23 +195,43 @@ def parseArguments(argv):
|
||||||
default_key = None
|
default_key = None
|
||||||
if configs.has_option('settings', 'api_key'):
|
if configs.has_option('settings', 'api_key'):
|
||||||
default_key = configs.get('settings', 'api_key')
|
default_key = configs.get('settings', 'api_key')
|
||||||
|
elif configs.has_option('settings', 'apikey'):
|
||||||
|
default_key = configs.get('settings', 'apikey')
|
||||||
if default_key:
|
if default_key:
|
||||||
args.key = default_key
|
args.key = default_key
|
||||||
else:
|
else:
|
||||||
parser.error('Missing api key')
|
parser.error('Missing api key')
|
||||||
if not args.ignore:
|
if not args.exclude:
|
||||||
args.ignore = []
|
args.exclude = []
|
||||||
if configs.has_option('settings', 'ignore'):
|
if configs.has_option('settings', 'ignore'):
|
||||||
try:
|
try:
|
||||||
for pattern in configs.get('settings', 'ignore').split("\n"):
|
for pattern in configs.get('settings', 'ignore').split("\n"):
|
||||||
if pattern.strip() != '':
|
if pattern.strip() != '':
|
||||||
args.ignore.append(pattern)
|
args.exclude.append(pattern)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
if configs.has_option('settings', 'exclude'):
|
||||||
|
try:
|
||||||
|
for pattern in configs.get('settings', 'exclude').split("\n"):
|
||||||
|
if pattern.strip() != '':
|
||||||
|
args.exclude.append(pattern)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
if not args.include:
|
||||||
|
args.include = []
|
||||||
|
if configs.has_option('settings', 'include'):
|
||||||
|
try:
|
||||||
|
for pattern in configs.get('settings', 'include').split("\n"):
|
||||||
|
if pattern.strip() != '':
|
||||||
|
args.include.append(pattern)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
if args.offline and configs.has_option('settings', 'offline'):
|
if args.offline and configs.has_option('settings', 'offline'):
|
||||||
args.offline = configs.getboolean('settings', 'offline')
|
args.offline = configs.getboolean('settings', 'offline')
|
||||||
if not args.hidefilenames and configs.has_option('settings', 'hidefilenames'):
|
if not args.hidefilenames and configs.has_option('settings', 'hidefilenames'):
|
||||||
args.hidefilenames = configs.getboolean('settings', 'hidefilenames')
|
args.hidefilenames = configs.getboolean('settings', 'hidefilenames')
|
||||||
|
if not args.proxy and configs.has_option('settings', 'proxy'):
|
||||||
|
args.proxy = configs.get('settings', 'proxy')
|
||||||
if not args.verbose and configs.has_option('settings', 'verbose'):
|
if not args.verbose and configs.has_option('settings', 'verbose'):
|
||||||
args.verbose = configs.getboolean('settings', 'verbose')
|
args.verbose = configs.getboolean('settings', 'verbose')
|
||||||
if not args.verbose and configs.has_option('settings', 'debug'):
|
if not args.verbose and configs.has_option('settings', 'debug'):
|
||||||
|
@ -218,20 +242,34 @@ def parseArguments(argv):
|
||||||
return args, configs
|
return args, configs
|
||||||
|
|
||||||
|
|
||||||
def should_ignore(fileName, patterns):
|
def should_exclude(fileName, include, exclude):
|
||||||
try:
|
if fileName is not None and fileName.strip() != '':
|
||||||
for pattern in patterns:
|
try:
|
||||||
try:
|
for pattern in include:
|
||||||
compiled = re.compile(pattern, re.IGNORECASE)
|
try:
|
||||||
if compiled.search(fileName):
|
compiled = re.compile(pattern, re.IGNORECASE)
|
||||||
return pattern
|
if compiled.search(fileName):
|
||||||
except re.error as ex:
|
return False
|
||||||
log.warning(u('Regex error ({msg}) for ignore pattern: {pattern}').format(
|
except re.error as ex:
|
||||||
msg=u(ex),
|
log.warning(u('Regex error ({msg}) for include pattern: {pattern}').format(
|
||||||
pattern=u(pattern),
|
msg=u(ex),
|
||||||
))
|
pattern=u(pattern),
|
||||||
except TypeError:
|
))
|
||||||
pass
|
except TypeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
for pattern in exclude:
|
||||||
|
try:
|
||||||
|
compiled = re.compile(pattern, re.IGNORECASE)
|
||||||
|
if compiled.search(fileName):
|
||||||
|
return pattern
|
||||||
|
except re.error as ex:
|
||||||
|
log.warning(u('Regex error ({msg}) for exclude pattern: {pattern}').format(
|
||||||
|
msg=u(ex),
|
||||||
|
pattern=u(pattern),
|
||||||
|
))
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -248,19 +286,23 @@ def get_user_agent(plugin):
|
||||||
user_agent=user_agent,
|
user_agent=user_agent,
|
||||||
plugin=u(plugin),
|
plugin=u(plugin),
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
user_agent = u('{user_agent} Unknown/0').format(
|
||||||
|
user_agent=user_agent,
|
||||||
|
)
|
||||||
return user_agent
|
return user_agent
|
||||||
|
|
||||||
|
|
||||||
def send_heartbeat(project=None, branch=None, stats={}, key=None, targetFile=None,
|
def send_heartbeat(project=None, branch=None, stats={}, key=None, targetFile=None,
|
||||||
timestamp=None, isWrite=None, plugin=None, offline=None,
|
timestamp=None, isWrite=None, plugin=None, offline=None,
|
||||||
hidefilenames=None, **kwargs):
|
hidefilenames=None, notfile=False, proxy=None, **kwargs):
|
||||||
url = 'https://wakatime.com/api/v1/heartbeats'
|
url = 'https://wakatime.com/api/v1/heartbeats'
|
||||||
log.debug('Sending heartbeat to api at %s' % url)
|
log.debug('Sending heartbeat to api at %s' % url)
|
||||||
data = {
|
data = {
|
||||||
'time': timestamp,
|
'time': timestamp,
|
||||||
'file': targetFile,
|
'file': targetFile,
|
||||||
}
|
}
|
||||||
if hidefilenames and targetFile is not None:
|
if hidefilenames and targetFile is not None and not notfile:
|
||||||
data['file'] = data['file'].rsplit('/', 1)[-1].rsplit('\\', 1)[-1]
|
data['file'] = data['file'].rsplit('/', 1)[-1].rsplit('\\', 1)[-1]
|
||||||
if len(data['file'].strip('.').split('.', 1)) > 1:
|
if len(data['file'].strip('.').split('.', 1)) > 1:
|
||||||
data['file'] = u('HIDDEN.{ext}').format(ext=u(data['file'].strip('.').rsplit('.', 1)[-1]))
|
data['file'] = u('HIDDEN.{ext}').format(ext=u(data['file'].strip('.').rsplit('.', 1)[-1]))
|
||||||
|
@ -282,15 +324,17 @@ def send_heartbeat(project=None, branch=None, stats={}, key=None, targetFile=Non
|
||||||
|
|
||||||
# setup api request
|
# setup api request
|
||||||
request_body = json.dumps(data)
|
request_body = json.dumps(data)
|
||||||
request = Request(url=url, data=str.encode(request_body) if is_py3 else request_body)
|
api_key = u(base64.b64encode(str.encode(key) if is_py3 else key))
|
||||||
request.add_header('User-Agent', get_user_agent(plugin))
|
auth = u('Basic {api_key}').format(api_key=api_key)
|
||||||
request.add_header('Content-Type', 'application/json')
|
headers = {
|
||||||
auth = u('Basic {key}').format(key=u(base64.b64encode(str.encode(key) if is_py3 else key)))
|
'User-Agent': get_user_agent(plugin),
|
||||||
request.add_header('Authorization', auth)
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
ALWAYS_LOG_CODES = [
|
'Authorization': auth,
|
||||||
401,
|
}
|
||||||
]
|
proxies = {}
|
||||||
|
if proxy:
|
||||||
|
proxies['https'] = proxy
|
||||||
|
|
||||||
# add Olson timezone to request
|
# add Olson timezone to request
|
||||||
try:
|
try:
|
||||||
|
@ -298,68 +342,48 @@ def send_heartbeat(project=None, branch=None, stats={}, key=None, targetFile=Non
|
||||||
except:
|
except:
|
||||||
tz = None
|
tz = None
|
||||||
if tz:
|
if tz:
|
||||||
request.add_header('TimeZone', u(tz.zone))
|
headers['TimeZone'] = u(tz.zone)
|
||||||
|
|
||||||
# log time to api
|
# log time to api
|
||||||
response = None
|
response = None
|
||||||
try:
|
try:
|
||||||
response = urlopen(request)
|
response = requests.post(url, data=request_body, headers=headers,
|
||||||
except HTTPError as exc:
|
proxies=proxies)
|
||||||
|
except RequestException:
|
||||||
exception_data = {
|
exception_data = {
|
||||||
'response_code': exc.getcode(),
|
|
||||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||||
}
|
}
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
exception_data['traceback'] = traceback.format_exc()
|
exception_data['traceback'] = traceback.format_exc()
|
||||||
if offline:
|
if offline:
|
||||||
if response is None or response.getcode() != 400:
|
queue = Queue()
|
||||||
queue = Queue()
|
queue.push(data, json.dumps(stats), plugin)
|
||||||
queue.push(data, json.dumps(stats), plugin)
|
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
log.warn(exception_data)
|
log.warn(exception_data)
|
||||||
if response is not None and response.getcode() in ALWAYS_LOG_CODES:
|
|
||||||
log.error({
|
|
||||||
'response_code': response.getcode(),
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
log.error(exception_data)
|
|
||||||
except:
|
|
||||||
exception_data = {
|
|
||||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
|
||||||
}
|
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
|
||||||
exception_data['traceback'] = traceback.format_exc()
|
|
||||||
if offline:
|
|
||||||
if response is None or response.getcode() != 400:
|
|
||||||
queue = Queue()
|
|
||||||
queue.push(data, json.dumps(stats), plugin)
|
|
||||||
if 'unknown url type: https' in u(sys.exc_info()[1]):
|
|
||||||
log.error(exception_data)
|
|
||||||
elif log.isEnabledFor(logging.DEBUG):
|
|
||||||
log.warn(exception_data)
|
|
||||||
if response is not None and response.getcode() in ALWAYS_LOG_CODES:
|
|
||||||
log.error({
|
|
||||||
'response_code': response.getcode(),
|
|
||||||
})
|
|
||||||
else:
|
else:
|
||||||
log.error(exception_data)
|
log.error(exception_data)
|
||||||
else:
|
else:
|
||||||
if response is not None and response.getcode() == 201:
|
response_code = response.status_code if response is not None else None
|
||||||
|
response_content = response.text if response is not None else None
|
||||||
|
if response_code == 201:
|
||||||
log.debug({
|
log.debug({
|
||||||
'response_code': response.getcode(),
|
'response_code': response_code,
|
||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
response_code = response.getcode() if response is not None else None
|
|
||||||
response_content = response.read() if response is not None else None
|
|
||||||
if offline:
|
if offline:
|
||||||
if response is None or response.getcode() != 400:
|
if response_code != 400:
|
||||||
queue = Queue()
|
queue = Queue()
|
||||||
queue.push(data, json.dumps(stats), plugin)
|
queue.push(data, json.dumps(stats), plugin)
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
if response_code == 401:
|
||||||
log.warn({
|
log.error({
|
||||||
'response_code': response_code,
|
'response_code': response_code,
|
||||||
'response_content': response_content,
|
'response_content': response_content,
|
||||||
})
|
})
|
||||||
|
elif log.isEnabledFor(logging.DEBUG):
|
||||||
|
log.warn({
|
||||||
|
'response_code': response_code,
|
||||||
|
'response_content': response_content,
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
log.error({
|
log.error({
|
||||||
'response_code': response_code,
|
'response_code': response_code,
|
||||||
|
@ -383,18 +407,20 @@ def main(argv=None):
|
||||||
|
|
||||||
setup_logging(args, __version__)
|
setup_logging(args, __version__)
|
||||||
|
|
||||||
ignore = should_ignore(args.targetFile, args.ignore)
|
exclude = should_exclude(args.targetFile, args.include, args.exclude)
|
||||||
if ignore is not False:
|
if exclude is not False:
|
||||||
log.debug(u('File ignored because matches pattern: {pattern}').format(
|
log.debug(u('File not logged because matches exclude pattern: {pattern}').format(
|
||||||
pattern=u(ignore),
|
pattern=u(exclude),
|
||||||
))
|
))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if os.path.isfile(args.targetFile):
|
if os.path.isfile(args.targetFile) or args.notfile:
|
||||||
|
|
||||||
stats = get_file_stats(args.targetFile)
|
stats = get_file_stats(args.targetFile, notfile=args.notfile)
|
||||||
|
|
||||||
project = find_project(args.targetFile, configs=configs)
|
project = None
|
||||||
|
if not args.notfile:
|
||||||
|
project = find_project(args.targetFile, configs=configs)
|
||||||
branch = None
|
branch = None
|
||||||
project_name = args.project_name
|
project_name = args.project_name
|
||||||
if project:
|
if project:
|
||||||
|
@ -421,7 +447,9 @@ def main(argv=None):
|
||||||
isWrite=heartbeat['is_write'],
|
isWrite=heartbeat['is_write'],
|
||||||
plugin=heartbeat['plugin'],
|
plugin=heartbeat['plugin'],
|
||||||
offline=args.offline,
|
offline=args.offline,
|
||||||
hidefilenames=args.hidefilenames)
|
hidefilenames=args.hidefilenames,
|
||||||
|
notfile=args.notfile,
|
||||||
|
proxy=args.proxy)
|
||||||
if not sent:
|
if not sent:
|
||||||
break
|
break
|
||||||
return 0 # success
|
return 0 # success
|
|
@ -1,6 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
wakatime-cli
|
wakatime.cli
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
Command-line entry point.
|
Command-line entry point.
|
||||||
|
@ -9,11 +9,9 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
import wakatime
|
import wakatime
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
|
@ -12,7 +12,6 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from . import TokenParser
|
from . import TokenParser
|
||||||
from ..compat import u
|
|
||||||
|
|
||||||
|
|
||||||
FILES = {
|
FILES = {
|
|
@ -30,7 +30,6 @@ class CustomEncoder(json.JSONEncoder):
|
||||||
try:
|
try:
|
||||||
encoded = super(CustomEncoder, self).default(obj)
|
encoded = super(CustomEncoder, self).default(obj)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
encoding = sys.getfilesystemencoding()
|
|
||||||
obj = u(obj)
|
obj = u(obj)
|
||||||
encoded = super(CustomEncoder, self).default(obj)
|
encoded = super(CustomEncoder, self).default(obj)
|
||||||
return encoded
|
return encoded
|
||||||
|
@ -63,7 +62,7 @@ class JsonFormatter(logging.Formatter):
|
||||||
return CustomEncoder().encode(data)
|
return CustomEncoder().encode(data)
|
||||||
|
|
||||||
def formatException(self, exc_info):
|
def formatException(self, exc_info):
|
||||||
return exec_info[2].format_exc()
|
return sys.exec_info[2].format_exc()
|
||||||
|
|
||||||
|
|
||||||
def set_log_level(logger, args):
|
def set_log_level(logger, args):
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue