Coverage for apio/commands/apio_examples.py: 92%
97 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
1# This file is part of the Apio project
2# (C) 2016-present FPGAwars
3# Project authors:
4# * Jesús Arroyo (Jesus89) (2016-2019)
5# * Juan Gonzalez (obijuan) (2019-2024)
6# * The Apio Team (2024-present)
7# License: GPLv2
9"""Implementation of 'apio examples' command"""
11import re
12from datetime import date
13from pathlib import Path
14from typing import Any
15import click
16from rich.table import Table
17from rich import box
18from apio.common import apio_console
19from apio.common.apio_console import cout, ctable, cwrite, fatal_error
20from apio.common.apio_styles import INFO, BORDER, EMPH1
21from apio.managers.examples import Examples, ExampleInfo
22from apio.commands import options
23from apio.apio_context import (
24 ApioContext,
25 PackagesPolicy,
26 ProjectPolicy,
27 RemoteConfigPolicy,
28)
29from apio.utils import util
30from apio.utils.cmd_util import ApioGroup, ApioSubgroup, ApioCommand
32# ---- apio examples list
35# -- Text in the rich-text format of the python rich library.
36APIO_EXAMPLES_LIST_HELP = """
37The command 'apio examples list' lists the available Apio project examples \
38that you can use.
40Examples:[code]
41 apio examples list # List all examples
42 apio examples list -v # More verbose output.
43 apio examples list | grep alhambra-ii # Show alhambra-ii examples.
44 apio examples list | grep -i blink # Show blinking examples.
45 apio examples list --docs # Use Apio docs format.[/code]
46"""
49def examples_sort_key(entry: ExampleInfo) -> Any:
50 """A key for sorting the fpga entries in our preferred order."""
51 return (util.fpga_arch_sort_key(entry.fpga_arch), entry.name)
54def list_examples(apio_ctx: ApioContext, verbose: bool) -> None:
55 """Print all the examples available. Return a process exit
56 code, 0 if ok, non zero otherwise."""
58 # -- Get list of examples.
59 entries: list[ExampleInfo] = Examples(apio_ctx).get_examples_infos()
61 # -- Sort boards by case insensitive board id.
62 entries.sort(key=examples_sort_key)
64 # -- Define the table.
65 table = Table(
66 show_header=True,
67 show_lines=False,
68 box=box.SQUARE,
69 border_style=BORDER,
70 title="Apio Examples",
71 title_justify="left",
72 )
74 # -- Add columns.
75 table.add_column("BOARD/EXAMPLE", no_wrap=True, style=EMPH1)
76 table.add_column("ARCH", no_wrap=True)
77 if verbose: 77 ↛ 78line 77 didn't jump to line 78 because the condition on line 77 was never true
78 table.add_column("PART-NUM", no_wrap=True)
79 table.add_column("SIZE", no_wrap=True)
80 table.add_column(
81 "DESCRIPTION",
82 no_wrap=True,
83 max_width=40 if verbose else 70, # Limit in verbose mode.
84 )
86 # -- Add rows.
87 last_arch = None
88 for entry in entries:
89 # -- Separation before each architecture group, unless piped out.
90 if last_arch != entry.fpga_arch and apio_console.is_terminal():
91 table.add_section()
92 last_arch = entry.fpga_arch
94 # -- Collect row's values.
95 values = []
96 values.append(entry.name)
97 values.append(entry.fpga_arch)
98 if verbose: 98 ↛ 99line 98 didn't jump to line 99 because the condition on line 98 was never true
99 values.append(entry.fpga_vendor_part)
100 values.append(entry.fpga_size)
101 values.append(entry.description)
103 # -- Append the row
104 table.add_row(*values)
106 # -- Render the table.
107 cout()
108 ctable(table)
110 # -- Print summary.
111 if apio_console.is_terminal(): 111 ↛ exitline 111 didn't return from function 'list_examples' because the condition on line 111 was always true
112 cout(f"Total of {util.plurality(entries, 'example')}")
113 if not verbose: 113 ↛ exitline 113 didn't return from function 'list_examples' because the condition on line 113 was always true
114 cout(
115 "Run 'apio examples list -v' for additional columns.",
116 style=INFO,
117 )
120def list_examples_docs_format(apio_ctx: ApioContext):
121 """Output examples information in a format for Apio Docs."""
123 # -- Get the version of the 'definitions' package use. At this point it's
124 # -- expected to be installed.
125 definitions_package_version = (
126 apio_ctx.package_manager.get_installed_package_version("definitions")
127 )
129 # -- Get list of examples.
130 entries: list[ExampleInfo] = Examples(apio_ctx).get_examples_infos()
132 # -- Sort boards by case insensitive board id.
133 entries.sort(key=examples_sort_key)
135 # -- Determine column sizes
136 w1 = max(len("EXAMPLE"), *(len(entry.name) for entry in entries))
137 w2 = max(
138 len("DESCRIPTION"),
139 *(len(entry.description) for entry in entries),
140 )
142 # -- Print page header
143 today = date.today()
144 today_str = f"{today.strftime('%B')} {today.day}, {today.year}"
145 cwrite("\n<!-- BEGIN generation by 'apio examples list --docs' -->\n")
146 cwrite("\n# Apio Examples\n")
147 cwrite(
148 f"\nThis markdown page was generated automatically on {today_str} "
149 f"from version `{definitions_package_version}` of the Apio definitions package.\n"
150 )
151 cwrite(
152 "\n> Apio project examples can be submitted to the "
153 "[apio-examples](https://github.com/FPGAwars/apio-examples) Github "
154 "repository.\n"
155 )
157 # -- Add the rows, with separation line between architecture groups.
158 last_arch = None
159 for entry in entries:
160 # -- If switching architecture, add an horizontal separation line.
161 if last_arch != entry.fpga_arch:
163 cout(f"\n## {entry.fpga_arch.upper()} examples")
165 cwrite(
166 "\n| {0} | {1} |\n".format(
167 "EXAMPLE".ljust(w1),
168 "DESCRIPTION".ljust(w2),
169 )
170 )
171 cwrite(
172 "| {0} | {1} |\n".format(
173 ":-".ljust(w1, "-"),
174 ":-".ljust(w2, "-"),
175 )
176 )
178 last_arch = entry.fpga_arch
180 # -- Write the entry
181 cwrite(
182 "| {0} | {1} |\n".format(
183 entry.name.ljust(w1),
184 entry.description.ljust(w2),
185 )
186 )
188 cwrite("\n<!-- END generation by 'apio examples list --docs' -->\n\n")
191@click.command(
192 name="list",
193 cls=ApioCommand,
194 short_help="List the available apio examples.",
195 help=APIO_EXAMPLES_LIST_HELP,
196)
197@options.docs_format_option
198@options.verbose_option
199def _list_cli(
200 *,
201 docs: bool,
202 verbose: bool,
203):
204 """Implements the 'apio examples list' command group."""
206 # -- Create the apio context.
207 apio_ctx = ApioContext(
208 project_policy=ProjectPolicy.NO_PROJECT,
209 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
210 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
211 )
213 # -- List the examples.
214 if docs:
215 list_examples_docs_format(apio_ctx)
216 else:
217 list_examples(apio_ctx, verbose)
220# ---- apio examples fetch
222# -- Text in the rich-text format of the python rich library.
223APIO_EXAMPLES_FETCH_HELP = """
224The command 'apio examples fetch' fetches a single examples or all the \
225examples of a board. The destination directory is either the current \
226directory or the directory specified with '--dst' and it should be empty \
227and non existing.
229Examples:[code]
230 apio examples fetch alhambra-ii/ledon # Single example
231 apio examples fetch alhambra-ii # All board's examples
232 apio examples fetch alhambra-ii -d work # Explicit destination
234"""
237@click.command(
238 name="fetch",
239 cls=ApioCommand,
240 short_help="Fetch the files of an example.",
241 help=APIO_EXAMPLES_FETCH_HELP,
242)
243@click.argument("example", metavar="EXAMPLE", nargs=1, required=True)
244@options.dst_option_gen(short_help="Set a different destination directory.")
245def _fetch_cli(
246 *,
247 # Arguments
248 example: str,
249 # Options
250 dst: Path | None,
251):
252 """Implements the 'apio examples fetch' command."""
254 # -- Create the apio context.
255 apio_ctx = ApioContext(
256 project_policy=ProjectPolicy.NO_PROJECT,
257 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
258 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
259 )
261 # -- Create the examples manager.
262 examples = Examples(apio_ctx)
264 # -- Determine the destination directory.
265 dst_dir_path = util.user_directory_or_cwd(
266 dst, description="Destination", must_exist=False
267 )
269 # Parse the argument as board or board/example
270 pattern = r"^([a-zA-Z0-9-]+)(?:[/]([a-zA-Z0-9-]+))?$"
271 match = re.match(pattern, example)
272 if not match: 272 ↛ 273line 272 didn't jump to line 273 because the condition on line 272 was never true
273 fatal_error(
274 f"Invalid example specification '{example}.",
275 info=[
276 "Expecting board-id or board/example-name, e.g. "
277 + "'alhambra-ii' or 'alhambra-ii/blinky."
278 ],
279 )
280 board_id: str = match.group(1)
281 example_name: str | None = match.group(2)
283 if example_name:
284 # -- Copy the files of a single example.
285 examples.copy_example_files(example, dst_dir_path)
286 else:
287 # -- Copy the directories of the board's examples.
288 examples.copy_board_examples(board_id, dst_dir_path)
291# ---- apio examples
293# -- Text in the rich-text format of the python rich library.
294APIO_EXAMPLES_HELP = """
295The command group 'apio examples' provides subcommands for listing and \
296fetching Apio provided examples. Each example is a self contained \
297mini project that can be built and uploaded to an FPGA board.
298"""
301# -- We have only a single group with the title 'Subcommands'.
302SUBGROUPS = [
303 ApioSubgroup(
304 "Subcommands",
305 [
306 _list_cli,
307 _fetch_cli,
308 ],
309 )
310]
313@click.command(
314 name="examples",
315 cls=ApioGroup,
316 subgroups=SUBGROUPS,
317 short_help="List and fetch apio examples.",
318 help=APIO_EXAMPLES_HELP,
319)
320def cli():
321 """Implements the 'apio examples' command group."""
323 # pass