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

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 

8 

9"""Implementation of 'apio examples' command""" 

10 

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 

31 

32# ---- apio examples list 

33 

34 

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. 

39 

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""" 

47 

48 

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) 

52 

53 

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.""" 

57 

58 # -- Get list of examples. 

59 entries: list[ExampleInfo] = Examples(apio_ctx).get_examples_infos() 

60 

61 # -- Sort boards by case insensitive board id. 

62 entries.sort(key=examples_sort_key) 

63 

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 ) 

73 

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 ) 

85 

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 

93 

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) 

102 

103 # -- Append the row 

104 table.add_row(*values) 

105 

106 # -- Render the table. 

107 cout() 

108 ctable(table) 

109 

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 ) 

118 

119 

120def list_examples_docs_format(apio_ctx: ApioContext): 

121 """Output examples information in a format for Apio Docs.""" 

122 

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 ) 

128 

129 # -- Get list of examples. 

130 entries: list[ExampleInfo] = Examples(apio_ctx).get_examples_infos() 

131 

132 # -- Sort boards by case insensitive board id. 

133 entries.sort(key=examples_sort_key) 

134 

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 ) 

141 

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 ) 

156 

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: 

162 

163 cout(f"\n## {entry.fpga_arch.upper()} examples") 

164 

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 ) 

177 

178 last_arch = entry.fpga_arch 

179 

180 # -- Write the entry 

181 cwrite( 

182 "| {0} | {1} |\n".format( 

183 entry.name.ljust(w1), 

184 entry.description.ljust(w2), 

185 ) 

186 ) 

187 

188 cwrite("\n<!-- END generation by 'apio examples list --docs' -->\n\n") 

189 

190 

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.""" 

205 

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 ) 

212 

213 # -- List the examples. 

214 if docs: 

215 list_examples_docs_format(apio_ctx) 

216 else: 

217 list_examples(apio_ctx, verbose) 

218 

219 

220# ---- apio examples fetch 

221 

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. 

228 

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 

233 

234""" 

235 

236 

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.""" 

253 

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 ) 

260 

261 # -- Create the examples manager. 

262 examples = Examples(apio_ctx) 

263 

264 # -- Determine the destination directory. 

265 dst_dir_path = util.user_directory_or_cwd( 

266 dst, description="Destination", must_exist=False 

267 ) 

268 

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) 

282 

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) 

289 

290 

291# ---- apio examples 

292 

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""" 

299 

300 

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] 

311 

312 

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.""" 

322 

323 # pass