Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

from __future__ import annotations 

 

import argparse 

import functools 

import logging 

import sys 

from typing import Any, Callable, Protocol 

 

from . import tools 

 

 

class ErrorFn(Protocol): 

def __call__(self, message: str, explain: str | None, hint: str | None) -> None: 

... 

 

 

class AbortExecutionError(Exception): 

@staticmethod 

def _strip(txt): 

txt = txt or "" 

txt = txt[1:] if txt.startswith("\n") else txt 

txt = tools.indent(txt, pre="") 

return txt[:-1] if txt.endswith("\n") else txt 

 

def __init__( 

self, 

message: str, 

explain: str | None = None, 

hint: str | None = None, 

usage: str | None = None, 

): 

self.message = message.strip() 

self.explain = explain 

self.hint = hint 

self.usage = usage 

 

def __str__(self): 

out = [] 

if self.usage: 

out.extend(self.usage.strip().split("\n")) 

if self.message: 

out.extend(self._strip(self.message).split("\n")) 

if self.explain: 

out.append("reason:") 

out.extend(tools.indent(self.explain).split("\n")) 

if self.hint: 

out.append("hint:") 

out.extend(tools.indent(self.hint).split("\n")) 

return "\n".join((line.strip() if not line.strip() else line) for line in out) 

 

 

def _add_arguments( 

parser: argparse.ArgumentParser, 

) -> None: 

"""parses args from the command line 

 

Args: 

args: command line arguments or None to pull from sys.argv 

doc: text to use in cli description 

""" 

parser.add_argument("-n", "--dry-run", dest="dryrun", action="store_true") 

parser.add_argument("-v", "--verbose", action="store_true") 

 

 

def _process_options( 

options: argparse.Namespace, errorfn: ErrorFn 

) -> argparse.Namespace | None: 

logging.basicConfig( 

format="%(levelname)s:%(name)s:(dry-run) %(message)s" 

if options.dryrun 

else "%(levelname)s:%(name)s:%(message)s", 

level=logging.DEBUG if options.verbose else logging.INFO, 

) 

 

for d in [ 

"verbose", 

]: 

delattr(options, d) 

return options 

 

 

def cli( 

add_arguments: Callable[[argparse.ArgumentParser], None] | None = None, 

process_options: Callable[[argparse.Namespace, ErrorFn], argparse.Namespace | None] 

| None = None, 

doc: str | None = None, 

): 

@functools.wraps(cli) 

def _fn(main: Callable[[argparse.Namespace], Any]): 

@functools.wraps(main) 

def _fn1(args: None | list[str] = None) -> Any: 

try: 

 

class ParserFormatter( 

argparse.ArgumentDefaultsHelpFormatter, 

argparse.RawDescriptionHelpFormatter, 

): 

pass 

 

description, _, epilog = (doc or "").partition("\n") 

parser = argparse.ArgumentParser( 

formatter_class=ParserFormatter, 

description=description, 

epilog=epilog, 

) 

_add_arguments(parser) 

if add_arguments: 

add_arguments(parser) 

 

options = parser.parse_args(args=args) 

 

def error( 

message: str, 

explain: str = "", 

hint: str = "", 

usage: str | None = None, 

): 

raise AbortExecutionError(message, explain, hint, usage) 

 

errorfn: ErrorFn = functools.partial(error, usage=parser.format_usage()) 

options.error = errorfn 

 

options = _process_options(options, errorfn) or options 

if process_options: 

options = process_options(options, errorfn) or options 

 

return main(options) 

except AbortExecutionError as err: 

print(str(err), file=sys.stderr) # noqa: T201 

raise SystemExit(2) from None 

except Exception: 

raise 

 

return _fn1 

 

return _fn