Coverage for apio/commands/apio_api.py: 88%

250 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 api' command""" 

9 

10import sys 

11import os 

12from typing import Dict, List, Self, Optional 

13from dataclasses import dataclass 

14import json 

15from pathlib import Path 

16import click 

17from apio.commands import options 

18 

19# from apio.managers import packages 

20from apio.managers.examples import Examples, ExampleInfo 

21from apio.common.apio_console import cout, cerror 

22from apio.common.apio_styles import INFO 

23from apio.common.common_util import get_project_source_files 

24from apio.utils import cmd_util, usb_util, serial_util, util 

25from apio.utils.usb_util import UsbDevice 

26from apio.utils.serial_util import SerialDevice 

27from apio.apio_context import ( 

28 ApioContext, 

29 PackagesPolicy, 

30 ProjectPolicy, 

31 RemoteConfigPolicy, 

32) 

33from apio.utils.cmd_util import ( 

34 ApioGroup, 

35 ApioSubgroup, 

36 ApioCommand, 

37 ApioCmdContext, 

38) 

39 

40 

41timestamp_option = click.option( 

42 "timestamp", # Var name. 

43 "-t", 

44 "--timestamp", 

45 type=str, 

46 metavar="text", 

47 help="Set a user provided timestamp.", 

48 cls=cmd_util.ApioOption, 

49) 

50 

51output_option = click.option( 

52 "output", # Var name. 

53 "-o", 

54 "--output", 

55 type=str, 

56 metavar="file-name", 

57 help="Set output file.", 

58 cls=cmd_util.ApioOption, 

59) 

60 

61 

62def write_as_json_doc(top_dict: Dict, output_flag: str, force_flag: bool): 

63 """A common function to write a dict as a JSON doc.""" 

64 # -- Format the top dict as json text. 

65 text = json.dumps(top_dict, indent=2) 

66 

67 if output_flag: 

68 # -- Output the json text to a user specified file. 

69 output_path = Path(output_flag) 

70 

71 if output_path.is_dir(): 71 ↛ 72line 71 didn't jump to line 72 because the condition on line 71 was never true

72 cerror(f"The output path {output_path} is a directory.") 

73 sys.exit(1) 

74 

75 if output_path.exists() and not force_flag: 75 ↛ 76line 75 didn't jump to line 76 because the condition on line 75 was never true

76 cerror(f"The file already exists {output_path}.") 

77 cout("Use the --force option to allow overwriting.", style=INFO) 

78 sys.exit(1) 

79 

80 with open(output_flag, "w", encoding="utf-8") as f: 

81 f.write(text) 

82 else: 

83 # -- Output the json text to stdout. 

84 print(text, file=sys.stdout) 

85 

86 

87# ------ apio api get-system 

88 

89 

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

91APIO_API_GET_SYSTEM_HELP = """ 

92The command 'apio api get-system' exports information about apio and \ 

93the underlying system as a JSON foc. It is similar to the command \ 

94'apio info system' which is intended for human consumption. 

95 

96 

97The optional flag '--timestamp' allows the caller to embed in the JSON \ 

98document a known timestamp that allows to verify that the JSON document \ 

99was indeed was generated by the same invocation. 

100 

101Examples:[code] 

102 apio api get-system # Write to stdout 

103 apio api get-system -o apio.json # Write to a file[/code] 

104""" 

105 

106 

107@click.command( 

108 name="get-system", 

109 cls=ApioCommand, 

110 short_help="Retrieve apio and system information.", 

111 help=APIO_API_GET_SYSTEM_HELP, 

112) 

113# @click.pass_context 

114@timestamp_option 

115@output_option 

116@options.force_option_gen(short_help="Overwrite output file.") 

117def _get_system_cli( 

118 # Options 

119 timestamp: str, 

120 output: str, 

121 force: bool, 

122): 

123 """Implements the 'apio apio get-system' command.""" 

124 

125 apio_ctx = ApioContext( 

126 project_policy=ProjectPolicy.NO_PROJECT, 

127 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

128 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

129 ) 

130 

131 # -- The top dict that we will emit as json. 

132 top_dict = {} 

133 

134 # -- Append user timestamp if specified. 

135 if timestamp: 135 ↛ 138line 135 didn't jump to line 138 because the condition on line 135 was always true

136 top_dict["timestamp"] = timestamp 

137 

