Coverage for structured_tutorials / cli.py: 100%

39 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-21 19:08 +0100

1# Copyright (c) 2025 Mathias Ertl 

2# Licensed under the MIT License. See LICENSE file for details. 

3 

4"""Main CLI entrypoint.""" 

5 

6import argparse 

7import sys 

8from collections.abc import Sequence 

9from pathlib import Path 

10 

11import yaml 

12 

13from structured_tutorials import __version__ 

14from structured_tutorials.errors import InvalidAlternativesSelectedError 

15from structured_tutorials.models import TutorialModel 

16from structured_tutorials.output import error, setup_logging 

17from structured_tutorials.runners.local import LocalTutorialRunner 

18 

19 

20def main(argv: Sequence[str] | None = None) -> None: 

21 """Main entry function for the command-line.""" 

22 parser = argparse.ArgumentParser() 

23 parser.add_argument("path", type=Path) 

24 parser.add_argument("--version", action="version", version=__version__) 

25 parser.add_argument("-a", "--alternative", dest="alternatives", action="append", default=[]) 

26 parser.add_argument("--no-colors", action="store_true", default=False) 

27 parser.add_argument( 

28 "-n", 

29 "--non-interactive", 

30 dest="interactive", 

31 action="store_false", 

32 default=True, 

33 help="Never prompt for any user input.", 

34 ) 

35 parser.add_argument( 

36 "--log-level", 

37 choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], 

38 default="INFO", 

39 help="Override root log level", 

40 ) 

41 parser.add_argument( 

42 "--hide-commands", 

43 dest="show_commands", 

44 action="store_false", 

45 default=True, 

46 help="Do not show commands that are run by the tutorial.", 

47 ) 

48 parser.add_argument( 

49 "--hide-command-output", 

50 dest="show_command_output", 

51 action="store_false", 

52 default=True, 

53 help="Do not show the output of commands that are run on the terminal.", 

54 ) 

55 args = parser.parse_args(argv) 

56 

57 setup_logging(level=args.log_level, no_colors=args.no_colors, show_commands=args.show_commands) 

58 

59 try: 

60 tutorial = TutorialModel.from_file(args.path) 

61 except yaml.YAMLError as exc: # an invalid YAML file 

62 error(f"{args.path}: Invalid YAML file:") 

63 print(exc, file=sys.stderr) 

64 sys.exit(1) 

65 except ValueError as ex: # thrown by Pydantic model loading 

66 error(f"{args.path}: File is not a valid Tutorial:") 

67 print(ex, file=sys.stderr) 

68 sys.exit(1) 

69 

70 runner = LocalTutorialRunner( 

71 tutorial, 

72 alternatives=tuple(args.alternatives), 

73 show_command_output=args.show_command_output, 

74 interactive=args.interactive, 

75 ) 

76 

77 try: 

78 runner.validate_alternatives() 

79 except InvalidAlternativesSelectedError as ex: 

80 error(str(ex)) 

81 sys.exit(1) 

82 

83 runner.run()