This commit is contained in:
Philipp Hack 2016-01-08 08:50:53 +01:00
commit e73aeb414e
17 changed files with 536 additions and 218 deletions

View file

@ -1,2 +1,2 @@
[report]
omit = */python2.7/*,*/test*,*/__init__.py
omit = */python2.7/*,*/test*,*/__init__.py,*/taskwiki/coverage.py

View file

@ -5,7 +5,8 @@ env:
- TASK_VERSION=v2.4.2
- TASK_VERSION=v2.4.3
- TASK_VERSION=v2.4.4
- TASK_VERSION=2.5.0
- TASK_VERSION=v2.5.0
- TASK_VERSION=2.5.1
python:
- "2.7"
before_install:

View file

@ -1,4 +1,4 @@
Copyright 2014-2015 Tomas Babej
Copyright 2014-2016 Tomas Babej
http://github.com/tbabej/taskwiki
This software is released under MIT licence.

View file

@ -1,59 +1,10 @@
import vim # pylint: disable=F0401
import re
import util
import vwtask
import viewport
import regexp
from tasklib import TaskWarrior
class WarriorStore(object):
"""
Stores all instances of TaskWarrior objects.
"""
def __init__(self):
# Determine defaults
default_rc = vim.vars.get('taskwiki_taskrc_location') or '~/.taskrc'
default_data = vim.vars.get('taskwiki_data_location') or '~/.task'
default_kwargs = dict(
data_location=default_data,
taskrc_location=default_rc,
)
# Setup the store of TaskWarrior objects
self.warriors = {'default': TaskWarrior(**default_kwargs)}
extra_warrior_defs = vim.vars.get('taskwiki_extra_warriors', {})
for key in extra_warrior_defs.keys():
current_kwargs = default_kwargs.copy()
current_kwargs.update(extra_warrior_defs[key])
self.warriors[key] = TaskWarrior(**current_kwargs)
# Make sure context is not respected in any TaskWarrior
for tw in self.warriors.values():
tw.overrides.update({'context':''})
def __getitem__(self, key):
try:
return self.warriors[key]
except KeyError as e:
raise util.TaskWikiException(
"Taskwarrior with key '{0}' not available."
.format(key))
def __setitem__(self, key, value):
self.warriors[key] = value
def values(self):
return self.warriors.values()
def iteritems(self):
return self.warriors.iteritems()
import store
class TaskCache(object):
@ -63,89 +14,22 @@ class TaskCache(object):
"""
def __init__(self):
self.task_cache = dict()
self.vimwikitask_cache = dict()
self.viewport_cache = dict()
self.warriors = WarriorStore()
# Determine defaults
default_rc = vim.vars.get('taskwiki_taskrc_location') or '~/.taskrc'
default_data = vim.vars.get('taskwiki_data_location') or '~/.task'
extra_warrior_defs = vim.vars.get('taskwiki_extra_warriors', {})
self.task = store.TaskStore(self)
self.vwtask = store.VwtaskStore(self)
self.viewport = store.ViewportStore(self)
self.line = store.LineStore(self)
self.warriors = store.WarriorStore(default_rc, default_data, extra_warrior_defs)
self.buffer_has_authority = True
def __getitem__(self, key):
# Integer keys (line numbers) refer to the VimwikiTask objects
if type(key) is int:
vimwikitask = self.vimwikitask_cache.get(key)
if vimwikitask is None:
vimwikitask = vwtask.VimwikiTask.from_line(self, key)
# If we successfully parsed a task, save it to the cache
if vimwikitask is not None:
self.vimwikitask_cache[key] = vimwikitask
return vimwikitask # May return None if the line has no task
# ShortUUID objects refer to Task cache
elif type(key) == vwtask.ShortUUID:
task = self.task_cache.get(key)
if task is None:
task = key.tw.tasks.get(uuid=key.value)
self.task_cache[key] = task
return task
# Anything else is wrong
else:
raise ValueError("Wrong key type: %s (%s)" % (key, type(key)))
def __setitem__(self, key, value):
# Never store None in the cache, treat it as deletion
if value is None:
if key in self:
del self[key]
return
# String keys refer to the Task objects
if type(key) is vwtask.ShortUUID:
self.task_cache[key] = value
# Integer keys (line numbers) refer to the VimwikiTask objects
elif type(key) is int:
self.vimwikitask_cache[key] = value
# Anything else is wrong
else:
raise ValueError("Wrong key type: %s (%s)" % (key, type(key)))
def __delitem__(self, key):
# String keys refer to the Task objects
if type(key) is vwtask.ShortUUID:
del self.task_cache[key]
# Integer keys (line numbers) refer to the VimwikiTask objects
elif type(key) is int:
del self.vimwikitask_cache[key]
# Anything else is wrong
else:
raise ValueError("Wrong key type: %s (%s)" % (key, type(key)))
def __contains__(self, key):
# String keys refer to the Task objects
if type(key) is vwtask.ShortUUID:
return key in self.task_cache
# Integer keys (line numbers) refer to the VimwikiTask objects
elif type(key) is int:
return key in self.vimwikitask_cache
# Anything else is wrong
else:
raise ValueError("Wrong key type: %s (%s)" % (key, type(key)))
@property
def vimwikitask_dependency_order(self):
iterated_cache = {
k:v for k,v in self.vimwikitask_cache.iteritems()
k:v for k,v in self.vwtask.iteritems()
if v is not None
}
@ -158,9 +42,10 @@ class TaskCache(object):
yield task
def reset(self):
self.task_cache = dict()
self.vimwikitask_cache = dict()
self.viewport_cache = dict()
self.task.store = dict()
self.vwtask.store = dict()
self.viewport.store = dict()
self.line.store = dict()
def load_vwtasks(self, buffer_has_authority=True):
# Set the authority flag, which determines which data (Buffer or TW)
@ -169,7 +54,7 @@ class TaskCache(object):
self.buffer_has_authority = buffer_has_authority
for i in range(len(vim.current.buffer)):
self[i] # Loads the line into the cache
self.vwtask[i] # Loads the line into the cache
# Restore the old authority flag value
self.buffer_has_authority = old_authority
@ -184,10 +69,10 @@ class TaskCache(object):
port.load_tasks()
# Save the viewport in the cache
self.viewport_cache[i] = port
self.viewport[i] = port
def update_vwtasks_in_buffer(self):
for task in self.vimwikitask_cache.values():
for task in self.vwtask.values():
task.update_in_buffer()
def save_tasks(self):
@ -227,14 +112,14 @@ class TaskCache(object):
# Update each task in the cache
for task in tasks:
key = vwtask.ShortUUID(task['uuid'], tw)
self.task_cache[key] = task
self.task[key] = task
def update_vwtasks_from_tasks(self):
for vwtask in self.vimwikitask_cache.values():
for vwtask in self.vwtask.values():
vwtask.update_from_task()
def evaluate_viewports(self):
for port in self.viewport_cache.values():
for port in self.viewport.values():
port.sync_with_taskwarrior()
def get_viewport_by_task(self, task):
@ -245,65 +130,50 @@ class TaskCache(object):
Returns the viewport, or None if not found.
"""
for port in self.viewport_cache.values():
for port in self.viewport.values():
if task in port.viewport_tasks:
return port
def rebuild_vimwikitask_cache(self):
new_cache = dict()
for vimwikitask in self.vimwikitask_cache.values():
new_cache[vimwikitask['line_number']] = vimwikitask
self.vimwikitask_cache = new_cache
def insert_line(self, line, position):
# Insert the line
vim.current.buffer.append(line, position)
# Update the position of all the things shifted by the insertion
for vimwikitask in self.vimwikitask_cache.values():
if vimwikitask['line_number'] >= position:
vimwikitask['line_number'] += 1
self.vwtask.shift(position, 1)
self.viewport.shift(position, 1)
# Rebuild cache keys
self.rebuild_vimwikitask_cache()
# Shift lines in the line cache
self.line.shift(position, 1)
def remove_line(self, position):
# Remove the line
del vim.current.buffer[position]
# Remove the vimwikitask from cache
del self.vimwikitask_cache[position]
del self.vwtask[position]
del self.viewport[position]
# Delete the line from the line cache
del self.line[position]
# Update the position of all the things shifted by the removal
for vimwikitask in self.vimwikitask_cache.values():
if vimwikitask['line_number'] > position:
vimwikitask['line_number'] -= 1
self.vwtask.shift(position, -1)
self.viewport.shift(position, -1)
# Rebuild cache keys
self.rebuild_vimwikitask_cache()
# Shift lines in the line cache
self.line.shift(position, -1)
def swap_lines(self, position1, position2):
buffer_size = len(vim.current.buffer)
if position1 >= buffer_size or position2 >= buffer_size:
raise ValueError("Tring to swap %d with %d" % (position1, position2))
# Swap both the lines and vimwikitasks indexes
temp = vim.current.buffer[position2]
vim.current.buffer[position2] = vim.current.buffer[position1]
vim.current.buffer[position1] = temp
# Swap lines in the line cache
self.line.swap(position1, position2)
temp = self[position2]
self[position2] = self.vimwikitask_cache.get(position1)
self[position1] = temp
# Update the line numbers cached in the vimwikitasks
for position in (position1, position2):
if self[position] is not None:
self[position]['line_number'] = position
# Rebuild of the cache is not necessary, only those two lines are affected
# Swap both the viewport and vimwikitasks indexes
self.vwtask.swap(position1, position2)
self.viewport.swap(position1, position2)
def get_relevant_tw(self):
# Find closest task

