Skip to content

Commit

Permalink
Use a fluid layout for TODOs
Browse files Browse the repository at this point in the history
Stop drawing a table as done previously, and use a fluid layout. This
reduces internal whitespace, which makes the ids for todos easier to
recognise in may scenarios.

Also makes the description and location blocks easier to copy-paste
verbatim without additional indentation.

Due to dropping the table rendering, this should slightly improve
performance, though that's not a main goal here.
  • Loading branch information
Hugo Osvaldo Barrera committed Dec 12, 2021
1 parent c2962a0 commit 586c0c2
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 71 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Changelog
This file contains a brief summary of new features and dependency changes or
releases, in reverse chronological order.

v4.1.0
------

* The "table" layout has been dropped in favour of a simpler, fluid layout. As
such, ``tabulate`` is not longer a required dependency.

v4.0.1
------

Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"parsedatetime",
"python-dateutil",
"pyxdg",
"tabulate",
"urwid",
],
long_description=open("README.rst").read(),
Expand Down
18 changes: 10 additions & 8 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,12 +401,14 @@ def test_color_due_dates(tmpdir, runner, create, hours):
assert not result.exception
due_str = due.strftime("%Y-%m-%d")
if hours == 72:
assert result.output == f"1 [ ] {due_str} aaa @default\x1b[0m\n"
expected = (
f"[ ] 1 \x1b[35m\x1b[0m \x1b[37m{due_str}\x1b[0m aaa @default\x1b[0m\n"
)
else:
assert (
result.output
== f"1 [ ] \x1b[31m{due_str}\x1b[0m aaa @default\x1b[0m\n"
expected = (
f"[ ] 1 \x1b[35m\x1b[0m \x1b[31m{due_str}\x1b[0m aaa @default\x1b[0m\n"
)
assert result.output == expected


def test_color_flag(runner, todo_factory):
Expand All @@ -415,16 +417,16 @@ def test_color_flag(runner, todo_factory):
result = runner.invoke(cli, ["--color", "always"], color=True)
assert (
result.output.strip()
== "1 [ ] \x1b[31m2007-03-22\x1b[0m YARR! @default\x1b[0m"
== "[ ] 1 \x1b[35m\x1b[0m \x1b[31m2007-03-22\x1b[0m YARR! @default\x1b[0m"
)
result = runner.invoke(cli, color=True)
assert (
result.output.strip()
== "1 [ ] \x1b[31m2007-03-22\x1b[0m YARR! @default\x1b[0m"
== "[ ] 1 \x1b[35m\x1b[0m \x1b[31m2007-03-22\x1b[0m YARR! @default\x1b[0m"
)

result = runner.invoke(cli, ["--color", "never"], color=True)
assert result.output.strip() == "1 [ ] 2007-03-22 YARR! @default"
assert result.output.strip() == "[ ] 1 2007-03-22 YARR! @default"


def test_flush(tmpdir, runner, create, todo_factory, todos):
Expand Down Expand Up @@ -740,7 +742,7 @@ def test_cancel(runner, todo_factory, todos):
def test_id_printed_for_new(runner):
result = runner.invoke(cli, ["new", "-l", "default", "show me an id"])
assert not result.exception
assert result.output.strip().startswith("1")
assert result.output.strip().startswith("[ ] 1")


def test_repl(runner):
Expand Down
22 changes: 13 additions & 9 deletions tests/test_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,19 @@ def test_detailed_format(runner, todo_factory):

# TODO:use formatter instead of runner?
result = runner.invoke(cli, ["show", "1"])
expected = (
"1 [ ] YARR! @default\n\n"
"Description Test detailed formatting\n"
" This includes multiline descriptions\n"
" Blah!\n"
"Location Over the hills, and far away"
)
expected = [
"[ ] 1 (no due date) YARR! @default",
"",
"Description:",
"Test detailed formatting",
"This includes multiline descriptions",
"Blah!",
"",
"Location: Over the hills, and far away",
]

assert not result.exception
assert result.output.strip() == expected
assert result.output.strip().splitlines() == expected


def test_parse_time(default_formatter):
Expand Down Expand Up @@ -168,7 +172,7 @@ def test_format_multiple_with_list(default_formatter, todo_factory):
assert todo.list
assert (
default_formatter.compact_multiple([todo])
== "1 [ ] YARR! @default\x1b[0m"
== "[ ] 1 \x1b[35m\x1b[0m \x1b[37m(no due date)\x1b[0m YARR! @default\x1b[0m"
)


Expand Down
94 changes: 41 additions & 53 deletions todoman/formatters.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import json
from datetime import date
from datetime import datetime
from datetime import timedelta
from time import mktime
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

import click
import humanize
import parsedatetime
import pytz
from dateutil.tz import tzlocal
from tabulate import tabulate

from todoman.model import Todo
from todoman.model import TodoList
Expand Down Expand Up @@ -63,18 +61,36 @@ def compact(self, todo: Todo) -> str:
return self.compact_multiple([todo])

def compact_multiple(self, todos: Iterable[Todo], hide_list=False) -> str:
# TODO: format lines fuidly and drop the table
# it can end up being more readable when too many columns are empty.
# show dates that are in the future in yellow (in 24hs) or grey (future)
table = []
for todo in todos:
completed = "X" if todo.is_completed else " "
percent = todo.percent_complete or ""
if percent:
percent = f" ({percent}%)"
priority = self.format_priority_compact(todo.priority)
priority = click.style(
self.format_priority_compact(todo.priority),
fg="magenta",
)

due = self.format_datetime(todo.due)
due = self.format_datetime(todo.due) or "(no due date)"
now = self.now if isinstance(todo.due, datetime) else self.now.date()
if todo.due and todo.due <= now and not todo.is_completed:
due = click.style(str(due), fg="red")

due_colour = None
if todo.due:
if todo.due <= now and not todo.is_completed:
due_colour = "red"
elif todo.due >= now + timedelta(hours=24):
due_colour = "white"
elif todo.due >= now:
due_colour = "yellow"
else:
due_colour = "white"

if due_colour:
due = click.style(str(due), fg=due_colour)

recurring = "⟳" if todo.is_recurring else ""

Expand All @@ -93,64 +109,36 @@ def compact_multiple(self, todos: Iterable[Todo], hide_list=False) -> str:
percent,
)

