Coverage for apio / commands / apio_info.py: 92%

185 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-08 02:47 +0000

1# -*- coding: utf-8 -*- 

2# -- This file is part of the Apio project 

3# -- (C) 2016-2024 FPGAwars 

4# -- Authors 

5# -- * Jesús Arroyo (2016-2019) 

6# -- * Juan Gonzalez (obijuan) (2019-2024) 

7# -- License GPLv2 

8"""Implementation of 'apio info' command""" 

9 

10import sys 

11from typing import List 

12from datetime import date 

13import click 

14from rich.table import Table 

15from rich.text import Text 

16from rich import box 

17from rich.color import ANSI_COLOR_NAMES 

18from apio.common.apio_styles import BORDER, EMPH1, EMPH2, EMPH3, INFO 

19from apio.utils import util 

20from apio.commands import options 

21from apio.apio_context import ( 

22 ApioContext, 

23 PackagesPolicy, 

24 ProjectPolicy, 

25 RemoteConfigPolicy, 

26) 

27from apio.utils.cmd_util import ApioGroup, ApioSubgroup, ApioCommand 

28from apio.common.apio_themes import THEMES_TABLE, THEME_LIGHT 

29from apio.profile import get_datetime_stamp, days_between_datetime_stamps 

30from apio.common.apio_console import ( 

31 PADDING, 

32 cout, 

33 cwrite, 

34 cstyle, 

35 ctable, 

36 get_theme, 

37 configure, 

38) 

39 

40 

41# ------ apio info system 

42 

43 

44def construct_remote_config_status_str(apio_ctx: ApioContext) -> str: 

45 """Query the apio profile and construct a short string indicating the 

46 status of the cached remote config.""" 

47 config = apio_ctx.profile.remote_config 

48 metadata = config.get("metadata", {}) 

49 timestamp_now = get_datetime_stamp() 

50 config_status = [] 

51 # -- Handle the case of a having a cached config. 

52 if config: 52 ↛ 68line 52 didn't jump to line 68 because the condition on line 52 was always true

53 config_days = days_between_datetime_stamps( 

54 metadata.get("loaded-at", ""), timestamp_now, None 

55 ) 

56 # -- Determine cache age in days, if possible. 

57 if config_days is not None: 57 ↛ 62line 57 didn't jump to line 62 because the condition on line 57 was always true

58 config_status.append( 

59 f"Cached {util.plurality(config_days, 'day')} ago" 

60 ) 

61 else: 

62 config_status.append("Cached") 

63 # -- Indicate if there is a sign of a failed refresh attempt. 

64 if "refresh-failure-on" in metadata: 64 ↛ 65line 64 didn't jump to line 65 because the condition on line 64 was never true

65 config_status.append("refresh failed.") 

66 # -- Handle the case of not having a cached config. 

67 else: 

68 config_status.append("Not cached") 

69 

70 # -- Concatenate and return. 

71 config_status = ", ".join(config_status) 

72 return config_status 

73 

74 

75# -- Text in the rich-text format of the python rich library. 

76APIO_INFO_SYSTEM_HELP = """ 

77The command 'apio info system' provides general information about your \ 

78system and Apio CLI installation, which is useful for diagnosing Apio \ 

79CLI installation issues. 

80 

81Examples:[code] 

82 apio info system # System info.[/code] 

83 

84[NOTE] For programmatic access to this information use 'apio api get-system'. 

85 

86[ADVANCED] The default location of the Apio CLI home directory, \ 

87where apio saves preferences and packages, is in the '.apio' directory \ 

88under the user home directory but can be changed using the system \ 

89environment variable 'APIO_HOME'. 

90""" 

91 

92 

93@click.command( 

94 name="system", 

95 cls=ApioCommand, 

96 short_help="Show system information.", 

97 help=APIO_INFO_SYSTEM_HELP, 

98) 

99def _system_cli(): 

100 """Implements the 'apio info system' command.""" 

101 

102 # -- Create the apio context. We use 'cached_ok' to cause the config 

103 # -- to be loaded so we can report it. 