138 section_dict = {} 

139 

140 # -- Add fields. 

141 section_dict["apio-version"] = util.get_apio_version() 

142 section_dict["python-version"] = util.get_python_version() 

143 section_dict["platform-id"] = apio_ctx.platform_id 

144 section_dict["apio-python_package"] = str( 

145 util.get_path_in_apio_package("") 

146 ) 

147 section_dict["apio-home-dir"] = str(apio_ctx.apio_home_dir) 

148 section_dict["apio-packages-dir"] = str(apio_ctx.apio_packages_dir) 

149 section_dict["remote-config-url"] = apio_ctx.profile.remote_config_url 

150 section_dict["verible-formatter"] = str( 

151 apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-format" 

152 ) 

153 section_dict["verible-language-server"] = str( 

154 apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-ls" 

155 ) 

156 

157 # -- Add section 

158 top_dict["system"] = section_dict 

159 

160 # -- Write out 

161 write_as_json_doc(top_dict, output, force) 

162 

163 

164# ------ apio api get-project 

165 

166 

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

168APIO_API_GET_PROJECT_HELP = """ 

169The command 'apio api get-project' exports information about an Apio 

170project as a JSON foc. 

171 

172The optional flag '--timestamp' allows the caller to embed in the JSON \ 

173document a known timestamp that allows to verify that the JSON document \ 

174was indeed was generated by the same invocation. 

175 

176Examples:[code] 

177 apio api get-project # Report default env 

178 apio api get-project -e env1 # Report specified env 

179 apio api get-project -p foo/bar # Project in another dir 

180 apio api get-project -o apio.json # Write to a file[/code] 

181""" 

182 

183 

184@click.command( 

185 name="get-project", 

186 cls=ApioCommand, 

187 short_help="Get project information.", 

188 help=APIO_API_GET_PROJECT_HELP, 

189) 

190# @click.pass_context 

191@options.env_option_gen() 

192@options.project_dir_option 

193@timestamp_option 

194@output_option 

195@options.force_option_gen(short_help="Overwrite output file.") 

196def _get_project_cli( 

197 # Options 

198 env: str, 

199 project_dir: Optional[Path], 

200 timestamp: str, 

201 output: str, 

202 force: bool, 

203): 

204 """Implements the 'apio apio get-project' command.""" 

205 

206 apio_ctx = ApioContext( 

207 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

208 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

209 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

210 project_dir_arg=project_dir, 

211 env_arg=env, 

212 ) 

213 

214 # -- Change to the project's folder. 

215 os.chdir(apio_ctx.project_dir) 

216 

217 # -- The top dict that we will emit as json. 

218 top_dict = {} 

219 

220 # -- Append user timestamp if specified. 

221 if timestamp: 221 ↛ 224line 221 didn't jump to line 224 because the condition on line 221 was always true

222 top_dict["timestamp"] = timestamp 

223 

224 section_dict = {} 

225 

226 active_env_dict = {} 

227 active_env_dict["name"] = apio_ctx.project.env_name 

228 active_env_dict["options"] = apio_ctx.project.env_options 

229 section_dict["active-env"] = active_env_dict 

230 

231 section_dict["envs"] = apio_ctx.project.env_names 

232 

233 synth_srcs, test_srcs = get_project_source_files() 

234 section_dict["synth-files"] = synth_srcs 

235 section_dict["test-benches"] = test_srcs 

236 

237 # -- Add section 

238 top_dict["project"] = section_dict 

239 

240 # -- Write out 

241 write_as_json_doc(top_dict, output, force) 

242 

243 

244# ------ apio api get-boards 

245 

246 

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

248APIO_API_GET_BOARDS_HELP = """ 

249The command 'apio api get-boards' exports apio boards information as a \ 

250JSON document. 

251 

252The optional flag '--timestamp' allows the caller to embed in the JSON \ 

253document a known timestamp that allows to verify that the JSON document \ 

254was indeed was generated by the same invocation. 

255 

256Examples:[code] 

257 apio api get-boards # Write to stdout 

258 apio api get-boards -o apio.json # Write to a file[/code] 

259""" 

260 

261 

262@click.command( 

263 name="get-boards", 

264 cls=ApioCommand, 

265 short_help="Retrieve boards information.", 

266 help=APIO_API_GET_BOARDS_HELP, 

267) 

268@timestamp_option 

