Coverage for apio/commands/apio_examples.py: 87%

76 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-06 10:20 +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 pathlib import Path 

13from typing import List, Any, Optional 

14import click 

15from rich.table import Table 

16from rich import box 

17from apio.common.apio_console import cerror 

18from apio.common import apio_console 

19from apio.common.apio_console import cout, ctable 

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 

33# ---- apio examples list 

34 

35 

36# -- Text in the rich-text format of the python rich library. 

37APIO_EXAMPLES_LIST_HELP = """ 

38The command 'apio examples list' lists the available Apio project examples \ 

39that you can use. 

40 

41Examples:[code] 

42 apio examples list # List all examples 

43 apio examples list -v # More verbose output. 

44 apio examples list | grep alhambra-ii # Show alhambra-ii examples. 

45 apio examples list | grep -i blink # Show blinking examples.[/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_part_num) 

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 

120@click.command( 

121 name="list", 

122 cls=ApioCommand, 

123 short_help="List the available apio examples.", 

124 help=APIO_EXAMPLES_LIST_HELP, 

125) 

126@options.verbose_option 

127def _list_cli(verbose: bool): 

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

129 

130 # -- Create the apio context. 

131 apio_ctx = ApioContext( 

132 project_policy=ProjectPolicy.NO_PROJECT, 

133 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

134 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

135 ) 

136 

137 # --List all available examples. 

138 list_examples(apio_ctx, verbose) 

139 

140 

141# ---- apio examples fetch 

142 

143# -- Text in the rich-text format of the python rich library. 

144APIO_EXAMPLES_FETCH_HELP = """ 

145The command 'apio examples fetch' fetches a single examples or all the \ 

146examples of a board. The destination directory is either the current \ 

147directory or the directory specified with '--dst' and it should be empty \ 

148and non existing. 

149 

150Examples:[code] 

151 apio examples fetch alhambra-ii/ledon # Single example 

152 apio examples fetch alhambra-ii # All board's examples 

153 apio examples fetch alhambra-ii -d work # Explicit destination 

154 

155""" 

156 

157 

158@click.command( 

159 name="fetch", 

160 cls=ApioCommand, 

161 short_help="Fetch the files of an example.", 

162 help=APIO_EXAMPLES_FETCH_HELP, 

163) 

164@click.argument("example", metavar="EXAMPLE", nargs=1, required=True) 

165@options.dst_option_gen(short_help="Set a different destination directory.") 

166def _fetch_cli( 

167 # Arguments 

168 example: str, 

169 # Options 

170 dst: Optional[Path], 

171): 

172 """Implements the 'apio examples fetch' command.""" 

173 

174 # -- Create the apio context. 

175 apio_ctx = ApioContext( 

176 project_policy=ProjectPolicy.NO_PROJECT, 

177 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

178 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

179 ) 

180 

181 # -- Create the examples manager. 

182 examples = Examples(apio_ctx) 

183 

184 # -- Determine the destination directory. 

185 dst_dir_path = util.user_directory_or_cwd( 

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

187 ) 

188 

189 # Parse the argument as board or board/example 

190 pattern = r"^([a-zA-Z0-9-]+)(?:[/]([a-zA-Z0-9-]+))?$" 

191 match = re.match(pattern, example) 

192 if not match: 192 ↛ 193line 192 didn't jump to line 193 because the condition on line 192 was never true

193 cerror(f"Invalid example specification '{example}.") 

194 cout( 

195 "Expecting board-id or board/example-name, e.g. " 

196 "'alhambra-ii' or 'alhambra-ii/blinky.", 

197 style=INFO, 

198 ) 

199 sys.exit(1) 

200 board_id: str = match.group(1) 

201 example_name: Optional[str] = match.group(2) 

202 

203 if example_name: 

204 # -- Copy the files of a single example. 

205 examples.copy_example_files(example, dst_dir_path) 

206 else: 

207 # -- Copy the directories of the board's examples. 

208 examples.copy_board_examples(board_id, dst_dir_path) 

209 

210 

211# ---- apio examples 

212 

213# -- Text in the rich-text format of the python rich library. 

214APIO_EXAMPLES_HELP = """ 

215The command group 'apio examples' provides subcommands for listing and \ 

216fetching Apio provided examples. Each example is a self contained \ 

217mini project that can be built and uploaded to an FPGA board. 

218""" 

219 

220 

221# -- We have only a single group with the title 'Subcommands'. 

222SUBGROUPS = [ 

223 ApioSubgroup( 

224 "Subcommands", 

225 [ 

226 _list_cli, 

227 _fetch_cli, 

228 ], 

229 ) 

230] 

231 

232 

233@click.command( 

234 name="examples", 

235 cls=ApioGroup, 

236 subgroups=SUBGROUPS, 

237 short_help="List and fetch apio examples.", 

238 help=APIO_EXAMPLES_HELP, 

239) 

240def cli(): 

241 """Implements the 'apio examples' command group.""" 

242 

243 # pass