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