269@output_option 

270@options.force_option_gen(short_help="Overwrite output file.") 

271def _get_boards_cli( 

272 # Options 

273 timestamp: str, 

274 output: str, 

275 force: bool, 

276): 

277 """Implements the 'apio apio get-boards' command.""" 

278 

279 # -- For now, the information is not in a project context. That may 

280 # -- change in the future. 

281 apio_ctx = ApioContext( 

282 project_policy=ProjectPolicy.NO_PROJECT, 

283 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

284 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

285 ) 

286 

287 # -- The top dict that we will emit as json. 

288 top_dict = {} 

289 

290 # -- Append user timestamp if specified. 

291 if timestamp: 291 ↛ 295line 291 didn't jump to line 295 because the condition on line 291 was always true

292 top_dict["timestamp"] = timestamp 

293 

294 # -- Generate the boards section. 

295 section = {} 

296 for board_id, board_info in apio_ctx.boards.items(): 

297 # -- The board output dict. 

298 board_dict = {} 

299 

300 # -- Add board description 

301 board_dict["description"] = board_info.get("description", None) 

302 

303 # -- Add board's fpga information. 

304 fpga_dict = {} 

305 fpga_id = board_info.get("fpga-id", None) 

306 fpga_info = apio_ctx.fpgas.get(fpga_id, {}) 

307 fpga_dict["id"] = fpga_id 

308 fpga_dict["part-num"] = fpga_info.get("part-num", None) 

309 fpga_dict["arch"] = fpga_info.get("arch", None) 

310 fpga_dict["size"] = fpga_info.get("size", None) 

311 board_dict["fpga"] = fpga_dict 

312 

313 # -- Add board's programmer information. 

314 programmer_dict = {} 

315 programmer_id = board_info.get("programmer", {}).get("id", None) 

316 programmer_dict["id"] = programmer_id 

317 board_dict["programmer"] = programmer_dict 

318 

319 # -- Add the board to the boards dict. 

320 section[board_id] = board_dict 

321 

322 top_dict["boards"] = section 

323 

324 # -- Write out 

325 write_as_json_doc(top_dict, output, force) 

326 

327 

328# ------ apio api get-fpgas 

329 

330 

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

332APIO_API_GET_FPGAS_HELP = """ 

333The command 'apio api get-fpgas' exports apio FPGAss information as a \ 

334JSON document. 

335 

336The optional flag '--timestamp' allows the caller to embed in the JSON \ 

337document a known timestamp that allows to verify that the JSON document \ 

338was indeed was generated by the same invocation. 

339 

340Examples:[code] 

341 apio api get-fpgas # Write to stdout 

342 apio api get-fpgas -o apio.json # Write to a file[/code] 

343""" 

344 

345 

346@click.command( 

347 name="get-fpgas", 

348 cls=ApioCommand, 

349 short_help="Retrieve FPGAs information.", 

350 help=APIO_API_GET_FPGAS_HELP, 

351) 

352@timestamp_option 

353@output_option 

354@options.force_option_gen(short_help="Overwrite output file.") 

355def _get_fpgas_cli( 

356 # Options 

357 timestamp: str, 

358 output: str, 

359 force: bool, 

360): 

361 """Implements the 'apio apio get-fpgas' command.""" 

362 

363 # -- For now, the information is not in a project context. That may 

364 # -- change in the future. 

365 apio_ctx = ApioContext( 

366 project_policy=ProjectPolicy.NO_PROJECT, 

367 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

368 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

369 ) 

370 

371 # -- The top dict that we will emit as json. 

372 top_dict = {} 

373 

374 # -- Append user timestamp if specified. 

375 if timestamp: 375 ↛ 379line 375 didn't jump to line 379 because the condition on line 375 was always true

376 top_dict["timestamp"] = timestamp 

377 

378 # -- Generate the fpgas section 

379 section = {} 

380 for fpga_id, fpga_info in apio_ctx.fpgas.items(): 

381 # -- The fpga output dict. 

382 fpga_dict = {} 

383 

384 fpga_dict["part-num"] = fpga_info.get("part-num", None) 

385 fpga_dict["arch"] = fpga_info.get("arch", None) 

386 fpga_dict["size"] = fpga_info.get("size", None) 

387 

388 # -- Add the fpga to the fpgas dict. 

389 section[fpga_id] = fpga_dict 

390 

