Coverage for  / home / runner / work / bijux-cli / bijux-cli / src / bijux_cli / cli / commands / help_command.py: 99%

121 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"""Runtime execution for the `help` command (IO + exit behavior).""" 

5 

6from __future__ import annotations 

7 

8from collections.abc import Callable 

9import io 

10import sys 

11import time 

12 

13import typer 

14 

15from bijux_cli.cli.color import resolve_click_color 

16from bijux_cli.cli.commands.help import ( 

17 _HUMAN, 

18 _VALID_FORMATS, 

19 _build_help_intent, 

20 _build_help_payload, 

21 _find_target_command, 

22 _get_formatted_help, 

23) 

24from bijux_cli.cli.core.command import ( 

25 contains_non_ascii_env, 

26 raise_exit_intent, 

27 record_history, 

28 validate_common_flags, 

29) 

30from bijux_cli.cli.core.constants import ( 

31 OPT_FORMAT, 

32 OPT_LOG_LEVEL, 

33 OPT_PRETTY, 

34 OPT_QUIET, 

35) 

36from bijux_cli.cli.core.help_text import ( 

37 HELP_FORMAT_HELP, 

38 HELP_LOG_LEVEL, 

39 HELP_NO_PRETTY, 

40 HELP_QUIET, 

41) 

42from bijux_cli.core.di import DIContainer 

43from bijux_cli.core.enums import ( 

44 ErrorType, 

45 ExitCode, 

46 OutputFormat, 

47) 

48from bijux_cli.core.exit_policy import ExitIntent, ExitIntentError 

49from bijux_cli.core.precedence import ( 

50 EffectiveConfig, 

51 Flags, 

52 OutputConfig, 

53 default_execution_policy, 

54) 

55from bijux_cli.core.runtime import AsyncTyper 

56 

57typer.core.rich = None # type: ignore[attr-defined] 

58 

59 

60def _resolve_help_config() -> tuple[EffectiveConfig, OutputConfig]: 

61 """Resolve effective and output config for help handling.""" 

62 try: 

63 effective = DIContainer.current().resolve(EffectiveConfig) 

64 except Exception: 

65 policy = default_execution_policy() 

66 effective = EffectiveConfig( 

67 flags=Flags( 

68 quiet=policy.quiet, 

69 log_level=policy.log_level, 

70 color=policy.color, 

71 format=policy.output_format, 

72 ) 

73 ) 

74 try: 

75 output = DIContainer.current().resolve(OutputConfig) 

76 except Exception: 

77 policy = default_execution_policy() 

78 output = OutputConfig( 

79 include_runtime=policy.log_policy.show_internal, 

80 pretty=policy.log_policy.pretty_default, 

81 log_level=effective.flags.log_level, 

82 color=effective.flags.color, 

83 format=effective.flags.format, 

84 log_policy=policy.log_policy, 

85 ) 

86 return effective, output 

87 

88 

89def _emit_structured_help( 

90 *, 

91 command: str, 

92 payload: dict[str, object], 

93 output_format: OutputFormat, 

94 pretty: bool, 

95 emit_output: bool, 

96) -> None: 

97 """Emit structured help payload with history recording.""" 

98 record_history(command, ExitCode.SUCCESS) 

99 if not emit_output: 

100 raise ExitIntentError( 

101 ExitIntent( 

102 code=ExitCode.SUCCESS, 

103 stream=None, 

104 payload=None, 

105 fmt=output_format, 

106 pretty=pretty, 

107 show_traceback=False, 

108 ) 

109 ) 

110 raise ExitIntentError( 

111 ExitIntent( 

112 code=ExitCode.SUCCESS, 

113 stream="stdout", 

114 payload=payload, 

115 fmt=output_format, 

116 pretty=pretty, 

117 show_traceback=False, 

118 ) 

119 ) 

120 

121 

122def _emit_human_help( 

123 *, 

124 emit_output: bool, 

125 color: bool, 

126 help_text_provider: Callable[[], str], 

127) -> None: 

128 """Emit human help output without building text in quiet mode.""" 

129 if not emit_output: 

130 raise ExitIntentError( 

131 ExitIntent( 

132 code=ExitCode.SUCCESS, 

133 stream=None, 

134 payload=None, 

135 fmt=OutputFormat.JSON, 

136 pretty=False, 

137 show_traceback=False, 

138 ) 

139 ) 