# TODO: add spaces on the left based on max todos"

# FIXME: double space when no priority
table.append(
[
todo.id,
f"[{completed}]",
priority,
f"{due} {recurring}",
summary,
]
f"[{completed}] {todo.id} {priority} {due} {recurring}{summary}"
)

return tabulate(table, tablefmt="plain")
return "\n".join(table)

def _columnize_text(
self,
label: str,
text: Optional[str],
) -> List[Tuple[Optional[str], str]]:
"""Display text, split text by line-endings, on multiple colums.
Do nothing if text is empty or None.
"""
lines = text.splitlines() if text else None
def _format_multiline(self, title: str, value: str) -> str:
formatted_title = click.style(title, fg="white")

return self._columnize_list(label, lines)

def _columnize_list(
self,
label: str,
lst: Optional[List[str]],
) -> List[Tuple[Optional[str], str]]:
"""Display list on multiple columns.
Do nothing if list is empty or None.
"""

rows: List[Tuple[Optional[str], str]] = []

if lst:
rows.append((label, lst[0]))
for line in lst[1:]:
rows.append((None, line))

return rows
if value.strip().count("\n") == 0:
return f"\n\n{formatted_title}: {value}"
else:
return f"\n\n{formatted_title}:\n{value}"

def detailed(self, todo: Todo) -> str:
"""Returns a detailed representation of a task.
:param todo: The todo component.
"""
extra_rows = []
extra_rows += self._columnize_text("Description", todo.description)
extra_rows += self._columnize_text("Location", todo.location)
extra_lines = []
if todo.description:
extra_lines.append(self._format_multiline("Description", todo.description))

if extra_rows:
return "{}\n\n{}".format(
self.compact(todo), tabulate(extra_rows, tablefmt="plain")
)
return self.compact(todo)
if todo.location:
extra_lines.append(self._format_multiline("Location", todo.location))

return f"{self.compact(todo)}{''.join(extra_lines)}"

def format_datetime(self, dt: Optional[date]) -> Union[str, int, None]:
if not dt:
Expand Down

0 comments on commit 586c0c2

Please sign in to comment.