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
7838a8ba
Commit
7838a8ba
authored
Oct 30, 2015
by
Bram Schoenmakers
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into ls-n
parents
76a38dcc
10ec25eb
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
292 additions
and
114 deletions
+292
-114
CONTRIBUTING.md
CONTRIBUTING.md
+0
-4
README.md
README.md
+1
-1
test/TestEditCommand.py
test/TestEditCommand.py
+36
-24
test/TestGetSubCommand.py
test/TestGetSubCommand.py
+85
-0
test/data/aliases.conf
test/data/aliases.conf
+3
-0
topydo.conf
topydo.conf
+11
-0
topydo/Commands.py
topydo/Commands.py
+25
-12
topydo/cli/CLIApplicationBase.py
topydo/cli/CLIApplicationBase.py
+11
-5
topydo/cli/Prompt.py
topydo/cli/Prompt.py
+4
-5
topydo/cli/TopydoCompleter.py
topydo/cli/TopydoCompleter.py
+3
-1
topydo/commands/EditCommand.py
topydo/commands/EditCommand.py
+9
-3
topydo/commands/ListCommand.py
topydo/commands/ListCommand.py
+2
-2
topydo/commands/SortCommand.py
topydo/commands/SortCommand.py
+1
-2
topydo/lib/ChangeSet.py
topydo/lib/ChangeSet.py
+8
-3
topydo/lib/Config.py
topydo/lib/Config.py
+93
-52
No files found.
CONTRIBUTING.md
View file @
7838a8ba
...
...
@@ -5,10 +5,6 @@ smoothly into topydo.
### General
*
This Github page defaults to the
**stable**
branch which is for
**
bug fixes
only
**
. If you would like to add a new feature, make sure to make a Pull
Request on the
`master`
branch.
*
Use descriptive commit messages. The post
[
How to write a commit message
](
http://chris.beams.io/posts/git-commit/
)
by
Chris Beams has some good guidelines.
...
...
README.md
View file @
7838a8ba
...
...
@@ -43,6 +43,6 @@ Demo
[
2
]:
https://github.com/ginatrapani/todo.txt-cli
[
3
]:
https://github.com/bram85/todo.txt-tools
[
4
]:
https://github.com/bram85/topydo/wiki
[
5
]:
https://raw.githubusercontent.com/bram85/topydo/
stable
/doc/topydo.gif
[
5
]:
https://raw.githubusercontent.com/bram85/topydo/
master
/doc/topydo.gif
[
6
]:
https://github.com/jonathanslenders/python-prompt-toolkit
[
7
]:
https://github.com/collective/icalendar
test/TestEditCommand.py
View file @
7838a8ba
...
...
@@ -45,24 +45,30 @@ class EditCommandTest(CommandTest):
self
.
todolist
=
TodoList
(
todos
)
@
mock
.
patch
(
'topydo.commands.EditCommand._is_edited'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._todos_from_temp'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._open_in_editor'
)
def
test_edit
1
(
self
,
mock_open_in_editor
):
def
test_edit
01
(
self
,
mock_open_in_editor
,
mock_todos_from_temp
,
mock_is_edited
):
""" Preserve dependencies after editing. """
mock_open_in_editor
.
return_value
=
0
mock_todos_from_temp
.
return_value
=
[
Todo
(
'Foo id:1'
)]
mock_is_edited
.
return_value
=
True
command
=
EditCommand
([
"1"
],
self
.
todolist
,
self
.
out
,
self
.
error
,
None
)
command
.
execute
()
self
.
assertTrue
(
self
.
todolist
.
is_dirty
())
self
.
assertEqual
(
self
.
errors
,
""
)
self
.
assertTrue
(
self
.
todolist
.
is_dirty
())
self
.
assertEqual
(
self
.
todolist
.
print_todos
(),
u
(
"Bar p:1 @test
\
n
Baz @test
\
n
Fo
\
u00f3
B
\
u0105
\
u017a
\
n
Foo id:1"
))
@
mock
.
patch
(
'topydo.commands.EditCommand._is_edited'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._todos_from_temp'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._open_in_editor'
)
def
test_edit
2
(
self
,
mock_open_in_editor
,
mock_todos_from_temp
):
def
test_edit
02
(
self
,
mock_open_in_editor
,
mock_todos_from_temp
,
mock_is_edited
):
""" Edit some todo. """
mock_open_in_editor
.
return_value
=
0
mock_todos_from_temp
.
return_value
=
[
Todo
(
'Lazy Cat'
)]
mock_is_edited
.
return_value
=
True
command
=
EditCommand
([
"Bar"
],
self
.
todolist
,
self
.
out
,
self
.
error
,
None
)
...
...
@@ -72,7 +78,7 @@ class EditCommandTest(CommandTest):
self
.
assertEqual
(
self
.
errors
,
""
)
self
.
assertEqual
(
self
.
todolist
.
print_todos
(),
u
(
"Foo id:1
\
n
Baz @test
\
n
Fo
\
u00f3
B
\
u0105
\
u017a
\
n
Lazy Cat"
))
def
test_edit3
(
self
):
def
test_edit
0
3
(
self
):
""" Throw an error after invalid todo number given as argument. """
command
=
EditCommand
([
"FooBar"
],
self
.
todolist
,
self
.
out
,
self
.
error
,
None
)
...
...
@@ -81,7 +87,7 @@ class EditCommandTest(CommandTest):
self
.
assertFalse
(
self
.
todolist
.
is_dirty
())
self
.
assertEqual
(
self
.
errors
,
"Invalid todo number given.
\
n
"
)
def
test_edit4
(
self
):
def
test_edit
0
4
(
self
):
""" Throw an error with pointing invalid argument. """
command
=
EditCommand
([
"Bar"
,
"5"
],
self
.
todolist
,
self
.
out
,
self
.
error
,
None
)
...
...
@@ -90,22 +96,7 @@ class EditCommandTest(CommandTest):
self
.
assertFalse
(
self
.
todolist
.
is_dirty
())
self
.
assertEqual
(
self
.
errors
,
"Invalid todo number given: 5.
\
n
"
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._todos_from_temp'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._open_in_editor'
)
def
test_edit5
(
self
,
mock_open_in_editor
,
mock_todos_from_temp
):
""" Don't let to delete todos acidentally while editing. """
mock_open_in_editor
.
return_value
=
0
mock_todos_from_temp
.
return_value
=
[
Todo
(
'Only one line'
)]
command
=
EditCommand
([
"1"
,
"Bar"
],
self
.
todolist
,
self
.
out
,
self
.
error
,
None
)
command
.
execute
()
self
.
assertFalse
(
self
.
todolist
.
is_dirty
())
self
.
assertEqual
(
self
.
errors
,
"Number of edited todos is not equal to number of supplied todo IDs.
\
n
"
)
self
.
assertEqual
(
self
.
todolist
.
print_todos
(),
u
(
"Foo id:1
\
n
Bar p:1 @test
\
n
Baz @test
\
n
Fo
\
u00f3
B
\
u0105
\
u017a
"
))
def
test_edit6
(
self
):
def
test_edit05
(
self
):
"""
Throw an error with invalid argument containing special characters.
"""
...
...
@@ -117,12 +108,14 @@ class EditCommandTest(CommandTest):
self
.
assertEqual
(
self
.
errors
,
u
(
"Invalid todo number given: Fo
\
u00d3
B
\
u0105
r.
\
n
"
))
@
mock
.
patch
(
'topydo.commands.EditCommand._is_edited'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._todos_from_temp'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._open_in_editor'
)
def
test_edit
7
(
self
,
mock_open_in_editor
,
mock_todos_from_temp
):
def
test_edit
06
(
self
,
mock_open_in_editor
,
mock_todos_from_temp
,
mock_is_edited
):
""" Edit todo with special characters. """
mock_open_in_editor
.
return_value
=
0
mock_todos_from_temp
.
return_value
=
[
Todo
(
'Lazy Cat'
)]
mock_is_edited
.
return_value
=
True
command
=
EditCommand
([
u
(
"Fo
\
u00f3
B
\
u0105
\
u017a
"
)],
self
.
todolist
,
self
.
out
,
self
.
error
,
None
)
...
...
@@ -133,13 +126,32 @@ class EditCommandTest(CommandTest):
self
.
assertEqual
(
self
.
todolist
.
print_todos
(),
u
(
"Foo id:1
\
n
Bar p:1 @test
\
n
Baz @test
\
n
Lazy Cat"
))
@
mock
.
patch
(
'topydo.commands.EditCommand._is_edited'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._todos_from_temp'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._open_in_editor'
)
def
test_edit_expr
(
self
,
mock_open_in_editor
,
mock_todos_from_temp
):
def
test_edit07
(
self
,
mock_open_in_editor
,
mock_todos_from_temp
,
mock_is_edited
):
""" Don't perform write if tempfile is unchanged """
mock_open_in_editor
.
return_value
=
0
mock_todos_from_temp
.
return_value
=
[
Todo
(
'Only one line'
)]
mock_is_edited
.
return_value
=
False
command
=
EditCommand
([
"1"
,
"Bar"
],
self
.
todolist
,
self
.
out
,
self
.
error
,
None
)
command
.
execute
()
self
.
assertFalse
(
self
.
todolist
.
is_dirty
())
self
.
assertEqual
(
self
.
errors
,
"Editing aborted. Nothing to do.
\
n
"
)
self
.
assertEqual
(
self
.
todolist
.
print_todos
(),
u
(
"Foo id:1
\
n
Bar p:1 @test
\
n
Baz @test
\
n
Fo
\
u00f3
B
\
u0105
\
u017a
"
))
@
mock
.
patch
(
'topydo.commands.EditCommand._is_edited'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._todos_from_temp'
)
@
mock
.
patch
(
'topydo.commands.EditCommand.EditCommand._open_in_editor'
)
def
test_edit_expr
(
self
,
mock_open_in_editor
,
mock_todos_from_temp
,
mock_is_edited
):
""" Edit todos matching expression. """
mock_open_in_editor
.
return_value
=
0
mock_todos_from_temp
.
return_value
=
[
Todo
(
'Lazy Cat'
),
Todo
(
'Lazy Dog'
)]
mock_is_edited
.
return_value
=
True
command
=
EditCommand
([
"-e"
,
"@test"
],
self
.
todolist
,
self
.
out
,
self
.
error
,
None
)
...
...
@@ -147,8 +159,8 @@ class EditCommandTest(CommandTest):
expected
=
u
(
"| 3| Lazy Cat
\
n
| 4| Lazy Dog
\
n
"
)
self
.
assertTrue
(
self
.
todolist
.
is_dirty
())
self
.
assertEqual
(
self
.
errors
,
""
)
self
.
assertTrue
(
self
.
todolist
.
is_dirty
())
self
.
assertEqual
(
self
.
output
,
expected
)
self
.
assertEqual
(
self
.
todolist
.
print_todos
(),
u
(
"Foo id:1
\
n
Fo
\
u00f3
B
\
u0105
\
u017a
\
n
Lazy Cat
\
n
Lazy Dog"
))
...
...
test/TestGetSubCommand.py
0 → 100644
View file @
7838a8ba
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import
unittest
from
six
import
u
from
test.TopydoTestCase
import
TopydoTest
from
topydo.Commands
import
get_subcommand
from
topydo.commands.AddCommand
import
AddCommand
from
topydo.commands.DeleteCommand
import
DeleteCommand
from
topydo.commands.ListCommand
import
ListCommand
from
topydo.commands.ListProjectCommand
import
ListProjectCommand
from
topydo.lib.Config
import
config
class
GetSubcommandTest
(
TopydoTest
):
def
test_normal_cmd
(
self
):
args
=
[
"add"
]
real_cmd
,
final_args
=
get_subcommand
(
args
)
self
.
assertTrue
(
issubclass
(
real_cmd
,
AddCommand
))
def
test_cmd_help
(
self
):
args
=
[
"help"
,
"add"
]
real_cmd
,
final_args
=
get_subcommand
(
args
)
self
.
assertTrue
(
issubclass
(
real_cmd
,
AddCommand
))
self
.
assertEqual
(
final_args
,
[
"help"
])
def
test_alias
(
self
):
config
(
"test/data/aliases.conf"
)
args
=
[
"foo"
]
real_cmd
,
final_args
=
get_subcommand
(
args
)
self
.
assertTrue
(
issubclass
(
real_cmd
,
DeleteCommand
))
self
.
assertEqual
(
final_args
,
[
"-f"
,
"test"
])
def
test_default_cmd01
(
self
):
args
=
[
"bar"
]
real_cmd
,
final_args
=
get_subcommand
(
args
)
self
.
assertTrue
(
issubclass
(
real_cmd
,
ListCommand
))
self
.
assertEqual
(
final_args
,
[
"bar"
])
def
test_default_cmd02
(
self
):
args
=
[]
real_cmd
,
final_args
=
get_subcommand
(
args
)
self
.
assertTrue
(
issubclass
(
real_cmd
,
ListCommand
))
self
.
assertEqual
(
final_args
,
[])
def
test_alias_default_cmd01
(
self
):
config
(
"test/data/aliases.conf"
,
{(
'topydo'
,
'default_command'
):
'foo'
})
args
=
[
"bar"
]
real_cmd
,
final_args
=
get_subcommand
(
args
)
self
.
assertTrue
(
issubclass
(
real_cmd
,
DeleteCommand
))
self
.
assertEqual
(
final_args
,
[
"-f"
,
"test"
,
"bar"
])
def
test_alias_default_cmd02
(
self
):
config
(
"test/data/aliases.conf"
,
{(
'topydo'
,
'default_command'
):
'foo'
})
args
=
[]
real_cmd
,
final_args
=
get_subcommand
(
args
)
self
.
assertTrue
(
issubclass
(
real_cmd
,
DeleteCommand
))
self
.
assertEqual
(
final_args
,
[
"-f"
,
"test"
])
def
test_wrong_alias
(
self
):
config
(
"test/data/aliases.conf"
)
args
=
[
"baz"
]
real_cmd
,
final_args
=
get_subcommand
(
args
)
self
.
assertEqual
(
real_cmd
,
None
)
if
__name__
==
'__main__'
:
unittest
.
main
()
test/data/aliases.conf
0 → 100644
View file @
7838a8ba
[
aliases
]
foo
=
rm
-
f
test
baz
=
FooBar
topydo.conf
View file @
7838a8ba
...
...
@@ -46,3 +46,14 @@ append_parent_contexts = 0
;
context_color
=
magenta
;
metadata_color
=
green
;
link_color
=
light
-
cyan
[
aliases
]
;
showall
=
ls
-
x
;
lsproj
=
lsprj
;
listprj
=
lsprj
;
listproj
=
lsprj
;
listproject
=
lsprj
;
listprojects
=
lsprj
;
listcon
=
lscon
;
listcontext
=
lscon
;
listcontexts
=
lscon
topydo/Commands.py
View file @
7838a8ba
...
...
@@ -35,15 +35,7 @@ _SUBCOMMAND_MAP = {
'exit'
:
'ExitCommand'
,
# used for the prompt
'ls'
:
'ListCommand'
,
'lscon'
:
'ListContextCommand'
,
'listcon'
:
'ListContextCommand'
,
'listcontext'
:
'ListContextCommand'
,
'listcontexts'
:
'ListContextCommand'
,
'lsprj'
:
'ListProjectCommand'
,
'lsproj'
:
'ListProjectCommand'
,
'listprj'
:
'ListProjectCommand'
,
'listproj'
:
'ListProjectCommand'
,
'listproject'
:
'ListProjectCommand'
,
'listprojects'
:
'ListProjectCommand'
,
'postpone'
:
'PostponeCommand'
,
'pri'
:
'PriorityCommand'
,
'quit'
:
'ExitCommand'
,
...
...
@@ -53,7 +45,6 @@ _SUBCOMMAND_MAP = {
'tag'
:
'TagCommand'
,
}
def
get_subcommand
(
p_args
):
"""
Retrieves the to-be executed Command and returns a tuple (Command, args).
...
...
@@ -80,13 +71,31 @@ def get_subcommand(p_args):
__import__
(
modulename
,
globals
(),
locals
(),
[
classname
],
0
)
return
getattr
(
sys
.
modules
[
modulename
],
classname
)
def
resolve_alias
(
p_alias
,
p_args
):
"""
Resolves a subcommand alias and returns a tuple (Command, args).
If alias resolves to non-existent command, main help message is
returned.
"""
real_subcommand
,
alias_args
=
alias_map
[
p_alias
]
try
:
result
=
import_subcommand
(
real_subcommand
)
args
=
alias_args
+
p_args
return
(
result
,
args
)
except
KeyError
:
return
get_subcommand
([
'help'
])
result
=
None
args
=
p_args
alias_map
=
config
().
aliases
()
try
:
subcommand
=
p_args
[
0
]
if
subcommand
in
_SUBCOMMAND_MAP
:
if
subcommand
in
alias_map
:
result
,
args
=
resolve_alias
(
subcommand
,
args
[
1
:])
elif
subcommand
in
_SUBCOMMAND_MAP
:
result
=
import_subcommand
(
subcommand
)
args
=
args
[
1
:]
elif
subcommand
==
'help'
:
...
...
@@ -101,12 +110,16 @@ def get_subcommand(p_args):
pass
else
:
p_command
=
config
().
default_command
()
if
p_command
in
_SUBCOMMAND_MAP
:
if
p_command
in
alias_map
:
result
,
args
=
resolve_alias
(
p_command
,
args
)
elif
p_command
in
_SUBCOMMAND_MAP
:
result
=
import_subcommand
(
p_command
)
# leave args unchanged
except
IndexError
:
p_command
=
config
().
default_command
()
if
p_command
in
_SUBCOMMAND_MAP
:
if
p_command
in
alias_map
:
result
,
args
=
resolve_alias
(
p_command
,
args
)
elif
p_command
in
_SUBCOMMAND_MAP
:
result
=
import_subcommand
(
p_command
)
return
(
result
,
args
)
topydo/cli/CLIApplicationBase.py
View file @
7838a8ba
...
...
@@ -101,12 +101,9 @@ except ConfigError as config_error:
error
(
str
(
config_error
))
sys
.
exit
(
1
)
from
topydo.commands.ArchiveCommand
import
ArchiveCommand
from
topydo.commands.SortCommand
import
SortCommand
from
topydo.lib
import
TodoFile
from
topydo.lib
import
TodoList
from
topydo.lib
import
TodoListBase
from
topydo.lib.ChangeSet
import
ChangeSet
from
topydo.lib.Utils
import
escape_ansi
...
...
@@ -178,6 +175,7 @@ class CLIApplicationBase(object):
self
.
backup
.
add_archive
(
archive
)
if
archive
:
from
topydo.commands.ArchiveCommand
import
ArchiveCommand
command
=
ArchiveCommand
(
self
.
todolist
,
archive
)
command
.
execute
()
...
...
@@ -196,14 +194,21 @@ class CLIApplicationBase(object):
"""
return
input
def
is_read_only
(
self
,
p_command
):
""" Returns True when the given command class is read-only. """
read_only_commands
=
tuple
(
cmd
+
'Command'
for
cmd
in
(
'Revert'
,
)
+
READ_ONLY_COMMANDS
)
return
p_command
.
__module__
.
endswith
(
read_only_commands
)
def
_execute
(
self
,
p_command
,
p_args
):
"""
Execute a subcommand with arguments. p_command is a class (not an
object).
"""
cmds_wo_backup
=
tuple
(
cmd
+
'Command'
for
cmd
in
(
'Revert'
,
)
+
READ_ONLY_COMMANDS
)
if
config
().
backup_count
()
>
0
and
p_command
and
not
p_command
.
__module__
.
endswith
(
cmds_wo_backup
):
if
config
().
backup_count
()
>
0
and
p_command
and
not
self
.
is_read_only
(
p_command
):
call
=
[
p_command
.
__module__
.
lower
()[
16
:
-
7
]]
+
p_args
# strip "topydo.commands" and "Command"
from
topydo.lib.ChangeSet
import
ChangeSet
self
.
backup
=
ChangeSet
(
self
.
todolist
,
p_call
=
call
)
command
=
p_command
(
...
...
@@ -232,6 +237,7 @@ class CLIApplicationBase(object):
self
.
_archive
()
if
config
().
keep_sorted
():
from
topydo.commands.SortCommand
import
SortCommand
self
.
_execute
(
SortCommand
,
[])
if
self
.
backup
:
...
...
topydo/cli/Prompt.py
View file @
7838a8ba
...
...
@@ -99,15 +99,14 @@ class PromptApplication(CLIApplicationBase):
sys
.
exit
(
0
)
mtime_after
=
_todotxt_mtime
()
(
subcommand
,
args
)
=
get_subcommand
(
user_input
)
if
self
.
mtime
!=
mtime_after
:
# refuse to perform operations such as 'del' and 'do' if the
# todo.txt file has been changed in the background.
# refuse to perform operations such as 'del' and 'do' if the
# todo.txt file has been changed in the background.
if
not
self
.
is_read_only
(
subcommand
)
and
self
.
mtime
!=
mtime_after
:
error
(
"WARNING: todo.txt file was modified by another application.
\
n
To prevent unintended changes, this operation was not executed."
)
continue
(
subcommand
,
args
)
=
get_subcommand
(
user_input
)
try
:
if
self
.
_execute
(
subcommand
,
args
)
!=
False
:
self
.
_post_execute
()
...
...
topydo/cli/TopydoCompleter.py
View file @
7838a8ba
...
...
@@ -30,7 +30,9 @@ from topydo.lib.RelativeDate import relative_date_to_date
def
_subcommands
(
p_word_before_cursor
):
""" Generator for subcommand name completion. """
subcommands
=
[
sc
for
sc
in
sorted
(
_SUBCOMMAND_MAP
.
keys
())
if
sc_map
=
config
().
aliases
()
sc_map
.
update
(
_SUBCOMMAND_MAP
)
subcommands
=
[
sc
for
sc
in
sorted
(
sc_map
.
keys
())
if
sc
.
startswith
(
p_word_before_cursor
)]
for
command
in
subcommands
:
yield
Completion
(
command
,
-
len
(
p_word_before_cursor
))
...
...
topydo/commands/EditCommand.py
View file @
7838a8ba
...
...
@@ -33,6 +33,11 @@ DEFAULT_EDITOR = 'vi'
# cannot use super() inside the class itself
BASE_TODOLIST
=
lambda
tl
:
super
(
TodoList
,
tl
)
def
_get_file_mtime
(
p_file
):
return
os
.
stat
(
p_file
.
name
).
st_mtime
def
_is_edited
(
p_orig_mtime
,
p_file
):
return
p_orig_mtime
<
_get_file_mtime
(
p_file
)
class
EditCommand
(
MultiCommand
):
def
__init__
(
self
,
p_args
,
p_todolist
,
p_output
,
p_error
,
p_input
):
...
...
@@ -105,10 +110,12 @@ class EditCommand(MultiCommand):
self
.
printer
.
add_filter
(
PrettyPrinterNumbers
(
self
.
todolist
))
temp_todos
=
self
.
_todos_to_temp
()
orig_mtime
=
_get_file_mtime
(
temp_todos
)
if
not
self
.
_open_in_editor
(
temp_todos
.
name
):
new_todos
=
self
.
_todos_from_temp
(
temp_todos
)
if
len
(
new_todos
)
==
len
(
self
.
todos
):
if
_is_edited
(
orig_mtime
,
temp_todos
):
for
todo
in
self
.
todos
:
BASE_TODOLIST
(
self
.
todolist
).
delete
(
todo
)
...
...
@@ -116,8 +123,7 @@ class EditCommand(MultiCommand):
self
.
todolist
.
add_todo
(
todo
)
self
.
out
(
self
.
printer
.
print_todo
(
todo
))
else
:
self
.
error
(
'Number of edited todos is not equal to '
'number of supplied todo IDs.'
)
self
.
error
(
'Editing aborted. Nothing to do.'
)
else
:
self
.
error
(
self
.
usage
())
...
...
topydo/commands/ListCommand.py
View file @
7838a8ba
...
...
@@ -16,8 +16,6 @@
from
topydo.lib.Config
import
config
from
topydo.lib.ExpressionCommand
import
ExpressionCommand
from
topydo.lib.IcalPrinter
import
IcalPrinter
from
topydo.lib.JsonPrinter
import
JsonPrinter
from
topydo.lib.PrettyPrinter
import
pretty_printer_factory
from
topydo.lib.PrettyPrinterFilter
import
(
PrettyPrinterHideTagFilter
,
PrettyPrinterIndentFilter
)
...
...
@@ -58,9 +56,11 @@ class ListCommand(ExpressionCommand):
self
.
sort_expression
=
value
elif
opt
==
'-f'
:
if
value
==
'json'
:
from
topydo.lib.JsonPrinter
import
JsonPrinter
self
.
printer
=
JsonPrinter
()
elif
value
==
'ical'
:
if
self
.
_poke_icalendar
():
from
topydo.lib.IcalPrinter
import
IcalPrinter
self
.
printer
=
IcalPrinter
(
self
.
todolist
)
else
:
self
.
printer
=
None
...
...
topydo/commands/SortCommand.py
View file @
7838a8ba
...
...
@@ -39,8 +39,7 @@ class SortCommand(Command):
sorter
=
Sorter
(
expression
)
# TODO: validate
sorted_todos
=
sorter
.
sort
(
self
.
todolist
.
todos
())
self
.
todolist
.
erase
()
self
.
todolist
.
add_todos
(
sorted_todos
)
self
.
todolist
.
replace
(
sorted_todos
)
def
usage
(
self
):
return
"""Synopsis: sort [expression]"""
...
...
topydo/lib/ChangeSet.py
View file @
7838a8ba
...
...
@@ -110,7 +110,7 @@ class ChangeSet(object):
self
.
_write
()
self
.
close
()
def
delete
(
self
,
p_timestamp
=
None
):
def
delete
(
self
,
p_timestamp
=
None
,
p_write
=
True
):
""" Removes backup from the backup file. """
timestamp
=
p_timestamp
or
self
.
timestamp
index
=
self
.
_get_index
()
...
...
@@ -119,7 +119,9 @@ class ChangeSet(object):
del
self
.
backup_dict
[
timestamp
]
index
.
remove
(
index
[[
change
[
0
]
for
change
in
index
].
index
(
timestamp
)])
self
.
_save_index
(
index
)
self
.
_write
()
if
p_write
:
self
.
_write
()
except
KeyError
:
pass
...
...
@@ -143,12 +145,15 @@ class ChangeSet(object):
"""
Removes oldest backups that exceed the limit configured in backup_count
option.
Does not write back to file system, make sure to call self._write()
afterwards.
"""
index
=
self
.
_get_index
()
backup_limit
=
config
().
backup_count
()
-
1
for
changeset
in
index
[
backup_limit
:]:
self
.
delete
(
changeset
[
0
])
self
.
delete
(
changeset
[
0
]
,
p_write
=
False
)
def
get_backup
(
self
,
p_todolist
):
"""
...
...
topydo/lib/Config.py
View file @
7838a8ba
...
...
@@ -16,9 +16,9 @@
import
os
from
six
import
iteritems
from
six.moves
import
configparser
class
ConfigError
(
Exception
):
def
__init__
(
self
,
p_text
):
self
.
text
=
p_text
...
...
@@ -42,6 +42,7 @@ class _Config:
"""
self
.
sections
=
[
'add'
,
'aliases'
,
'colorscheme'
,
'dep'
,
'ls'
,
...
...
@@ -51,47 +52,71 @@ class _Config:
]
self
.
defaults
=
{
# topydo
'default_command'
:
'ls'
,
'colors'
:
'1'
,
'filename'
:
'todo.txt'
,
'archive_filename'
:
'done.txt'
,
'identifiers'
:
'linenumber'
,
'backup_count'
:
'5'
,
# add
'auto_creation_date'
:
'1'
,
# ls
'hide_tags'
:
'id,p,ical'
,
'indent'
:
0
,
'list_limit'
:
'-1'
,
# tags
'tag_start'
:
't'
,
'tag_due'
:
'due'
,
'tag_star'
:
'star'
,
# sort
'keep_sorted'
:
'0'
,
'sort_string'
:
'desc:importance,due,desc:priority'
,
'ignore_weekends'
:
'1'
,
# dep
'append_parent_projects'
:
'0'
,
'append_parent_contexts'
:
'0'
,
# colorscheme
'project_color'
:
'red'
,
'context_color'
:
'magenta'
,
'metadata_color'
:
'green'
,
'link_color'
:
'cyan'
,
'priority_colors'
:
'A:cyan,B:yellow,C:blue'
,
'topydo'
:
{
'default_command'
:
'ls'
,
'colors'
:
'1'
,
'filename'
:
'todo.txt'
,
'archive_filename'
:
'done.txt'
,
'identifiers'
:
'linenumber'
,
'backup_count'
:
'5'
,
},
'add'
:
{
'auto_creation_date'
:
'1'
,
},
'ls'
:
{
'hide_tags'
:
'id,p,ical'
,
'indent'
:
'0'
,
'list_limit'
:
'-1'
,
},
'tags'
:
{
'tag_start'
:
't'
,
'tag_due'
:
'due'
,
'tag_star'
:
'star'
,
},
'sort'
:
{
'keep_sorted'
:
'0'
,
'sort_string'
:
'desc:importance,due,desc:priority'
,
'ignore_weekends'
:
'1'
,
},
'dep'
:
{
'append_parent_projects'
:
'0'
,
'append_parent_contexts'
:
'0'
,
},
'colorscheme'
:
{
'project_color'
:
'red'
,
'context_color'
:
'magenta'
,
'metadata_color'
:
'green'
,
'link_color'
:
'cyan'
,
'priority_colors'
:
'A:cyan,B:yellow,C:blue'
,
},
'aliases'
:
{
'lsproj'
:
'lsprj'
,
'listprj'
:
'lsprj'
,
'listproj'
:
'lsprj'
,
'listproject'
:
'lsprj'
,
'listprojects'
:
'lsprj'
,
'listcon'
:
'lscon'
,
'listcontext'
:
'lscon'
,
'listcontexts'
:
'lscon'
,
},
}
self
.
config
=
{}
self
.
cp
=
configparser
.
ConfigParser
(
self
.
defaults
)
self
.
cp
=
configparser
.
ConfigParser
()
for
section
in
self
.
defaults
:
self
.
cp
.
add_section
(
section
)
for
option
,
value
in
iteritems
(
self
.
defaults
[
section
]):
self
.
cp
.
set
(
section
,
option
,
value
)
files
=
[
"/etc/topydo.conf"
,
...
...
@@ -129,7 +154,7 @@ class _Config:
try
:
return
self
.
cp
.
getboolean
(
'topydo'
,
'colors'
)
except
ValueError
:
return
self
.
defaults
[
'colors'
]
==
'1'
return
self
.
defaults
[
'
topydo'
][
'
colors'
]
==
'1'
def
todotxt
(
self
):
return
os
.
path
.
expanduser
(
self
.
cp
.
get
(
'topydo'
,
'filename'
))
...
...
@@ -147,25 +172,25 @@ class _Config:
value
=
0
return
value
except
ValueError
:
return
int
(
self
.
defaults
[
'backup_count'
])
return
int
(
self
.
defaults
[
'
topydo'
][
'
backup_count'
])
def
list_limit
(
self
):
try
:
return
self
.
cp
.
getint
(
'ls'
,
'list_limit'
)
except
ValueError
:
return
int
(
self
.
defaults
[
'list_limit'
])
return
int
(
self
.
defaults
[
'l
s'
][
'l
ist_limit'
])
def
list_indent
(
self
):
try
:
return
self
.
cp
.
getint
(
'ls'
,
'indent'
)
except
ValueError
:
return
int
(
self
.
defaults
[
'indent'
])
return
int
(
self
.
defaults
[
'
ls'
][
'
indent'
])
def
keep_sorted
(
self
):
try
:
return
self
.
cp
.
getboolean
(
'sort'
,
'keep_sorted'
)
except
ValueError
:
return
self
.
defaults
[
'keep_sorted'
]
==
'1'
return
self
.
defaults
[
'
sort'
][
'
keep_sorted'
]
==
'1'
def
sort_string
(
self
):
return
self
.
cp
.
get
(
'sort'
,
'sort_string'
)
...
...
@@ -174,19 +199,19 @@ class _Config:
try
:
return
self
.
cp
.
getboolean
(
'sort'
,
'ignore_weekends'
)
except
ValueError
:
return
self
.
defaults
[
'ignore_weekends'
]
==
'1'
return
self
.
defaults
[
'
sort'
][
'
ignore_weekends'
]
==
'1'
def
append_parent_projects
(
self
):
try
:
return
self
.
cp
.
getboolean
(
'dep'
,
'append_parent_projects'
)
except
ValueError
:
return
self
.
defaults
[
'append_parent_projects'
]
==
'1'
return
self
.
defaults
[
'
dep'
][
'
append_parent_projects'
]
==
'1'
def
append_parent_contexts
(
self
):
try
:
return
self
.
cp
.
getboolean
(
'dep'
,
'append_parent_contexts'
)
except
ValueError
:
return
self
.
defaults
[
'append_parent_contexts'
]
==
'1'
return
self
.
defaults
[
'
dep'
][
'
append_parent_contexts'
]
==
'1'
def
_get_tag
(
self
,
p_tag
):
try
:
...
...
@@ -232,7 +257,7 @@ class _Config:
else
:
pri_colors_dict
=
_str_to_dict
(
pri_colors_str
)
except
ValueError
:
pri_colors_dict
=
_str_to_dict
(
self
.
defaults
[
'priority_colors'
])
pri_colors_dict
=
_str_to_dict
(
self
.
defaults
[
'
colorscheme'
][
'
priority_colors'
])
return
pri_colors_dict
...
...
@@ -240,31 +265,47 @@ class _Config:
try
:
return
self
.
cp
.
get
(
'colorscheme'
,
'project_color'
)
except
ValueError
:
return
int
(
self
.
defaults
[
'project_color'
])
return
int
(
self
.
defaults
[
'
colorscheme'
][
'
project_color'
])
def
context_color
(
self
):
try
:
return
self
.
cp
.
get
(
'colorscheme'
,
'context_color'
)
except
ValueError
:
return
int
(
self
.
defaults
[
'context_color'
])
return
int
(
self
.
defaults
[
'co
lorscheme'
][
'co
ntext_color'
])
def
metadata_color
(
self
):
try
:
return
self
.
cp
.
get
(
'colorscheme'
,
'metadata_color'
)
except
ValueError
:
return
int
(
self
.
defaults
[
'metadata_color'
])
return
int
(
self
.
defaults
[
'
colorscheme'
][
'
metadata_color'
])
def
link_color
(
self
):
try
:
return
self
.
cp
.
get
(
'colorscheme'
,
'link_color'
)
except
ValueError
:
return
int
(
self
.
defaults
[
'link_color'
])
return
int
(
self
.
defaults
[
'
colorscheme'
][
'
link_color'
])
def
auto_creation_date
(
self
):
try
:
return
self
.
cp
.
getboolean
(
'add'
,
'auto_creation_date'
)
except
ValueError
:
return
self
.
defaults
[
'auto_creation_date'
]
==
'1'
return
self
.
defaults
[
'add'
][
'auto_creation_date'
]
==
'1'
def
aliases
(
self
):
"""
Returns dict with aliases names as keys and pairs of actual
subcommand and alias args as values.
"""
aliases
=
self
.
cp
.
items
(
'aliases'
)
alias_dict
=
dict
()
for
alias
,
meaning
in
aliases
:
meaning
=
meaning
.
split
()
real_subcommand
=
meaning
[
0
]
alias_args
=
meaning
[
1
:]
alias_dict
[
alias
]
=
(
real_subcommand
,
alias_args
)
return
alias_dict
def
config
(
p_path
=
None
,
p_overrides
=
None
):
"""
...
...
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