Coverage for src/prosemark/ports/console_port.py: 100%

25 statements  

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

1"""Abstract base class for output formatting.""" 

2 

3from abc import ABC, abstractmethod 

4from typing import TYPE_CHECKING 

5 

6if TYPE_CHECKING: # pragma: no cover 

7 from prosemark.domain.models import Binder, BinderItem 

8 

9 

10class ConsolePort(ABC): 

11 """Abstract base class for output formatting. 

12 

13 Defines the contract for displaying formatted text output to users. 

14 This abstract base class enables: 

15 

16 * Clean separation between business logic and UI presentation 

17 * Testable output through dependency injection and mocking 

18 * Support for different output targets (console, GUI, web interface) 

19 * Hexagonal architecture compliance by isolating presentation concerns 

20 * Future extensibility to different user interface adapters 

21 

22 The MVP uses this for displaying binder structure trees, audit results, 

23 and general command output. Implementations should handle formatting, 

24 colors, tree rendering, and other presentation concerns. 

25 

26 Examples: 

27 >>> class TestConsolePort(ConsolePort): 

28 ... def print(self, msg: str) -> None: 

29 ... print(f'[TEST] {msg}') 

30 >>> console = TestConsolePort() 

31 >>> console.print('Hello, world!') 

32 [TEST] Hello, world! 

33 

34 """ 

35 

36 @abstractmethod 

37 def print(self, msg: str) -> None: 

38 """Display formatted message to the user. 

39 

40 This method must be implemented by concrete subclasses to provide 

41 specific output formatting and targeting (stdout, GUI, web, etc.). 

42 

43 Implementations should handle all presentation concerns including: 

44 - Message formatting and styling 

45 - Color support and terminal detection 

46 - Tree rendering with appropriate connectors 

47 - Output stream selection (stdout vs stderr) 

48 

49 Args: 

50 msg: The formatted message content to display 

51 

52 Raises: 

53 NotImplementedError: If not implemented by a concrete subclass 

54 

55 """ 

56 msg = 'Subclasses must implement the print() method' # pragma: no cover 

57 raise NotImplementedError(msg) # pragma: no cover 

58 

59 def print_info(self, msg: str) -> None: 

60 """Display an informational message. 

61 

62 Args: 

63 msg: The informational message content to display 

64 

65 """ 

66 self.print(f'INFO: {msg}') 

67 

68 def print_success(self, msg: str) -> None: 

69 """Display a success message. 

70 

71 Args: 

72 msg: The success message content to display 

73 

74 """ 

75 self.print(f'SUCCESS: {msg}') 

76 

77 def print_warning(self, msg: str) -> None: 

78 """Display a warning message. 

79 

80 Args: 

81 msg: The warning message content to display 

82 

83 """ 

84 self.print(f'WARNING: {msg}') 

85 

86 def print_error(self, msg: str) -> None: 

87 """Display an error message. 

88 

89 Args: 

90 msg: The error message content to display 

91 

92 """ 

93 self.print(f'ERROR: {msg}') 

94 

95 def print_tree(self, binder: 'Binder') -> None: 

96 """Display a formatted tree representation of a binder structure. 

97 

98 Default implementation provides basic tree rendering. Subclasses can 

99 override for custom formatting and visual styles. 

100 

101 Args: 

102 binder: The Binder object containing the hierarchical structure 

103 

104 """ 

105 # Basic implementation - just print a simple representation 

106 self.print(f'Binder: {binder.project_title}') 

107 for item in binder.children: 

108 self._print_item(item, indent=0) 

109 

110 def _print_item(self, item: 'BinderItem', indent: int) -> None: 

111 """Helper method to print a binder item with indentation.""" 

112 prefix = ' ' * indent + '- ' 

113 display = f'{item.display_title}' 

114 if item.node_id: 

115 display += f' ({item.node_id})' 

116 self.print(f'{prefix}{display}') 

117 

118 for child in item.children: 

119 self._print_item(child, indent + 1)