Coverage for apio/commands/apio_info.py: 92%
187 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +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"""
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, apio_platforms
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)
40# ------ apio info system
43def construct_remote_config_status_str(apio_ctx: ApioContext) -> str:
44 """Query the apio profile and construct a short string indicating the
45 status of the cached remote config."""
46 config = apio_ctx.profile.remote_config
47 metadata = config.get("metadata", {})
48 timestamp_now = get_datetime_stamp()
49 config_status = []
50 # -- Handle the case of a having a cached config.
51 if config: 51 ↛ 67line 51 didn't jump to line 67 because the condition on line 51 was always true
52 config_days = days_between_datetime_stamps(
53 metadata.get("loaded-at", ""), timestamp_now, 0
54 )
55 # -- Determine cache age in days, if possible.
56 if config_days is not None: 56 ↛ 61line 56 didn't jump to line 61 because the condition on line 56 was always true
57 config_status.append(
58 f"Cached {util.plurality(config_days, 'day')} ago"
59 )
60 else:
61 config_status.append("Cached")
62 # -- Indicate if there is a sign of a failed refresh attempt.
63 if "refresh-failure-on" in metadata: 63 ↛ 64line 63 didn't jump to line 64 because the condition on line 63 was never true
64 config_status.append("refresh failed.")
65 # -- Handle the case of not having a cached config.
66 else:
67 config_status.append("Not cached")
69 # -- Concatenate and return.
70 config_status = ", ".join(config_status)
71 return config_status
74# -- Text in the rich-text format of the python rich library.
75APIO_INFO_SYSTEM_HELP = """
76The command 'apio info system' provides general information about your \
77system and Apio CLI installation, which is useful for diagnosing Apio \
78CLI installation issues.
80Examples:[code]
81 apio info system # System info.[/code]
83[NOTE] For programmatic access to this information use 'apio api get-system'.
85[ADVANCED] The default location of the Apio CLI home directory, \
86where apio saves preferences and packages, is in the '.apio' directory \
87under the user home directory but can be changed using the system \
88environment variable 'APIO_HOME'.
89"""
92@click.command(
93 name="system",
94 cls=ApioCommand,
95 short_help="Show system information.",
96 help=APIO_INFO_SYSTEM_HELP,
97)
98def _system_cli():
99 """Implements the 'apio info system' command."""
101 # -- Create the apio context. We use 'cached_ok' to cause the config
102 # -- to be loaded so we can report it.
103 apio_ctx = ApioContext(
104 project_policy=ProjectPolicy.NO_PROJECT,
105 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
106 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
107 )
109 platform = apio_ctx.platform
111 # -- Define the table.
112 table = Table(
113 show_header=True,
114 show_lines=True,
115 padding=PADDING,
116 box=box.SQUARE,
117 border_style=BORDER,
118 title="Apio System Information",
119 title_justify="left",
120 )
122 table.add_column("ITEM", no_wrap=True, min_width=20)
123 table.add_column("VALUE", no_wrap=True, style=EMPH1)
125 # -- Add rows
126 table.add_row("Apio CLI version", util.get_apio_version_str())
127 table.add_row("Release info", util.get_apio_release_info() or "(none)")
128 table.add_row("Python version", util.get_python_version())
129 table.add_row("Python executable", sys.executable)
130 table.add_row("System info", apio_platforms.get_system_info())
131 table.add_row("Platform id", platform.id)
132 table.add_row("Is Darwin", str(platform.is_darwin))
133 table.add_row("Is Linux", str(platform.is_linux))
134 table.add_row("Is Windows", str(platform.is_windows))
135 table.add_row("Scons shell id", apio_ctx.scons_shell_id)
136 table.add_row("VSCode debugger", str(util.is_under_vscode_debugger()))
137 table.add_row("Pyinstaller", str(util.is_pyinstaller_app()))
138 table.add_row(
139 "Apio Python package", str(util.get_path_in_apio_package(""))
140 )
141 table.add_row("Apio home dir", str(apio_ctx.apio_home_dir))
142 table.add_row("Apio packages dir", str(apio_ctx.apio_packages_dir))
143 table.add_row("Remote config URL", apio_ctx.profile.remote_config_url)
144 table.add_row(
145 "Remote config status", construct_remote_config_status_str(apio_ctx)
146 )
147 table.add_row(
148 "Veriable formatter",
149 str(apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-format"),
150 )
151 table.add_row(
152 "Veriable language server",
153 str(apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-ls"),
154 )
156 # -- Render the table.
157 cout()
158 ctable(table)
159 cout(
160 "For programmatic system info use 'apio api get-system'.",
161 style=INFO,
162 )
165# ------ apio info platforms
167# -- Text in the rich-text format of the python rich library.
168APIO_INFO_PLATFORMS_HELP = """
169The command 'apio info platforms' lists the platform IDs supported by Apio, \
170with the effective platform ID of your system highlighted.
172Examples:[code]
173 apio info platforms # List supported platform ids.[/code]
175The automatic platform ID detection of Apio can be overridden by \
176defining a different platform ID using the APIO_PLATFORM environment variable.
177"""
180@click.command(
181 name="platforms",
182 cls=ApioCommand,
183 short_help="Supported platforms.",
184 help=APIO_INFO_PLATFORMS_HELP,
185)
186def _platforms_cli():
187 """Implements the 'apio info platforms' command."""
189 # Create the apio context.
190 apio_ctx = ApioContext(
191 project_policy=ProjectPolicy.NO_PROJECT,
192 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
193 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
194 )
196 # -- Define the table.
197 table = Table(
198 show_header=True,
199 show_lines=True,
200 padding=PADDING,
201 box=box.SQUARE,
202 border_style=BORDER,
203 title="Apio Supported Platforms",
204 title_justify="left",
205 )
207 table.add_column(" PLATFORM ID", no_wrap=True)
208 table.add_column("TYPE", no_wrap=True)
209 table.add_column("VARIANT", no_wrap=True)
211 # -- Add rows.
212 for (
213 platform_id,
214 apio_platform,
215 ) in apio_platforms.get_all_apio_platforms().items():
217 # -- Mark the current platform.
218 if platform_id == apio_ctx.platform_id:
219 style = EMPH3
220 marker = "* "
221 else:
222 style = None
223 marker = " "
225 table.add_row(
226 f"{marker}{platform_id}",
227 apio_platform.type,
228 apio_platform.variant,
229 style=style,
230 )
232 # -- Render the table.
233 cout()
234 ctable(table)
237# ------ apio info colors
239# -- Text in the rich-text format of the python rich library.
240APIO_INFO_COLORS_HELP = """
241The command 'apio info colors' shows how ansi colors are rendered on \
242the platform, and is typically used to diagnose color related issues.
244The command shows the themes colors even if the current theme is 'no-colors'.
246Examples:[code]
247 apio info colors # Rich library output (default)
248 apio inf col -p # Using shortcuts.[/code]
249"""
252@click.command(
253 name="colors",
254 cls=ApioCommand,
255 short_help="Colors table.",
256 help=APIO_INFO_COLORS_HELP,
257)
258def _colors_cli():
259 """Implements the 'apio info colors' command."""
261 # -- This initializes the output console.
262 ApioContext(
263 project_policy=ProjectPolicy.NO_PROJECT,
264 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
265 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
266 )
268 # -- Print title.
269 cout("", "ANSI Colors", "")
271 # -- Create a reversed num->name map
272 lookup = {}
273 for name, num in ANSI_COLOR_NAMES.items():
274 assert 0 <= num <= 255
275 lookup[num] = name
277 # -- Make sure the current theme supports colors, otherwise they will
278 # -- suppressed
279 if get_theme().colors_enabled: 279 ↛ 282line 279 didn't jump to line 282 because the condition on line 279 was always true
280 saved_theme_name = None
281 else:
282 saved_theme_name = get_theme().name
283 configure(theme_name=THEME_LIGHT.name)
285 # -- Print the table.
286 num_rows = 64
287 num_cols = 4
288 for row in range(num_rows):
289 values = []
290 for col in range(num_cols):
291 num = row + (col * num_rows)
292 name = lookup.get(num, None)
293 if name is None:
294 # -- No color name.
295 values.append(" " * 24)
296 else:
297 # -- Color name is available.
298 # -- Note that the color names and styling is always done by
299 # -- the rich library regardless of the choice of output.
300 s = f"{num:3} {name:20}"
301 values.append(cstyle(s, style=name))
303 # -- Construct the line.
304 line = " ".join(values)
306 # -- Output the line.
307 cout(line)
309 cout()
311 # -- Restore the original theme.
312 if saved_theme_name: 312 ↛ 313line 312 didn't jump to line 313 because the condition on line 312 was never true
313 configure(theme_name=saved_theme_name)
316# ------ apio info themes
318# -- Text in the rich-text format of the python rich library.
319APIO_INFO_THEMES_HELP = """
320The command 'apio info themes' shows the colors of the Apio themes. It can \
321be used to select the theme that works the best for you. Type \
322'apio preferences -h' for information on our to select a theme.
324The command shows colors even if the current theme is 'no-colors'.
326[code]
327Examples:
328 apio info themes # Show themes colors
329 apio inf col -p # Using shortcuts.[/code]
330"""
333@click.command(
334 name="themes",
335 cls=ApioCommand,
336 short_help="Show apio themes.",
337 help=APIO_INFO_THEMES_HELP,
338)
339def _themes_cli():
340 """Implements the 'apio info themes' command."""
342 # -- This initializes the output console.
343 ApioContext(
344 project_policy=ProjectPolicy.NO_PROJECT,
345 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
346 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
347 )
349 # -- Collect the list of apio list names.
350 style_names = set()
351 for theme_info in THEMES_TABLE.values():
352 style_names.update(list(theme_info.styles.keys()))
353 style_names = sorted(list(style_names), key=str.lower)
355 # -- Define the table.
356 table = Table(
357 show_header=True,
358 show_lines=True,
359 padding=PADDING,
360 box=box.SQUARE,
361 border_style=BORDER,
362 title="Apio Themes Style Colors",
363 title_justify="left",
364 )
366 # -- Get selected theme
367 selected_theme = get_theme()
368 selected_theme_name = selected_theme.name
370 # -- Add the table columns, one per theme.
371 for theme_name, theme in THEMES_TABLE.items():
372 assert theme_name == theme.name
373 column_name = theme_name.upper()
374 if theme_name == selected_theme_name:
375 column_name = f"*{column_name}*"
376 table.add_column(column_name, no_wrap=True, justify="center")
378 # -- Append the table rows
379 for style_name in style_names:
380 row_values: List[Text] = []
381 for theme_name, theme_info in THEMES_TABLE.items():
382 # Get style
383 colors_enabled = theme_info.colors_enabled
384 if colors_enabled:
385 if style_name not in theme_info.styles: 385 ↛ 386line 385 didn't jump to line 386 because the condition on line 385 was never true
386 styled_text = Text("---")
387 else:
388 styled_text = Text(
389 style_name, style=theme_info.styles[style_name]
390 )
391 else:
392 styled_text = Text(style_name)
394 # -- Apply the style
395 row_values.append(styled_text)
397 table.add_row(*row_values)
399 # -- Make sure the current theme supports colors, otherwise they will
400 # -- suppressed
401 if get_theme().colors_enabled: 401 ↛ 404line 401 didn't jump to line 404 because the condition on line 401 was always true
402 saved_theme_name = None
403 else:
404 saved_theme_name = get_theme().name
405 configure(theme_name=THEME_LIGHT.name)
407 # -- Render the table.
408 cout()
409 ctable(table)
411 if saved_theme_name: 411 ↛ 412line 411 didn't jump to line 412 because the condition on line 411 was never true
412 configure(theme_name=saved_theme_name)
414 cout("To change your theme use 'apio preferences -t ...'", style=INFO)
415 cout()
418# ------ apio info commands
421def _list_boards_table_format(commands):
422 """Format and output the commands table. 'commands' is a sorted
423 list of [command_name, command_description]
424 """
425 # -- Generate the table.
426 table = Table(
427 show_header=True,
428 show_lines=True,
429 padding=PADDING,
430 box=box.SQUARE,
431 border_style=BORDER,
432 title="Apio commands",
433 title_justify="left",
434 )
436 table.add_column("APIO COMMAND", no_wrap=True, min_width=20, style=EMPH2)
437 table.add_column("DESCRIPTION", no_wrap=True)
439 for cmd in commands:
440 table.add_row(cmd[0], cmd[1])
442 # -- Render the table.
443 cout()
444 ctable(table)
447def _list_boards_docs_format(commands):
448 """Format and output the commands markdown doc. 'commands' is a sorted
449 list of [command_name, command_description]
450 """
452 header1 = "COMMAND"
453 header2 = "DESCRIPTION"
455 # -- Replace the command names with markdown link to the command doc page.
456 tagged_commands = [
457 [f"[{c[0]}](cmd-apio-{c[0]}.md)", c[1]] for c in commands
458 ]
460 # -- Determine column sizes
461 w1 = max(len(header1), *(len(cmd[0]) for cmd in tagged_commands))
462 w2 = max(len(header2), *(len(cmd[1]) for cmd in tagged_commands))
464 # -- Print page header
465 today = date.today()
466 today_str = f"{today.strftime('%B')} {today.day}, {today.year}"
467 cwrite("\n<!-- BEGIN generation by 'apio commands --docs' -->\n")
468 cwrite("\n# Apio CLI commands\n")
469 cwrite(
470 f"\nThis markdown page was generated automatically on {today_str}.\n\n"
471 )
473 # -- Table header
474 cwrite(
475 "| {0} | {1} |\n".format(
476 header1.ljust(w1),
477 header2.ljust(w2),
478 )
479 )
481 cwrite(
482 "| {0} | {1} |\n".format(
483 "-" * w1,
484 "-" * w2,
485 )
486 )
488 # -- Add the rows
489 for tagged_cmd in tagged_commands:
490 cwrite(
491 "| {0} | {1} |\n".format(
492 tagged_cmd[0].ljust(w1),
493 tagged_cmd[1].ljust(w2),
494 )
495 )
497 # -- All done.
498 cwrite("\n<!-- END generation by 'apio commands --docs' -->\n\n")
501# -- Text in the rich-text format of the python rich library.
502APIO_INFO_COMMANDS_HELP = """
503The command 'apio info commands' lists the the available apio commands \
504in a table format. If the option '--docs' is specified, the command outputs \
505the list as a markdown document that is used to automatically update the \
506Apio documentation.
508Examples:[code]
509 apio info commands
510 apio info commands --docs > docs/commands-list.md[/code]
511"""
514@click.command(
515 name="commands",
516 cls=ApioCommand,
517 short_help="Show apio commands.",
518 help=APIO_INFO_COMMANDS_HELP,
519)
520@options.docs_format_option
521def _commands_cli(
522 *,
523 # Options
524 docs,
525):
526 """Implements the 'apio info commands' command."""
528 # -- We perform this lazy cyclic import here to allow the two modules
529 # -- to initialize properly without a cyclic import.
530 #
531 # pylint: disable=import-outside-toplevel
532 # pylint: disable=cyclic-import
533 from apio.commands import apio as apio_main
535 # -- This initializes the output console.
536 ApioContext(
537 project_policy=ProjectPolicy.NO_PROJECT,
538 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
539 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
540 )
542 # -- Collect commands as a list of <name, description>
543 commands = []
544 for subgroup in apio_main.SUBGROUPS:
545 for cmd in subgroup.commands:
546 commands.append([cmd.name, cmd.get_short_help_str()])
548 # -- Sort the commands list alphabetically.
549 commands.sort()
551 # -- Generate the output
552 if docs:
553 _list_boards_docs_format(commands)
554 else:
555 _list_boards_table_format(commands)
558# ------ apio info
560# -- Text in the rich-text format of the python rich library.
561APIO_INFO_HELP = """
562The command group 'apio info' contains subcommands that provide \
563additional information about Apio and your system.
564"""
566# -- We have only a single group with the title 'Subcommands'.
567SUBGROUPS = [
568 ApioSubgroup(
569 "Subcommands",
570 [
571 _system_cli,
572 _platforms_cli,
573 _colors_cli,
574 _themes_cli,
575 _commands_cli,
576 ],
577 ),
578]
581@click.command(
582 name="info",
583 cls=ApioGroup,
584 subgroups=SUBGROUPS,
585 short_help="Apio's info and info.",
586 help=APIO_INFO_HELP,
587)
588def cli():
589 """Implements the 'apio info' command group."""
591 # pass