Coverage for apio / commands / apio_examples.py: 90%
101 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-24 01:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-24 01: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 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(docs: bool, verbose: bool):
200 """Implements the 'apio examples list' command group."""
202 # -- Create the apio context.
203 apio_ctx = ApioContext(
204 project_policy=ProjectPolicy.NO_PROJECT,
205 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
206 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
207 )
209 # -- List the examples.
210 if docs:
211 list_examples_docs_format(apio_ctx)
212 else:
213 list_examples(apio_ctx, verbose)
216# ---- apio examples fetch
218# -- Text in the rich-text format of the python rich library.
219APIO_EXAMPLES_FETCH_HELP = """
220The command 'apio examples fetch' fetches a single examples or all the \
221examples of a board. The destination directory is either the current \
222directory or the directory specified with '--dst' and it should be empty \
223and non existing.
225Examples:[code]
226 apio examples fetch alhambra-ii/ledon # Single example
227 apio examples fetch alhambra-ii # All board's examples
228 apio examples fetch alhambra-ii -d work # Explicit destination
230"""
233@click.command(
234 name="fetch",
235 cls=ApioCommand,
236 short_help="Fetch the files of an example.",
237 help=APIO_EXAMPLES_FETCH_HELP,
238)
239@click.argument("example", metavar="EXAMPLE", nargs=1, required=True)
240@options.dst_option_gen(short_help="Set a different destination directory.")
241def _fetch_cli(
242 # Arguments
243 example: str,
244 # Options
245 dst: Optional[Path],
246):
247 """Implements the 'apio examples fetch' command."""
249 # -- Create the apio context.
250 apio_ctx = ApioContext(
251 project_policy=ProjectPolicy.NO_PROJECT,
252 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
253 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
254 )
256 # -- Create the examples manager.
257 examples = Examples(apio_ctx)
259 # -- Determine the destination directory.
260 dst_dir_path = util.user_directory_or_cwd(
261 dst, description="Destination", must_exist=False
262 )
264 # Parse the argument as board or board/example
265 pattern = r"^([a-zA-Z0-9-]+)(?:[/]([a-zA-Z0-9-]+))?$"
266 match = re.match(pattern, example)
267 if not match: 267 ↛ 268line 267 didn't jump to line 268 because the condition on line 267 was never true
268 cerror(f"Invalid example specification '{example}.")
269 cout(
270 "Expecting board-id or board/example-name, e.g. "
271 "'alhambra-ii' or 'alhambra-ii/blinky.",
272 style=INFO,
273 )
274 sys.exit(1)
275 board_id: str = match.group(1)
276 example_name: Optional[str] = match.group(2)
278 if example_name:
279 # -- Copy the files of a single example.
280 examples.copy_example_files(example, dst_dir_path)
281 else:
282 # -- Copy the directories of the board's examples.
283 examples.copy_board_examples(board_id, dst_dir_path)
286# ---- apio examples
288# -- Text in the rich-text format of the python rich library.
289APIO_EXAMPLES_HELP = """
290The command group 'apio examples' provides subcommands for listing and \
291fetching Apio provided examples. Each example is a self contained \
292mini project that can be built and uploaded to an FPGA board.
293"""
296# -- We have only a single group with the title 'Subcommands'.
297SUBGROUPS = [
298 ApioSubgroup(
299 "Subcommands",
300 [
301 _list_cli,
302 _fetch_cli,
303 ],
304 )
305]
308@click.command(
309 name="examples",
310 cls=ApioGroup,
311 subgroups=SUBGROUPS,
312 short_help="List and fetch apio examples.",
313 help=APIO_EXAMPLES_HELP,
314)
315def cli():
316 """Implements the 'apio examples' command group."""
318 # pass