391 top_dict["fpgas"] = section 

392 

393 # -- Write out 

394 write_as_json_doc(top_dict, output, force) 

395 

396 

397# ------ apio api get-programmers 

398 

399 

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

401APIO_API_GET_PROGRAMMERS_HELP = """ 

402The command 'apio api get-programmers' exports apio programmers information \ 

403as a JSON document. 

404 

405The optional flag '--timestamp' allows the caller to embed in the JSON \ 

406document a known timestamp that allows to verify that the JSON document \ 

407was indeed was generated by the same invocation. 

408 

409Examples:[code] 

410 apio api get-programmers # Write to stdout 

411 apio api get-programmers -o apio.json # Write to a file[/code] 

412""" 

413 

414 

415@click.command( 

416 name="get-programmers", 

417 cls=ApioCommand, 

418 short_help="Retrieve programmers information.", 

419 help=APIO_API_GET_PROGRAMMERS_HELP, 

420) 

421@timestamp_option 

422@output_option 

423@options.force_option_gen(short_help="Overwrite output file.") 

424def _get_programmers_cli( 

425 # Options 

426 timestamp: str, 

427 output: str, 

428 force: bool, 

429): 

430 """Implements the 'apio apio get-programmers' command.""" 

431 

432 # -- For now, the information is not in a project context. That may 

433 # -- change in the future. 

434 apio_ctx = ApioContext( 

435 project_policy=ProjectPolicy.NO_PROJECT, 

436 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

437 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

438 ) 

439 

440 # -- The top dict that we will emit as json. 

441 top_dict = {} 

442 

443 # -- Append user timestamp if specified. 

444 if timestamp: 444 ↛ 448line 444 didn't jump to line 448 because the condition on line 444 was always true

445 top_dict["timestamp"] = timestamp 

446 

447 # -- Generate the 'programmers' section. 

448 section = {} 

449 for programmer_id, programmer_info in apio_ctx.programmers.items(): 

450 section[programmer_id] = programmer_info 

451 

452 top_dict["programmers"] = section 

453 

454 # -- Write out 

455 write_as_json_doc(top_dict, output, force) 

456 

457 

458# ------ apio api get-examples 

459 

460 

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

462APIO_API_GET_EXAMPLES_HELP = """ 

463The command 'apio api get-examples' exports apio examples information as a \ 

464JSON document. 

465 

466The optional flag '--timestamp' allows the caller to embed in the JSON \ 

467document a known timestamp that allows to verify that the JSON document \ 

468was indeed was generated by the same invocation. 

469 

470Examples:[code] 

471 apio api get-examples # Write to stdout 

472 apio api get-examples -o apio.json # Write to a file[/code] 

473""" 

474 

475 

476@click.command( 

477 name="get-examples", 

478 cls=ApioCommand, 

479 short_help="Retrieve examples information.", 

480 help=APIO_API_GET_EXAMPLES_HELP, 

481) 

482@timestamp_option 

483@output_option 

484@options.force_option_gen(short_help="Overwrite output file.") 

485def _get_examples_cli( 

486 # Options 

487 timestamp: str, 

488 output: str, 

489 force: bool, 

490): 

491 """Implements the 'apio apio get-examples' command.""" 

492 

493 # -- For now, the information is not in a project context. That may 

494 # -- change in the future. 

495 apio_ctx = ApioContext( 

496 project_policy=ProjectPolicy.NO_PROJECT, 

497 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

498 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

499 ) 

500 

501 # -- Get examples infos. 

502 examples: List[ExampleInfo] = Examples(apio_ctx).get_examples_infos() 

503 

504 # -- Group examples by boards 

505 boards_examples: Dict[str, List[ExampleInfo]] = {} 

506 for example in examples: 

507 board_examples = boards_examples.get(example.board_id, []) 

508 board_examples.append(example) 

509 boards_examples[example.board_id] = board_examples 

510 

511 # -- The top dict that we will emit as json. 

512 top_dict = {} 

513 

514 # -- Append user timestamp if specified. 

515 if timestamp: 515 ↛ 519line 515 didn't jump to line 519 because the condition on line 515 was always true

516 top_dict["timestamp"] = timestamp 

517 

518 # -- Generate the 'examples' section. 

519 section = {} 

520 for board, board_examples in boards_examples.items(): 

521 board_dict = {} 

522 # -- Generate board examples 

