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

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

32 

33 

34# ---- apio examples list 

35 

36 

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. 

41 

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

49 

50 

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) 

54 

55 

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

59 

60 # -- Get list of examples. 

61 entries: List[ExampleInfo] = Examples(apio_ctx).get_examples_infos() 

62 

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

64 entries.sort(key=examples_sort_key) 

65 

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 ) 

75 

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 ) 

87 

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 

95 

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) 

104 

105 # -- Append the row 

106 table.add_row(*values) 

107 

108 # -- Render the table. 

109 cout() 

110 ctable(table) 

111 

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 ) 

120 

121 

122def list_examples_docs_format(apio_ctx: ApioContext): 

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

124 

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

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 `{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 ) 

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: Optional[Path], 

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

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