mirror of
https://github.com/tbabej/taskwiki.git
synced 2025-08-18 21:33:07 +02:00
Add new header type "Preset Header"
As first envisioned in #69, these preset headers allow us to have a hierarchical structure of taskwarrior filters and defaults.
This commit is contained in:
parent
489d00f24e
commit
4372d52298
6 changed files with 175 additions and 5 deletions
|
@ -2,6 +2,7 @@ import vim # pylint: disable=F0401
|
|||
import re
|
||||
import six
|
||||
|
||||
from taskwiki import preset
|
||||
from taskwiki import viewport
|
||||
from taskwiki import regexp
|
||||
from taskwiki import store
|
||||
|
@ -115,6 +116,7 @@ class TaskCache(object):
|
|||
|
||||
self.buffer = BufferProxy(buffer_number)
|
||||
self.task = store.TaskStore(self)
|
||||
self.presets = store.PresetStore(self)
|
||||
self.vwtask = store.VwtaskStore(self)
|
||||
self.viewport = store.ViewportStore(self)
|
||||
self.line = store.LineStore(self)
|
||||
|
@ -143,6 +145,23 @@ class TaskCache(object):
|
|||
self.viewport.store = dict()
|
||||
self.line.store = dict()
|
||||
|
||||
def load_presets(self):
|
||||
stack = []
|
||||
|
||||
for i in range(len(self.buffer)):
|
||||
header = preset.PresetHeader.from_line(i, self, stack[-1] if stack else None)
|
||||
|
||||
if header is None:
|
||||
continue
|
||||
|
||||
# Save the preset header in the cache
|
||||
self.presets[i] = header
|
||||
|
||||
while stack and stack[-1].level >= header.level:
|
||||
stack.pop()
|
||||
|
||||
stack.append(header)
|
||||
|
||||
def load_vwtasks(self, buffer_has_authority=True):
|
||||
# Set the authority flag, which determines which data (Buffer or TW)
|
||||
# will be considered authoritative
|
||||
|
|
|
@ -35,6 +35,7 @@ class WholeBuffer(object):
|
|||
c = cache()
|
||||
c.reset()
|
||||
c.load_tasks()
|
||||
c.load_presets()
|
||||
c.load_vwtasks(buffer_has_authority=False)
|
||||
c.load_viewports()
|
||||
c.update_vwtasks_from_tasks()
|
||||
|
@ -53,6 +54,7 @@ class WholeBuffer(object):
|
|||
c = cache()
|
||||
c.reset()
|
||||
c.load_tasks()
|
||||
c.load_presets()
|
||||
c.load_vwtasks()
|
||||
c.load_viewports()
|
||||
c.save_tasks()
|
||||
|
|
96
taskwiki/preset.py
Normal file
96
taskwiki/preset.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
import re
|
||||
import six
|
||||
|
||||
from taskwiki import regexp
|
||||
from taskwiki import util
|
||||
|
||||
class PresetHeader(object):
|
||||
"""
|
||||
== Taskwiki tasks || project:taskwiki ==
|
||||
"""
|
||||
|
||||
def __init__(self, cache, parent, level, filterstring, defaultstring):
|
||||
|
||||
self.level = level
|
||||
self.parent = parent
|
||||
|
||||
if parent:
|
||||
taskfilter = list(parent.taskfilter)
|
||||
else:
|
||||
taskfilter = []
|
||||
|
||||
if filterstring:
|
||||
taskfilter += '('
|
||||
taskfilter += util.tw_modstring_to_args(filterstring)
|
||||
taskfilter += ')'
|
||||
|
||||
self.taskfilter = taskfilter
|
||||
|
||||
|
||||
if parent:
|
||||
defaults = dict(parent.defaults)
|
||||
else:
|
||||
defaults = dict()
|
||||
|
||||
if defaultstring:
|
||||
defaults.update(util.tw_modstring_to_kwargs(defaultstring))
|
||||
else:
|
||||
defaults.update(util.tw_args_to_kwargs(taskfilter))
|
||||
|
||||
self.defaults = defaults
|
||||
|
||||
@classmethod
|
||||
def parse_line(cls, cache, number):
|
||||
header = re.search(regexp.GENERIC_HEADER, cache.buffer[number])
|
||||
|
||||
if header:
|
||||
preset = re.search(regexp.GENERIC_PRESET, cache.buffer[number])
|
||||
if preset:
|
||||
return preset
|
||||
|
||||
return header
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, number, cache, previous=None):
|
||||
match = cache.line[(cls, number)]
|
||||
|
||||
if not match:
|
||||
return None
|
||||
|
||||
level = len(match.group('header_start'))
|
||||
|
||||
if level == 1:
|
||||
parent = None
|
||||
else:
|
||||
# Manually get previous header
|
||||
if not previous:
|
||||
for i in reversed(range(0, number)):
|
||||
previous = cls.from_line(i, cache)
|
||||
if previous:
|
||||
break
|
||||
|
||||
# find parent
|
||||
parent = previous
|
||||
while parent and parent.level >= level:
|
||||
parent = parent.parent
|
||||
|
||||
if parent is None:
|
||||
# Create an empty root stub
|
||||
parent = cls(cache, None, 0, None, None)
|
||||
|
||||
|
||||
# use parent object, if no additional filters / defaults are applied
|
||||
try:
|
||||
filterstring = match.group('filter')
|
||||
except IndexError:
|
||||
return parent
|
||||
|
||||
|
||||
defaults = match.group('defaults')
|
||||
|
||||
if six.PY2:
|
||||
filterstring = filterstring.decode('utf-8')
|
||||
defaults = defaults.decode('utf-8') if defaults is not None else defaults
|
||||
|
||||
self = cls(cache, parent, level, filterstring, defaults)
|
||||
return self
|
|
@ -60,12 +60,27 @@ GENERIC_VIEWPORT = re.compile(
|
|||
'[=]+' # Header ending
|
||||
)
|
||||
|
||||
GENERIC_PRESET = re.compile(
|
||||
'^' # Starts at the beginning of the line
|
||||
'(?P<header_start>[=]+)' # With a positive number of =
|
||||
'([^=\|\[\{]*)' # Heading caption, everything up to ||
|
||||
# Cannot include '[', '=', '|, and '{'
|
||||
'\|\|' # Delimiter
|
||||
'(?P<filter>[^=\|]+?)' # Filter preset
|
||||
'(' # Optional defaults
|
||||
'\|\|' # Delimiter
|
||||
'(?P<defaults>[^=\|]+?)' # Default attrs preset
|
||||
')?'
|
||||
'\s*' # Any whitespace
|
||||
'[=]+' # Header ending
|
||||
)
|
||||
|
||||
GENERIC_HEADER = re.compile(
|
||||
'^' # Starts at the beginning of the line
|
||||
'[=]+' # With a positive number of =
|
||||
'[^=]+' # Character other than =
|
||||
'[=]+' # Positive number of =, closing the header
|
||||
'\s*' # Allow trailing whitespace
|
||||
'^' # Starts at the beginning of the line
|
||||
'(?P<header_start>[=]+)' # With a positive number of =
|
||||
'[^=]+' # Character other than =
|
||||
'[=]+' # Positive number of =, closing the header
|
||||
'\s*' # Allow trailing whitespace
|
||||
)
|
||||
|
||||
ANSI_ESCAPE_SEQ = re.compile(
|
||||
|
|
|
@ -149,6 +149,13 @@ class ViewportStore(LineNumberedKeyedStoreMixin, NoNoneStore):
|
|||
return viewport.ViewPort.from_line(line, self.cache)
|
||||
|
||||
|
||||
class PresetStore(LineNumberedKeyedStoreMixin, NoNoneStore):
|
||||
|
||||
def get_method(self, line):
|
||||
import preset
|
||||
return preset.PresetHeader.from_line(line, self.cache)
|
||||
|
||||
|
||||
class LineStore(NoNoneStore):
|
||||
|
||||
def __delitem__(self, number):
|
||||
|
|
31
tests/test_preset_parsing.py
Normal file
31
tests/test_preset_parsing.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from tests.base import MockVim, MockCache
|
||||
import sys
|
||||
|
||||
|
||||
class TestParsingPresetHeader(object):
|
||||
def setup(self):
|
||||
self.mockvim = MockVim()
|
||||
self.cache = MockCache()
|
||||
|
||||
sys.modules['vim'] = self.mockvim
|
||||
from taskwiki.preset import PresetHeader
|
||||
self.PresetHeader = PresetHeader
|
||||
|
||||
def teardown(self):
|
||||
self.mockvim.reset()
|
||||
self.cache.reset()
|
||||
|
||||
def test_simple(self):
|
||||
self.cache.buffer[0] = "== Test || project:Home =="
|
||||
header = self.PresetHeader.from_line(0, self.cache)
|
||||
|
||||
assert header.taskfilter == ["(", "project:Home", ")"]
|
||||
assert header.defaults == {'project': 'Home'}
|
||||
|
||||
def test_defaults(self):
|
||||
self.cache.buffer[0] = "== Test || project:Home || +home =="
|
||||
header = self.PresetHeader.from_line(0, self.cache)
|
||||
|
||||
assert header.taskfilter == ["(", "project:Home", ")"]
|
||||
assert header.defaults == {'tags': ['home']}
|
Loading…
Add table
Add a link
Reference in a new issue