Coverage for  / home / runner / work / bijux-cli / bijux-cli / src / bijux_cli / cli / core / flags.py: 100%

87 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-01-26 17:59 +0000

1# SPDX-License-Identifier: Apache-2.0 

2# Copyright © 2025 Bijan Mousavi 

3 

4"""Global CLI flag parsing helpers.""" 

5 

6from __future__ import annotations 

7 

8from dataclasses import dataclass 

9 

10from bijux_cli.cli.core.constants import ( 

11 OPT_COLOR, 

12 OPT_FORMAT, 

13 OPT_LOG_LEVEL, 

14 OPT_QUIET, 

15) 

16from bijux_cli.core.enums import ColorMode, LogLevel, OutputFormat 

17from bijux_cli.core.precedence import FlagError 

18 

19 

20@dataclass(frozen=True) 

21class Flags: 

22 """Parsed global CLI flags without precedence or policy decisions.""" 

23 

24 quiet: bool | None = None 

25 log_level: LogLevel | None = None 

26 color: ColorMode | None = None 

27 format: OutputFormat | None = None 

28 

29 

30def parse_global_flags(argv: list[str]) -> Flags: 

31 """Parse global CLI flags into a data-only Flags bundle.""" 

32 quiet: bool | None = None 

33 log_level: LogLevel | None = None 

34 color: ColorMode | None = None 

35 fmt: OutputFormat | None = None 

36 i = 0 

37 while i < len(argv): 

38 flag = argv[i] 

39 if flag in OPT_QUIET: 

40 quiet = True 

41 i += 1 

42 continue 

43 if flag in OPT_LOG_LEVEL: 

44 try: 

45 log_level = LogLevel(argv[i + 1]) 

46 i += 2 

47 except (IndexError, ValueError): 

48 i += 1 

49 continue 

50 if flag in OPT_COLOR: 

51 try: 

52 color = ColorMode(argv[i + 1]) 

53 i += 2 

54 except (IndexError, ValueError): 

55 i += 1 

56 continue 

57 if flag in OPT_FORMAT: 

58 try: 

59 fmt = OutputFormat(argv[i + 1]) 

60 i += 2 

61 except (IndexError, ValueError): 

62 i += 1 

63 continue 

64 i += 1 

65 return Flags( 

66 quiet=quiet, 

67 log_level=log_level, 

68 color=color, 

69 format=fmt, 

70 ) 

71 

72 

73def collect_global_flag_errors(argv: list[str]) -> tuple[FlagError, ...]: 

74 """Collect parse-time flag errors without applying precedence or defaults.""" 

75 errors: list[FlagError] = [] 

76 i = 0 

77 while i < len(argv): 

78 flag = argv[i] 

79 if flag in OPT_LOG_LEVEL: 

80 try: 

81 LogLevel(argv[i + 1]) 

82 i += 2 

83 except IndexError: 

84 errors.append( 

85 FlagError( 

86 message="Missing value for --log-level.", 

87 failure="missing_argument", 

88 flag="--log-level", 

89 ) 

90 ) 

91 i += 1 

92 except ValueError: 

93 errors.append( 

94 FlagError( 

95 message="Invalid log level.", 

96 failure="invalid_log_level", 

97 flag="--log-level", 

98 ) 

99 ) 

100 i += 2 

101 continue 

102 if flag in OPT_COLOR: 

103 try: 

104 ColorMode(argv[i + 1]) 

105 i += 2 

106 except IndexError: 

107 errors.append( 

108 FlagError( 

109 message="Missing value for --color.", 

110 failure="missing_argument", 

111 flag="--color", 

112 ) 

113 ) 

114 i += 1 

115 except ValueError: 

116 errors.append( 

117 FlagError( 

118 message="Invalid color mode.", 

119 failure="invalid_color", 

120 flag="--color", 

121 ) 

122 ) 

123 i += 2 

124 continue 

125 if flag in OPT_FORMAT: 

126 try: 

127 OutputFormat(argv[i + 1]) 

128 i += 2 

129 except IndexError: 

130 errors.append( 

131 FlagError( 

132 message="Missing value for --format.", 

133 failure="missing_argument", 

134 flag="--format", 

135 ) 

136 ) 

137 i += 1 

138 except ValueError: 

139 errors.append( 

140 FlagError( 

141 message=f"Unsupported format: {argv[i + 1]}", 

142 failure="invalid_format", 

143 flag="--format", 

144 ) 

145 ) 

146 i += 2 

147 continue 

148 i += 1 

149 return tuple(errors) 

150 

151 

152__all__ = ["Flags", "collect_global_flag_errors", "parse_global_flags"]