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

38 statements  

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

1"""CLI command for displaying project structure.""" 

2 

3import json 

4from pathlib import Path 

5from typing import TYPE_CHECKING, Any, Union 

6 

7import click 

8 

9from prosemark.adapters.binder_repo_fs import BinderRepoFs 

10from prosemark.adapters.logger_stdout import LoggerStdout 

11from prosemark.app.use_cases import ShowStructure 

12from prosemark.domain.binder import Item 

13from prosemark.exceptions import FileSystemError 

14 

15if TYPE_CHECKING: # pragma: no cover 

16 from prosemark.domain.models import BinderItem 

17 

18 

19@click.command() 

20@click.option( 

21 '--format', 

22 '-f', 

23 'output_format', 

24 default='tree', 

25 type=click.Choice(['tree', 'json']), 

26 help='Output format', 

27) 

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

29def structure_command(output_format: str, path: Path | None) -> None: 

30 """Display project hierarchy.""" 

31 try: 

32 project_root = path or Path.cwd() 

33 

34 # Wire up dependencies 

35 binder_repo = BinderRepoFs(project_root) 

36 logger = LoggerStdout() 

37 

38 # Execute use case 

39 interactor = ShowStructure( 

40 binder_repo=binder_repo, 

41 logger=logger, 

42 ) 

43 

44 structure_str = interactor.execute() 

45 

46 if output_format == 'tree': 

47 click.echo('Project Structure:') 

48 click.echo(structure_str) 

49 elif output_format == 'json': # pragma: no branch 

50 # For JSON format, we need to convert the tree to JSON 

51 binder = binder_repo.load() 

52 

53 def item_to_dict(item: Union[Item, 'BinderItem']) -> dict[str, Any]: 

54 result: dict[str, Any] = { 

55 'display_title': item.display_title, 

56 } 

57 node_id = item.id if hasattr(item, 'id') else (item.node_id if hasattr(item, 'node_id') else None) 

58 if node_id: 

59 result['node_id'] = str(node_id) 

60 item_children = item.children if hasattr(item, 'children') else [] 

61 if item_children: 

62 result['children'] = [item_to_dict(child) for child in item_children] 

63 return result 

64 

65 data: dict[str, list[dict[str, Any]]] = {'roots': [item_to_dict(item) for item in binder.roots]} 

66 click.echo(json.dumps(data, indent=2)) 

67 

68 except FileSystemError as e: 

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

70 raise SystemExit(1) from e