104 apio_ctx = ApioContext( 

105 project_policy=ProjectPolicy.NO_PROJECT, 

106 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

107 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

108 ) 

109 

110 # -- Define the table. 

111 table = Table( 

112 show_header=True, 

113 show_lines=True, 

114 padding=PADDING, 

115 box=box.SQUARE, 

116 border_style=BORDER, 

117 title="Apio System Information", 

118 title_justify="left", 

119 ) 

120 

121 table.add_column("ITEM", no_wrap=True, min_width=20) 

122 table.add_column("VALUE", no_wrap=True, style=EMPH1) 

123 

124 # -- Add rows 

125 table.add_row("Apio CLI version", util.get_apio_version_str()) 

126 table.add_row("Release info", util.get_apio_release_info() or "(none)") 

127 table.add_row("Python version", util.get_python_version()) 

128 table.add_row("Python executable", sys.executable) 

129 table.add_row("Platform id", apio_ctx.platform_id) 

130 table.add_row("Platform info", util.get_platform_info()) 

131 table.add_row("Scons shell id", apio_ctx.scons_shell_id) 

132 table.add_row("VSCode debugger", str(util.is_under_vscode_debugger())) 

133 table.add_row("Pyinstaller", str(util.is_pyinstaller_app())) 

134 table.add_row( 

135 "Apio Python package", str(util.get_path_in_apio_package("")) 

136 ) 

137 table.add_row("Apio home dir", str(apio_ctx.apio_home_dir)) 

138 table.add_row("Apio packages dir", str(apio_ctx.apio_packages_dir)) 

139 table.add_row("Remote config URL", apio_ctx.profile.remote_config_url) 

140 table.add_row( 

141 "Remote config status", construct_remote_config_status_str(apio_ctx) 

142 ) 

