Coverage for src/prosemark/cli/add.py: 100%
60 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-09-24 18:08 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-09-24 18:08 +0000
1"""CLI command for adding nodes to the binder."""
3from pathlib import Path
5import click
7from prosemark.adapters.binder_repo_fs import BinderRepoFs
8from prosemark.adapters.clock_system import ClockSystem
9from prosemark.adapters.console_pretty import ConsolePretty
10from prosemark.adapters.editor_launcher_system import EditorLauncherSystem
11from prosemark.adapters.id_generator_uuid7 import IdGeneratorUuid7
12from prosemark.adapters.logger_stdout import LoggerStdout
13from prosemark.adapters.node_repo_fs import NodeRepoFs
14from prosemark.app.use_cases import AddNode, InitProject
15from prosemark.domain.models import NodeId
16from prosemark.exceptions import FileSystemError, NodeNotFoundError
19@click.command()
20@click.argument('title')
21@click.option('--parent', help='Parent node ID')
22@click.option('--position', type=int, help="Position in parent's children")
23@click.option('--path', '-p', type=click.Path(path_type=Path), help='Project directory')
24def add_command(title: str, parent: str | None, position: int | None, path: Path | None) -> None: # noqa: PLR0914
25 """Add a new node to the binder hierarchy."""
26 try:
27 project_root = path or Path.cwd()
29 # Auto-initialize project if it doesn't exist
30 binder_path = project_root / '_binder.md'
31 if not binder_path.exists():
32 # Initialize empty project silently
33 from prosemark.cli.init import FileSystemConfigPort
35 binder_repo_init = BinderRepoFs(project_root)
36 config_port = FileSystemConfigPort()
37 console_port = ConsolePretty()
38 logger_init = LoggerStdout()
39 clock_init = ClockSystem()
41 init_interactor = InitProject(
42 binder_repo=binder_repo_init,
43 config_port=config_port,
44 console_port=console_port,
45 logger=logger_init,
46 clock=clock_init,
47 )
48 init_interactor.execute(project_root)
50 # Wire up dependencies
51 binder_repo = BinderRepoFs(project_root)
52 clock = ClockSystem()
53 editor = EditorLauncherSystem()
54 node_repo = NodeRepoFs(project_root, editor, clock)
55 id_generator = IdGeneratorUuid7()
56 logger = LoggerStdout()
58 # Execute use case
59 interactor = AddNode(
60 binder_repo=binder_repo,
61 node_repo=node_repo,
62 id_generator=id_generator,
63 logger=logger,
64 clock=clock,
65 )
67 # Validate position if provided
68 if position is not None and position < 0:
69 click.echo('Error: Invalid position index', err=True)
70 raise SystemExit(2)
72 parent_id = None
73 if parent:
74 try:
75 parent_id = NodeId(parent)
76 except ValueError as err:
77 # Invalid parent ID format, treat as "parent not found"
78 click.echo('Error: Parent node not found', err=True)
79 raise SystemExit(1) from err
80 node_id = interactor.execute(
81 title=title,
82 synopsis=None,
83 parent_id=parent_id,
84 position=position,
85 )
87 # Success output
88 click.echo(f'Added "{title}" ({node_id})')
89 click.echo(f'Created files: {node_id}.md, {node_id}.notes.md')
90 click.echo('Updated binder structure')
92 except NodeNotFoundError as err:
93 click.echo('Error: Parent node not found', err=True)
94 raise SystemExit(1) from err
95 except ValueError as err:
96 click.echo('Error: Invalid position index', err=True)
97 raise SystemExit(2) from err
98 except FileSystemError as err:
99 click.echo(f'Error: File creation failed - {err}', err=True)
100 raise SystemExit(3) from err