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
« 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"""
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)
41# ------ apio info system
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")
70 # -- Concatenate and return.
71 config_status = ", ".join(config_status)
72 return config_status
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.
81Examples:[code]
82 apio info system # System info.[/code]
84[NOTE] For programmatic access to this information use 'apio api get-system'.
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"""
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."""
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 )
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 )
121 table.add_column("ITEM", no_wrap=True, min_width=20)
122 table.add_column("VALUE", no_wrap=True, style=EMPH1)
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 )
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 )
161# ------ apio info platforms
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.
168Examples:[code]
169 apio info platforms # List supported platform ids.[/code]
171The automatic platform ID detection of Apio can be overridden by \
172defining a different platform ID using the APIO_PLATFORM environment variable.
173"""
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."""
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 )
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 )
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)
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")
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 = " "
220 table.add_row(
221 f"{marker}{platform_id}",
222 platform_type,
223 platform_variant,
224 style=style,
225 )
227 # -- Render the table.
228 cout()
229 ctable(table)
232# ------ apio info colors
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.
239The command shows the themes colors even if the current theme is 'no-colors'.
241Examples:[code]
242 apio info colors # Rich library output (default)
243 apio inf col -p # Using shortcuts.[/code]
244"""
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."""
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 )
263 # -- Print title.
264 cout("", "ANSI Colors", "")
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
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)
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))
298 # -- Construct the line.
299 line = " ".join(values)
301 # -- Output the line.
302 cout(line)
304 cout()
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)
311# ------ apio info themes
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.
319The command shows colors even if the current theme is 'no-colors'.
321[code]
322Examples:
323 apio info themes # Show themes colors
324 apio inf col -p # Using shortcuts.[/code]
325"""
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."""
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 )
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)
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 )
361 # -- Get selected theme
362 selected_theme = get_theme()
363 selected_theme_name = selected_theme.name
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")
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)
389 # -- Apply the style
390 row_values.append(styled_text)
392 table.add_row(*row_values)
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)
402 # -- Render the table.
403 cout()
404 ctable(table)
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)
409 cout("To change your theme use 'apio preferences -t ...'", style=INFO)
410 cout()
413# ------ apio info commands
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 )
431 table.add_column("APIO COMMAND", no_wrap=True, min_width=20, style=EMPH2)
432 table.add_column("DESCRIPTION", no_wrap=True)
434 for cmd in commands:
435 table.add_row(cmd[0], cmd[1])
437 # -- Render the table.
438 cout()
439 ctable(table)
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 """
447 header1 = "COMMAND"
448 header2 = "DESCRIPTION"
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 ]
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))
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 )
468 # -- Table header
469 cwrite(
470 "| {0} | {1} |\n".format(
471 header1.ljust(w1),
472 header2.ljust(w2),
473 )
474 )
476 cwrite(
477 "| {0} | {1} |\n".format(
478 "-" * w1,
479 "-" * w2,
480 )
481 )
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 )
492 # -- All done.
493 cwrite("\n<!-- END generation by 'apio commands --docs' -->\n\n")
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.
503Examples:[code]
504 apio info commands
505 apio info commands --docs > docs/commands-list.md[/code]
506"""
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."""
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
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 )
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()])
542 # -- Sort the commands list alphabetically.
543 commands.sort()
545 # -- Generate the output
546 if docs:
547 _list_boards_docs_format(commands)
548 else:
549 _list_boards_table_format(commands)
552# ------ apio info
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"""
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]
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."""
585 # pass