diff --git a/wakatime/__init__.py b/wakatime/__init__.py index 1cb2a12..a0e2c83 100644 --- a/wakatime/__init__.py +++ b/wakatime/__init__.py @@ -40,6 +40,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 .compat import u, open, is_py2, is_py3 from .queue import Queue from .log import setup_logging from .project import find_project @@ -54,11 +55,6 @@ except: log = logging.getLogger('WakaTime') -try: - unicode -except NameError: - unicode = str - class FileAction(argparse.Action): @@ -82,7 +78,7 @@ def upgradeConfigFile(configFile): 'ignore': [], } - with open(oldConfig) as fh: + with open(oldConfig, 'r', encoding='utf-8') as fh: for line in fh.readlines(): line = line.split('=', 1) if len(line) == 2 and line[0].strip() and line[1].strip(): @@ -91,7 +87,7 @@ def upgradeConfigFile(configFile): else: configs[line[0].strip()] = line[1].strip() - with open(configFile, 'w') as fh: + with open(configFile, 'w', encoding='utf-8') as fh: fh.write("[settings]\n") for name, value in configs.items(): if isinstance(value, list): @@ -119,7 +115,7 @@ def parseConfigFile(configFile): configs = configparser.SafeConfigParser() try: - with open(configFile) as fh: + with open(configFile, 'r', encoding='utf-8') as fh: try: configs.readfp(fh) except configparser.Error: @@ -231,7 +227,7 @@ def should_ignore(fileName, patterns): if compiled.search(fileName): return pattern except re.error as ex: - log.warning(unicode('Regex error ({msg}) for ignore pattern: {pattern}').format( + log.warning(u('Regex error ({msg}) for ignore pattern: {pattern}').format( msg=str(ex), pattern=pattern, )) @@ -243,13 +239,13 @@ def should_ignore(fileName, patterns): def get_user_agent(plugin): ver = sys.version_info python_version = '%d.%d.%d.%s.%d' % (ver[0], ver[1], ver[2], ver[3], ver[4]) - user_agent = unicode('wakatime/{ver} ({platform}) Python{py_ver}').format( + user_agent = u('wakatime/{ver} ({platform}) Python{py_ver}').format( ver=__version__, platform=platform.platform(), py_ver=python_version, ) if plugin: - user_agent = unicode('{user_agent} {plugin}').format( + user_agent = u('{user_agent} {plugin}').format( user_agent=user_agent, plugin=plugin, ) @@ -268,9 +264,9 @@ def send_action(project=None, branch=None, stats=None, key=None, targetFile=None if hidefilenames and targetFile is not None: data['file'] = data['file'].rsplit('/', 1)[-1].rsplit('\\', 1)[-1] if len(data['file'].strip('.').split('.', 1)) > 1: - data['file'] = unicode('HIDDEN.{ext}').format(ext=data['file'].strip('.').rsplit('.', 1)[-1]) + data['file'] = u('HIDDEN.{ext}').format(ext=data['file'].strip('.').rsplit('.', 1)[-1]) else: - data['file'] = unicode('HIDDEN') + data['file'] = u('HIDDEN') if stats.get('lines'): data['lines'] = stats['lines'] if stats.get('language'): @@ -284,16 +280,17 @@ def send_action(project=None, branch=None, stats=None, key=None, targetFile=None log.debug(data) # setup api request - request = Request(url=url, data=str.encode(json.dumps(data))) + request_body = json.dumps(data) + request = Request(url=url, data=str.encode(request_body) if is_py3 else request_body) request.add_header('User-Agent', get_user_agent(plugin)) request.add_header('Content-Type', 'application/json') - auth = unicode('Basic {key}').format(key=bytes.decode(base64.b64encode(str.encode(key)))) + auth = u('Basic {key}').format(key=u(base64.b64encode(str.encode(key) if is_py3 else key))) request.add_header('Authorization', auth) # add Olson timezone to request tz = tzlocal.get_localzone() if tz: - request.add_header('TimeZone', unicode(tz.zone)) + request.add_header('TimeZone', u(tz.zone)) # log time to api response = None @@ -302,7 +299,7 @@ def send_action(project=None, branch=None, stats=None, key=None, targetFile=None except HTTPError as exc: exception_data = { 'response_code': exc.getcode(), - sys.exc_info()[0].__name__: unicode(sys.exc_info()[1]), + sys.exc_info()[0].__name__: u(sys.exc_info()[1]), } if log.isEnabledFor(logging.DEBUG): exception_data['traceback'] = traceback.format_exc() @@ -315,7 +312,7 @@ def send_action(project=None, branch=None, stats=None, key=None, targetFile=None log.error(exception_data) except: exception_data = { - sys.exc_info()[0].__name__: unicode(sys.exc_info()[1]), + sys.exc_info()[0].__name__: u(sys.exc_info()[1]), } if log.isEnabledFor(logging.DEBUG): exception_data['traceback'] = traceback.format_exc() @@ -365,7 +362,7 @@ def main(argv=None): ignore = should_ignore(args.targetFile, args.ignore) if ignore is not False: - log.debug(unicode('File ignored because matches pattern: {pattern}').format( + log.debug(u('File ignored because matches pattern: {pattern}').format( pattern=ignore, )) return 0 diff --git a/wakatime/compat.py b/wakatime/compat.py new file mode 100644 index 0000000..1dc37d7 --- /dev/null +++ b/wakatime/compat.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" + wakatime.compat + ~~~~~~~~~~~~~~~ + + For working with Python2 and Python3. + + :copyright: (c) 2014 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + +import codecs +import io +import sys + + +is_py2 = (sys.version_info[0] == 2) +is_py3 = (sys.version_info[0] == 3) + + +if is_py2: + + def u(text): + if isinstance(text, str): + return text.decode('utf-8') + return unicode(text) + open = codecs.open + basestring = basestring + + +elif is_py3: + + def u(text): + if isinstance(text, bytes): + return text.decode('utf-8') + return str(text) + open = open + basestring = (str, bytes) diff --git a/wakatime/log.py b/wakatime/log.py index 88a21a7..b70d21b 100644 --- a/wakatime/log.py +++ b/wakatime/log.py @@ -14,6 +14,7 @@ import os import sys from .packages import simplejson as json +from .compat import u try: from collections import OrderedDict except ImportError: @@ -30,7 +31,7 @@ class CustomEncoder(json.JSONEncoder): encoded = super(CustomEncoder, self).default(obj) except UnicodeDecodeError: encoding = sys.getfilesystemencoding() - obj = obj.decode(encoding, 'ignore').encode('utf-8') + obj = u(obj) encoded = super(CustomEncoder, self).default(obj) return encoded diff --git a/wakatime/projects/git.py b/wakatime/projects/git.py index 79c925a..cf4d51e 100644 --- a/wakatime/projects/git.py +++ b/wakatime/projects/git.py @@ -13,18 +13,12 @@ import logging import os from .base import BaseProject +from ..compat import u, open log = logging.getLogger('WakaTime') -# str is unicode in Python3 -try: - unicode -except NameError: - unicode = str - - class Git(BaseProject): def process(self): @@ -34,7 +28,7 @@ class Git(BaseProject): def name(self): base = self._project_base() if base: - return unicode(os.path.basename(base)) + return u(os.path.basename(base)) return None def branch(self): @@ -42,8 +36,8 @@ class Git(BaseProject): if base: head = os.path.join(self._project_base(), '.git', 'HEAD') try: - with open(head) as fh: - return unicode(fh.readline().strip().rsplit('/', 1)[-1]) + with open(head, 'r', encoding='utf-8') as fh: + return u(fh.readline().strip().rsplit('/', 1)[-1]) except IOError: pass return None diff --git a/wakatime/projects/mercurial.py b/wakatime/projects/mercurial.py index afaac95..5f222ee 100644 --- a/wakatime/projects/mercurial.py +++ b/wakatime/projects/mercurial.py @@ -13,18 +13,12 @@ import logging import os from .base import BaseProject +from ..compat import u, open log = logging.getLogger('WakaTime') -# str is unicode in Python3 -try: - unicode -except NameError: - unicode = str - - class Mercurial(BaseProject): def process(self): @@ -33,18 +27,18 @@ class Mercurial(BaseProject): def name(self): if self.configDir: - return unicode(os.path.basename(os.path.dirname(self.configDir))) + return u(os.path.basename(os.path.dirname(self.configDir))) return None def branch(self): 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]) + with open(branch_file, 'r', encoding='utf-8') as fh: + return u(fh.readline().strip().rsplit('/', 1)[-1]) except IOError: pass - return unicode('default') + return u('default') def _find_hg_config_dir(self, path): path = os.path.realpath(path) diff --git a/wakatime/projects/projectmap.py b/wakatime/projects/projectmap.py index 9a4a73e..44b19fc 100644 --- a/wakatime/projects/projectmap.py +++ b/wakatime/projects/projectmap.py @@ -24,18 +24,12 @@ import logging import os from .base import BaseProject +from ..compat import u log = logging.getLogger('WakaTime') -# str is unicode in Python3 -try: - unicode -except NameError: - unicode = str - - class ProjectMap(BaseProject): def process(self): @@ -68,5 +62,5 @@ class ProjectMap(BaseProject): def name(self): if self.project: - return unicode(self.project) + return u(self.project) return None diff --git a/wakatime/projects/subversion.py b/wakatime/projects/subversion.py index a8f4fdd..8929d87 100644 --- a/wakatime/projects/subversion.py +++ b/wakatime/projects/subversion.py @@ -15,6 +15,7 @@ import platform from subprocess import Popen, PIPE from .base import BaseProject +from ..compat import u, open try: from collections import OrderedDict except ImportError: @@ -24,13 +25,6 @@ except ImportError: log = logging.getLogger('WakaTime') -# str is unicode in Python3 -try: - unicode -except NameError: - unicode = str - - class Subversion(BaseProject): binary_location = None @@ -38,11 +32,11 @@ class Subversion(BaseProject): return self._find_project_base(self.path) def name(self): - return unicode(self.info['Repository Root'].split('/')[-1]) + return u(self.info['Repository Root'].split('/')[-1]) def branch(self): if self.base: - unicode(os.path.basename(self.base)) + u(os.path.basename(self.base)) return None def _find_binary(self): @@ -54,7 +48,7 @@ class Subversion(BaseProject): '/usr/local/bin/svn', ] for location in locations: - with open(os.devnull, 'wb') as DEVNULL: + with open(os.devnull, 'wb', encoding='utf-8') as DEVNULL: try: Popen([location, '--version'], stdout=DEVNULL, stderr=DEVNULL) self.binary_location = location diff --git a/wakatime/projects/wakatime.py b/wakatime/projects/wakatime.py index 14ebe4f..fa33f4f 100644 --- a/wakatime/projects/wakatime.py +++ b/wakatime/projects/wakatime.py @@ -15,18 +15,12 @@ import logging import os from .base import BaseProject +from ..compat import u, open log = logging.getLogger('WakaTime') -# str is unicode in Python3 -try: - unicode -except NameError: - unicode = str - - class WakaTime(BaseProject): def process(self): @@ -37,9 +31,9 @@ class WakaTime(BaseProject): if self.config: try: - with open(self.config) as fh: - self._project_name = unicode(fh.readline().strip()) - self._project_branch = unicode(fh.readline().strip()) + with open(self.config, 'r', encoding='utf-8') as fh: + self._project_name = u(fh.readline().strip()) + self._project_branch = u(fh.readline().strip()) except IOError as e: log.exception("Exception:") diff --git a/wakatime/stats.py b/wakatime/stats.py index 07d0f78..f601aae 100644 --- a/wakatime/stats.py +++ b/wakatime/stats.py @@ -13,6 +13,8 @@ import logging import os import sys +from .compat import u, open + if sys.version_info[0] == 2: sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages', 'pygments2')) else: @@ -22,11 +24,6 @@ from pygments.lexers import guess_lexer_for_filename log = logging.getLogger('WakaTime') -try: - unicode -except NameError: - unicode = str - # force file name extensions to be recognized as a certain language EXTENSIONS = { @@ -54,12 +51,12 @@ def guess_language(file_name): return language lexer = None try: - with open(file_name) as f: - lexer = guess_lexer_for_filename(file_name, f.read(512000)) + with open(file_name, 'r', encoding='utf-8') as fh: + lexer = guess_lexer_for_filename(file_name, fh.read(512000)) except: pass if lexer: - return translate_language(unicode(lexer.name)) + return translate_language(u(lexer.name)) else: return None @@ -82,8 +79,8 @@ def translate_language(language): def number_lines_in_file(file_name): lines = 0 try: - with open(file_name) as f: - for line in f: + with open(file_name, 'r', encoding='utf-8') as fh: + for line in fh: lines += 1 except IOError: return None