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
306d7497
Commit
306d7497
authored
Dec 03, 2015
by
Bram Schoenmakers
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'colorblock' into dot/colorblock
Conflicts: topydo/lib/ListFormat.py
parents
0284842e
4352ece9
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
280 additions
and
142 deletions
+280
-142
test/test_colors.py
test/test_colors.py
+36
-36
test/test_list_format.py
test/test_list_format.py
+23
-0
topydo/lib/Colorblock.py
topydo/lib/Colorblock.py
+101
-0
topydo/lib/Colors.py
topydo/lib/Colors.py
+110
-95
topydo/lib/ListFormat.py
topydo/lib/ListFormat.py
+9
-4
topydo/lib/prettyprinters/Colors.py
topydo/lib/prettyprinters/Colors.py
+1
-7
No files found.
test/test_colors.py
View file @
306d7497
...
...
@@ -122,49 +122,49 @@ class ColorsTest(TopydoTest):
def
test_priority_color1
(
self
):
config
(
"test/data/ColorsTest1.conf"
)
color
=
Colors
().
get_priority_c
olors
()
color
s
=
C
olors
()
self
.
assertEqual
(
color
[
'A'
]
,
'
\
033
[0;38;5;1m'
)
self
.
assertEqual
(
color
[
'B'
]
,
'
\
033
[0;38;5;2m'
)
self
.
assertEqual
(
color
[
'C'
]
,
'
\
033
[0;38;5;3m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'A'
)
,
'
\
033
[0;38;5;1m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'B'
)
,
'
\
033
[0;38;5;2m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'C'
)
,
'
\
033
[0;38;5;3m'
)
def
test_priority_color2
(
self
):
config
(
"test/data/ColorsTest2.conf"
)
color
=
Colors
().
get_priority_c
olors
()
color
s
=
C
olors
()
self
.
assertEqual
(
color
[
'A'
]
,
'
\
033
[0;35m'
)
self
.
assertEqual
(
color
[
'B'
]
,
'
\
033
[0;1;36m'
)
self
.
assertEqual
(
color
[
'C'
]
,
'
\
033
[0;37m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'A'
)
,
'
\
033
[0;35m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'B'
)
,
'
\
033
[0;1;36m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'C'
)
,
'
\
033
[0;37m'
)
def
test_priority_color3
(
self
):
config
(
"test/data/ColorsTest3.conf"
)
color
=
Colors
().
get_priority_c
olors
()
color
s
=
C
olors
()
self
.
assertEqual
(
color
[
'A'
]
,
'
\
033
[0;35m'
)
self
.
assertEqual
(
color
[
'B'
]
,
'
\
033
[0;1;36m'
)
self
.
assertEqual
(
color
[
'Z'
]
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
color
[
'D'
]
,
'
\
033
[0;31m'
)
self
.
assertEqual
(
color
[
'C'
]
,
'
\
033
[0;38;5;7m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'A'
)
,
'
\
033
[0;35m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'B'
)
,
'
\
033
[0;1;36m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'Z'
)
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'D'
)
,
'
\
033
[0;31m'
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'C'
)
,
'
\
033
[0;38;5;7m'
)
def
test_priority_color4
(
self
):
config
(
"test/data/ColorsTest4.conf"
)
color
=
Colors
().
get_priority_c
olors
()
color
s
=
C
olors
()
self
.
assertEqual
(
color
[
'A'
]
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
color
[
'B'
]
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
color
[
'C'
]
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'A'
)
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'B'
)
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
color
s
.
get_priority_color
(
'C'
)
,
NEUTRAL_COLOR
)
def
test_empty_color_values
(
self
):
config
(
"test/data/ColorsTest5.conf"
)
pri_color
=
Colors
().
get_priority_c
olors
()
project_color
=
Colors
()
.
get_project_color
()
context_color
=
Colors
()
.
get_context_color
()
link_color
=
Colors
()
.
get_link_color
()
metadata_color
=
Colors
()
.
get_metadata_color
()
self
.
assertEqual
(
pri_color
[
'A'
]
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
pri_color
[
'B'
]
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
pri_color
[
'C'
]
,
NEUTRAL_COLOR
)
colors
=
C
olors
()
project_color
=
colors
.
get_project_color
()
context_color
=
colors
.
get_context_color
()
link_color
=
colors
.
get_link_color
()
metadata_color
=
colors
.
get_metadata_color
()
self
.
assertEqual
(
colors
.
get_priority_color
(
'A'
)
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
colors
.
get_priority_color
(
'B'
)
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
colors
.
get_priority_color
(
'C'
)
,
NEUTRAL_COLOR
)
self
.
assertEqual
(
project_color
,
''
)
self
.
assertEqual
(
context_color
,
''
)
self
.
assertEqual
(
link_color
,
''
)
...
...
@@ -172,15 +172,15 @@ class ColorsTest(TopydoTest):
def
test_empty_colorscheme
(
self
):
config
(
"test/data/config1"
)
pri_color
=
Colors
().
get_priority_c
olors
()
project_color
=
Colors
()
.
get_project_color
()
context_color
=
Colors
()
.
get_context_color
()
link_color
=
Colors
()
.
get_link_color
()
metadata_color
=
Colors
()
.
get_metadata_color
()
self
.
assertEqual
(
pri_color
[
'A'
]
,
'
\
033
[0;36m'
)
self
.
assertEqual
(
pri_color
[
'B'
]
,
'
\
033
[0;33m'
)
self
.
assertEqual
(
pri_color
[
'C'
]
,
'
\
033
[0;34m'
)
colors
=
C
olors
()
project_color
=
colors
.
get_project_color
()
context_color
=
colors
.
get_context_color
()
link_color
=
colors
.
get_link_color
()
metadata_color
=
colors
.
get_metadata_color
()
self
.
assertEqual
(
colors
.
get_priority_color
(
'A'
)
,
'
\
033
[0;36m'
)
self
.
assertEqual
(
colors
.
get_priority_color
(
'B'
)
,
'
\
033
[0;33m'
)
self
.
assertEqual
(
colors
.
get_priority_color
(
'C'
)
,
'
\
033
[0;34m'
)
self
.
assertEqual
(
project_color
,
'
\
033
[1;31m'
)
self
.
assertEqual
(
context_color
,
'
\
033
[1;35m'
)
self
.
assertEqual
(
link_color
,
'
\
033
[4;36m'
)
...
...
test/test_list_format.py
View file @
306d7497
...
...
@@ -648,5 +648,28 @@ Z Z
"""
self.assertEqual(self.output, result)
@mock.patch('
topydo
.
lib
.
ListFormat
.
get_terminal_size
')
def test_list_format43(self, mock_terminal_size):
""" Colorblocks should not affect truncating or right_alignment. """
self.maxDiff = None
mock_terminal_size.return_value = self.terminal_size(100, 25)
config(p_overrides={('
ls
', '
list_format
'): '
%
Z
|%
I
|
%
x
%
p
%
S
%
k
\\
t
%
{(}
h
{)}
'})
command1 = ListCommand(["-x"], self.todolist, self.out, self.error)
command1.execute()
config(p_overrides={('
ls
', '
list_format
'): '
%
z
|%
I
|
%
x
%
p
%
S
%
k
\\
t
%
{(}
h
{)}
'})
command2 = ListCommand(["-x"], self.todolist, self.out, self.error)
command2.execute()
result = u""" | 1| D Bar @Context1 +Project2 (due a month ago, started a month ago)
| 2| Z Lorem ipsum dolorem sit amet. Red @fox +jumpe... lazy:bar (due in 2 days, starts in a day)
| 3| C Foo @Context2 Not@Context +Project1 Not+Project
| 4| C Baz @Context1 +Project1 key:value
| 5| Drink beer @ home
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result * 2)
if __name__ == '
__main__
':
unittest.main()
topydo/lib/Colorblock.py
0 → 100644
View file @
306d7497
# 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
re
from
topydo.lib.Colors
import
int_to_ansi
,
Colors
from
topydo.lib.Recurrence
import
relative_date_to_date
COLOR16_RANGE
=
[
10
,
# light green
2
,
# green
3
,
# yellow
1
,
# red
]
# https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
# a gradient from green to yellow to red
COLOR256_RANGE
=
\
[
22
,
28
,
34
,
40
,
46
,
82
,
118
,
154
,
190
,
226
,
220
,
214
,
208
,
202
,
196
]
def
progress_color_code
(
p_todo
,
p_safe
=
True
):
def
get_length
():
"""
Returns the length of the p_todo item in days, based on the recurrence
period + due date, or the start/due date.
"""
result
=
0
def
diff_days
(
p_start
,
p_end
):
if
p_start
<
p_end
:
diff
=
p_end
-
p_start
return
diff
.
days
return
0
if
p_todo
.
has_tag
(
'rec'
)
and
p_todo
.
due_date
():
# add negation, offset is based on due date
recurrence_pattern
=
p_todo
.
tag_value
(
'rec'
)
neg_recurrence_pattern
=
re
.
sub
(
'^
\
+?
'
, '
-
', recurrence_pattern)
start = relative_date_to_date(
neg_recurrence_pattern, p_todo.due_date())
due = p_todo.due_date()
result = diff_days(start, due)
else:
result = p_todo.length()
return result
def get_progress():
"""
Returns a value from 0 to 1 where we are today in a date range. Returns
a value >1 when a todo item is overdue.
"""
if p_todo.is_overdue():
return 1.1
elif p_todo.due_date():
days_till_due = p_todo.days_till_due()
length = get_length() or 14
return max((length - days_till_due), 0) / length
else:
return 0
def progress_to_color():
color_range = COLOR16_RANGE if p_safe else COLOR256_RANGE
progress = get_progress()
# TODO: remove linear scale to exponential scale
if progress > 1:
# overdue, return the last color
return color_range[-1]
else:
# not overdue, calculate position over color range excl. due date
# color
pos = round(progress * (len(color_range) - 2))
return color_range[pos]
return progress_to_color()
def color_block(p_todo, p_safe=True):
color_code = progress_color_code(p_todo, p_safe)
ansi_code = int_to_ansi(color_code, p_safe=p_safe, p_background=color_code)
priority_color = Colors().get_priority_color(p_todo.priority())
return '
{}
{}
'.format(ansi_code, priority_color)
topydo/lib/Colors.py
View file @
306d7497
...
...
@@ -20,113 +20,128 @@ from topydo.lib.Config import config
NEUTRAL_COLOR
=
'
\
033
[0m'
def
int_to_ansi
(
p_int
,
p_decorator
=
'normal'
,
p_safe
=
True
,
p_background
=
''
):
"""
Returns ansi code for color based on xterm color id (0-255) and
decoration, where decoration can be one of: normal, bold, faint,
italic, or underline. When p_safe is True, resulting ansi code is
constructed in most compatible way, but with support for only base 16
colors.
"""
decoration_dict
=
{
'normal'
:
'0'
,
'bold'
:
'1'
,
'faint'
:
'2'
,
'italic'
:
'3'
,
'underline'
:
'4'
}
decoration
=
decoration_dict
[
p_decorator
]
try
:
if
p_safe
:
if
p_background
:
p_background
=
';4{}'
.
format
(
p_background
)
if
8
>
int
(
p_int
)
>=
0
:
return
'
\
033
[{};3{}{}m'
.
format
(
decoration
,
str
(
p_int
),
p_background
)
elif
16
>
int
(
p_int
):
p_int
=
int
(
p_int
)
-
8
return
'
\
033
[{};1;3{}{}m'
.
format
(
decoration
,
str
(
p_int
),
p_background
)
if
256
>
int
(
p_int
)
>=
0
:
if
p_background
:
p_background
=
';48;5;{}'
.
format
(
str
(
p_int
))
return
'
\
033
[{};38;5;{}{}m'
.
format
(
decoration
,
str
(
p_int
),
p_background
)
else
:
return
NEUTRAL_COLOR
except
ValueError
:
return
None
def
_name_to_int
(
p_color_name
):
""" Returns xterm color id from color name. """
color_names_dict
=
{
'black'
:
0
,
'red'
:
1
,
'green'
:
2
,
'yellow'
:
3
,
'blue'
:
4
,
'magenta'
:
5
,
'cyan'
:
6
,
'gray'
:
7
,
'darkgray'
:
8
,
'light-red'
:
9
,
'light-green'
:
10
,
'light-yellow'
:
11
,
'light-blue'
:
12
,
'light-magenta'
:
13
,
'light-cyan'
:
14
,
'white'
:
15
,
}
try
:
return
color_names_dict
[
p_color_name
]
except
KeyError
:
return
404
def
_name_to_ansi
(
p_color_name
,
p_decorator
):
""" Returns ansi color code from color name. """
number
=
_name_to_int
(
p_color_name
)
return
int_to_ansi
(
number
,
p_decorator
)
def
_get_ansi
(
p_color
,
p_decorator
):
""" Returns ansi color code from color name or xterm color id. """
if
p_color
==
''
:
ansi
=
''
else
:
ansi
=
int_to_ansi
(
p_color
,
p_decorator
,
False
)
if
not
ansi
:
ansi
=
_name_to_ansi
(
p_color
,
p_decorator
)
return
ansi
def
_get_priority_colors
():
pri_ansi_colors
=
dict
()
pri_colors
=
config
().
priority_colors
()
for
pri
in
pri_colors
:
color
=
_get_ansi
(
pri_colors
[
pri
],
'normal'
)
if
color
==
''
:
color
=
NEUTRAL_COLOR
pri_ansi_colors
[
pri
]
=
color
return
pri_ansi_colors
class
Colors
(
object
):
def
__init__
(
self
):
self
.
priority_colors
=
config
().
priority_colors
()
self
.
priority_colors
=
_get_
priority_colors
()
self
.
project_color
=
config
().
project_color
()
self
.
context_color
=
config
().
context_color
()
self
.
metadata_color
=
config
().
metadata_color
()
self
.
link_color
=
config
().
link_color
()
def
_int_to_ansi
(
self
,
p_int
,
p_decorator
=
'normal'
,
p_safe
=
True
):
"""
Returns ansi code for color based on xterm color id (0-255) and
decoration, where decoration can be one of: normal, bold, faint,
italic, or underline. When p_safe is True, resulting ansi code is
constructed in most compatible way, but with support for only base 16
colors.
"""
decoration_dict
=
{
'normal'
:
'0'
,
'bold'
:
'1'
,
'faint'
:
'2'
,
'italic'
:
'3'
,
'underline'
:
'4'
}
decoration
=
decoration_dict
[
p_decorator
]
try
:
if
p_safe
:
if
8
>
int
(
p_int
)
>=
0
:
return
'
\
033
[{};3{}m'
.
format
(
decoration
,
str
(
p_int
))
elif
16
>
int
(
p_int
):
p_int
=
int
(
p_int
)
-
8
return
'
\
033
[{};1;3{}m'
.
format
(
decoration
,
str
(
p_int
))
if
256
>
int
(
p_int
)
>=
0
:
return
'
\
033
[{};38;5;{}m'
.
format
(
decoration
,
str
(
p_int
))
else
:
return
NEUTRAL_COLOR
except
ValueError
:
return
None
def
_name_to_int
(
self
,
p_color_name
):
""" Returns xterm color id from color name. """
color_names_dict
=
{
'black'
:
0
,
'red'
:
1
,
'green'
:
2
,
'yellow'
:
3
,
'blue'
:
4
,
'magenta'
:
5
,
'cyan'
:
6
,
'gray'
:
7
,
'darkgray'
:
8
,
'light-red'
:
9
,
'light-green'
:
10
,
'light-yellow'
:
11
,
'light-blue'
:
12
,
'light-magenta'
:
13
,
'light-cyan'
:
14
,
'white'
:
15
,
}
try
:
return
color_names_dict
[
p_color_name
]
except
KeyError
:
return
404
def
_name_to_ansi
(
self
,
p_color_name
,
p_decorator
):
""" Returns ansi color code from color name. """
number
=
self
.
_name_to_int
(
p_color_name
)
return
self
.
_int_to_ansi
(
number
,
p_decorator
)
def
_get_ansi
(
self
,
p_color
,
p_decorator
):
""" Returns ansi color code from color name or xterm color id. """
if
p_color
==
''
:
ansi
=
''
else
:
ansi
=
self
.
_int_to_ansi
(
p_color
,
p_decorator
,
False
)
if
not
ansi
:
ansi
=
self
.
_name_to_ansi
(
p_color
,
p_decorator
)
return
ansi
def
get_priority_colors
(
self
):
pri_ansi_colors
=
dict
()
for
pri
in
self
.
priority_colors
:
color
=
self
.
_get_ansi
(
self
.
priority_colors
[
pri
],
'normal'
)
if
color
==
''
:
color
=
NEUTRAL_COLOR
pri_ansi_colors
[
pri
]
=
color
return
pri_ansi_colors
def
get_project_color
(
self
):
return
self
.
_get_ansi
(
self
.
project_color
,
'bold'
)
return
_get_ansi
(
self
.
project_color
,
'bold'
)
def
get_context_color
(
self
):
return
self
.
_get_ansi
(
self
.
context_color
,
'bold'
)
return
_get_ansi
(
self
.
context_color
,
'bold'
)
def
get_metadata_color
(
self
):
return
self
.
_get_ansi
(
self
.
metadata_color
,
'bold'
)
return
_get_ansi
(
self
.
metadata_color
,
'bold'
)
def
get_link_color
(
self
):
return
self
.
_get_ansi
(
self
.
link_color
,
'underline'
)
return
_get_ansi
(
self
.
link_color
,
'underline'
)
def
get_priority_color
(
self
,
p_priority
):
try
:
priority_color
=
self
.
priority_colors
[
p_priority
]
except
KeyError
:
priority_color
=
NEUTRAL_COLOR
return
priority_color
topydo/lib/ListFormat.py
View file @
306d7497
...
...
@@ -20,7 +20,8 @@ import arrow
import
re
from
topydo.lib.Config
import
config
from
topydo.lib.Utils
import
get_terminal_size
,
humanize_date
from
topydo.lib.Colorblock
import
color_block
from
topydo.lib.Utils
import
get_terminal_size
,
escape_ansi
,
humanize_date
MAIN_PATTERN
=
(
r'^({{(?P<before>.+?)}})?'
r'(?P<placeholder>{ph}|\
[{ph}
\])'
...
...
@@ -101,7 +102,7 @@ def _truncate(p_str, p_repl):
Place of the truncation is calculated depending on p_max_width.
"""
# 4 is for '...' and an extra space at the end
text_lim
=
_columns
()
-
len
(
p_str
)
-
4
text_lim
=
_columns
()
-
len
(
escape_ansi
(
p_str
)
)
-
4
truncated_str
=
re
.
sub
(
re
.
escape
(
p_repl
),
p_repl
[:
text_lim
]
+
'...'
,
p_str
)
return
truncated_str
...
...
@@ -113,7 +114,7 @@ def _right_align(p_str):
Right alignment is done using proper number of spaces calculated from
'line_width' attribute.
"""
to_fill
=
_columns
()
-
len
(
p_str
)
to_fill
=
_columns
()
-
len
(
escape_ansi
(
p_str
)
)
if
to_fill
>
0
:
p_str
=
re
.
sub
(
'
\
t
'
,
' '
*
to_fill
,
p_str
)
...
...
@@ -183,6 +184,10 @@ class ListFormatParser(object):
# relative completion date
'X'
:
lambda
t
:
'x '
+
humanize_date
(
t
.
completion_date
())
if
t
.
is_completed
()
else
''
,
'z'
:
lambda
t
:
color_block
(
t
)
if
config
().
colors
()
else
' '
,
'Z'
:
lambda
t
:
color_block
(
t
,
p_safe
=
False
)
if
config
().
colors
()
else
' '
,
}
self
.
format_list
=
self
.
_preprocess_format
()
...
...
@@ -259,7 +264,7 @@ class ListFormatParser(object):
parsed_str = _unescape_percent_sign(''.join(parsed_list))
parsed_str = _remove_redundant_spaces(parsed_str)
if self.one_line and len(
parsed_str
) >= _columns():
if self.one_line and len(
escape_ansi(parsed_str)
) >= _columns():
parsed_str = _truncate(parsed_str, repl_trunc)
if re.search('
.
*
\
t
', parsed_str):
...
...
topydo/lib/prettyprinters/Colors.py
View file @
306d7497
...
...
@@ -34,18 +34,12 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter):
""" Applies the colors. """
if
config
().
colors
():
colorscheme
=
Colors
()
priority_color
s
=
colorscheme
.
get_priority_colors
(
)
priority_color
=
colorscheme
.
get_priority_color
(
p_todo
.
priority
()
)
project_color
=
colorscheme
.
get_project_color
()
context_color
=
colorscheme
.
get_context_color
()
metadata_color
=
colorscheme
.
get_metadata_color
()
link_color
=
colorscheme
.
get_link_color
()
priority_color
=
NEUTRAL_COLOR
try
:
priority_color
=
priority_colors
[
p_todo
.
priority
()]
except
KeyError
:
pass
# color projects / contexts
p_todo_str
=
re
.
sub
(
r'\
B(
\+|@)(\
S*
\w)'
,
...
...
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