Skip to content

Commit

Permalink
refactor: switch to rich for all printers
Browse files Browse the repository at this point in the history
  • Loading branch information
u8slvn committed Jul 15, 2022
1 parent 566c09b commit e61f269
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 127 deletions.
2 changes: 1 addition & 1 deletion hyperfocus/commands/new_day.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def execute(self) -> None:
printer.echo(
f"Unfinished task(s) from {formatter.date(date=self._previous_day.date)}:"
)
printer.tasks(tasks=unfinished_tasks, newline=True)
printer.tasks(tasks=unfinished_tasks)
if not printer.confirm(
f"Review {len(unfinished_tasks)} unfinished task(s)",
default=True,
Expand Down
4 changes: 2 additions & 2 deletions hyperfocus/commands/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def check_task_id_or_ask(
def ask_task_id(self, text: str, exclude: list[TaskStatus] | None = None) -> int:
self.show_tasks(newline=True, exclude=exclude)

return printer.ask(text, type=int)
return printer.ask_int(text)

def show_tasks(
self, exclude: list[TaskStatus] | None = None, newline=False
Expand All @@ -41,7 +41,7 @@ def show_tasks(
printer.echo("No tasks for today...")
raise HyperfocusExit()

printer.tasks(tasks=tasks, newline=newline)
printer.tasks(tasks=tasks)

def get_task(self, task_id: int) -> Task:
task = self._session.daily_tracker.get_task(task_id=task_id)
Expand Down
60 changes: 23 additions & 37 deletions hyperfocus/termui/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

import datetime
from enum import Enum, IntEnum, auto
from typing import List

import click
from tabulate import tabulate

from hyperfocus.database.models import Task, TaskStatus
from hyperfocus.termui import icons


class NotificationLevel(IntEnum):
Expand Down Expand Up @@ -41,58 +38,47 @@ def date(date: datetime.date) -> str:
return date.strftime("%a, %d %B %Y")


def task_status(status: TaskStatus):
symbol = "⬢"
color = {
TaskStatus.TODO: Colors.WHITE,
TaskStatus.BLOCKED: Colors.BRIGHT_YELLOW,
TaskStatus.DELETED: Colors.RED,
TaskStatus.DONE: Colors.GREEN,
}.get(status, Colors.BLACK)

return click.style(symbol, fg=color)


def prompt(text: str):
symbol = click.style("?", fg=Colors.BRIGHT_GREEN)
return f"{symbol} {text}"
return f"[chartreuse3]{icons.PROMPT}[/] {text}"


def task(task: Task, show_details: bool = False, show_prefix: bool = False) -> str:
empty_details = "No details provided ..."

title_style = {
TaskStatus.DELETED: {"fg": Colors.BRIGHT_BLACK},
TaskStatus.DONE: {"strikethrough": True},
}.get(task.status, {})
TaskStatus.DELETED: "[bright_black]{title}[/]",
TaskStatus.DONE: "[strike]{title}[/]",
}.get(task.status, "{title}")

title = click.style(task.title, **title_style) # type: ignore
details = "⊕" if task.details else "◌"
prefix = f"Task: #{task.id} " if show_prefix else ""
title = title_style.format(title=task.title)
prefix = f"Task: #{str(task.id)} " if show_prefix else ""

headline = f"{prefix}{task_status(task.status)} {title}"

if show_details:
return f"{headline}\n{task.details or empty_details}"

return f"{headline} {details}"
return headline


def tasks(tasks: List[Task], newline: bool = False) -> str:
headers = ["#", "tasks"]
suffix = "\n" if newline else ""
lines = [[t.id, task(t)] for t in tasks]
def task_status(status: TaskStatus):
color = {
TaskStatus.TODO: "bright_white",
TaskStatus.BLOCKED: "orange1",
TaskStatus.DELETED: "deep_pink2",
TaskStatus.DONE: "chartreuse3",
}.get(status, "bright_black")

return f"{tabulate(lines, headers)} {suffix}"
return f"[{color}]{icons.TASK_STATUS}[/]"


def notification(text: str, event: str, status: NotificationLevel) -> str:
symbol, color = {
NotificationLevel.SUCCESS: ("", Colors.BRIGHT_GREEN),
NotificationLevel.INFO: ("", Colors.BRIGHT_CYAN),
NotificationLevel.WARNING: ("", Colors.BRIGHT_YELLOW),
NotificationLevel.ERROR: ("", Colors.BRIGHT_RED),
}.get(status, (">", Colors.BRIGHT_WHITE))
prefix = click.style(f"{symbol}({event})", fg=color)
color, icon = {
NotificationLevel.SUCCESS: ("chartreuse3", icons.NOTIFICATION_SUCCESS),
NotificationLevel.INFO: ("steel_blue1", icons.NOTIFICATION_INFO),
NotificationLevel.WARNING: ("orange1", icons.NOTIFICATION_WARNING),
NotificationLevel.ERROR: ("deep_pink2", icons.NOTIFICATION_ERROR),
}.get(status, ("bright_white", Colors.BRIGHT_WHITE))
prefix = f"[{color}]{icon}({event})[/]"

return f"{prefix} {text}"
13 changes: 13 additions & 0 deletions hyperfocus/termui/icons.py
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
NEW_DAY = "✨"

DETAILS = "■"
NO_DETAILS = "□"

TASK_STATUS = "⬢"

NOTIFICATION_SUCCESS = "✔"
NOTIFICATION_INFO = "ℹ"
NOTIFICATION_WARNING = "▼"
NOTIFICATION_ERROR = "✘"
NOTIFICATION = ">"

PROMPT = "?"
41 changes: 30 additions & 11 deletions hyperfocus/termui/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

from typing import Any, List

import click
import rich
from rich.box import HORIZONTALS
from rich.console import Console
from rich.prompt import Confirm, IntPrompt, Prompt
from rich.table import Table

from hyperfocus.database.models import Task
from hyperfocus.termui import formatter, icons


console = Console(highlight=False)


def echo(text: str):
click.secho(text)
console.print(text)


def task(task: Task, show_details: bool = False, show_prefix: bool = False):
Expand All @@ -20,9 +26,17 @@ def task(task: Task, show_details: bool = False, show_prefix: bool = False):
echo(text=formatted_task)


def tasks(tasks: List[Task], newline: bool = False):
formatted_tasks = formatter.tasks(tasks=tasks, newline=newline)
echo(text=formatted_tasks)
def tasks(tasks: List[Task]):
table = Table(
box=HORIZONTALS,
)
table.add_column("#", justify="right")
table.add_column("tasks")
table.add_column("details", justify="center")
for task in tasks:
details = icons.DETAILS if task.details else icons.NO_DETAILS
table.add_row(str(task.id), formatter.task(task), details)
rich.print(table, end="")


def notification(text: str, event: str, status: formatter.NotificationLevel):
Expand All @@ -49,18 +63,23 @@ def error(text: str, event: str):


def ask(text: str, **kwargs) -> Any:
formatted_prompt = formatter.prompt(text=text)
return click.prompt(text=formatted_prompt, **kwargs)
text = formatter.prompt(text)
return Prompt.ask(text, **kwargs)


def ask_int(text: str, **kwargs) -> Any:
text = formatter.prompt(text)
return IntPrompt.ask(text, **kwargs)


def confirm(text: str, **kwargs) -> Any:
formatted_prompt = formatter.prompt(text=text)
return click.confirm(text=formatted_prompt, **kwargs)
text = formatter.prompt(text)
return Confirm.ask(text, **kwargs)


def banner(text: str) -> None:
rich.print(f"[italic khaki1]{text}[/]")
console.print(f"[italic khaki1]{text}[/]")


def new_day(text: str) -> None:
rich.print(f"{icons.NEW_DAY} [deep_sky_blue1]{text}[/]")
console.print(f"{icons.NEW_DAY} [steel_blue1]{text}[/]")
14 changes: 1 addition & 13 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ include = [
[tool.poetry.dependencies]
python = "^3.8"
peewee = "^3.15.1"
tabulate = "^0.8.9"
click = "^8.1.3"
pyperclip = "^1.8.2"
rich = "^12.5.1"
Expand Down
24 changes: 12 additions & 12 deletions tests/commands/test_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def test_show_tasks(self, session, printer):
TaskCmd(session).show_tasks()

session._daily_tracker.get_tasks.assert_called_once_with(exclude=[])
printer.tasks.assert_called_with(tasks=tasks, newline=False)
printer.tasks.assert_called_with(tasks=tasks)

def test_show_tasks_fails(self, session, printer):
session._daily_tracker.get_tasks.return_value = []
Expand All @@ -65,25 +65,25 @@ def test_ask_task_id(self, session, printer):

TaskCmd(session).ask_task_id("Test")

printer.tasks.assert_called_with(tasks=tasks, newline=True)
printer.ask.assert_called_once_with("Test", type=int)
printer.tasks.assert_called_with(tasks=tasks)
printer.ask_int.assert_called_once_with("Test")

def test_check_task_id_or_ask(self, mocker, session, printer):
tasks = [Task("foo"), Task("bar")]
session._daily_tracker.get_tasks.return_value = tasks
printer.ask.return_value = mocker.sentinel.task_id
printer.ask_int.return_value = mocker.sentinel.task_id

task_id = TaskCmd(session).check_task_id_or_ask(None, "Test")

assert task_id == mocker.sentinel.task_id
printer.tasks.assert_called_with(tasks=tasks, newline=True)
printer.ask.assert_called_once_with("Test", type=int)
printer.tasks.assert_called_with(tasks=tasks)
printer.ask_int.assert_called_once_with("Test")

def test_check_task_id_or_ask_with_id(self, session, printer):
task_id = TaskCmd(session).check_task_id_or_ask(1, "Test")

assert task_id == 1
printer.ask.assert_not_called()
printer.ask_int.assert_not_called()


class TestAddTaskCmd:
Expand Down Expand Up @@ -141,7 +141,7 @@ def test_update_tasks_cmd_ask_for_id_if_none(self, session, printer, formatter):
)

session._daily_tracker.get_tasks(exclude=[TaskStatus.DONE])
printer.ask.assert_called_once_with("Test", type=int)
printer.ask_int.assert_called_once_with("Test")

def test_update_tasks_cmd_with_same_status(self, session, printer, formatter):
task = Task(id=1, title="foo", details="bar", status=TaskStatus.DELETED)
Expand All @@ -166,7 +166,7 @@ def test_list_task_cmd(self, session, printer):
ListTaskCmd(session).execute()

session._daily_tracker.get_tasks.assert_called_once_with(exclude=[])
printer.tasks.assert_called_with(tasks=tasks, newline=False)
printer.tasks.assert_called_with(tasks=tasks)


class TestShowTaskCmd:
Expand All @@ -175,7 +175,7 @@ def test_show_task_cmd(self, session, printer):
session._daily_tracker.get_task.return_value = task
ShowTaskCmd(session).execute(task_id=1)

printer.ask.assert_not_called()
printer.ask_int.assert_not_called()
printer.task.assert_called_once_with(
task=task, show_details=True, show_prefix=True
)
Expand All @@ -186,7 +186,7 @@ def test_show_task_cmd_ask_for_id_if_none(self, session, printer):

ShowTaskCmd(session).execute(task_id=None)

printer.ask.assert_called_once_with("Show task", type=int)
printer.ask_int.assert_called_once_with("Show task")


class TestCopyCmd:
Expand Down Expand Up @@ -219,4 +219,4 @@ def test_copy_task_details_cmd_ask_for_id_if_none(

CopyTaskDetailsCmd(session).execute(task_id=None)

printer.ask.assert_called_once_with("Copy task details", type=int)
printer.ask_int.assert_called_once_with("Copy task details")
Loading

0 comments on commit e61f269

Please sign in to comment.