Commit ad854716 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Store tags as a dictionary

Store tags in a dictionary where the key is the tag name and the value
is a list of values for that tag.

This makes tag operations much quicker (especially TodoBase::has_tag()
and TodoBase::tag_values()). They made quite a dent in execution time
with heavy todo.txt files with many tags, due to the numerous list
comprehensions that took place.
parent 089c3f05
...@@ -48,17 +48,20 @@ class TodoBase(object): ...@@ -48,17 +48,20 @@ class TodoBase(object):
Returns a tag value associated with p_key. Returns p_default if p_key Returns a tag value associated with p_key. Returns p_default if p_key
does not exist (which defaults to None). does not exist (which defaults to None).
""" """
values = self.tag_values(p_key) try:
return values[0] if len(values) else p_default return self.tag_values(p_key)[0]
except IndexError:
return p_default
def tag_values(self, p_key): def tag_values(self, p_key):
""" """
Returns a list of all tag values associated with p_key. Returns Returns a list of all tag values associated with p_key. Returns
empty list if p_key does not exist. empty list if p_key does not exist.
""" """
tags = self.fields['tags'] try:
matches = [tag[1] for tag in tags if tag[0] == p_key] return self.fields['tags'][p_key]
return matches if len(matches) else [] except KeyError:
return []
def has_tag(self, p_key, p_value=""): def has_tag(self, p_key, p_value=""):
""" """
...@@ -66,14 +69,28 @@ class TodoBase(object): ...@@ -66,14 +69,28 @@ class TodoBase(object):
value is passed, it will only return true when there exists a tag with value is passed, it will only return true when there exists a tag with
the given key-value combination. the given key-value combination.
""" """
result = [t for t in self.tag_values(p_key) tags = self.fields['tags']
if p_value == "" or t == p_value] return p_key in tags and (p_value == "" or p_value in tags[p_key])
return len(result) > 0
def add_tag(self, p_key, p_value): def add_tag(self, p_key, p_value):
""" Adds a tag to the todo. """ """ Adds a tag to the todo. """
self.set_tag(p_key, p_value, True) self.set_tag(p_key, p_value, True)
def _remove_tag_helper(self, p_key, p_value):
"""
Removes a tag from the internal todo dictionary. Only those instances
with the given value are removed. If the value is empty, all tags with
the given key are removed.
"""
tags = self.fields['tags']
try:
tags[p_key] = [t for t in tags[p_key] if p_value != "" and t != p_value]
if len(tags[p_key]) == 0:
del tags[p_key]
except KeyError:
pass
def set_tag(self, p_key, p_value="", p_force_add=False, p_old_value=""): def set_tag(self, p_key, p_value="", p_force_add=False, p_old_value=""):
""" """
Sets a occurrence of the tag identified by p_key. Sets an arbitrary Sets a occurrence of the tag identified by p_key. Sets an arbitrary
...@@ -92,12 +109,11 @@ class TodoBase(object): ...@@ -92,12 +109,11 @@ class TodoBase(object):
self.remove_tag(p_key, p_old_value) self.remove_tag(p_key, p_old_value)
return return
tags = self.fields['tags']
value = p_old_value if p_old_value else self.tag_value(p_key) value = p_old_value if p_old_value else self.tag_value(p_key)
if not p_force_add and value: if not p_force_add and value:
# remove old value from the tags self._remove_tag_helper(p_key, value)
self.fields['tags'] = [t for t in self.fields['tags']
if not (t[0] == p_key and t[1] == value)]
self.src = re.sub( self.src = re.sub(
r'\b' + p_key + ':' + value + r'\b', r'\b' + p_key + ':' + value + r'\b',
...@@ -107,7 +123,10 @@ class TodoBase(object): ...@@ -107,7 +123,10 @@ class TodoBase(object):
else: else:
self.src += ' ' + p_key + ':' + p_value self.src += ' ' + p_key + ':' + p_value
self.fields['tags'].append((p_key, p_value)) try:
tags[p_key].append(p_value)
except KeyError:
tags[p_key] = [p_value]
def remove_tag(self, p_key, p_value=""): def remove_tag(self, p_key, p_value=""):
""" """
...@@ -116,12 +135,7 @@ class TodoBase(object): ...@@ -116,12 +135,7 @@ class TodoBase(object):
removed. removed.
Else, only those tags with the value will be removed. Else, only those tags with the value will be removed.
""" """
self._remove_tag_helper(p_key, p_value)
# Build a new list that excludes the specified tag, match by value when
# p_value is given.
self.fields['tags'] = [t for t in self.fields['tags']
if not (t[0] == p_key and (p_value == "" or
t[1] == p_value))]
# when value == "", match any value having key p_key # when value == "", match any value having key p_key
value = p_value if p_value != "" else r'\S+' value = p_value if p_value != "" else r'\S+'
...@@ -132,7 +146,8 @@ class TodoBase(object): ...@@ -132,7 +146,8 @@ class TodoBase(object):
Returns a list of tuples with key-value pairs representing tags in Returns a list of tuples with key-value pairs representing tags in
this todo item. this todo item.
""" """
return self.fields['tags'] tags = self.fields['tags']
return [(t, v) for t in tags for v in tags[t]]
def set_priority(self, p_priority): def set_priority(self, p_priority):
""" """
......
...@@ -33,7 +33,7 @@ _NORMAL_HEAD_MATCH = re.compile( ...@@ -33,7 +33,7 @@ _NORMAL_HEAD_MATCH = re.compile(
r'(\((?P<priority>[A-Z])\) )?' + '((?P<creationDate>' + _DATE_MATCH + r'(\((?P<priority>[A-Z])\) )?' + '((?P<creationDate>' + _DATE_MATCH +
') )?(?P<rest>.*)') ') )?(?P<rest>.*)')
_TAG_MATCH = re.compile('(?P<key>[^:]+):(?P<value>.+)') _TAG_MATCH = re.compile('(?P<tag>[^:]+):(?P<value>.+)')
_PROJECT_MATCH = re.compile(r'\+(\S*\w)') _PROJECT_MATCH = re.compile(r'\+(\S*\w)')
_CONTEXT_MATCH = re.compile(r'@(\S*\w)') _CONTEXT_MATCH = re.compile(r'@(\S*\w)')
...@@ -57,7 +57,7 @@ def parse_line(p_string): ...@@ -57,7 +57,7 @@ def parse_line(p_string):
'text': "", 'text': "",
'projects': [], 'projects': [],
'contexts': [], 'contexts': [],
'tags': [] 'tags': {},
} }
completed_head = _COMPLETED_HEAD_MATCH.match(p_string) completed_head = _COMPLETED_HEAD_MATCH.match(p_string)
...@@ -94,9 +94,13 @@ def parse_line(p_string): ...@@ -94,9 +94,13 @@ def parse_line(p_string):
tag = _TAG_MATCH.match(word) tag = _TAG_MATCH.match(word)
if tag: if tag:
result['tags'].append((tag.group('key'), tag.group('value'))) tag_name = tag.group('tag')
continue tag_value = tag.group('value')
try:
result['tags'][tag_name].append(tag_value)
except KeyError:
result['tags'][tag_name] = [tag_value]
else:
result['text'] += word + ' ' result['text'] += word + ' '
# strip trailing space from resulting text # strip trailing space from resulting text
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment