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

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(docs: bool, verbose: bool): 

200 """Implements the 'apio examples list' command group.""" 

201 

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 ) 

208 

209 # -- List the examples. 

210 if docs: 

211 list_examples_docs_format(apio_ctx) 

212 else: 

213 list_examples(apio_ctx, verbose) 

214 

215 

216# ---- apio examples fetch 

217 

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. 

224 

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 

229 

230""" 

231 

232 

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

248 

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 ) 

255 

256 # -- Create the examples manager. 

257 examples = Examples(apio_ctx) 

258 

259 # -- Determine the destination directory. 

260 dst_dir_path = util.user_directory_or_cwd( 

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

262 ) 

263 

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) 

277 

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) 

284 

285 

286# ---- apio examples 

287 

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

294 

295 

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] 

306 

307 

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

317 

318 # pass