Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,57 @@ def fake_update_todo(index: int, task: str | None, category: str | None) -> None
assert captured["task"] == "New"
# and treats the remainder as the category
assert captured["category"] == "text"


def test_add_task_parses_quoted_category(monkeypatch) -> None:
"""`task add "<task>" "<category>"` stores the supplied category (gh-46)."""
from trushell.commands.tasks import add_task

captured: dict[str, Any] = {}

def fake_insert_todo(todo: Any) -> None:
captured["task"] = todo.task
captured["category"] = todo.category

monkeypatch.setattr("trushell.commands.tasks.insert_todo", fake_insert_todo)

add_task('"Buy milk" "Shopping"')

assert captured["task"] == "Buy milk"
assert captured["category"] == "Shopping"


def test_add_task_defaults_category_when_omitted(monkeypatch) -> None:
"""A quoted task with no category falls back to the "General" category."""
from trushell.commands.tasks import add_task

captured: dict[str, Any] = {}

def fake_insert_todo(todo: Any) -> None:
captured["task"] = todo.task
captured["category"] = todo.category

monkeypatch.setattr("trushell.commands.tasks.insert_todo", fake_insert_todo)

add_task('"Pay rent"')

assert captured["task"] == "Pay rent"
assert captured["category"] == "General"


def test_add_task_unquoted_remains_backwards_compatible(monkeypatch) -> None:
"""Unquoted input keeps the whole remainder as the task text (back-compat)."""
from trushell.commands.tasks import add_task

captured: dict[str, Any] = {}

def fake_insert_todo(todo: Any) -> None:
captured["task"] = todo.task
captured["category"] = todo.category

monkeypatch.setattr("trushell.commands.tasks.insert_todo", fake_insert_todo)

add_task("buy groceries")

assert captured["task"] == "buy groceries"
assert captured["category"] == "General"
2 changes: 1 addition & 1 deletion trushell/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def _handle_todo_command(command: str) -> bool:
if add_match:
from trushell.commands.tasks import add_task

add_task(f"{add_match.group(1)} {add_match.group(2)}")
add_task(f'"{add_match.group(1)}" "{add_match.group(2)}"')
return True

update_match = re.match(r'updatetask\s+(\d+)\s+"([^"]+)"\s+"([^"]+)"', command)
Expand Down
30 changes: 25 additions & 5 deletions trushell/commands/tasks.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
from __future__ import annotations

import re
from typing import Callable

from trushell.core.database import complete_todo, delete_todo, get_all_todos, insert_todo, update_todo
from trushell.core.models import Todo


def add_task(args: str) -> None:
"""Add a new task to the todo list. The full remainder is treated as the task."""
if not args.strip():
print("Usage: task add <task>")
"""Add a new task to the todo list.

Accepts an optional quoted category, mirroring ``update_task`` and the
documented ``addtask "<task>" "<category>"`` syntax::

task add "Buy milk" "Shopping" -> task="Buy milk", category="Shopping"
task add "Buy milk" -> task="Buy milk", category="General"
task add Buy milk -> task="Buy milk", category="General"

When no category is supplied the task falls back to the "General" category,
preserving the previous default behaviour.
"""
text = args.strip()
if not text:
print('Usage: task add "<task>" ["<category>"]')
return

task_text = args.strip()
todo = Todo(task=task_text, category="General")
match = re.fullmatch(r'"([^"]+)"(?:\s+"([^"]*)")?', text)
if match:
task_text = match.group(1)
category = match.group(2) or "General"
else:
task_text = text
category = "General"

todo = Todo(task=task_text, category=category)
insert_todo(todo)
print("Task added.")

Expand Down
Loading