Coverage for .tox/cov/lib/python3.11/site-packages/confattr/quickstart.py: 100%

92 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-30 12:07 +0100

1#!./runmodule.sh 

2 

3''' 

4Importing this module defines several settings which are useful in many applications and provides the :class:`~confattr.quickstart.ConfigManager` class. 

5This allows for an easy configuration setup in exchange for flexibility. 

6If you want more flexibility you can either subclass :class:`~confattr.quickstart.ConfigManager` and override specific methods or you do not import this module at all and use :class:`~confattr.configfile.ConfigFile` and :class:`~confattr.config.Config` directly. 

7''' 

8 

9import sys 

10import argparse 

11from types import ModuleType 

12from collections.abc import Callable 

13 

14from . import Config, ConfigFile, UiNotifier, Message, NotificationLevel 

15from .types import SubprocessCommandWithAlternatives as Command, OptionalExistingDirectory, TYPE_CONTEXT 

16from .utils import CallAction, HelpFormatter 

17from .configfile import Include, HelpWriter 

18 

19 

20Include.home = Config('include.home', OptionalExistingDirectory(''), help="The directory where the include command looks for relative paths. An empty string is the default, the directory where the config file is located.") 

21 

22class ConfigManager: 

23 

24 #: A setting allowing the user to specify how to open text files, e.g. the config file 

25 editor = Config('editor', Command.editor(visual=False), help="The editor to be used when opening the config file.") 

26 

27 #: A setting allowing the user to specify which messages they want to see when loading a config file with :meth:`~confattr.quickstart.ConfigManager.load` 

28 notification_level_config = Config('notification-level.config-file', NotificationLevel.ERROR) 

29 

30 #: A setting allowing the user to specify which messages they want to see when parsing a line with :meth:`~confattr.quickstart.ConfigManager.parse_line` 

31 notification_level_ui = Config('notification-level.user-interface', NotificationLevel.INFO) 

32 

33 #: Can be used to print messages to the user interface, uses :attr:`~confattr.quickstart.ConfigManager.notification_level_ui` 

34 ui_notifier: UiNotifier 

35 

36 def __init__(self, appname: str, version: str, doc: 'str|None', *, 

37 changelog_url: 'str|None' = None, 

38 show_python_version_in_version: bool = False, 

39 show_additional_modules_in_version: 'list[ModuleType]' = [], 

40 ) -> None: 

41 ''' 

42 Defines two :class:`~confattr.configfile.ConfigFile` instances with separately configurable notification levels for :meth:`~confattr.quickstart.ConfigManager.load` and :meth:`~confattr.quickstart.ConfigManager.parse_line`. 

43 This object also provides :meth:`~confattr.quickstart.ConfigManager.create_argument_parser` to create an :class:`argparse.ArgumentParser` with commonly needed arguments. 

44 Note that all :class:`~confattr.config.Config` instances must be created before instantiating this class. 

45 

46 :param appname: The name of the app, used to initialize :class:`~confattr.configfile.ConfigFile` and when printing the version number 

47 :param version: The version of the app, used when printing the version number 

48 :param doc: The package doc string, used as description when creating an :class:`~argparse.ArgumentParser` 

49 

50 :param changelog_url: The URL to the change log, used when printing the version number 

51 :param show_additional_modules_in_version: A sequence of libraries which should be included when printing the version number 

52 :param show_python_version_in_version: If true: include the Python version number when printing the version number 

53 ''' 

54 self.appname = appname 

55 self.version = version 

56 self.doc = doc 

57 self.changelog_url = changelog_url 

58 self.show_python_version_in_version = show_python_version_in_version 

59 self.show_additional_modules_in_version = show_additional_modules_in_version 

60 

61 self.config_file = ConfigFile(appname=appname, notification_level=type(self).notification_level_config) 

62 self.user_interface = ConfigFile(appname=appname, notification_level=type(self).notification_level_ui, show_line_always=False) 

63 self.user_interface.command_dict['include'].config_file = self.config_file 

64 self.ui_notifier = self.user_interface.ui_notifier 

65 

66 def set_ui_callback(self, callback: 'Callable[[Message], None]') -> None: 

67 ''' 

68 Register a user interface notification callback function to all :class:`~confattr.configfile.ConfigFile` instances created by this object. 

69 

70 See :meth:`ConfigFile.set_ui_callback() <confattr.configfile.ConfigFile.set_ui_callback>`. 

71 ''' 

72 self.config_file.set_ui_callback(callback) 

73 self.user_interface.set_ui_callback(callback) 

74 

75 def print_errors_without_ui(self) -> None: 

76 ''' 

77 Call :meth:`~confattr.quickstart.ConfigManager.set_ui_callback` with :func:`print` so that all messages are printed to the terminal. 

78 ''' 

79 self.set_ui_callback(print) 

80 

81 

82 # ------- config file ------- 

83 

84 def load(self) -> bool: 

85 ''' 

86 Load settings from config file and environment variables. 

87 

88 :return: true if no errors have occurred, false if one or more errors have occurred 

89 ''' 

90 return self.config_file.load() 

91 

92 def parse_line(self, ln: str) -> bool: 

93 ''' 

94 Parse a line from the user interface. 

95 

96 :return: true if no errors have occurred, false if one or more errors have occurred 

97 ''' 

98 return self.user_interface.parse_line(ln) 

99 

100 def edit_config(self, *, context: TYPE_CONTEXT, update: bool = False) -> None: 

101 ''' 

102 Open the config file in a text editor. 

103 

104 If the config file does not exist it is created first. 

105 The text editor can be configured with :attr:`~confattr.quickstart.ConfigManager.editor`. 

106 

107 :param context: Returns a context manager which can be used to stop and start an urwid screen. 

108 It takes the command to be executed as argument so that it can log the command 

109 and it returns the command to be executed so that it can modify the command, 

110 e.g. processing and intercepting some environment variables. 

111 :param update: Load and rewrite the config file if it is already existing. 

112 ''' 