523 for example_info in board_examples: 

524 example_dict = {} 

525 example_dict["description"] = example_info.description 

526 board_dict[example_info.example_name] = example_dict 

527 

528 section[board] = board_dict 

529 

530 top_dict["examples"] = section 

531 

532 # -- Write out 

533 write_as_json_doc(top_dict, output, force) 

534 

535 

536# ------ apio api get-commands 

537 

538 

539@dataclass(frozen=True) 

540class CmdInfo: 

541 """Represents the information of a single apio command.""" 

542 

543 name: str 

544 path: List[str] 

545 cli: click.Command 

546 children: List[Self] 

547 

548 

549def scan_children(cmd_cli) -> Dict: 

550 """Return a dict describing this command subtree.""" 

551 result = {} 

552 

553 # -- Sanity check 

554 assert isinstance(result, dict), type(result) 

555 

556 # -- If this is a simple command, it has no sub commands. 

557 if isinstance(cmd_cli, ApioCommand): 

558 return result 

559 

560 # -- Here we have a group and it should have at least one sub command. 

561 assert isinstance(cmd_cli, ApioGroup), type(cmd_cli) 

562 subgroups: List[ApioSubgroup] = cmd_cli.subgroups 

563 

564 # -- Create the dict for the command subgroups. 

565 subcommands_dict = {} 

566 result["commands"] = subcommands_dict 

567 

568 # -- Iterate the subgroups and populate them. We flaten the subcommands 

569 # -- group into a single list of commands. 

570 for subgroup in subgroups: 

571 assert isinstance(subgroup, ApioSubgroup), type(subgroup) 

572 assert isinstance(subgroup.title, str), type(subgroup.title) 

573 for subcommand in subgroup.commands: 

574 subcommand_dict = scan_children(subcommand) 

575 subcommands_dict[subcommand.name] = subcommand_dict 

576 

577 # -- All done ok. 

578 return result 

579 

580 

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

582APIO_API_GET_COMMANDS_HELP = """ 

583The command 'apio api get-commands' exports apio command structure \ 

584of Apio as a JSON doc. This is used by various tools such as 

585documentation generators and tests. 

586 

587The optional flag '--timestamp' allows the caller to embed in the JSON \ 

588document a known timestamp that allows to verify that the JSON document \ 

589was indeed was generated by the same invocation. 

590 

591Examples:[code] 

592 apio api get-commands # Write to stdout 

593 apio api get-commands -o apio.json # Write to a file[/code] 

594""" 

595 

596 

597@click.command( 

598 name="get-commands", 

599 cls=ApioCommand, 

600 short_help="Retrieve apio commands information.", 

601 help=APIO_API_GET_COMMANDS_HELP, 

602) 

603@click.pass_context 

604@timestamp_option 

605@output_option 

606@options.force_option_gen(short_help="Overwrite output file.") 

607def _get_commands_cli( 

608 # Click context 

609 cmd_ctx: ApioCmdContext, 

610 # Options 

611 timestamp: str, 

612 output: str, 

613 force: bool, 

614): 

615 """Implements the 'apio apio get-commands' command.""" 

616 

617 # -- Find the top cli which is the "apio" command. Would access it 

618 # -- directly but it would create a circular python import. 

619 ctx = cmd_ctx 

620 while ctx.parent: 

621 ctx = ctx.parent 

622 assert isinstance(ctx, ApioCmdContext), type(ctx) 

623 top_cli = ctx.command 

624 assert top_cli.name == "apio", top_cli 

625 

626 # -- This initializes the console, print active env vars, etc. 

627 ApioContext( 

628 project_policy=ProjectPolicy.NO_PROJECT, 

629 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

630 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

631 ) 

632 

633 # -- The top dict that we will emit as json. 

634 top_dict = {} 

635 

636 # -- Append user timestamp if specified. 

637 if timestamp: 637 ↛ 640line 637 didn't jump to line 640 because the condition on line 637 was always true

638 top_dict["timestamp"] = timestamp 

639 

640 section_dict = {} 

641 section_dict["apio"] = scan_children(top_cli) 

642 top_dict["commands"] = section_dict 

643 

644 # -- Write out 

645 write_as_json_doc(top_dict, output, force) 

646 

647 

648# ------ apio api scan-devices 

649 

650 

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

