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

75 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-27 13:51 +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 collections.abc import Callable 

12 

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

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

15from .utils import CallAction, HelpFormatter 

16from .configfile import Include, HelpWriter 

17 

18 

19Include.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.") 

20 

21class ConfigManager: 

22 

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

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

25 

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

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

28 

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

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

31 

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

33 ui_notifier: UiNotifier 

34 

35 def __init__(self, appname: str, version: str, doc: 'str|None') -> None: 

36 ''' 

37 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`. 

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

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

40 

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

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

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

44 ''' 

45 self.appname = appname 

46 self.version = version 

47 self.doc = doc 

48 

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

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

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

52 self.ui_notifier = self.user_interface.ui_notifier 

53 

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

55 ''' 

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

57 

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

59 ''' 

60 self.config_file.set_ui_callback(callback) 

61 self.user_interface.set_ui_callback(callback) 

62 

63 def print_errors_without_ui(self) -> None: 

64 ''' 

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

66 ''' 

67 self.set_ui_callback(print) 

68 

69 

70 # ------- config file ------- 

71 

72 def load(self) -> bool: 

73 ''' 

74 Load settings from config file and environment variables. 

75 

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

77 ''' 

78 return self.config_file.load() 

79 

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

81 ''' 

82 Parse a line from the user interface. 

83 

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

85 ''' 

86 return self.user_interface.parse_line(ln) 

87 

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

89 ''' 

90 Open the config file in a text editor. 

91 

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

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

94 

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

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

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

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

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

100 ''' 

101 if update: 

102 self.config_file.load() 

103 self.editor \ 

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

105 .run(context=context) 

106 self.config_file.load() 

107 

108 def get_save_path(self) -> str: 

109 ''' 

110 :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. 

111 ''' 

112 return self.config_file.get_save_path() 

113 

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

115 ''' 

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

117 Directories are created as necessary. 

118 

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

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

121 ''' 

122 

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

124 

125 

126 # ------- creating an argument parser ------- 

127 

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

129 ''' 

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

131 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`. 

132 ''' 

133 p = self.create_empty_argument_parser() 

134 self.add_config_help_argument(p) 

135 self.add_version_argument(p) 

136 self.add_config_related_arguments(p) 

137 return p 

138 

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

140 ''' 

141 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. 

142 ''' 

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

144 

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

146 ''' 

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

148 

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

150 

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

152 ''' 

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

154 return p 

155 

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

157 ''' 

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

159 

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

161 ''' 

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

163 return p 

164 

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

166 ''' 

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

168 

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

170 ''' 

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

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

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

174 return p 

175 

176 

177 # ------- argument parser option callbacks ------- 

178 

179 def print_version_and_exit(self) -> None: 

180 '''show the version and exit''' 

181 print("%s %s" % (self.appname, self.version)) 

182 sys.exit() 

183 

184 def print_config_help_and_exit(self) -> None: 

185 '''show the config help and exit''' 

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

187 sys.exit() 

188 

189 def edit_config_and_exit(self) -> None: 

190 '''edit the config file and exit''' 

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

192 self.print_errors_without_ui() 

193 sys.exit() 

194 

195 def update_config_and_exit(self) -> None: 

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

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

198 self.print_errors_without_ui() 

199 sys.exit() 

200 

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

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

203 ConfigFile.config_path = path