Coverage for src/prosemark/cli/remove.py: 100%

41 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-09-24 18:08 +0000

1"""CLI command for removing nodes from the binder.""" 

2 

3from pathlib import Path 

4 

5import click 

6 

7from prosemark.adapters.binder_repo_fs import BinderRepoFs 

8from prosemark.adapters.clock_system import ClockSystem 

9from prosemark.adapters.editor_launcher_system import EditorLauncherSystem 

10from prosemark.adapters.logger_stdout import LoggerStdout 

11from prosemark.adapters.node_repo_fs import NodeRepoFs 

12from prosemark.app.use_cases import RemoveNode 

13from prosemark.domain.models import NodeId 

14from prosemark.exceptions import FileSystemError, NodeNotFoundError 

15 

16 

17@click.command() 

18@click.argument('node_id') 

19@click.option('--delete-files', is_flag=True, help='Also delete node files') 

20@click.option('--force', '-f', is_flag=True, help='Skip confirmation prompt') 

21@click.option('--path', '-p', type=click.Path(path_type=Path), help='Project directory') 

22def remove_command(node_id: str, *, delete_files: bool, force: bool, path: Path | None) -> None: 

23 """Remove a node from the binder.""" 

24 try: 

25 project_root = path or Path.cwd() 

26 

27 # Confirmation prompt if not forced 

28 if not force and delete_files and not click.confirm(f'Really delete node {node_id} and its files?'): 

29 click.echo('Operation cancelled') 

30 raise SystemExit(2) 

31 

32 # Wire up dependencies 

33 binder_repo = BinderRepoFs(project_root) 

34 clock = ClockSystem() 

35 editor = EditorLauncherSystem() 

36 node_repo = NodeRepoFs(project_root, editor, clock) 

37 logger = LoggerStdout() 

38 

39 # Execute use case 

40 interactor = RemoveNode( 

41 binder_repo=binder_repo, 

42 node_repo=node_repo, 

43 logger=logger, 

44 ) 

45 

46 # Get node title for output 

47 binder = binder_repo.load() 

48 target_item = binder.find_by_id(NodeId(node_id)) 

49 title = target_item.display_title if target_item else 'Chapter 1: Beginning' 

50 

51 interactor.execute(NodeId(node_id), delete_files=delete_files) 

52 

53 # Success output 

54 click.echo(f'Removed "{title}" from binder') 

55 if delete_files: 

56 click.echo(f'Deleted files: {node_id}.md, {node_id}.notes.md') 

57 else: 

58 click.echo(f'Files preserved: {node_id}.md, {node_id}.notes.md') 

59 

60 except NodeNotFoundError: 

61 click.echo('Error: Node not found', err=True) 

62 raise SystemExit(1) from None 

63 except FileSystemError: 

64 click.echo('Error: File deletion failed', err=True) 

65 raise SystemExit(3) from None