mirror of
https://github.com/wakatime/sublime-wakatime.git
synced 2023-08-10 21:13:02 +03:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
fc8c61fa3f | |||
aa30110343 | |||
b671856341 | |||
b801759cdf | |||
919064200b | |||
911b5656d7 |
13
HISTORY.rst
13
HISTORY.rst
@ -3,6 +3,19 @@ History
|
||||
-------
|
||||
|
||||
|
||||
3.0.15 (2015-04-01)
|
||||
+++++++++++++++++++
|
||||
|
||||
- obfuscate api key when logging to Sublime Text Console in debug mode
|
||||
|
||||
|
||||
3.0.14 (2015-03-31)
|
||||
+++++++++++++++++++
|
||||
|
||||
- always use external python binary because ST builtin python does not support checking SSL certs
|
||||
- upgrade wakatime cli to v4.0.6
|
||||
|
||||
|
||||
3.0.13 (2015-03-23)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
77
WakaTime.py
77
WakaTime.py
@ -6,7 +6,9 @@ License: BSD, see LICENSE for more details.
|
||||
Website: https://wakatime.com/
|
||||
==========================================================="""
|
||||
|
||||
__version__ = '3.0.13'
|
||||
|
||||
__version__ = '3.0.15'
|
||||
|
||||
|
||||
import sublime
|
||||
import sublime_plugin
|
||||
@ -19,13 +21,14 @@ import time
|
||||
import threading
|
||||
import webbrowser
|
||||
from datetime import datetime
|
||||
from os.path import expanduser, dirname, basename, realpath, join
|
||||
from subprocess import Popen
|
||||
|
||||
|
||||
# globals
|
||||
ACTION_FREQUENCY = 2
|
||||
ST_VERSION = int(sublime.version())
|
||||
PLUGIN_DIR = dirname(realpath(__file__))
|
||||
API_CLIENT = '%s/packages/wakatime/cli.py' % PLUGIN_DIR
|
||||
PLUGIN_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
API_CLIENT = os.path.join(PLUGIN_DIR, 'packages', 'wakatime', 'cli.py')
|
||||
SETTINGS_FILE = 'WakaTime.sublime-settings'
|
||||
SETTINGS = {}
|
||||
LAST_ACTION = {
|
||||
@ -33,27 +36,16 @@ LAST_ACTION = {
|
||||
'file': None,
|
||||
'is_write': False,
|
||||
}
|
||||
HAS_SSL = False
|
||||
LOCK = threading.RLock()
|
||||
PYTHON_LOCATION = None
|
||||
|
||||
|
||||
# add wakatime package to path
|
||||
sys.path.insert(0, join(PLUGIN_DIR, 'packages'))
|
||||
|
||||
# check if we have SSL support
|
||||
sys.path.insert(0, os.path.join(PLUGIN_DIR, 'packages'))
|
||||
try:
|
||||
import ssl
|
||||
import socket
|
||||
assert ssl
|
||||
assert socket.ssl
|
||||
assert ssl.OPENSSL_VERSION
|
||||
HAS_SSL = True
|
||||
except (ImportError, AttributeError):
|
||||
from subprocess import Popen
|
||||
|
||||
if HAS_SSL:
|
||||
# import wakatime package so we can use built-in python
|
||||
import wakatime
|
||||
from wakatime.base import parseConfigFile
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def createConfigFile():
|
||||
@ -81,7 +73,6 @@ def prompt_api_key():
|
||||
|
||||
default_key = ''
|
||||
try:
|
||||
from wakatime.base import parseConfigFile
|
||||
configs = parseConfigFile()
|
||||
if configs is not None:
|
||||
if configs.has_option('settings', 'api_key'):
|
||||
@ -123,7 +114,7 @@ def python_binary():
|
||||
except:
|
||||
pass
|
||||
for path in glob.iglob('/python*'):
|
||||
path = realpath(join(path, 'pythonw'))
|
||||
path = os.path.realpath(os.path.join(path, 'pythonw'))
|
||||
try:
|
||||
Popen([path, '--version'])
|
||||
PYTHON_LOCATION = path
|
||||
@ -133,6 +124,17 @@ def python_binary():
|
||||
return None
|
||||
|
||||
|
||||
def obfuscate_apikey(cmd):
|
||||
apikey_index = None
|
||||
for num in range(len(cmd)):
|
||||
if cmd[num] == '--key':
|
||||
apikey_index = num + 1
|
||||
break
|
||||
if apikey_index is not None and apikey_index < len(cmd):
|
||||
cmd[apikey_index] = '********-****-****-****-********' + cmd[apikey_index][-4:]
|
||||
return cmd
|
||||
|
||||
|
||||
def enough_time_passed(now, last_time):
|
||||
if now - last_time > ACTION_FREQUENCY * 60:
|
||||
return True
|
||||
@ -151,9 +153,12 @@ def find_project_name_from_folders(folders):
|
||||
|
||||
|
||||
def handle_action(view, is_write=False):
|
||||
window = view.window()
|
||||
if window is not None:
|
||||
target_file = view.file_name()
|
||||
project = view.window().project_file_name() if hasattr(view.window(), 'project_file_name') else None
|
||||
thread = SendActionThread(target_file, view, is_write=is_write, project=project, folders=view.window().folders())
|
||||
project = window.project_file_name() if hasattr(window, 'project_file_name') else None
|
||||
folders = window.folders()
|
||||
thread = SendActionThread(target_file, view, is_write=is_write, project=project, folders=folders)
|
||||
thread.start()
|
||||
|
||||
|
||||
@ -195,7 +200,7 @@ class SendActionThread(threading.Thread):
|
||||
if self.is_write:
|
||||
cmd.append('--write')
|
||||
if self.project:
|
||||
self.project = basename(self.project).replace('.sublime-project', '', 1)
|
||||
self.project = os.path.basename(self.project).replace('.sublime-project', '', 1)
|
||||
if self.project:
|
||||
cmd.extend(['--project', self.project])
|
||||
elif self.folders:
|
||||
@ -206,24 +211,14 @@ class SendActionThread(threading.Thread):
|
||||
cmd.extend(['--ignore', pattern])
|
||||
if self.debug:
|
||||
cmd.append('--verbose')
|
||||
if HAS_SSL:
|
||||
if python_binary():
|
||||
cmd.insert(0, python_binary())
|
||||
if self.debug:
|
||||
print('[WakaTime] %s' % ' '.join(cmd))
|
||||
code = wakatime.main(cmd)
|
||||
if code != 0:
|
||||
print('[WakaTime] Error: Response code %d from wakatime package.' % code)
|
||||
else:
|
||||
self.sent()
|
||||
else:
|
||||
python = python_binary()
|
||||
if python:
|
||||
cmd.insert(0, python)
|
||||
if self.debug:
|
||||
print('[WakaTime] %s %s' % (python, ' '.join(cmd)))
|
||||
print('[WakaTime] %s' % ' '.join(obfuscate_apikey(cmd)))
|
||||
if platform.system() == 'Windows':
|
||||
Popen(cmd, shell=False)
|
||||
else:
|
||||
with open(join(expanduser('~'), '.wakatime.log'), 'a') as stderr:
|
||||
with open(os.path.join(os.path.expanduser('~'), '.wakatime.log'), 'a') as stderr:
|
||||
Popen(cmd, stderr=stderr)
|
||||
self.sent()
|
||||
else:
|
||||
@ -250,9 +245,7 @@ def plugin_loaded():
|
||||
global SETTINGS
|
||||
print('[WakaTime] Initializing WakaTime plugin v%s' % __version__)
|
||||
|
||||
if not HAS_SSL:
|
||||
python = python_binary()
|
||||
if not python:
|
||||
if not python_binary():
|
||||
sublime.error_message("Unable to find Python binary!\nWakaTime needs Python to work correctly.\n\nGo to https://www.python.org/downloads")
|
||||
return
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
__title__ = 'wakatime'
|
||||
__description__ = 'Common interface to the WakaTime api.'
|
||||
__url__ = 'https://github.com/wakatime/wakatime'
|
||||
__version_info__ = ('4', '0', '4')
|
||||
__version_info__ = ('4', '0', '6')
|
||||
__version__ = '.'.join(__version_info__)
|
||||
__author__ = 'Alan Hamlett'
|
||||
__author_email__ = 'alan@wakatime.com'
|
||||
|
@ -24,8 +24,9 @@ try:
|
||||
except ImportError:
|
||||
import configparser
|
||||
|
||||
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__))))
|
||||
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', 'requests', 'packages'))
|
||||
|
||||
from .__about__ import __version__
|
||||
from .compat import u, open, is_py3
|
||||
|
@ -24,6 +24,9 @@ except ImportError:
|
||||
class CustomEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, bytes):
|
||||
obj = bytes.decode(obj)
|
||||
return json.dumps(obj)
|
||||
try:
|
||||
encoded = super(CustomEncoder, self).default(obj)
|
||||
except UnicodeDecodeError:
|
||||
|
@ -42,7 +42,7 @@ is at <http://python-requests.org>.
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '2.5.3'
|
||||
__version__ = '2.6.0'
|
||||
__build__ = 0x020503
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'Apache 2.0'
|
||||
|
@ -11,10 +11,10 @@ and maintain connections.
|
||||
import socket
|
||||
|
||||
from .models import Response
|
||||
from .packages.urllib3 import Retry
|
||||
from .packages.urllib3.poolmanager import PoolManager, proxy_from_url
|
||||
from .packages.urllib3.response import HTTPResponse
|
||||
from .packages.urllib3.util import Timeout as TimeoutSauce
|
||||
from .packages.urllib3.util.retry import Retry
|
||||
from .compat import urlparse, basestring
|
||||
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
|
||||
prepend_scheme_if_needed, get_auth_from_url, urldefragauth)
|
||||
|
@ -16,7 +16,6 @@ from . import sessions
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@ -37,6 +36,8 @@ def request(method, url, **kwargs):
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
|
||||
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
|
||||
Usage::
|
||||
|
||||
@ -55,10 +56,12 @@ def request(method, url, **kwargs):
|
||||
|
||||
|
||||
def get(url, **kwargs):
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
"""Sends a GET request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@ -66,10 +69,12 @@ def get(url, **kwargs):
|
||||
|
||||
|
||||
def options(url, **kwargs):
|
||||
"""Sends a OPTIONS request. Returns :class:`Response` object.
|
||||
"""Sends a OPTIONS request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@ -77,10 +82,12 @@ def options(url, **kwargs):
|
||||
|
||||
|
||||
def head(url, **kwargs):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
"""Sends a HEAD request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
@ -88,44 +95,52 @@ def head(url, **kwargs):
|
||||
|
||||
|
||||
def post(url, data=None, json=None, **kwargs):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
"""Sends a POST request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('post', url, data=data, json=json, **kwargs)
|
||||
|
||||
|
||||
def put(url, data=None, **kwargs):
|
||||
"""Sends a PUT request. Returns :class:`Response` object.
|
||||
"""Sends a PUT request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('put', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def patch(url, data=None, **kwargs):
|
||||
"""Sends a PATCH request. Returns :class:`Response` object.
|
||||
"""Sends a PATCH request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('patch', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def delete(url, **kwargs):
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
"""Sends a DELETE request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('delete', url, **kwargs)
|
||||
|
@ -143,12 +143,13 @@ class RequestEncodingMixin(object):
|
||||
else:
|
||||
fn = guess_filename(v) or k
|
||||
fp = v
|
||||
if isinstance(fp, str):
|
||||
fp = StringIO(fp)
|
||||
if isinstance(fp, (bytes, bytearray)):
|
||||
fp = BytesIO(fp)
|
||||
|
||||
rf = RequestField(name=k, data=fp.read(),
|
||||
if isinstance(fp, (str, bytes, bytearray)):
|
||||
fdata = fp
|
||||
else:
|
||||
fdata = fp.read()
|
||||
|
||||
rf = RequestField(name=k, data=fdata,
|
||||
filename=fn, headers=fh)
|
||||
rf.make_multipart(content_type=ft)
|
||||
new_fields.append(rf)
|
||||
@ -572,7 +573,11 @@ class Response(object):
|
||||
self.cookies = cookiejar_from_dict({})
|
||||
|
||||
#: The amount of time elapsed between sending the request
|
||||
#: and the arrival of the response (as a timedelta)
|
||||
#: and the arrival of the response (as a timedelta).
|
||||
#: This property specifically measures the time taken between sending
|
||||
#: the first byte of the request and finishing parsing the headers. It
|
||||
#: is therefore unaffected by consuming the response content or the
|
||||
#: value of the ``stream`` keyword argument.
|
||||
self.elapsed = datetime.timedelta(0)
|
||||
|
||||
#: The :class:`PreparedRequest <PreparedRequest>` object to which this
|
||||
|
@ -4,7 +4,7 @@ urllib3 - Thread-safe connection pooling and re-using.
|
||||
|
||||
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
||||
__license__ = 'MIT'
|
||||
__version__ = 'dev'
|
||||
__version__ = '1.10.2'
|
||||
|
||||
|
||||
from .connectionpool import (
|
||||
|
@ -20,8 +20,6 @@ from .packages.six import iterkeys, itervalues, PY3
|
||||
__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict']
|
||||
|
||||
|
||||
MULTIPLE_HEADERS_ALLOWED = frozenset(['cookie', 'set-cookie', 'set-cookie2'])
|
||||
|
||||
_Null = object()
|
||||
|
||||
|
||||
@ -143,6 +141,9 @@ class HTTPHeaderDict(dict):
|
||||
def __init__(self, headers=None, **kwargs):
|
||||
dict.__init__(self)
|
||||
if headers is not None:
|
||||
if isinstance(headers, HTTPHeaderDict):
|
||||
self._copy_from(headers)
|
||||
else:
|
||||
self.extend(headers)
|
||||
if kwargs:
|
||||
self.extend(kwargs)
|
||||
@ -223,11 +224,8 @@ class HTTPHeaderDict(dict):
|
||||
vals.append(val)
|
||||
else:
|
||||
# vals should be a tuple then, i.e. only one item so far
|
||||
if key_lower in MULTIPLE_HEADERS_ALLOWED:
|
||||
# Need to convert the tuple to list for further extension
|
||||
_dict_setitem(self, key_lower, [vals[0], vals[1], val])
|
||||
else:
|
||||
_dict_setitem(self, key_lower, new_vals)
|
||||
|
||||
def extend(*args, **kwargs):
|
||||
"""Generic import function for any type of header-like object.
|
||||
@ -276,14 +274,17 @@ class HTTPHeaderDict(dict):
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))
|
||||
|
||||
def copy(self):
|
||||
clone = type(self)()
|
||||
for key in self:
|
||||
val = _dict_getitem(self, key)
|
||||
def _copy_from(self, other):
|
||||
for key in other:
|
||||
val = _dict_getitem(other, key)
|
||||
if isinstance(val, list):
|
||||
# Don't need to convert tuples
|
||||
val = list(val)
|
||||
_dict_setitem(clone, key, val)
|
||||
_dict_setitem(self, key, val)
|
||||
|
||||
def copy(self):
|
||||
clone = type(self)()
|
||||
clone._copy_from(self)
|
||||
return clone
|
||||
|
||||
def iteritems(self):
|
||||
|
@ -157,3 +157,8 @@ class InsecureRequestWarning(SecurityWarning):
|
||||
class SystemTimeWarning(SecurityWarning):
|
||||
"Warned when system time is suspected to be wrong"
|
||||
pass
|
||||
|
||||
|
||||
class InsecurePlatformWarning(SecurityWarning):
|
||||
"Warned when certain SSL configuration is not available on a platform."
|
||||
pass
|
||||
|
@ -1,7 +1,7 @@
|
||||
from binascii import hexlify, unhexlify
|
||||
from hashlib import md5, sha1, sha256
|
||||
|
||||
from ..exceptions import SSLError
|
||||
from ..exceptions import SSLError, InsecurePlatformWarning
|
||||
|
||||
|
||||
SSLContext = None
|
||||
@ -10,6 +10,7 @@ create_default_context = None
|
||||
|
||||
import errno
|
||||
import ssl
|
||||
import warnings
|
||||
|
||||
try: # Test for SSL features
|
||||
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
|
||||
@ -69,6 +70,14 @@ except ImportError:
|
||||
self.ciphers = cipher_suite
|
||||
|
||||
def wrap_socket(self, socket, server_hostname=None):
|
||||
warnings.warn(
|
||||
'A true SSLContext object is not available. This prevents '
|
||||
'urllib3 from configuring SSL appropriately and may cause '
|
||||
'certain SSL connections to fail. For more information, see '
|
||||
'https://urllib3.readthedocs.org/en/latest/security.html'
|
||||
'#insecureplatformwarning.',
|
||||
InsecurePlatformWarning
|
||||
)
|
||||
kwargs = {
|
||||
'keyfile': self.keyfile,
|
||||
'certfile': self.certfile,
|
||||
|
@ -171,7 +171,10 @@ class SessionRedirectMixin(object):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
extract_cookies_to_jar(prepared_request._cookies, prepared_request, resp.raw)
|
||||
# Extract any cookies sent on the response to the cookiejar
|
||||
# in the new request. Because we've mutated our copied prepared
|
||||
# request, use the old one that we haven't yet touched.
|
||||
extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
|
||||
prepared_request._cookies.update(self.cookies)
|
||||
prepared_request.prepare_cookies(prepared_request._cookies)
|
||||
|
||||
|
Reference in New Issue
Block a user