652APIO_API_SCAN_DEVICES_HELP = """ 

653The command 'apio api scan-devices' scans and report the available usb and \ 

654serial devices. 

655 

656The optional flag '--timestamp' allows the caller to embed in the JSON \ 

657document a known timestamp that allows to verify that the JSON document \ 

658was indeed was generated by the same invocation. 

659 

660Examples:[code] 

661 apio api scan-devices # Write to stdout 

662 apio api scan-devices -o apio.json # Write to a file[/code] 

663""" 

664 

665 

666@click.command( 

667 name="scan-devices", 

668 cls=ApioCommand, 

669 short_help="Scan and report available devices.", 

670 help=APIO_API_SCAN_DEVICES_HELP, 

671) 

672@timestamp_option 

673@output_option 

674@options.force_option_gen(short_help="Overwrite output file.") 

675def _scan_devices_cli( 

676 # Options 

677 timestamp: str, 

678 output: str, 

679 force: bool, 

680): 

681 """Implements the 'apio apio scan-devices' command.""" 

682 

683 # -- For now, the information is not in a project context. That may 

684 # -- change in the future. We need the config since we use libusb from 

685 # -- the packages. 

686 apio_ctx = ApioContext( 

687 project_policy=ProjectPolicy.NO_PROJECT, 

688 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

689 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

690 ) 

691 

692 # -- The top dict that we will emit as json. 

693 top_dict = {} 

694 

695 # -- Append user timestamp if specified. 

696 if timestamp: 696 ↛ 702line 696 didn't jump to line 702 because the condition on line 696 was always true

697 top_dict["timestamp"] = timestamp 

698 

699 # -- We need the packages for the 'libusb' backend. 

700 # packages.install_missing_packages_on_the_fly(apio_ctx.packages_context) 

701 

702 usb_devices: List[UsbDevice] = usb_util.scan_usb_devices(apio_ctx) 

703 

704 # -- Scan and report usb devices. 

705 section = [] 

706 for device in usb_devices: 706 ↛ 707line 706 didn't jump to line 707 because the loop on line 706 never started

707 dev = {} 

708 dev["vid"] = device.vendor_id 

709 dev["pid"] = device.product_id 

710 dev["bus"] = device.bus 

711 dev["device"] = device.device 

712 dev["manufacturer"] = device.manufacturer 

713 dev["product"] = device.product 

714 dev["serial-number"] = device.serial_number 

715 dev["device_type"] = device.device_type 

716 

717 section.append(dev) 

718 

719 top_dict["usb-devices"] = section 

720 

721 # -- Scan and report serial devices. 

722 serial_devices: List[SerialDevice] = serial_util.scan_serial_devices() 

723 

724 section = [] 

725 for device in serial_devices: 725 ↛ 726line 725 didn't jump to line 726 because the loop on line 725 never started

726 dev = {} 

727 dev["port"] = device.port 

728 dev["port-name"] = device.port_name 

729 dev["vendor-id"] = device.vendor_id 

730 dev["product-id"] = device.product_id 

731 dev["manufacturer"] = device.manufacturer 

732 dev["product"] = device.product 

733 dev["serial-number"] = device.serial_number 

734 dev["device-type"] = device.device_type 

735 

736 section.append(dev) 

737 

738 top_dict["serial-devices"] = section 

739 

740 # -- Write out 

741 write_as_json_doc(top_dict, output, force) 

742 

743 

744# ------ apio apio 

745 

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

747APIO_API_HELP = """ 

748The command group 'apio api' contains subcommands that that are intended \ 

749to be used by tools and programs such as icestudio, rather than being used \ 

750directly by users. 

751""" 

752 

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

754SUBGROUPS = [ 

755 ApioSubgroup( 

756 "Subcommands", 

757 [ 

758 _get_system_cli, 

759 _get_project_cli, 

760 _get_boards_cli, 

761 _get_fpgas_cli, 

762 _get_programmers_cli, 

763 _get_examples_cli, 

764 _get_commands_cli, 

765 _scan_devices_cli, 

766 ], 

767 ) 

768] 

769 

770 

771@click.command( 

772 name="api", 

773 cls=ApioGroup, 

774 subgroups=SUBGROUPS, 

775 short_help="Apio programmatic interface.", 

776 help=APIO_API_HELP, 

777) 

778def cli(): 

779 """Implements the 'apio apio' command group.""" 

780 

781 # pass