8
taskwiki/errors.py Normal file
View file

@ -0,0 +1,8 @@
"""
Contains TaskWiki-specific exceptions.
"""
class TaskWikiException(Exception):
"""Used to interrupt a TaskWiki command/event and notify the user."""
pass

View file

@ -57,8 +57,6 @@ class TaskSorter(object):
vwtasks_sorted[offset].vwtask['line_number']
)
self.cache.rebuild_vimwikitask_cache()
class CustomNodeComparator(object):
"""

198
taskwiki/store.py Normal file
View file

@ -0,0 +1,198 @@
from tasklib import TaskWarrior
import errors
class WarriorStore(object):
"""
Stores all instances of TaskWarrior objects.
"""
def __init__(self, default_rc, default_data, extra_warrior_defs):
default_kwargs = dict(
data_location=default_data,
taskrc_location=default_rc,
)
# Setup the store of TaskWarrior objects
self.warriors = {'default': TaskWarrior(**default_kwargs)}
for key in extra_warrior_defs.keys():
current_kwargs = default_kwargs.copy()
current_kwargs.update(extra_warrior_defs[key])
self.warriors[key] = TaskWarrior(**current_kwargs)
# Make sure context is not respected in any TaskWarrior
for tw in self.warriors.values():
tw.overrides.update({'context':''})
def __getitem__(self, key):
try:
return self.warriors[key]
except KeyError:
raise errors.TaskWikiException(
"Taskwarrior with key '{0}' not available."
.format(key))
def __setitem__(self, key, value):
self.warriors[key] = value
def values(self):
return self.warriors.values()
def iteritems(self):
return self.warriors.iteritems()
class NoNoneStore(object):
def __init__(self, cache):
self.cache = cache
self.store = dict()
def __getitem__(self, key):
item = self.store.get(key)
if item is None:
item = self.get_method(key)
# If we successfully obtained an item, save it to the cache
if item is not None:
self.store[key] = item
return item # May return None if the line has no task
def __setitem__(self, key, value):
# Never store None in the cache, treat it as deletion
if value is None:
if key in self:
del self[key]
return
# Otherwise store the given value
self.store[key] = value
def __delitem__(self, key):
if key in self.store:
del self.store[key]
def values(self):
return self.store.values()
def iteritems(self):
return self.store.iteritems()
def clear(self):
return self.store.clear()
class LineNumberedKeyedStoreMixin(object):
def shift(self, position, offset):
self.store = {
(i + offset if i >= position else i): self.store[i]
for i in self.store.keys()
}
def swap(self, position1, position2):
temp = self.store.get(position1)
self[position1] = self.store.get(position2)
self[position2] = temp
class TaskStore(NoNoneStore):
def get_method(self, key):
return key.tw.tasks.get(uuid=key.value)
class VwtaskStore(LineNumberedKeyedStoreMixin, NoNoneStore):
def shift(self, position, offset):
for line, vwtask in self.store.items():
if line >= position:
vwtask['line_number'] += offset
super(VwtaskStore, self).shift(position, offset)
def swap(self, position1, position2):
super(VwtaskStore, self).swap(position1, position2)
for index in (position1, position2):
if self.store.get(index) is not None:
self[index]['line_number'] = index
def get_method(self, line):
import vwtask
return vwtask.VimwikiTask.from_line(self.cache, line)
class ViewportStore(LineNumberedKeyedStoreMixin, NoNoneStore):
def shift(self, position, offset):
for line, viewport in self.store.items():
if line >= position:
viewport.line_number += offset
super(ViewportStore, self).shift(position, offset)
def swap(self, position1, position2):
super(ViewportStore, self).swap(position1, position2)
for index in (position1, position2):
if self.store.get(index) is not None:
self[index].line_number = index
def get_method(self, line):
import viewport
return viewport.ViewPort.from_line(line, self.cache)
class LineStore(NoNoneStore):
def __delitem__(self, number):
for cls, i in self.store.keys():
if i == number:
del self.store[(cls, i)]
def get_method(self, key):
cls, line = key
return cls.parse_line(line)
def shift(self, position, offset):
new_store = {
(cls, i + offset if i >= position else i): self.store[(cls, i)]
for cls, i in self.store.keys()
}
self.store = new_store
def swap(self, position1, position2):
import vim
temp_store1 = {
(cls, i): self.store[(cls, i)]
for cls, i in self.store.keys()
if i == position1
}
temp_store2 = {
(cls, i): self.store[(cls, i)]
for cls, i in self.store.keys()
if i == position2
}
for cls, i in self.store.keys():
if i == position1 or i == position2:
del self.store[(cls, i)]
for cls, i in temp_store1.keys():
self.store[(cls, position2)] = temp_store1[(cls, i)]
for cls, i in temp_store2.keys():
self.store[(cls, position1)] = temp_store2[(cls, i)]
# Also change the actual line content
temp = vim.current.buffer[position1]
vim.current.buffer[position1] = vim.current.buffer[position2]
vim.current.buffer[position2] = temp

View file

@ -5,15 +5,14 @@ import pickle
import sys
import vim # pylint: disable=F0401
from tasklib import TaskWarrior
# Insert the taskwiki on the python path
BASE_DIR = vim.eval("s:plugin_path")
sys.path.insert(0, os.path.join(BASE_DIR, 'taskwiki'))
import errors
# Handle exceptions without traceback, if they're TaskWikiException
def output_exception(exception_type, value, tb):
if exception_type is util.TaskWikiException:
if exception_type is errors.TaskWikiException:
print(unicode(value), file=sys.stderr)
else:
sys.__excepthook__(exception_type, value, tb)
@ -63,13 +62,14 @@ class WholeBuffer(object):
class SelectedTasks(object):
def __init__(self):
self.tw = cache.get_relevant_tw()
# Reset cache, otherwise old line content may be used
cache.reset()
# Find relevant TaskWarrior instance
self.tw = cache.get_relevant_tw()
# Load the current tasks
range_tasks = [cache[i] for i in util.selected_line_numbers()]
range_tasks = [cache.vwtask[i] for i in util.selected_line_numbers()]
self.tasks = [t for t in range_tasks if t is not None]
if not self.tasks:
@ -118,9 +118,6 @@ class SelectedTasks(object):
if port:
vim.command("TW rc:{0} rc.context: {1}"
.format(port.tw.taskrc_location, port.raw_filter))
print("TW rc:{0} rc.context: {1}"
.format(port.tw.taskrc_location, port.raw_filter))
else:
print("No viewport detected.", file=sys.stderr)
@ -199,7 +196,7 @@ class Mappings(object):
# otherwise do the default VimwikiFollowLink
position = util.get_current_line_number()
if cache[position] is not None:
if cache.vwtask[position] is not None:
SelectedTasks().info()
else:
port = viewport.ViewPort.from_line(position, cache)
@ -369,6 +366,9 @@ class CallbackSplitMixin(object):
# Close the split if the user leaves it
vim.command('au BufLeave <buffer> :bwipe')
# SREMatch objecets cannot be pickled
cache.line.clear()
# We can't save the current instance in vim variable
# so save the pickled version
vim.current.buffer.vars['taskwiki_callback'] = pickle.dumps(self)
@ -509,7 +509,7 @@ class ChooseSplitTags(CallbackSplitMixin, SplitTags):
if match:
return match.group('name')
else:
raise util.TaskWikiException("No tag selected.")
raise errors.TaskWikiException("No tag selected.")
def callback(self):
tag = self.get_selected_tag()

View file

@ -10,15 +10,12 @@ import random
import sys
import vim # pylint: disable=F0401
from errors import TaskWikiException
# Detect if command AnsiEsc is available
ANSI_ESC_AVAILABLE = vim.eval('exists(":AnsiEsc")') == '2'
class TaskWikiException(Exception):
"""Used to interrupt a TaskWiki command/event and notify the user."""
pass
def tw_modstring_to_args(line):
output = []
escape_global_chars = ('"', "'")

View file

@ -5,6 +5,7 @@ import vim # pylint: disable=F0401
import vwtask
import regexp
import errors
import util
import sort
import constants
@ -83,7 +84,7 @@ class ViewPort(object):
context_args = util.tw_modstring_to_args(context_definition)
detected_contexts.append((token, context_args))
else:
raise util.TaskWikiException("Context definition for '{0}' "
raise errors.TaskWikiException("Context definition for '{0}' "
"could not be found.".format(token[1:]))
for context_token, context_args in detected_contexts:
@ -169,9 +170,13 @@ class ViewPort(object):
# All syntactic processing done, return the resulting filter args
return taskfilter_args, meta
@classmethod
def parse_line(cls, number):
return re.search(regexp.GENERIC_VIEWPORT, vim.current.buffer[number])
@classmethod
def from_line(cls, number, cache):
match = re.search(regexp.GENERIC_VIEWPORT, vim.current.buffer[number])
match = cache.line[(cls, number)]
if not match:
return None
@ -243,7 +248,7 @@ class ViewPort(object):
# -VISIBLE virtual tag used
elif self.meta.get('visible') is False:
# Determine which tasks are outside the viewport
all_vwtasks = set(self.cache.vimwikitask_cache.values())
all_vwtasks = set(self.cache.vwtask.values())
vwtasks_outside_viewport = all_vwtasks - set(self.tasks)
tasks_outside_viewport = set(
t.task for t in vwtasks_outside_viewport
@ -274,7 +279,7 @@ class ViewPort(object):
match = re.search(regexp.GENERIC_TASK, line)
if match:
self.tasks.add(self.cache[i])
self.tasks.add(self.cache.vwtask[i])
else:
# If we didn't found a valid task, terminate the viewport
break
@ -324,7 +329,7 @@ class ViewPort(object):
added_at = self.line_number + existing_tasks + added_tasks
# Add the task object to cache
self.cache[vwtask.ShortUUID(task['uuid'], self.tw)] = task
self.cache.task[vwtask.ShortUUID(task['uuid'], self.tw)] = task
# Create the VimwikiTask
vimwikitask = vwtask.VimwikiTask.from_task(self.cache, task)
@ -335,6 +340,6 @@ class ViewPort(object):
self.cache.insert_line(str(vimwikitask), added_at)
# Save it to cache
self.cache[added_at] = vimwikitask
self.cache.vwtask[added_at] = vimwikitask
sort.TaskSorter(self.cache, self.tasks, self.sort).execute()

View file

@ -105,9 +105,8 @@ class VimwikiTask(object):
return vwtask
@classmethod
def from_current_line(cls, cache):
line_number = util.get_current_line_number()
return cls.from_line(cache, line_number)
def parse_line(cls, number):
return re.search(regexp.GENERIC_TASK, vim.current.buffer[number])
@classmethod
def from_line(cls, cache, number):
@ -118,7 +117,7 @@ class VimwikiTask(object):
# Protected access is ok here
# pylint: disable=W0212
match = re.search(regexp.GENERIC_TASK, vim.current.buffer[number])
match = cache.line[(cls, number)]
if not match:
return None
@ -155,6 +154,8 @@ class VimwikiTask(object):
due = match.group('due')
if due:
# With strptime, we get a native datetime object
parsed_due = None
try:
parsed_due = datetime.strptime(due, regexp.DATETIME_FORMAT)
except ValueError:
@ -167,7 +168,8 @@ class VimwikiTask(object):
# We need to interpret it as timezone aware object in user's
# timezone, This properly handles DST and timezone offset.
self.task['due'] = parsed_due
if parsed_due:
self.task['due'] = parsed_due
# After all line-data parsing, save the data in the buffer
self._buffer_data = {key:self[key] for key in self.buffer_keys}
@ -226,7 +228,7 @@ class VimwikiTask(object):
# Else try to load it or create a new one
if self.uuid:
try:
return self.cache[self.uuid]
return self.cache.task[self.uuid]
except Task.DoesNotExist:
# Task with stale uuid, recreate
self.__unsaved_task = Task(self.tw)
@ -235,7 +237,7 @@ class VimwikiTask(object):
'echom "UUID \'{0}\' not found, Task on line {1} will be '
're-created in TaskWarrior."'.format(
self.uuid,
self['line_number']
self['line_number'] + 1
))
self.uuid = None
else:
@ -285,7 +287,7 @@ class VimwikiTask(object):
# the temporary reference
if self.__unsaved_task is not None:
self.uuid = ShortUUID(self.__unsaved_task['uuid'], self.tw)
self.cache[self.uuid] = self.__unsaved_task
self.cache.task[self.uuid] = self.__unsaved_task
self.__unsaved_task = None
# Mark task as done.
@ -344,11 +346,15 @@ class VimwikiTask(object):
])
def find_parent_task(self):
# If this task is not indented, we have nothing to do here
if not self['indent']:
return None
for i in reversed(range(0, self['line_number'])):
# The from_line constructor returns None if line doesn't match a task
task = self.cache[i]
if task and len(task['indent']) < len(self['indent']):
return task
line = self.cache.line[(VimwikiTask, i)]
if line and len(line.group('space')) < len(self['indent']):
return self.cache.vwtask[i]
def apply_defaults(self):
for i in reversed(range(0, self['line_number'])):

View file

@ -279,6 +279,13 @@ class MockCache(object):
warriors = {'default': 'default'}
buffer_has_authority = True
def __init__(self):
from taskwiki import store
self.line = store.LineStore(self)
self.vwtask = dict()
self.task = dict()
self.viewport = dict()
def reset(self):
self.warriors.clear()
self.warriors.update({'default': 'default'})

View file

@ -142,8 +142,8 @@ class TestChooseTag(IntegrationTest):
for task in self.tasks:
task.refresh()
assert self.tasks[0]['tags'] == ["home"]
assert self.tasks[1]['tags'] == ["home"]
assert self.tasks[0]['tags'] == set(["home"])
assert self.tasks[1]['tags'] == set(["home"])
class TestChooseTagCancelled(IntegrationTest):
@ -178,6 +178,44 @@ class TestChooseTagCancelled(IntegrationTest):
for task in self.tasks:
task.refresh()
assert self.tasks[0]['tags'] == ["home"]
assert self.tasks[1]['project'] == None
assert self.tasks[0]['tags'] == set(["home"])
assert self.tasks[1]['tags'] == set()
class TestChooseTagNoSelected(IntegrationTest):
viminput = """
* [ ] test task 1 #{uuid}
* [ ] test task 2 #{uuid}
"""
vimoutput = """
Tag Count
---- -----
home 1
"""
tasks = [
dict(description="test task 1", tags=["home"]),
dict(description="test task 2"),
]
def execute(self):
self.client.normal('1gg')
sleep(0.5)
self.command("TaskWikiChooseTag")
sleep(0.5)
# No tak on the 5th row
self.client.normal('5gg')
sleep(0.5)
self.client.feedkeys("\\<CR>")
sleep(0.5)
for task in self.tasks:
task.refresh()
assert self.tasks[0]['tags'] == set(["home"])
assert self.tasks[1]['tags'] == set()

View file

@ -259,6 +259,35 @@ class TestInfoAction(IntegrationTest):
assert re.search(data2, output, re.MULTILINE)
class TestInfoActionTriggeredByEnter(IntegrationTest):
viminput = """
* [ ] test task 1 #{uuid}
* [ ] test task 2 #{uuid}
"""
tasks = [
dict(description="test task 1"),
dict(description="test task 2"),
]
def execute(self):
self.client.type('1gg')
self.client.feedkeys("\\<CR>")
sleep(0.5)
assert self.command(":py print vim.current.buffer", silent=False).startswith("<buffer info")
output = '\n'.join(self.read_buffer())
header = r'\s*'.join(['Name', 'Value'])
data = r'\s*'.join(['Description', 'test task 1'])
data2 = r'\s*'.join(['Status', 'Pending'])
assert re.search(header, output, re.MULTILINE)
assert re.search(data, output, re.MULTILINE)
assert re.search(data2, output, re.MULTILINE)
class TestInfoActionMoved(IntegrationTest):
viminput = """
@ -692,8 +721,8 @@ class TestModInteractiveAction(IntegrationTest):
for task in self.tasks:
task.refresh()
assert self.tasks[0]['tags'] == ["work"]
assert self.tasks[1]['tags'] == []
assert self.tasks[0]['tags'] == set(["work"])
assert self.tasks[1]['tags'] == set()
class TestModVisibleAction(IntegrationTest):
@ -832,6 +861,35 @@ class TestDoneAction(IntegrationTest):
assert self.tasks[1]['end'] == None
class TestDoneNoSelected(IntegrationTest):
viminput = """
* [ ] test task 1 #{uuid}
* [ ] test task 2 #{uuid}
"""
vimoutput = """
* [ ] test task 1 #{uuid}
* [ ] test task 2 #{uuid}
"""
tasks = [
dict(description="test task 1"),
dict(description="test task 2"),
]
def execute(self):
self.client.type('2gg')
self.command(
"TaskWikiDone",
regex="No tasks selected.",
lines=1)
assert self.tasks[0]['status'] == "pending"
assert self.tasks[1]['status'] == "pending"
class TestDoneActionMoved(IntegrationTest):
@ -911,3 +969,30 @@ class TestDoneActionRange(IntegrationTest):
assert (now - self.tasks[1]['end']).total_seconds() < 5
assert (self.tasks[1]['end'] - now).total_seconds() < 5
class TestSortManually(IntegrationTest):
viminput = """
* [ ] test task 1 #{uuid}
* [ ] test task 2 #{uuid}
"""
vimoutput = """
* [ ] test task 2 #{uuid}
* [ ] test task 1 #{uuid}
"""
tasks = [
dict(description="test task 1"),
dict(description="test task 2"),
]
def execute(self):
self.client.normal('1gg')
sleep(0.5)
self.client.normal('VG')
sleep(0.5)
self.client.feedkeys(":TaskWikiSort description-")
self.client.type("<Enter>")
sleep(0.5)

