Coverage for apio / commands / apio_examples.py: 90%
101 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 02:31 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 02:31 +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 sys
11import re
12from datetime import date
13from pathlib import Path
14from typing import List, Any, Optional
15import click
16from rich.table import Table
17from rich import box
18from apio.common.apio_console import cerror
19from apio.common import apio_console
20from apio.common.apio_console import cout, ctable, cwrite
21from apio.common.apio_styles import INFO, BORDER, EMPH1
22from apio.managers.examples import Examples, ExampleInfo
23from apio.commands import options
24from apio.apio_context import (
25 ApioContext,
26 PackagesPolicy,
27 ProjectPolicy,
28 RemoteConfigPolicy,
29)
30from apio.utils import util
31from apio.utils.cmd_util import ApioGroup, ApioSubgroup, ApioCommand
34# ---- apio examples list
37# -- Text in the rich-text format of the python rich library.
38APIO_EXAMPLES_LIST_HELP = """
39The command 'apio examples list' lists the available Apio project examples \
40that you can use.
42Examples:[code]
43 apio examples list # List all examples
44 apio examples list -v # More verbose output.
45 apio examples list | grep alhambra-ii # Show alhambra-ii examples.
46 apio examples list | grep -i blink # Show blinking examples.
47 apio examples list --docs # Use Apio docs format.[/code]
48"""
51def examples_sort_key(entry: ExampleInfo) -> Any:
52 """A key for sorting the fpga entries in our preferred order."""
53 return (util.fpga_arch_sort_key(entry.fpga_arch), entry.name)
56def list_examples(apio_ctx: ApioContext, verbose: bool) -> None:
57 """Print all the examples available. Return a process exit
58 code, 0 if ok, non zero otherwise."""
60 # -- Get list of examples.
61 entries: List[ExampleInfo] = Examples(apio_ctx).get_examples_infos()
63 # -- Sort boards by case insensitive board id.
64 entries.sort(key=examples_sort_key)
66 # -- Define the table.
67 table = Table(
68 show_header=True,
69 show_lines=False,
70 box=box.SQUARE,
71 border_style=BORDER,
72 title="Apio Examples",
73 title_justify="left",
74 )
76 # -- Add columns.
77 table.add_column("BOARD/EXAMPLE", no_wrap=True, style=EMPH1)
78 table.add_column("ARCH", no_wrap=True)
79 if verbose: 79 ↛ 80line 79 didn't jump to line 80 because the condition on line 79 was never true
80 table.add_column("PART-NUM", no_wrap=True)
81 table.add_column("SIZE", no_wrap=True)
82 table.add_column(
83 "DESCRIPTION",
84 no_wrap=True,
85 max_width=40 if verbose else 70, # Limit in verbose mode.
86 )
88 # -- Add rows.
89 last_arch = None
90 for entry in entries:
91 # -- Separation before each architecture group, unless piped out.
92 if last_arch != entry.fpga_arch and apio_console.is_terminal():
93 table.add_section()
94 last_arch = entry.fpga_arch
96 # -- Collect row's values.
97 values = []
98 values.append(entry.name)
99 values.append(entry.fpga_arch)
100 if verbose: 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true
101 values.append(entry.fpga_part_num)
102 values.append(entry.fpga_size)
103 values.append(entry.description)
105 # -- Append the row
106 table.add_row(*values)
108 # -- Render the table.
109 cout()
110 ctable(table)
112 # -- Print summary.
113 if apio_console.is_terminal(): 113 ↛ exitline 113 didn't return from function 'list_examples' because the condition on line 113 was always true
114 cout(f"Total of {util.plurality(entries, 'example')}")
115 if not verbose: 115 ↛ exitline 115 didn't return from function 'list_examples' because the condition on line 115 was always true
116 cout(
117 "Run 'apio examples list -v' for additional columns.",
118 style=INFO,
119 )
122def list_examples_docs_format(apio_ctx: ApioContext):
123 """Output examples information in a format for Apio Docs."""
125 # -- Get the version of the 'definitions' package use. At this point it's
126 # -- expected to be installed.
127 def_version, _ = apio_ctx.profile.get_installed_package_info("definitions")
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 `{def_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: Optional[Path],
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 cerror(f"Invalid example specification '{example}.")
274 cout(
275 "Expecting board-id or board/example-name, e.g. "
276 "'alhambra-ii' or 'alhambra-ii/blinky.",
277 style=INFO,
278 )
279 sys.exit(1)
280 board_id: str = match.group(1)
281 example_name: Optional[str] = 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