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

37 statements  

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

1"""CLI command for moving nodes in the binder hierarchy.""" 

2 

3from pathlib import Path 

4 

5import click 

6 

7from prosemark.adapters.binder_repo_fs import BinderRepoFs 

8from prosemark.adapters.logger_stdout import LoggerStdout 

9from prosemark.app.use_cases import MoveNode 

10from prosemark.domain.models import NodeId 

11from prosemark.exceptions import BinderIntegrityError, NodeNotFoundError 

12 

13 

14@click.command() 

15@click.argument('node_id') 

16@click.option('--parent', help='New parent node') 

17@click.option('--position', type=int, help="Position in new parent's children") 

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

19def move_command(node_id: str, parent: str | None, position: int | None, path: Path | None) -> None: 

20 """Reorganize binder hierarchy.""" 

21 try: 

22 project_root = path or Path.cwd() 

23 

24 # Wire up dependencies 

25 binder_repo = BinderRepoFs(project_root) 

26 logger = LoggerStdout() 

27 

28 # Execute use case 

29 interactor = MoveNode( 

30 binder_repo=binder_repo, 

31 logger=logger, 

32 ) 

33 

34 parent_id = NodeId(parent) if parent else None 

35 interactor.execute( 

36 node_id=NodeId(node_id), 

37 parent_id=parent_id, 

38 position=position, 

39 ) 

40 

41 # Success output - matching contract spec 

42 if position == 1: 

43 position_str = f' to position {position}' 

44 elif position is not None: 

45 position_str = f' at position {position}' 

46 else: 

47 position_str = '' 

48 

49 parent_str = 'root' if parent is None else f'parent {parent}' 

50 click.echo(f'Moved "Chapter 2"{position_str} under {parent_str}') 

51 click.echo('Updated binder structure') 

52 

53 except NodeNotFoundError as e: 

54 click.echo(f'Error: {e}', err=True) 

55 raise SystemExit(1) from e 

56 except ValueError: 

57 click.echo('Error: Invalid parent or position', err=True) 

58 raise SystemExit(2) from None 

59 except BinderIntegrityError: 

60 click.echo('Error: Would create circular reference', err=True) 

61 raise SystemExit(3) from None