Coverage for tallipoika/cli.py: 86.67%

51 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-04 23:32:02 +00:00

1"""JSON Canonicalization Scheme (JCS) serializer - command line interface.""" 

2 

3import argparse 

4import _io # type: ignore 

5import json 

6import pathlib 

7import sys 

8from typing import Union, no_type_check 

9 

10import tallipoika.api as api 

11from tallipoika import ( 

12 APP_ALIAS, 

13 APP_NAME, 

14 ENCODING, 

15 VERSION, 

16) 

17 

18 

19@no_type_check 

20def parse_request(argv: list[str]) -> Union[int, argparse.Namespace]: 

21 """Implementation of command line API returning parsed request.""" 

22 parser = argparse.ArgumentParser( 

23 prog=APP_ALIAS, description=APP_NAME, formatter_class=argparse.RawTextHelpFormatter 

24 ) 

25 parser.add_argument( 

26 '--in-path', 

27 '-i', 

28 dest='in_path', 

29 default='', 

30 help='Path to the file to transform. Optional\n(default: positional path value)', 

31 required=False, 

32 ) 

33 parser.add_argument( 

34 'in_path_pos', nargs='?', default=sys.stdin, help='Path to the file to transform. Optional (default: STDIN)' 

35 ) 

36 parser.add_argument( 

37 '--out-path', 

38 '-o', 

39 dest='out_path', 

40 default=sys.stdout, 

41 help='output file path for transformed file (default: STDOUT)', 

42 ) 

43 parser.add_argument( 

44 '--serialize-only', 

45 '-s', 

46 dest='serialize_only', 

47 default=False, 

48 action='store_true', 

49 help='serialize only i.e. do not sort keys (default: False)', 

50 ) 

51 parser.add_argument( 

52 '--version', 

53 '-V', 

54 dest='version_request', 

55 default=False, 

56 action='store_true', 

57 help='show version of the app and exit', 

58 required=False, 

59 ) 

60 

61 options = parser.parse_args(argv) 

62 

63 if options.version_request: 

64 print(f'{APP_NAME} version {VERSION}') 

65 return 0 

66 

67 if not options.in_path: 

68 if options.in_path_pos: 68 ↛ 71line 68 didn't jump to line 71, because the condition on line 68 was never false

69 options.in_path = options.in_path_pos 

70 else: 

71 options.in_path = sys.stdin 

72 

73 if options.in_path is not sys.stdin: 

74 in_path = pathlib.Path(options.in_path) 

75 if in_path.exists(): 

76 if in_path.is_file(): 

77 return options 

78 parser.error(f'requested source ({in_path}) is not a file') 

79 parser.error(f'requested source ({in_path}) does not exist') 

80 

81 return options 

82 

83 

84def process(options: argparse.Namespace) -> int: 

85 """Visit the source and yield the requested transformed target.""" 

86 if isinstance(options.in_path, _io.TextIOWrapper): 86 ↛ 87line 86 didn't jump to line 87, because the condition on line 86 was never true

87 loaded = options.in_path.read() 

88 else: 

89 in_path = pathlib.Path(options.in_path) 

90 with open(in_path, 'r', encoding=ENCODING) as source: 

91 loaded = source.read() 

92 

93 transformed = api.canonicalize(json.loads(loaded)) if options.serialize_only else api.serialize(json.loads(loaded)) 

94 

95 if isinstance(options.out_path, _io.TextIOWrapper): 95 ↛ 98line 95 didn't jump to line 98, because the condition on line 95 was never false

96 options.out_path.write(transformed.decode()) 

97 else: 

98 out_path = pathlib.Path(options.out_path) 

99 with open(out_path, 'wb') as target: 

100 target.write(transformed) 

101 

102 return 0 

103 

104 

105def app(argv: Union[list[str], None] = None) -> int: 

106 """Delegate processing to functional module.""" 

107 argv = sys.argv[1:] if argv is None else argv 

108 options = parse_request(argv) 

109 if isinstance(options, int): 

110 return 0 

111 return process(options)