113 if update: 

114 self.config_file.load() 

115 self.editor \ 

116 .replace(Command.WC_FILE_NAME, self.config_file.save(if_not_existing=not update)) \ 

117 .run(context=context) 

118 self.config_file.load() 

119 

120 def get_save_path(self) -> str: 

121 ''' 

122 :return: The first existing and writable file returned by :meth:`~confattr.configfile.ConfigFile.iter_config_paths` or the first path if none of the files are existing and writable. 

123 ''' 

124 return self.config_file.get_save_path() 

125 

126 def save(self, if_not_existing: bool = False) -> str: 

127 ''' 

128 Save the current values of all settings to the file returned by :meth:`~confattr.configfile.ConfigFile.get_save_path`. 

129 Directories are created as necessary. 

130 

131 :param if_not_existing: Do not overwrite the file if it is already existing. 

132 :return: The path to the file which has been written 

133 ''' 

134 

135 return self.config_file.save(if_not_existing=if_not_existing) 

136 

137 

138 # ------- creating an argument parser ------- 

139 

140 def create_argument_parser(self) -> argparse.ArgumentParser: 

141 ''' 

142 Create an :class:`argparse.ArgumentParser` with arguments to display the version, edit the config file and choose a config file 

143 by calling :meth:`~confattr.quickstart.ConfigManager.create_empty_argument_parser`, :meth:`~confattr.quickstart.ConfigManager.add_config_help_argument`, :meth:`~confattr.quickstart.ConfigManager.add_version_argument` and :meth:`~confattr.quickstart.ConfigManager.add_config_related_arguments`. 

144 ''' 

145 p = self.create_empty_argument_parser() 

146 self.add_config_help_argument(p) 

147 self.add_version_argument(p) 

148 self.add_config_related_arguments(p) 

149 return p 

150 

151 def create_empty_argument_parser(self) -> argparse.ArgumentParser: 

152 ''' 

153 Create an :class:`argparse.ArgumentParser` with the :paramref:`~confattr.quickstart.ConfigManager.doc` passed to the constructor as description and :class:`~confattr.utils.HelpFormatter` as formatter_class but without custom arguments. 

154 ''' 

155 return argparse.ArgumentParser(description=self.doc, formatter_class=HelpFormatter) 

156 

157 def add_config_help_argument(self, p: argparse.ArgumentParser) -> argparse.ArgumentParser: 

158 ''' 

159 Add a ``--help-config`` argument to an :class:`argparse.ArgumentParser`. 

160 

161 This is not part of :meth:`~confattr.quickstart.ConfigManager.add_config_related_arguments` so that this argument can be insterted between ``--help`` and ``--version``. 

162 

163 :return: The same object that has been passed in 

164 ''' 

165 p.add_argument('-H', '--help-config', action=CallAction, callback=self.print_config_help_and_exit) 

166 return p 

167 

168 def add_version_argument(self, p: argparse.ArgumentParser) -> argparse.ArgumentParser: 

169 ''' 

170 Add an argument to print the version and exit to an :class:`argparse.ArgumentParser`. 

171 

172 :return: The same object that has been passed in 

173 ''' 

174 p.add_argument('-v', '--version', action=CallAction, callback=self.print_version_and_exit) 

175 return p 

176 

177 def add_config_related_arguments(self, p: argparse.ArgumentParser) -> argparse.ArgumentParser: 

178 ''' 

179 Add arguments related to the config file to an :class:`argparse.ArgumentParser`. 

180 

181 :return: The same object that has been passed in 

182 ''' 

183 p.add_argument('-e', '--edit-config', action=CallAction, callback=self.edit_config_and_exit) 

184 p.add_argument('-E', '--update-and-edit-config', action=CallAction, callback=self.update_config_and_exit) 

185 p.add_argument('-c', '--config', action=CallAction, callback=self.select_config_file, nargs=1) 

186 return p 

187 

188 

189 # ------- printing version and help ------- 

190 

191 def print_version(self) -> None: 

192 versions: 'list[tuple[str, object]]' = [] 

193 versions.append((self.appname, self.version)) 

194 if self.show_python_version_in_version: 

195 versions.append(("python", sys.version)) 

196 for mod in self.show_additional_modules_in_version: 

197 versions.append((mod.__name__, getattr(mod, '__version__', "(unknown version)"))) 

198 

199 for name, version in versions: 

200 print("%s %s" % (name, version)) 

201 

202 if self.changelog_url: 

203 print("change log:", self.changelog_url) 

204 

205 def print_config_help(self) -> None: 

206 self.config_file.write_help(HelpWriter(sys.stdout)) 

207 

208 

209 # ------- argument parser option callbacks ------- 

210 

211 def print_version_and_exit(self) -> None: 

212 '''show the version and exit''' 

213 self.print_version() 

214 sys.exit() 

215 

216 def print_config_help_and_exit(self) -> None: 

217 '''show the config help and exit''' 

218 self.print_config_help() 

219 sys.exit() 

220 

221 def edit_config_and_exit(self) -> None: 

222 '''edit the config file and exit''' 

223 self.edit_config(context=None, update=False) 

224 self.print_errors_without_ui() 

225 sys.exit() 

226 

227 def update_config_and_exit(self) -> None: 

228 '''rewrite the config file, open it for editing and exit''' 

229 self.edit_config(context=None, update=True) 

230 self.print_errors_without_ui() 

231 sys.exit() 

232 

233 def select_config_file(self, path: str) -> None: 

234 '''use this config file instead of the default''' 

235 ConfigFile.config_path = path