use configparser from py3.5 so we can set delimiters to only equal char to fix #83

This commit is contained in:
Alan Hamlett 2017-03-04 10:07:00 -08:00
parent 079b40c865
commit 50141ef7b9
6 changed files with 1652 additions and 5 deletions

View file

@ -0,0 +1,7 @@
[settings]
debug = false
api_key = ba50f683-bb6b-4f31-9c84-7c70412234f7
[projectmap]
samples/projects/project_map:_with_colon/ = proj-map-nomatch-1
samples/projects/project_map = proj-map-match
samples/projects/project_map:_with_colon/ = proj-map-nomatch-2

View file

@ -0,0 +1,9 @@
[settings]
debug = false
api_key = ba50f683-bb6b-4f31-9c84-7c70412234f7
[projectmap]
samples/projects/project_map/ = proj-map-duplicate-1
samples/projects/project_map/ = proj-map-duplicate-2
samples/projects/project_map/ = proj-map-duplicate-3
samples/projects/project_map/ = proj-map-duplicate-4
samples/projects/project_map/ = proj-map-duplicate-5

View file

@ -345,7 +345,10 @@ class ProjectTestCase(utils.TestCase):
self.assertEquals('hg', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project'])
self.assertEquals('default', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['branch'])
def test_project_map(self):
@log_capture()
def test_project_map(self, logs):
logging.disable(logging.NOTSET)
response = Response()
response.status_code = 0
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
@ -360,7 +363,17 @@ class ProjectTestCase(utils.TestCase):
self.assertEquals('proj-map', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project'])
def test_project_map_group_usage(self):
self.assertEquals(sys.stdout.getvalue(), '')
self.assertEquals(sys.stderr.getvalue(), '')
log_output = "\n".join([u(' ').join(x) for x in logs.actual()])
expected = u('')
self.assertEquals(log_output, expected)
@log_capture()
def test_project_map_group_usage(self, logs):
logging.disable(logging.NOTSET)
response = Response()
response.status_code = 0
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
@ -375,6 +388,13 @@ class ProjectTestCase(utils.TestCase):
self.assertEquals('proj-map42', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project'])
self.assertEquals(sys.stdout.getvalue(), '')
self.assertEquals(sys.stderr.getvalue(), '')
log_output = "\n".join([u(' ').join(x) for x in logs.actual()])
expected = u('')
self.assertEquals(log_output, expected)
@log_capture()
def test_project_map_with_invalid_regex(self, logs):
logging.disable(logging.NOTSET)
@ -423,3 +443,53 @@ class ProjectTestCase(utils.TestCase):
log_output = "\n".join([u(' ').join(x) for x in logs.actual()])
expected = u('WakaTime WARNING Regex error (tuple index out of range) for projectmap pattern: proj-map{3}')
self.assertEquals(log_output, expected)
@log_capture()
def test_project_map_allows_duplicate_keys(self, logs):
logging.disable(logging.NOTSET)
response = Response()
response.status_code = 0
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
now = u(int(time.time()))
entity = 'tests/samples/projects/project_map/emptyfile.txt'
config = 'tests/samples/configs/project_map_with_duplicate_keys.cfg'
args = ['--file', entity, '--config', config, '--time', now]
execute(args)
self.assertEquals('proj-map-duplicate-5', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project'])
self.assertEquals(sys.stdout.getvalue(), '')
self.assertEquals(sys.stderr.getvalue(), '')
log_output = "\n".join([u(' ').join(x) for x in logs.actual()])
expected = u('')
self.assertEquals(log_output, expected)
@log_capture()
def test_project_map_allows_colon_in_key(self, logs):
logging.disable(logging.NOTSET)
response = Response()
response.status_code = 0
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
now = u(int(time.time()))
entity = 'tests/samples/projects/project_map/emptyfile.txt'
config = 'tests/samples/configs/project_map_with_colon_in_key.cfg'
args = ['--file', entity, '--config', config, '--time', now]
execute(args)
self.assertEquals('proj-map-match', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project'])
self.assertEquals(sys.stdout.getvalue(), '')
self.assertEquals(sys.stderr.getvalue(), '')
log_output = "\n".join([u(' ').join(x) for x in logs.actual()])
expected = u('')
self.assertEquals(log_output, expected)

View file

@ -21,9 +21,9 @@ from .constants import CONFIG_FILE_PARSE_ERROR
try:
import ConfigParser as configparser
except ImportError: # pragma: nocover
import configparser
except ImportError:
from .packages import configparser
def parseConfigFile(configFile=None):
@ -41,7 +41,7 @@ def parseConfigFile(configFile=None):
if not configFile:
configFile = os.path.join(os.path.expanduser('~'), '.wakatime.cfg')
configs = configparser.SafeConfigParser()
configs = configparser.SafeConfigParser(delimiters=('='), strict=False)
try:
with open(configFile, 'r', encoding='utf-8') as fh:
try:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,171 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from collections import MutableMapping
try:
from collections import UserDict
except ImportError:
from UserDict import UserDict
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
from io import open
import sys
try:
from thread import get_ident
except ImportError:
try:
from _thread import get_ident
except ImportError:
from _dummy_thread import get_ident
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
str = type('str')
def from_none(exc):
"""raise from_none(ValueError('a')) == raise ValueError('a') from None"""
exc.__cause__ = None
exc.__suppress_context__ = True
return exc
# from reprlib 3.2.1
def recursive_repr(fillvalue='...'):
'Decorator to make a repr function return fillvalue for a recursive call'
def decorating_function(user_function):
repr_running = set()
def wrapper(self):
key = id(self), get_ident()
if key in repr_running:
return fillvalue
repr_running.add(key)
try:
result = user_function(self)
finally:
repr_running.discard(key)
return result
# Can't use functools.wraps() here because of bootstrap issues
wrapper.__module__ = getattr(user_function, '__module__')
wrapper.__doc__ = getattr(user_function, '__doc__')
wrapper.__name__ = getattr(user_function, '__name__')
wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
return wrapper
return decorating_function
# from collections 3.2.1
class _ChainMap(MutableMapping):
''' A ChainMap groups multiple dicts (or other mappings) together
to create a single, updateable view.
The underlying mappings are stored in a list. That list is public and can
accessed or updated using the *maps* attribute. There is no other state.
Lookups search the underlying mappings successively until a key is found.
In contrast, writes, updates, and deletions only operate on the first
mapping.
'''
def __init__(self, *maps):
'''Initialize a ChainMap by setting *maps* to the given mappings.
If no mappings are provided, a single empty dictionary is used.
'''
self.maps = list(maps) or [{}] # always at least one map
def __missing__(self, key):
raise KeyError(key)
def __getitem__(self, key):
for mapping in self.maps:
try:
return mapping[key] # can't use 'key in mapping' with defaultdict
except KeyError:
pass
return self.__missing__(key) # support subclasses that define __missing__
def get(self, key, default=None):
return self[key] if key in self else default
def __len__(self):
return len(set().union(*self.maps)) # reuses stored hash values if possible
def __iter__(self):
return iter(set().union(*self.maps))
def __contains__(self, key):
return any(key in m for m in self.maps)
@recursive_repr()
def __repr__(self):
return '{0.__class__.__name__}({1})'.format(
self, ', '.join(map(repr, self.maps)))
@classmethod
def fromkeys(cls, iterable, *args):
'Create a ChainMap with a single dict created from the iterable.'
return cls(dict.fromkeys(iterable, *args))
def copy(self):
'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
return self.__class__(self.maps[0].copy(), *self.maps[1:])
__copy__ = copy
def new_child(self): # like Django's Context.push()
'New ChainMap with a new dict followed by all previous maps.'
return self.__class__({}, *self.maps)
@property
def parents(self): # like Django's Context.pop()
'New ChainMap from maps[1:].'
return self.__class__(*self.maps[1:])
def __setitem__(self, key, value):
self.maps[0][key] = value
def __delitem__(self, key):
try:
del self.maps[0][key]
except KeyError:
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
def popitem(self):
'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
try:
return self.maps[0].popitem()
except KeyError:
raise KeyError('No keys found in the first mapping.')
def pop(self, key, *args):
'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
try:
return self.maps[0].pop(key, *args)
except KeyError:
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
def clear(self):
'Clear maps[0], leaving maps[1:] intact.'
self.maps[0].clear()
try:
from collections import ChainMap
except ImportError:
ChainMap = _ChainMap