140 text = help_text_provider() 

141 typer.echo(text, color=color, err=False) 

142 raise ExitIntentError( 

143 ExitIntent( 

144 code=ExitCode.SUCCESS, 

145 stream=None, 

146 payload=None, 

147 fmt=OutputFormat.JSON, 

148 pretty=False, 

149 show_traceback=False, 

150 ) 

151 ) 

152 

153 

154def _capture_help_text(help_text_provider: Callable[[], str]) -> str: 

155 """Capture help text without leaking human output to stdout.""" 

156 buf = io.StringIO() 

157 original = sys.stdout 

158 sys.stdout = buf 

159 try: 

160 text = help_text_provider() 

161 finally: 

162 sys.stdout = original 

163 captured = buf.getvalue() 

164 if text.strip(): 

165 return text 

166 return captured 

167 

168 

169def _override_fmt_from_argv(fmt: str) -> str: 

170 """Prefer an explicit format flag value when provided on the CLI.""" 

171 if fmt.strip().lower() != _HUMAN: 

172 return fmt 

173 argv = sys.argv[1:] 

174 if "help" in argv: 

175 argv = argv[argv.index("help") + 1 :] 

176 for idx, arg in enumerate(argv): 

177 if arg in OPT_FORMAT and idx + 1 < len(argv): 

178 return argv[idx + 1] 

179 return fmt 

180 

181 

182help_app = AsyncTyper( 

183 name="help", 

184 add_completion=False, 

185 help="Show help for any CLI command or subcommand.", 

186 context_settings={ 

187 "help_option_names": ["-h", "--help"], 

188 "ignore_unknown_options": True, 

189 "allow_extra_args": True, 

190 "allow_interspersed_args": True, 

191 }, 

192) 

193 

194ARGS = typer.Argument(None, help="Command path, e.g. 'config get'.") 

195 

196 

197@help_app.callback(invoke_without_command=True) 

198def help_callback( 

199 ctx: typer.Context, 

200 command_path: list[str] | None = ARGS, 

201 quiet: bool = typer.Option(False, *OPT_QUIET, help=HELP_QUIET), 

202 fmt: str = typer.Option(_HUMAN, *OPT_FORMAT, help=HELP_FORMAT_HELP), 

203 pretty: bool = typer.Option(True, OPT_PRETTY, help=HELP_NO_PRETTY), 

204 log_level: str = typer.Option("info", *OPT_LOG_LEVEL, help=HELP_LOG_LEVEL), 

205) -> None: 

206 """Entry point for the `bijux help` command.""" 

207 _ = (quiet, pretty, log_level) 

208 started_at = time.perf_counter() 

209 effective, output = _resolve_help_config() 

210 emit_output = not effective.flags.quiet 

211 fmt = _override_fmt_from_argv(fmt) 

212 

213 if "-h" in sys.argv or "--help" in sys.argv: 

214 all_args = sys.argv[2:] 

215 known_flags_with_args = set(OPT_FORMAT) 

216 path_tokens = [] 

217 i = 0 

218 while i < len(all_args): 

219 arg = all_args[i] 

220 if arg in known_flags_with_args: 

221 i += 2 

222 elif arg.startswith("-"): 

223 i += 1 

224 else: 

225 path_tokens.append(arg) 

226 i += 1 

227 

228 target = _find_target_command(ctx, path_tokens) or _find_target_command(ctx, []) 

229 if target: 

230 target_cmd, target_ctx = target 

231 help_text = _get_formatted_help(target_cmd, target_ctx) 

232 if emit_output: 232 ↛ 238line 232 didn't jump to line 238 because the condition on line 232 was always true

233 typer.echo( 

234 help_text, 

235 color=resolve_click_color(quiet=False, fmt=None), 

236 err=False, 

237 ) 

238 raise ExitIntentError( 

239 ExitIntent( 

240 code=ExitCode.SUCCESS, 

241 stream=None, 

242 payload=None, 

243 fmt=OutputFormat.JSON, 

244 pretty=False, 

245 show_traceback=False, 

246 ) 

247 ) 

