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

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

9 

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 

30 

31# ---- apio examples list 

32 

33 

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. 

38 

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

46 

47 

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) 

51 

52 

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

56 

57 # -- Get list of examples. 

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

59 

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

61 entries.sort(key=examples_sort_key) 

62 

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 ) 

72 

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 ) 

84 

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 

92 

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) 

101 

102 # -- Append the row 

103 table.add_row(*values) 

104 

105 # -- Render the table. 

106 cout() 

107 ctable(table) 

108 

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 ) 

117 

118 

119def list_examples_docs_format(apio_ctx: ApioContext): 

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

121 

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 ) 

127 

128 # -- Get list of examples. 

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

130 

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

132 entries.sort(key=examples_sort_key) 

133 

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 ) 

140 

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 ) 

155 

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: 

161 

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

163 

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 ) 

176 

177 last_arch = entry.fpga_arch 

178 

179 # -- Write the entry 

180 cwrite( 

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

182 entry.name.ljust(w1), 

183 entry.description.ljust(w2), 

184 ) 

185 ) 

186 

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

188 

189 

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

204 

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 ) 

211 

212 # -- List the examples. 

213 if docs: 

214 list_examples_docs_format(apio_ctx) 

215 else: 

216 list_examples(apio_ctx, verbose) 

217 

218 

219# ---- apio examples fetch 

220 

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. 

227 

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 

232 

233""" 

234 

235 

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

252 

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 ) 

259 

260 # -- Create the examples manager. 

261 examples = Examples(apio_ctx) 

262 

263 # -- Determine the destination directory. 

264 dst_dir_path = util.user_directory_or_cwd( 

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

266 ) 

267 

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) 

281 

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) 

288 

289 

290# ---- apio examples 

291 

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

298 

299 

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] 

310 

311 

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

321 

322 # pass