143 table.add_row( 

144 "Veriable formatter", 

145 str(apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-format"), 

146 ) 

147 table.add_row( 

148 "Veriable language server", 

149 str(apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-ls"), 

150 ) 

151 

152 # -- Render the table. 

153 cout() 

154 ctable(table) 

155 cout( 

156 "To force a remote config refresh, run 'apio packages install'.", 

157 style=INFO, 

158 ) 

159 

160 

161# ------ apio info platforms 

162 

163# -- Text in the rich-text format of the python rich library. 

164APIO_INFO_PLATFORMS_HELP = """ 

165The command 'apio info platforms' lists the platform IDs supported by Apio, \ 

166with the effective platform ID of your system highlighted. 

167 

168Examples:[code] 

169 apio info platforms # List supported platform ids.[/code] 

170 

171The automatic platform ID detection of Apio can be overridden by \ 

172defining a different platform ID using the APIO_PLATFORM environment variable. 

173""" 

174 

175 

176@click.command( 

177 name="platforms", 

178 cls=ApioCommand, 

179 short_help="Supported platforms.", 

180 help=APIO_INFO_PLATFORMS_HELP, 

181) 

182def _platforms_cli(): 

183 """Implements the 'apio info platforms' command.""" 

184 

185 # Create the apio context. 

186 apio_ctx = ApioContext( 

187 project_policy=ProjectPolicy.NO_PROJECT, 

188 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

189 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

190 ) 

191 

192 # -- Define the table. 

193 table = Table( 

194 show_header=True, 

195 show_lines=True, 

196 padding=PADDING, 

197 box=box.SQUARE, 

198 border_style=BORDER, 

199 title="Apio Supported Platforms", 

200 title_justify="left", 

201 ) 

202 

203 table.add_column(" PLATFORM ID", no_wrap=True) 

204 table.add_column("TYPE", no_wrap=True) 

205 table.add_column("VARIANT", no_wrap=True) 

206 

207 # -- Add rows. 

208 for platform_id, platform_info in apio_ctx.platforms.items(): 

209 platform_type = platform_info.get("type") 

210 platform_variant = platform_info.get("variant") 

211 

212 # -- Mark the current platform. 

213 if platform_id == apio_ctx.platform_id: 

214 style = EMPH3 

215 marker = "* " 

216 else: 

217 style = None 

218 marker = " " 

219 

220 table.add_row( 

221 f"{marker}{platform_id}", 

222 platform_type, 

223 platform_variant, 

224 style=style, 

225 ) 

226 

227 # -- Render the table. 

228 cout() 

229 ctable(table) 

230 

231 

232# ------ apio info colors 

233 

234# -- Text in the rich-text format of the python rich library. 

235APIO_INFO_COLORS_HELP = """ 

236The command 'apio info colors' shows how ansi colors are rendered on \ 

237the platform, and is typically used to diagnose color related issues. 

238 

239The command shows the themes colors even if the current theme is 'no-colors'. 

240 

241Examples:[code] 

242 apio info colors # Rich library output (default) 

243 apio inf col -p # Using shortcuts.[/code] 

244""" 

245 

246 

247@click.command( 

248 name="colors", 

249 cls=ApioCommand, 

250 short_help="Colors table.", 

251 help=APIO_INFO_COLORS_HELP, 

252) 

253def _colors_cli(): 

254 """Implements the 'apio info colors' command.""" 

255 

256 # -- This initializes the output console. 

257 ApioContext( 

258 project_policy=ProjectPolicy.NO_PROJECT, 

259 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

260 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

261 ) 

262 

263 # -- Print title. 

264 cout("", "ANSI Colors", "") 

265 

266 # -- Create a reversed num->name map 

267 lookup = {} 

268 for name, num in ANSI_COLOR_NAMES.items(): 

269 assert 0 <= num <= 255 

270 lookup[num] = name 

271 

272 # -- Make sure the current theme supports colors, otherwise they will 

273 # -- suppressed 

274 if get_theme().colors_enabled: 274 ↛ 277line 274 didn't jump to line 277 because the condition on line 274 was always true

275 saved_theme_name = None 

276 else: 

277 saved_theme_name = get_theme().name 

278 configure(theme_name=THEME_LIGHT.name) 

279 

280 # -- Print the table. 

281 num_rows = 64 

282 num_cols = 4 

283 for row in range(num_rows): 

284 values = [] 

285 for col in range(num_cols): 

286 num = row + (col * num_rows) 

287 name = lookup.get(num, None) 

288 if name is None: 

289 # -- No color name. 

290 values.append(" " * 24) 

291 else: 

292 # -- Color name is available. 

293 # -- Note that the color names and styling is always done by 

294 # -- the rich library regardless of the choice of output. 

295 s = f"{num:3} {name:20}" 

296 values.append(cstyle(s, style=name)) 

297 

298 # -- Construct the line. 

299 line = " ".join(values) 

300 

301 # -- Output the line. 

302 cout(line) 

303 

304 cout() 

305 

306 # -- Restore the original theme. 

307 if saved_theme_name: 307 ↛ 308line 307 didn't jump to line 308 because the condition on line 307 was never true

308 configure(theme_name=saved_theme_name) 

309 

310 

311# ------ apio info themes 

312 

313# -- Text in the rich-text format of the python rich library. 

314APIO_INFO_THEMES_HELP = """ 

315The command 'apio info themes' shows the colors of the Apio themes. It can \ 

316be used to select the theme that works the best for you. Type \ 

317'apio preferences -h' for information on our to select a theme. 

318 

319The command shows colors even if the current theme is 'no-colors'. 

320 

321[code] 

322Examples: 

323 apio info themes # Show themes colors 

324 apio inf col -p # Using shortcuts.[/code] 

325""" 

326 

327 

328@click.command( 

329 name="themes", 

330 cls=ApioCommand, 

331 short_help="Show apio themes.", 

332 help=APIO_INFO_THEMES_HELP, 

333) 

334def _themes_cli(): 

335 """Implements the 'apio info themes' command.""" 

336 

337 # -- This initializes the output console. 

338 ApioContext( 

339 project_policy=ProjectPolicy.NO_PROJECT, 

340 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

341 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

342 ) 

343 

344 # -- Collect the list of apio list names. 

345 style_names = set() 

346 for theme_info in THEMES_TABLE.values(): 

347 style_names.update(list(theme_info.styles.keys())) 

348 style_names = sorted(list(style_names), key=str.lower) 

349 

350 # -- Define the table. 

351 table = Table( 

352 show_header=True, 

353 show_lines=True, 

354 padding=PADDING, 

355 box=box.SQUARE, 

356 border_style=BORDER, 

357 title="Apio Themes Style Colors", 

358 title_justify="left", 

359 ) 

360 

361 # -- Get selected theme 

362 selected_theme = get_theme() 

363 selected_theme_name = selected_theme.name 

364 

365 # -- Add the table columns, one per theme. 

366 for theme_name, theme in THEMES_TABLE.items(): 

367 assert theme_name == theme.name 

368 column_name = theme_name.upper() 

369 if theme_name == selected_theme_name: 

370 column_name = f"*{column_name}*" 

371 table.add_column(column_name, no_wrap=True, justify="center") 

372 

373 # -- Append the table rows 

374 for style_name in style_names: 

375 row_values: List[Text] = [] 

376 for theme_name, theme_info in THEMES_TABLE.items(): 

377 # Get style 

378 colors_enabled = theme_info.colors_enabled 

379 if colors_enabled: 

380 if style_name not in theme_info.styles: 380 ↛ 381line 380 didn't jump to line 381 because the condition on line 380 was never true

381 styled_text = Text("---") 

382 else: 

383 styled_text = Text( 

384 style_name, style=theme_info.styles[style_name] 

385 ) 

386 else: 

387 styled_text = Text(style_name) 

388 

389 # -- Apply the style 

390 row_values.append(styled_text) 

391 

392 table.add_row(*row_values) 

393 

394 # -- Make sure the current theme supports colors, otherwise they will 

395 # -- suppressed 

396 if get_theme().colors_enabled: 396 ↛ 399line 396 didn't jump to line 399 because the condition on line 396 was always true

397 saved_theme_name = None 

398 else: 

399 saved_theme_name = get_theme().name 

400 configure(theme_name=THEME_LIGHT.name) 

401 

402 # -- Render the table. 

403 cout() 

404 ctable(table) 

405 

406 if saved_theme_name: 406 ↛ 407line 406 didn't jump to line 407 because the condition on line 406 was never true

407 configure(theme_name=saved_theme_name) 

408 

409 cout("To change your theme use 'apio preferences -t ...'", style=INFO) 

410 cout() 

411 

412 

413# ------ apio info commands 

414 

415 

416def _list_boards_table_format(commands): 

417 """Format and output the commands table. 'commands' is a sorted 

418 list of [command_name, command_description] 

419 """ 

420 # -- Generate the table. 

421 table = Table( 

422 show_header=True, 

423 show_lines=True, 

424 padding=PADDING, 

425 box=box.SQUARE, 

426 border_style=BORDER, 

427 title="Apio commands", 

428 title_justify="left", 

429 ) 

430 

431 table.add_column("APIO COMMAND", no_wrap=True, min_width=20, style=EMPH2) 

432 table.add_column("DESCRIPTION", no_wrap=True) 

433 

434 for cmd in commands: 

435 table.add_row(cmd[0], cmd[1]) 

436 

437 # -- Render the table. 

438 cout() 

439 ctable(table) 

440 

441 

442def _list_boards_docs_format(commands): 

443 """Format and output the commands markdown doc. 'commands' is a sorted 

444 list of [command_name, command_description] 

445 """ 

446 

447 header1 = "COMMAND" 

448 header2 = "DESCRIPTION" 

449 

450 # -- Replace the command names with markdown link to the command doc page. 

451 tagged_commands = [ 

452 [f"[{c[0]}](cmd-apio-{c[0]}.md)", c[1]] for c in commands 

453 ] 

454 

455 # -- Determine column sizes 

456 w1 = max(len(header1), *(len(cmd[0]) for cmd in tagged_commands)) 

457 w2 = max(len(header2), *(len(cmd[1]) for cmd in tagged_commands)) 

458 

459 # -- Print page header 

460 today = date.today() 

461 today_str = f"{today.strftime('%B')} {today.day}, {today.year}" 

462 cwrite("\n<!-- BEGIN generation by 'apio commands --docs' -->\n") 

463 cwrite("\n# Apio CLI commands\n") 

464 cwrite( 

465 f"\nThis markdown page was generated automatically on {today_str}.\n\n" 

466 ) 

467 

468 # -- Table header 

469 cwrite( 

470 "| {0} | {1} |\n".format( 

471 header1.ljust(w1), 

472 header2.ljust(w2), 

473 ) 

474 ) 

475 

476 cwrite( 

477 "| {0} | {1} |\n".format( 

478 "-" * w1, 

479 "-" * w2, 

480 ) 

481 ) 

482 

483 # -- Add the rows 

484 for tagged_cmd in tagged_commands: 

485 cwrite( 

486 "| {0} | {1} |\n".format( 

487 tagged_cmd[0].ljust(w1), 

488 tagged_cmd[1].ljust(w2), 

489 ) 

490 ) 

491 

492 # -- All done. 

493 cwrite("\n<!-- END generation by 'apio commands --docs' -->\n\n") 

494 

495 

496# -- Text in the rich-text format of the python rich library. 

497APIO_INFO_COMMANDS_HELP = """ 

498The command 'apio info commands' lists the the available apio commands \ 

499in a table format. If the option '--docs' is specified, the command outputs \ 

500the list as a markdown document that is used to automatically update the \ 

501Apio documentation. 

502 

503Examples:[code] 

504 apio info commands 

505 apio info commands --docs > docs/commands-list.md[/code] 

506""" 

507 

508 

509@click.command( 

510 name="commands", 

511 cls=ApioCommand, 

512 short_help="Show apio commands.", 

513 help=APIO_INFO_COMMANDS_HELP, 

514) 

515@options.docs_format_option 

516def _commands_cli( 

517 # Options 

518 docs, 

519): 

520 """Implements the 'apio info commands' command.""" 

521 

522 # -- We perform this lazy cyclic import here to allow the two modules 

523 # -- to initialize properly without a cyclic import. 

524 # 

525 # pylint: disable=import-outside-toplevel 

526 # pylint: disable=cyclic-import 

527 from apio.commands import apio as apio_main 

528 

529 # -- This initializes the output console. 

530 ApioContext( 

531 project_policy=ProjectPolicy.NO_PROJECT, 

532 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

533 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

534 ) 

535 

536 # -- Collect commands as a list of <name, description> 

537 commands = [] 

538 for subgroup in apio_main.SUBGROUPS: 

539 for cmd in subgroup.commands: 

540 commands.append([cmd.name, cmd.get_short_help_str()]) 

541 

542 # -- Sort the commands list alphabetically. 

543 commands.sort() 

544 

545 # -- Generate the output 

546 if docs: 

547 _list_boards_docs_format(commands) 

548 else: 

549 _list_boards_table_format(commands) 

550 

551 

552# ------ apio info 

553 

554# -- Text in the rich-text format of the python rich library. 

555APIO_INFO_HELP = """ 

556The command group 'apio info' contains subcommands that provide \ 

557additional information about Apio and your system. 

558""" 

559 

560# -- We have only a single group with the title 'Subcommands'. 

561SUBGROUPS = [ 

562 ApioSubgroup( 

563 "Subcommands", 

564 [ 

565 _system_cli, 

566 _platforms_cli, 

567 _colors_cli, 

568 _themes_cli, 

569 _commands_cli, 

570 ], 

571 ), 

572] 

573 

574 

575@click.command( 

576 name="info", 

577 cls=ApioGroup, 

578 subgroups=SUBGROUPS, 

579 short_help="Apio's info and info.", 

580 help=APIO_INFO_HELP, 

581) 

582def cli(): 

583 """Implements the 'apio info' command group.""" 

584 

585 # pass