248 raise ExitIntentError( 

249 ExitIntent( 

250 code=ExitCode.SUCCESS, 

251 stream=None, 

252 payload=None, 

253 fmt=OutputFormat.JSON, 

254 pretty=False, 

255 show_traceback=False, 

256 ) 

257 ) 

258 

259 tokens = command_path or [] 

260 command = "help" 

261 intent = _build_help_intent(tokens, fmt, effective, output) 

262 

263 if intent.fmt_lower != "human": 

264 validate_common_flags( 

265 intent.format_value or OutputFormat.JSON, 

266 command, 

267 intent.quiet, 

268 include_runtime=intent.include_runtime, 

269 log_level=intent.log_level, 

270 ) 

271 

272 if intent.fmt_lower not in _VALID_FORMATS: 

273 raise_exit_intent( 

274 f"Unsupported format: '{fmt}'", 

275 code=2, 

276 failure="format", 

277 command=command, 

278 fmt=intent.error_fmt, 

279 quiet=intent.quiet, 

280 include_runtime=intent.include_runtime, 

281 log_level=intent.log_level, 

282 error_type=ErrorType.USER_INPUT, 

283 ) 

284 

285 for token in intent.tokens: 

286 if "\x00" in token: 

287 raise_exit_intent( 

288 "Embedded null byte in command path", 

289 code=3, 

290 failure="null_byte", 

291 command=command, 

292 fmt=intent.error_fmt, 

293 quiet=intent.quiet, 

294 include_runtime=intent.include_runtime, 

295 log_level=intent.log_level, 

296 error_type=ErrorType.ASCII, 

297 ) 

298 try: 

299 token.encode("ascii") 

300 except UnicodeEncodeError: 

301 raise_exit_intent( 

302 f"Non-ASCII characters in command path: {token!r}", 

303 code=3, 

304 failure="ascii", 

305 command=command, 

306 fmt=intent.error_fmt, 

307 quiet=intent.quiet, 

308 include_runtime=intent.include_runtime, 

309 log_level=intent.log_level, 

310 error_type=ErrorType.ASCII, 

311 ) 

312 

313 if contains_non_ascii_env(): 

314 raise_exit_intent( 

315 "Non-ASCII in environment", 

316 code=3, 

317 failure="ascii", 

318 command=command, 

319 fmt=intent.error_fmt, 

320 quiet=intent.quiet, 

321 include_runtime=intent.include_runtime, 

322 log_level=intent.log_level, 

323 error_type=ErrorType.ASCII, 

324 ) 

325 

326 target = _find_target_command(ctx, intent.tokens) 

327 if not target: 

328 raise_exit_intent( 

329 f"No such command: {' '.join(intent.tokens)}", 

330 code=2, 

331 failure="not_found", 

332 command=command, 

333 fmt=intent.error_fmt, 

334 quiet=intent.quiet, 

335 include_runtime=intent.include_runtime, 

336 log_level=intent.log_level, 

337 error_type=ErrorType.USER_INPUT, 

338 ) 

339 

340 target_cmd, target_ctx = target 

341 

342 if intent.fmt_lower == _HUMAN: 

343 _emit_human_help( 

344 emit_output=emit_output, 

345 color=bool(resolve_click_color(quiet=intent.quiet, fmt=None)), 

346 help_text_provider=lambda: _get_formatted_help(target_cmd, target_ctx), 

347 ) 

348 

349 help_text = _capture_help_text(lambda: _get_formatted_help(target_cmd, target_ctx)) 

350 try: 

351 payload = _build_help_payload(help_text, intent.include_runtime, started_at) 

352 except ValueError as exc: 

353 raise_exit_intent( 

354 str(exc), 

355 code=3, 

356 failure="ascii", 

357 command=command, 

358 fmt=intent.error_fmt, 

359 quiet=intent.quiet, 

360 include_runtime=intent.include_runtime, 

361 log_level=intent.log_level, 

362 ) 

363 

364 output_format = ( 

365 OutputFormat.YAML 

366 if intent.format_value == OutputFormat.YAML 

367 else OutputFormat.JSON 

368 ) 

369 _emit_structured_help( 

370 command=command, 

371 payload=payload, 

372 output_format=output_format, 

373 pretty=intent.pretty, 

374 emit_output=emit_output, 

375 ) 

376 

377 

378__all__ = ["ARGS", "help_app", "help_callback"]