View file

@ -246,6 +246,30 @@ class TestTagsSimple(IntegrationTest):
assert re.search(work, output, re.MULTILINE)
class TestTagsSimpleFiltered(IntegrationTest):
tasks = [
dict(description="home task"),
dict(description="home chore task 1", tags=['chore']),
dict(description="home chore task 2", tags=['chore']),
dict(description="work task 1", tags=['work']),
dict(description="work task 2", tags=['work']),
]
def execute(self):
self.command("TaskWikiTags +chore")
assert self.command(":py print vim.current.buffer", silent=False).startswith("<buffer tags")
output = '\n'.join(self.read_buffer())
header = r'\s*'.join(['Tag', 'Count'])
chores = r'\s*'.join(['chore', '2'])
work = r'\s*'.join(['work', '2'])
assert re.search(header, output, re.MULTILINE)
assert re.search(chores, output, re.MULTILINE)
assert not re.search(work, output, re.MULTILINE)
class TestSplitReplacement(IntegrationTest):
def execute(self):

View file

@ -147,7 +147,7 @@ class TestViewportDefaultsAssigment(IntegrationTest):
task = self.tw.tasks.pending()[0]
assert task['description'] == 'tag work task'
assert task['status'] == 'pending'
assert task['tags'] == ['work']
assert task['tags'] == set(['work'])
class TestViewportDefaultsExplicit(IntegrationTest):
@ -169,7 +169,7 @@ class TestViewportDefaultsExplicit(IntegrationTest):
assert task['description'] == 'home task'
assert task['status'] == 'pending'
assert task['project'] == 'Chores'
assert task['tags'] == []
assert task['tags'] == set()
class TestViewportDefaultsExplicitEmpty(IntegrationTest):
@ -191,7 +191,7 @@ class TestViewportDefaultsExplicitEmpty(IntegrationTest):
assert task['description'] == 'home task'
assert task['status'] == 'pending'
assert task['project'] == None
assert task['tags'] == []
assert task['tags'] == set()
class TestViewportInspection(IntegrationTest):
@ -227,6 +227,44 @@ class TestViewportInspection(IntegrationTest):
assert self.command(":py print vim.current.buffer", regex="<buffer taskwiki.")
class TestViewportInspectionWithVisibleTag(IntegrationTest):
viminput = """
=== Work tasks | +work -VISIBLE ===
* [ ] tag work task #{uuid}
=== Home tasks | +home ===
* [ ] tag work task #{uuid}
"""
vimoutput = """
ViewPort inspection:
--------------------
Name: Work tasks
Filter used: -DELETED -PARENT +work
Defaults used: tags:['work']
Ordering used: due+,pri-,project+
Matching taskwarrior tasks: 0
Displayed tasks: 0
Tasks to be added:
Tasks to be deleted:
"""
tasks = [
dict(description="tag work task", tags=['work', 'home']),
]
def execute(self):
self.command("w", regex="written$", lines=1)
sleep(0.5)
self.client.feedkeys('1gg')
sleep(0.5)
self.client.feedkeys(r'\<CR>')
sleep(0.5)
assert self.command(":py print vim.current.buffer", regex="<buffer taskwiki.")
class TestViewportsUnicodeTaskGeneration(IntegrationTest):
viminput = """

View file

@ -26,6 +26,27 @@ class TestSimpleTaskCreation(IntegrationTest):
assert task['status'] == 'pending'
class TestInvalidUUIDTask(IntegrationTest):
viminput = """
* [ ] This is a test task #abc123ef
"""
vimoutput = """
* [ ] This is a test task #{uuid}
"""
def execute(self):
self.command("w", regex="on line 1 will be re-created")
# Check that only one tasks with this description exists
assert len(self.tw.tasks.pending()) == 1
task = self.tw.tasks.pending()[0]
assert task['description'] == 'This is a test task'
assert task['status'] == 'pending'
class TestSimpleTaskModification(IntegrationTest):
viminput = """
@ -111,6 +132,28 @@ class TestSimpleTaskWithDueDatetimeCreation(IntegrationTest):
assert task['due'] == local_zone.localize(due)
class TestSimpleTaskWithFlawedDueDatetimeCreation(IntegrationTest):
viminput = """
* [ ] This is a test task (2015-94-53 12:00)
"""
vimoutput = """
* [ ] This is a test task #{uuid}
"""
def execute(self):
self.command("w", regex="Invalid timestamp", lines=2)
# Check that only one tasks with this description exists
assert len(self.tw.tasks.pending()) == 1
task = self.tw.tasks.pending()[0]
assert task['description'] == 'This is a test task'
assert task['status'] == 'pending'
assert task['due'] == None
class TestSimpleTaskWithDueDateCreation(IntegrationTest):
viminput = """