Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
topydo
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
topydo
Commits
bb714d07
Commit
bb714d07
authored
Aug 20, 2015
by
Bram Schoenmakers
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into stable
parents
838335a2
eb8a1d1d
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
175 additions
and
96 deletions
+175
-96
CHANGES.md
CHANGES.md
+14
-0
setup.py
setup.py
+29
-3
test/RecurrenceTest.py
test/RecurrenceTest.py
+33
-6
topydo/cli/Prompt.py
topydo/cli/Prompt.py
+4
-3
topydo/cli/TopydoCompleter.py
topydo/cli/TopydoCompleter.py
+68
-52
topydo/cli/UILoader.py
topydo/cli/UILoader.py
+4
-0
topydo/commands/DoCommand.py
topydo/commands/DoCommand.py
+6
-7
topydo/lib/Recurrence.py
topydo/lib/Recurrence.py
+16
-24
topydo/lib/Version.py
topydo/lib/Version.py
+1
-1
No files found.
CHANGES.md
View file @
bb714d07
0.6
---
*
Recurrence patterns can be prefixed with a
`+`
to indicate strict recurrence
(i.e. based on due date rather than completion date. This syntax is inspired
from the SimpleTask project by @mpcjansen.
*
Colors now work on the Windows commandline (thanks to @MinchinWeb). Requires
colorama to be installed.
*
Do not print spurious color codes when colors are disabled in the
configuration (thanks to @MinchinWeb).
*
In prompt mode, restore old auto-completion behavior: press Tab for
completion (instead of complete while typing).
*
Various other minor fixes (thanks to @MinchinWeb).
0.
5
---
...
...
setup.py
View file @
bb714d07
from
setuptools
import
setup
,
find_packages
import
os
import
re
import
codecs
import
sys
here
=
os
.
path
.
abspath
(
os
.
path
.
dirname
(
__file__
))
def
read
(
*
parts
):
# intentionally *not* adding an encoding option to open
return
codecs
.
open
(
os
.
path
.
join
(
here
,
*
parts
),
'r'
).
read
()
def
find_version
(
*
file_paths
):
version_file
=
read
(
*
file_paths
)
version_match
=
re
.
search
(
r"^VERSION = ['\"]([^'\"]*)['\"]"
,
version_file
,
re
.
M
)
if
version_match
:
return
version_match
.
group
(
1
)
raise
RuntimeError
(
"Unable to find version string."
)
conditional_dependencies
=
{
"colorama>=0.2.5"
:
"win32"
in
sys
.
platform
,
}
setup
(
name
=
"topydo"
,
packages
=
find_packages
(
exclude
=
[
"test"
]),
version
=
"0.5"
,
version
=
find_version
(
'topydo'
,
'lib'
,
'Version.py'
)
,
description
=
"A command-line todo list application using the todo.txt format."
,
author
=
"Bram Schoenmakers"
,
author_email
=
"me@bramschoenmakers.nl"
,
url
=
"https://github.com/bram85/topydo"
,
install_requires
=
[
'six >= 1.9.0'
,
],
]
+
[
p
for
p
,
cond
in
conditional_dependencies
.
items
()
if
cond
]
,
extras_require
=
{
'ical'
:
[
'icalendar'
],
'prompt-toolkit'
:
[
'prompt-toolkit >= 0.
39
'
],
'prompt-toolkit'
:
[
'prompt-toolkit >= 0.
47
'
],
'edit-cmd-tests'
:
[
'mock'
],
},
entry_points
=
{
...
...
test/RecurrenceTest.py
View file @
bb714d07
...
...
@@ -18,7 +18,7 @@ from datetime import date, timedelta
import
unittest
from
topydo.lib.Config
import
config
from
topydo.lib.Recurrence
import
advance_recurring_todo
,
strict_advance_recurring_todo
,
NoRecurrenceException
from
topydo.lib.Recurrence
import
advance_recurring_todo
,
NoRecurrenceException
from
topydo.lib.Todo
import
Todo
from
test.TopydoTest
import
TopydoTest
...
...
@@ -26,6 +26,7 @@ class RecurrenceTest(TopydoTest):
def
setUp
(
self
):
super
(
RecurrenceTest
,
self
).
setUp
()
self
.
todo
=
Todo
(
"Test rec:1w"
)
self
.
stricttodo
=
Todo
(
"Test rec:+1w"
)
def
test_duedate1
(
self
):
""" Where due date is in the future. """
...
...
@@ -63,7 +64,7 @@ class RecurrenceTest(TopydoTest):
new_due
=
date
.
today
()
-
timedelta
(
1
)
self
.
todo
.
set_tag
(
config
().
tag_due
(),
past
.
isoformat
())
new_todo
=
strict_advance_recurring_todo
(
self
.
todo
)
new_todo
=
advance_recurring_todo
(
self
.
todo
,
p_strict
=
True
)
self
.
assertEqual
(
new_todo
.
due_date
(),
new_due
)
...
...
@@ -73,7 +74,7 @@ class RecurrenceTest(TopydoTest):
new_due
=
date
.
today
()
+
timedelta
(
8
)
self
.
todo
.
set_tag
(
config
().
tag_due
(),
future
.
isoformat
())
new_todo
=
strict_advance_recurring_todo
(
self
.
todo
)
new_todo
=
advance_recurring_todo
(
self
.
todo
,
p_strict
=
True
)
self
.
assertEqual
(
new_todo
.
due_date
(),
new_due
)
...
...
@@ -83,7 +84,7 @@ class RecurrenceTest(TopydoTest):
new_due
=
date
.
today
()
+
timedelta
(
7
)
self
.
todo
.
set_tag
(
config
().
tag_due
(),
today
.
isoformat
())
new_todo
=
strict_advance_recurring_todo
(
self
.
todo
)
new_todo
=
advance_recurring_todo
(
self
.
todo
,
p_strict
=
True
)
self
.
assertEqual
(
new_todo
.
due_date
(),
new_due
)
...
...
@@ -96,7 +97,7 @@ class RecurrenceTest(TopydoTest):
def
test_noduedate2
(
self
):
new_due
=
date
.
today
()
+
timedelta
(
7
)
new_todo
=
strict_advance_recurring_todo
(
self
.
todo
)
new_todo
=
advance_recurring_todo
(
self
.
todo
,
p_strict
=
True
)
self
.
assertTrue
(
new_todo
.
has_tag
(
config
().
tag_due
()))
self
.
assertEqual
(
new_todo
.
due_date
(),
new_due
)
...
...
@@ -121,7 +122,7 @@ class RecurrenceTest(TopydoTest):
self
.
todo
.
set_tag
(
config
().
tag_start
(),
yesterday
.
isoformat
())
new_start
=
date
.
today
()
+
timedelta
(
5
)
new_todo
=
strict_advance_recurring_todo
(
self
.
todo
)
new_todo
=
advance_recurring_todo
(
self
.
todo
,
p_strict
=
True
)
self
.
assertEqual
(
new_todo
.
start_date
(),
new_start
)
...
...
@@ -135,6 +136,32 @@ class RecurrenceTest(TopydoTest):
self
.
assertEqual
(
new_todo
.
start_date
(),
new_start
)
def
test_strict_recurrence1
(
self
):
"""
Strict recurrence where due date is in the past, using + notation in
expression.
"""
past
=
date
.
today
()
-
timedelta
(
8
)
new_due
=
date
.
today
()
-
timedelta
(
1
)
self
.
stricttodo
.
set_tag
(
config
().
tag_due
(),
past
.
isoformat
())
new_todo
=
advance_recurring_todo
(
self
.
stricttodo
,
p_strict
=
True
)
self
.
assertEqual
(
new_todo
.
due_date
(),
new_due
)
def
test_strict_recurrence2
(
self
):
"""
Strict recurrence where due date is in the future, using + notation in
expression.
"""
future
=
date
.
today
()
+
timedelta
(
1
)
new_due
=
date
.
today
()
+
timedelta
(
8
)
self
.
stricttodo
.
set_tag
(
config
().
tag_due
(),
future
.
isoformat
())
new_todo
=
advance_recurring_todo
(
self
.
stricttodo
,
p_strict
=
True
)
self
.
assertEqual
(
new_todo
.
due_date
(),
new_due
)
def
test_no_recurrence
(
self
):
self
.
todo
.
remove_tag
(
'rec'
)
self
.
assertRaises
(
NoRecurrenceException
,
advance_recurring_todo
,
self
.
todo
)
...
...
topydo/cli/Prompt.py
View file @
bb714d07
...
...
@@ -22,7 +22,7 @@ import sys
from
topydo.cli.CLIApplicationBase
import
CLIApplicationBase
,
error
,
usage
from
topydo.cli.TopydoCompleter
import
TopydoCompleter
from
prompt_toolkit.shortcuts
import
get_input
from
prompt_toolkit.history
import
History
from
prompt_toolkit.history
import
InMemory
History
from
topydo.lib.Config
import
config
,
ConfigError
...
...
@@ -83,7 +83,7 @@ class PromptApplication(CLIApplicationBase):
def
run
(
self
):
""" Main entry function. """
history
=
History
()
history
=
InMemory
History
()
while
True
:
# (re)load the todo.txt file (only if it has been modified)
...
...
@@ -91,7 +91,8 @@ class PromptApplication(CLIApplicationBase):
try
:
user_input
=
get_input
(
u'topydo> '
,
history
=
history
,
completer
=
self
.
completer
).
split
()
completer
=
self
.
completer
,
complete_while_typing
=
False
).
split
()
except
(
EOFError
,
KeyboardInterrupt
):
sys
.
exit
(
0
)
...
...
topydo/cli/TopydoCompleter.py
View file @
bb714d07
...
...
@@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module provides a completer class that can be used by get_input provided
by the prompt toolkit.
"""
import
datetime
import
re
...
...
@@ -23,84 +28,95 @@ from topydo.lib.Config import config
from
topydo.Commands
import
_SUBCOMMAND_MAP
from
topydo.lib.RelativeDate
import
relative_date_to_date
def
_date_suggestions
():
"""
Returns a list of relative date that is presented to the user as auto
complete suggestions.
"""
# don't use strftime, prevent locales to kick in
days_of_week
=
{
0
:
"Monday"
,
1
:
"Tuesday"
,
2
:
"Wednesday"
,
3
:
"Thursday"
,
4
:
"Friday"
,
5
:
"Saturday"
,
6
:
"Sunday"
}
dates
=
[
'today'
,
'tomorrow'
,
]
# show days of week up to next week
dow
=
datetime
.
date
.
today
().
weekday
()
for
i
in
range
(
dow
+
2
%
7
,
dow
+
7
):
dates
.
append
(
days_of_week
[
i
%
7
])
# and some more relative days starting from next week
dates
+=
[
"1w"
,
"2w"
,
"1m"
,
"2m"
,
"3m"
,
"1y"
]
return
dates
def
_subcommands
(
p_word_before_cursor
):
""" Generator for subcommand name completion. """
subcommands
=
[
sc
for
sc
in
sorted
(
_SUBCOMMAND_MAP
.
keys
())
if
sc
.
startswith
(
p_word_before_cursor
)]
for
command
in
subcommands
:
yield
Completion
(
command
,
-
len
(
p_word_before_cursor
))
def
_dates
(
p_word_before_cursor
):
""" Generator for date completion. """
def
_date_suggestions
():
"""
Returns a list of relative date that is presented to the user as auto
complete suggestions.
"""
# don't use strftime, prevent locales to kick in
days_of_week
=
{
0
:
"Monday"
,
1
:
"Tuesday"
,
2
:
"Wednesday"
,
3
:
"Thursday"
,
4
:
"Friday"
,
5
:
"Saturday"
,
6
:
"Sunday"
}
dates
=
[
'today'
,
'tomorrow'
,
]
# show days of week up to next week
dow
=
datetime
.
date
.
today
().
weekday
()
for
i
in
range
(
dow
+
2
%
7
,
dow
+
7
):
dates
.
append
(
days_of_week
[
i
%
7
])
# and some more relative days starting from next week
dates
+=
[
"1w"
,
"2w"
,
"1m"
,
"2m"
,
"3m"
,
"1y"
]
return
dates
to_absolute
=
lambda
s
:
relative_date_to_date
(
s
).
isoformat
()
start_value_pos
=
p_word_before_cursor
.
find
(
':'
)
+
1
value
=
p_word_before_cursor
[
start_value_pos
:]
for
reldate
in
_date_suggestions
():
if
not
reldate
.
startswith
(
value
):
continue
yield
Completion
(
reldate
,
-
len
(
value
),
display_meta
=
to_absolute
(
reldate
))
class
TopydoCompleter
(
Completer
):
"""
Completer class that completes projects, contexts, dates and
subcommands.
"""
def
__init__
(
self
,
p_todolist
):
self
.
todolist
=
p_todolist
def
_subcommands
(
self
,
p_word_before_cursor
):
subcommands
=
[
sc
for
sc
in
sorted
(
_SUBCOMMAND_MAP
.
keys
())
if
sc
.
startswith
(
p_word_before_cursor
)]
for
command
in
subcommands
:
yield
Completion
(
command
,
-
len
(
p_word_before_cursor
))
def
_projects
(
self
,
p_word_before_cursor
):
projects
=
[
p
for
p
in
self
.
todolist
.
projects
()
if
p
.
startswith
(
p_word_before_cursor
[
1
:])]
""" Generator for project completion. """
projects
=
[
p
for
p
in
self
.
todolist
.
projects
()
if
p
.
startswith
(
p_word_before_cursor
[
1
:])]
for
project
in
projects
:
yield
Completion
(
"+"
+
project
,
-
len
(
p_word_before_cursor
))
def
_contexts
(
self
,
p_word_before_cursor
):
contexts
=
[
c
for
c
in
self
.
todolist
.
contexts
()
if
c
.
startswith
(
p_word_before_cursor
[
1
:])]
""" Generator for context completion. """
contexts
=
[
c
for
c
in
self
.
todolist
.
contexts
()
if
c
.
startswith
(
p_word_before_cursor
[
1
:])]
for
context
in
contexts
:
yield
Completion
(
"@"
+
context
,
-
len
(
p_word_before_cursor
))
def
_dates
(
self
,
p_word_before_cursor
):
to_absolute
=
lambda
s
:
relative_date_to_date
(
s
).
isoformat
()
start_value_pos
=
p_word_before_cursor
.
find
(
':'
)
+
1
value
=
p_word_before_cursor
[
start_value_pos
:]
for
reldate
in
_date_suggestions
():
if
not
reldate
.
startswith
(
value
):
continue
yield
Completion
(
reldate
,
-
len
(
value
),
display_meta
=
to_absolute
(
reldate
))
def
get_completions
(
self
,
p_document
,
_
):
# include all characters except whitespaces (for + and @)
word_before_cursor
=
p_document
.
get_word_before_cursor
(
True
)
is_first_word
=
not
re
.
match
(
r'\
s*
\S+\
s
', p_document.current_line_before_cursor)
if is_first_word:
return
self.
_subcommands(word_before_cursor)
return _subcommands(word_before_cursor)
elif word_before_cursor.startswith('
+
'):
return self._projects(word_before_cursor)
elif word_before_cursor.startswith('
@
'):
return self._contexts(word_before_cursor)
elif word_before_cursor.startswith(config().tag_due() + '
:
'):
return
self.
_dates(word_before_cursor)
return _dates(word_before_cursor)
elif word_before_cursor.startswith(config().tag_start() + '
:
'):
return
self.
_dates(word_before_cursor)
return _dates(word_before_cursor)
return []
topydo/cli/UILoader.py
View file @
bb714d07
...
...
@@ -20,6 +20,10 @@ import sys
import
getopt
from
topydo.cli.CLIApplicationBase
import
MAIN_OPTS
,
error
from
topydo.cli.CLI
import
CLIApplication
# enable color on windows CMD
if
"win32"
in
sys
.
platform
:
import
colorama
colorama
.
init
()
def
main
():
""" Main entry point of the CLI. """
...
...
topydo/commands/DoCommand.py
View file @
bb714d07
...
...
@@ -19,7 +19,7 @@ from datetime import date
from
topydo.lib.DCommand
import
DCommand
from
topydo.lib.PrettyPrinter
import
PrettyPrinter
from
topydo.lib.PrettyPrinterFilter
import
PrettyPrinterNumbers
from
topydo.lib.Recurrence
import
advance_recurring_todo
,
strict_advance_recurring_todo
,
NoRecurrenceException
from
topydo.lib.Recurrence
import
advance_recurring_todo
,
NoRecurrenceException
from
topydo.lib.Utils
import
date_string_to_date
class
DoCommand
(
DCommand
):
...
...
@@ -54,12 +54,11 @@ class DoCommand(DCommand):
def
_handle_recurrence
(
self
,
p_todo
):
if
p_todo
.
has_tag
(
'rec'
):
try
:
if
self
.
strict_recurrence
:
new_todo
=
strict_advance_recurring_todo
(
p_todo
,
self
.
completion_date
)
else
:
new_todo
=
advance_recurring_todo
(
p_todo
,
self
.
completion_date
)
new_todo
=
advance_recurring_todo
(
p_todo
,
p_offset
=
self
.
completion_date
,
p_strict
=
self
.
strict_recurrence
)
self
.
todolist
.
add_todo
(
new_todo
)
...
...
topydo/lib/Recurrence.py
View file @
bb714d07
...
...
@@ -25,24 +25,36 @@ from topydo.lib.Todo import Todo
class
NoRecurrenceException
(
Exception
):
pass
def
_advance_recurring_todo_helper
(
p_todo
,
p_offset
):
def
advance_recurring_todo
(
p_todo
,
p_offset
=
None
,
p_strict
=
False
):
"""
Given a Todo item, return a new instance of a Todo item with the dates
shifted according to the recurrence rule.
The new date is calculated from the given p_offset value.
Strict means that the real due date is taken as a offset, not today or a
future date to determine the offset.
When the todo item has no due date, then the date is used passed by the
caller (defaulting to today).
When no recurrence tag is present, an exception is raised.
"""
todo
=
Todo
(
p_todo
.
source
())
pattern
=
todo
.
tag_value
(
'rec'
)
if
not
pattern
:
raise
NoRecurrenceException
()
elif
pattern
.
startswith
(
'+'
):
p_strict
=
True
# strip off the +
pattern
=
pattern
[
1
:]
if
p_strict
:
offset
=
p_todo
.
due_date
()
or
p_offset
or
date
.
today
()
else
:
offset
=
p_offset
or
date
.
today
()
length
=
todo
.
length
()
new_due
=
relative_date_to_date
(
pattern
,
p_
offset
)
new_due
=
relative_date_to_date
(
pattern
,
offset
)
if
not
new_due
:
raise
NoRecurrenceException
()
...
...
@@ -57,23 +69,3 @@ def _advance_recurring_todo_helper(p_todo, p_offset):
todo
.
set_creation_date
(
date
.
today
())
return
todo
def
advance_recurring_todo
(
p_todo
,
p_offset
=
None
):
p_offset
=
p_offset
or
date
.
today
()
return
_advance_recurring_todo_helper
(
p_todo
,
p_offset
)
def
strict_advance_recurring_todo
(
p_todo
,
p_offset
=
None
):
"""
Given a Todo item, return a new instance of a Todo item with the dates
shifted according to the recurrence rule.
Strict means that the real due date is taken as a offset, not today or a
future date to determine the offset.
When the todo item has no due date, then the date is used passed by the
caller (defaulting to today).
When no recurrence tag is present, an exception is raised.
"""
offset
=
p_todo
.
due_date
()
or
p_offset
or
date
.
today
()
return
_advance_recurring_todo_helper
(
p_todo
,
offset
)
topydo/lib/Version.py
View file @
bb714d07
""" Version of Topydo. """
VERSION
=
'0.
5
'
VERSION
=
'0.
6
'
LICENSE
=
"""Copyright (C) 2014 - 2015 Bram Schoenmakers
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment