Skip to content
Open
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
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)