Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion tests/test_help_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def test_run_help_prints_docstring_for_known_command(monkeypatch, capsys):
"path": "trushell/commands/settings.py",
"function": "run_settings",
}
}
},
)

# Provide a minimal module object with the expected function and
Expand Down
5 changes: 1 addition & 4 deletions trushell/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ def app_with_lower() -> None:
if len(sys.argv) > 1:
argv_copy = sys.argv.copy()
if argv_copy[1].lower() not in {"--help", "-h", "version"}:
# Normalize the command name to lowercase for case-insensitive
# invocation, but preserve the case of subsequent arguments
# (e.g., filenames) which may be case-sensitive.
first = argv_copy[1].lower()
rest = argv_copy[2:]
raw = " ".join([first] + rest)
Expand Down Expand Up @@ -195,7 +192,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
63 changes: 44 additions & 19 deletions trushell/commands/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,92 @@

from typing import Callable

import typer

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."""
"""Add a new task to the todo list.

Supports two formats:
addtask "task text" "category" — explicit quotes
addtask task text here — entire text is the task
"""
if not args.strip():
print("Usage: task add <task>")
typer.secho('⚠️ Missing arguments. Syntax: addtask "task-name" "category"', fg=typer.colors.YELLOW)
return

task_text = args.strip()
todo = Todo(task=task_text, category="General")
# Only parse as "task" "category" when the user explicitly used quotes.
# Otherwise treat the entire args as the task text.
import shlex
stripped = args.strip()
if stripped.startswith('"') or stripped.startswith("'"):
try:
parts = shlex.split(stripped)
except ValueError:
parts = None
if parts and len(parts) >= 2:
task_text = parts[0]
category = parts[1]
else:
task_text = stripped.strip('"').strip("'")
category = "General"
else:
task_text = stripped
category = "General"

todo = Todo(task=task_text, category=category)
insert_todo(todo)
print("Task added.")
typer.secho("✅ Task added.", fg=typer.colors.GREEN)


def show_tasks(_: str) -> None:
"""Display the current todo list."""
tasks = get_all_todos()
if not tasks:
print("No tasks found.")
typer.echo("No tasks found.")
return

for index, task in enumerate(tasks, start=1):
status = "✅" if task.status == 2 else "❌"
print(f"{index}. {task.task} [{task.category}] {status}")
typer.echo(f"{index}. {task.task} [{task.category}] {status}")


def complete_task(args: str) -> None:
"""Mark a todo item as complete."""
if not args.strip() or not args.strip().isdigit():
print("Usage: task done <task-number>")
typer.secho("⚠️ Usage: task done <task-number>", fg=typer.colors.YELLOW)
return

index = int(args.strip()) - 1
try:
complete_todo(index)
print("Task completed.")
typer.secho("✅ Task completed.", fg=typer.colors.GREEN)
except Exception as error:
print(f"Task error: {error}")
typer.secho(f"Task error: {error}", fg=typer.colors.RED)


def remove_task(args: str) -> None:
"""Remove a task by its numeric position."""
if not args.strip() or not args.strip().isdigit():
print("Usage: task remove <task-number>")
typer.secho("⚠️ Usage: task remove <task-number>", fg=typer.colors.YELLOW)
return

index = int(args.strip()) - 1
try:
delete_todo(index)
print("Task removed.")
typer.secho("✅ Task removed.", fg=typer.colors.GREEN)
except Exception as error:
print(f"Task error: {error}")
typer.secho(f"Task error: {error}", fg=typer.colors.RED)


def update_task(args: str) -> None:
"""Update an existing task's text and/or category."""
parts = args.split(maxsplit=2)
if len(parts) < 2 or not parts[0].isdigit():
print('Usage: task update <task-number> "<task>" ["<category>"]')
typer.secho('⚠️ Usage: task update <task-number> "<task>" ["<category>"]', fg=typer.colors.YELLOW)
return

index = int(parts[0]) - 1
Expand All @@ -71,9 +96,9 @@ def update_task(args: str) -> None:

try:
update_todo(index, task_text, category)
print("Task updated.")
typer.secho("✅ Task updated.", fg=typer.colors.GREEN)
except Exception as error:
print(f"Task error: {error}")
typer.secho(f"Task error: {error}", fg=typer.colors.RED)


def list_tasks(_: str) -> None:
Expand All @@ -94,7 +119,7 @@ def run_task_command(args: str) -> None:
}

if not args.strip():
print("Usage: task <add|show|done|list|remove|update> [options]")
typer.secho("⚠️ Usage: task <add|show|done|list|remove|update> [options]", fg=typer.colors.YELLOW)
return

parts = args.split(maxsplit=1)
Expand All @@ -106,6 +131,6 @@ def run_task_command(args: str) -> None:
try:
handler(subargs)
except Exception as error:
print(f"Task error: {error}")
typer.secho(f"Task error: {error}", fg=typer.colors.RED)
else:
print(f"Unknown task subcommand: {subcmd}")
typer.secho(f"Unknown task subcommand: {subcmd}", fg=typer.colors.YELLOW)