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"""LICENSE 

2Copyright 2019 Hermann Krumrey <hermann@krumreyh.com> 

3 

4This file is part of puffotter. 

5 

6puffotter is free software: you can redistribute it and/or modify 

7it under the terms of the GNU General Public License as published by 

8the Free Software Foundation, either version 3 of the License, or 

9(at your option) any later version. 

10 

11puffotter is distributed in the hope that it will be useful, 

12but WITHOUT ANY WARRANTY; without even the implied warranty of 

13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

14GNU General Public License for more details. 

15 

16You should have received a copy of the GNU General Public License 

17along with puffotter. If not, see <http://www.gnu.org/licenses/>. 

18LICENSE""" 

19 

20import os 

21import sys 

22import logging 

23from logging.handlers import RotatingFileHandler 

24from typing import Callable, Optional, Union, List 

25from argparse import ArgumentParser, Namespace 

26from puffotter.os import makedirs 

27 

28 

29def cli_start( 

30 main_func: Union[ 

31 Callable[[], None], 

32 Callable[[Namespace], None], 

33 Callable[[Namespace, logging.Logger], None] 

34 ], 

35 arg_parser: ArgumentParser, 

36 exit_msg: str = "Goodbye", 

37 package_name: Optional[str] = None, 

38 sentry_dsn: Optional[str] = None, 

39 release_name: Optional[str] = None 

40): 

41 """ 

42 Starts a program and sets up logging, as well as sentry error tracking 

43 :param main_func: The main function to call 

44 :param arg_parser: The argument parser to use 

45 :param exit_msg: The message printed when the program's execution is 

46 stopped using a keyboard interrupt 

47 :param package_name: The package name of the application 

48 :param sentry_dsn: The sentry DSN to use 

49 :param release_name: The name of the release 

50 :return: None 

51 """ 

52 try: 

53 args = arg_parser.parse_args() 

54 setup_logging(args, package_name) 

55 

56 if release_name is None: 

57 if package_name is not None: 

58 import pkg_resources 

59 version = pkg_resources.get_distribution(package_name).version 

60 release_name = package_name + "-" + version 

61 else: 

62 release_name = "Unknown" 

63 package_name = "unknown" 

64 

65 if sentry_dsn is not None: 

66 import sentry_sdk 

67 sentry_sdk.init(sentry_dsn, release=release_name) 

68 

69 from inspect import signature 

70 sign = signature(main_func) 

71 if len(sign.parameters) == 0: 

72 main_func() # type: ignore 

73 elif len(sign.parameters) == 1: 

74 main_func(args) # type: ignore 

75 elif len(sign.parameters) == 2: 

76 logger = logging.getLogger(package_name) 

77 main_func(args, logger) # type: ignore 

78 else: 

79 print("Invalid amount of parameters for main function") 

80 except KeyboardInterrupt: 

81 print(exit_msg) 

82 

83 

84def argparse_add_verbosity(parser: ArgumentParser): 

85 """ 

86 Adds --quiet, --verbose and --debug parameters to an ArgumentParser 

87 :param parser: the parser to which to add those flags 

88 :return: None 

89 """ 

90 parser.add_argument("-q", "--quiet", action="store_true", 

91 help="Sets the verbosity level of the program to " 

92 "'quiet'") 

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

94 help="Sets the verbosity level of the program to " 

95 "'verbose'") 

96 parser.add_argument("-d", "--debug", action="store_true", 

97 help="Sets the verbosity level of the program to " 

98 "'debug'") 

99 parser.add_argument("--silent", action="store_true", 

100 help="Silences all output to STDOUT") 

101 

102 

103def argparse_add_logfile(parser: ArgumentParser): 

104 """ 

105 Adds the --logfile argument to the argument parser 

106 :param parser: The argument parser to modify 

107 :return: None 

108 """ 

109 parser.add_argument("--logfile", 

110 help="Specifies the location of a logfile") 

111 

112 

113def setup_logging(args: Namespace, package_name: Optional[str]): 

114 """ 

115 Sets up logging for the provided arguments 

116 :param args: The CLI arguments 

117 :param package_name: The package name 

118 :return: None 

119 """ 

120 

121 if "silent" in args and args.silent: 

122 loglevel = 100 # Disable all logging output 

123 sys.stdout = open(os.devnull, "w") # Disable all print output 

124 elif "quiet" in args and args.quiet: 

125 loglevel = logging.ERROR 

126 elif "verbose" in args and args.verbose: 

127 loglevel = logging.INFO 

128 elif "debug" in args and args.debug: 

129 loglevel = logging.DEBUG 

130 else: 

131 loglevel = logging.WARNING 

132 

133 file_formatter = logging.Formatter( 

134 "[%(asctime)s] %(levelname)s:[%(filename)s:%(funcName)s(%(lineno)d)] " 

135 "%(message)s", 

136 datefmt="%Y-%m-%d:%H-%M-%S") 

137 

138 stream_formatter = logging.Formatter("%(levelname)s: %(message)s") 

139 stream_handler = logging.StreamHandler() 

140 stream_handler.setFormatter(stream_formatter) 

141 stream_handler.setLevel(loglevel) 

142 

143 handlers = [stream_handler] # type: List[logging.Handler] 

144 

145 if "logfile" in args and args.logfile is not None: 

146 log_file = args.logfile 

147 file_handler = logging.FileHandler(log_file) 

148 file_handler.setFormatter(file_formatter) 

149 file_handler.setLevel(logging.DEBUG) 

150 handlers.append(file_handler) 

151 

152 if package_name is not None: 

153 log_dir = os.path.join( 

154 os.path.expanduser("~"), ".config", package_name 

155 ) 

156 makedirs(log_dir) 

157 log_file = os.path.join(log_dir, "logs.log") 

158 rotating_handler = RotatingFileHandler( 

159 log_file, maxBytes=50000000, backupCount=10 

160 ) 

161 rotating_handler.setFormatter(file_formatter) 

162 rotating_handler.setLevel(logging.DEBUG) 

163 handlers.append(rotating_handler) 

164 

165 logging.basicConfig(level=logging.DEBUG, handlers=handlers)