1# -*- Mode: Python -*- 2# pygobject - Python bindings for the GObject library 3# Copyright (C) 2006 Johannes Hoelzl 4# 5# glib/option.py: GOption command line parser 6# 7# This library is free software; you can redistribute it and/or 8# modify it under the terms of the GNU Lesser General Public 9# License as published by the Free Software Foundation; either 10# version 2.1 of the License, or (at your option) any later version. 11# 12# This library is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15# Lesser General Public License for more details. 16# 17# You should have received a copy of the GNU Lesser General Public 18# License along with this library; if not, see <http://www.gnu.org/licenses/>. 19 20"""GOption command line parser 21 22Extends optparse to use the GOptionGroup, GOptionEntry and GOptionContext 23objects. So it is possible to use the gtk, gnome_program and gstreamer command 24line groups and contexts. 25 26Use this interface instead of the raw wrappers of GOptionContext and 27GOptionGroup in glib. 28""" 29 30import sys 31import optparse 32from optparse import OptParseError, OptionError, OptionValueError, \ 33 BadOptionError, OptionConflictError 34from .module import get_introspection_module 35 36from gi import _gi 37from gi._error import GError 38GLib = get_introspection_module('GLib') 39 40OPTION_CONTEXT_ERROR_QUARK = GLib.quark_to_string(GLib.option_error_quark()) 41 42__all__ = [ 43 "OptParseError", 44 "OptionError", 45 "OptionValueError", 46 "BadOptionError", 47 "OptionConflictError", 48 "Option", 49 "OptionGroup", 50 "OptionParser", 51 "make_option", 52] 53 54 55class Option(optparse.Option): 56 """Represents a command line option 57 58 To use the extended possibilities of the GOption API Option 59 (and make_option) are extended with new types and attributes. 60 61 Types: 62 filename The supplied arguments are read as filename, GOption 63 parses this type in with the GLib filename encoding. 64 65 :ivar optional_arg: 66 This does not need a arguement, but it can be supplied. 67 :ivar hidden: 68 The help list does not show this option 69 :ivar in_main: 70 This option apears in the main group, this should only 71 be used for backwards compatibility. 72 73 Use Option.REMAINING as option name to get all positional arguments. 74 75 .. NOTE:: 76 Every argument to an option is passed as utf-8 coded string, the only 77 exception are options which use the 'filename' type, its arguments 78 are passed as strings in the GLib filename encoding. 79 80 For further help, see optparse.Option. 81 """ 82 TYPES = optparse.Option.TYPES + ( 83 'filename', 84 ) 85 86 ATTRS = optparse.Option.ATTRS + [ 87 'hidden', 88 'in_main', 89 'optional_arg', 90 ] 91 92 REMAINING = '--' + GLib.OPTION_REMAINING 93 94 def __init__(self, *args, **kwargs): 95 optparse.Option.__init__(self, *args, **kwargs) 96 if not self._long_opts: 97 raise ValueError("%s at least one long option name.") 98 99 if len(self._long_opts) < len(self._short_opts): 100 raise ValueError( 101 "%s at least more long option names than short option names.") 102 103 if not self.help: 104 raise ValueError("%s needs a help message.", self._long_opts[0]) 105 106 def _set_opt_string(self, opts): 107 if self.REMAINING in opts: 108 self._long_opts.append(self.REMAINING) 109 optparse.Option._set_opt_string(self, opts) 110 if len(self._short_opts) > len(self._long_opts): 111 raise OptionError("goption.Option needs more long option names " 112 "than short option names") 113 114 def _to_goptionentries(self): 115 flags = 0 116 117 if self.hidden: 118 flags |= GLib.OptionFlags.HIDDEN 119 120 if self.in_main: 121 flags |= GLib.OptionFlags.IN_MAIN 122 123 if self.takes_value(): 124 if self.optional_arg: 125 flags |= GLib.OptionFlags.OPTIONAL_ARG 126 else: 127 flags |= GLib.OptionFlags.NO_ARG 128 129 if self.type == 'filename': 130 flags |= GLib.OptionFlags.FILENAME 131 132 for (long_name, short_name) in zip(self._long_opts, self._short_opts): 133 short_bytes = short_name[1] 134 if not isinstance(short_bytes, bytes): 135 short_bytes = short_bytes.encode("utf-8") 136 yield (long_name[2:], short_bytes, flags, self.help, self.metavar) 137 138 for long_name in self._long_opts[len(self._short_opts):]: 139 yield (long_name[2:], b'\0', flags, self.help, self.metavar) 140 141 142class OptionGroup(optparse.OptionGroup): 143 """A group of command line options. 144 145 :param str name: 146 The groups name, used to create the --help-{name} option 147 :param str description: 148 Shown as title of the groups help view 149 :param str help_description: 150 Shown as help to the --help-{name} option 151 :param list option_list: 152 The options used in this group, must be option.Option() 153 :param dict defaults: 154 A dicitionary of default values 155 :param translation_domain: 156 Sets the translation domain for gettext(). 157 158 .. NOTE:: 159 This OptionGroup does not exactly map the optparse.OptionGroup 160 interface. There is no parser object to supply, but it is possible 161 to set default values and option_lists. Also the default values and 162 values are not shared with the OptionParser. 163 164 To pass a OptionGroup into a function which expects a GOptionGroup (e.g. 165 gnome_program_init() ). OptionGroup.get_option_group() can be used. 166 167 For further help, see optparse.OptionGroup. 168 """ 169 def __init__(self, name, description, help_description="", 170 option_list=None, defaults=None, 171 translation_domain=None): 172 optparse.OptionContainer.__init__(self, Option, 'error', description) 173 self.name = name 174 self.parser = None 175 self.help_description = help_description 176 if defaults: 177 self.defaults = defaults 178 179 self.values = None 180 181 self.translation_domain = translation_domain 182 183 if option_list: 184 for option in option_list: 185 self.add_option(option) 186 187 def _create_option_list(self): 188 self.option_list = [] 189 self._create_option_mappings() 190 191 def _to_goptiongroup(self, parser): 192 def callback(option_name, option_value, group): 193 if option_name.startswith('--'): 194 opt = self._long_opt[option_name] 195 else: 196 opt = self._short_opt[option_name] 197 198 try: 199 opt.process(option_name, option_value, self.values, parser) 200 except OptionValueError: 201 error = sys.exc_info()[1] 202 gerror = GError(str(error)) 203 gerror.domain = OPTION_CONTEXT_ERROR_QUARK 204 gerror.code = GLib.OptionError.BAD_VALUE 205 gerror.message = str(error) 206 raise gerror 207 208 group = _gi.OptionGroup(self.name, self.description, 209 self.help_description, callback) 210 if self.translation_domain: 211 group.set_translation_domain(self.translation_domain) 212 213 entries = [] 214 for option in self.option_list: 215 entries.extend(option._to_goptionentries()) 216 217 group.add_entries(entries) 218 219 return group 220 221 def get_option_group(self, parser=None): 222 """ Returns the corresponding GOptionGroup object. 223 224 Can be used as parameter for gnome_program_init(), gtk_init(). 225 """ 226 self.set_values_to_defaults() 227 return self._to_goptiongroup(parser) 228 229 def set_values_to_defaults(self): 230 for option in self.option_list: 231 default = self.defaults.get(option.dest) 232 if isinstance(default, str): 233 opt_str = option.get_opt_string() 234 self.defaults[option.dest] = option.check_value( 235 opt_str, default) 236 self.values = optparse.Values(self.defaults) 237 238 239class OptionParser(optparse.OptionParser): 240 """Command line parser with GOption support. 241 242 :param bool help_enabled: 243 The --help, --help-all and --help-{group} options are enabled (default). 244 :param bool ignore_unknown_options: 245 Do not throw a exception when a option is not knwon, the option 246 will be in the result list. 247 248 .. NOTE:: 249 The OptionParser interface is not the exactly the same as the 250 optparse.OptionParser interface. Especially the usage parameter 251 is only used to show the metavar of the arguements. 252 253 OptionParser.add_option_group() does not only accept OptionGroup instances 254 but also glib.OptionGroup, which is returned by gtk_get_option_group(). 255 256 Only glib.option.OptionGroup and glib.option.Option instances should 257 be passed as groups and options. 258 259 For further help, see optparse.OptionParser. 260 """ 261 262 def __init__(self, *args, **kwargs): 263 if 'option_class' not in kwargs: 264 kwargs['option_class'] = Option 265 self.help_enabled = kwargs.pop('help_enabled', True) 266 self.ignore_unknown_options = kwargs.pop('ignore_unknown_options', 267 False) 268 optparse.OptionParser.__init__(self, add_help_option=False, 269 *args, **kwargs) 270 271 def set_usage(self, usage): 272 if usage is None: 273 self.usage = '' 274 elif usage.startswith("%prog"): 275 self.usage = usage[len("%prog"):] 276 else: 277 self.usage = usage 278 279 def _to_goptioncontext(self, values): 280 if self.description: 281 parameter_string = self.usage + " - " + self.description 282 else: 283 parameter_string = self.usage 284 context = _gi.OptionContext(parameter_string) 285 context.set_help_enabled(self.help_enabled) 286 context.set_ignore_unknown_options(self.ignore_unknown_options) 287 288 for option_group in self.option_groups: 289 if isinstance(option_group, _gi.OptionGroup): 290 g_group = option_group 291 else: 292 g_group = option_group.get_option_group(self) 293 context.add_group(g_group) 294 295 def callback(option_name, option_value, group): 296 if option_name.startswith('--'): 297 opt = self._long_opt[option_name] 298 else: 299 opt = self._short_opt[option_name] 300 opt.process(option_name, option_value, values, self) 301 302 main_group = _gi.OptionGroup(None, None, None, callback) 303 main_entries = [] 304 for option in self.option_list: 305 main_entries.extend(option._to_goptionentries()) 306 main_group.add_entries(main_entries) 307 context.set_main_group(main_group) 308 309 return context 310 311 def add_option_group(self, *args, **kwargs): 312 if isinstance(args[0], str): 313 optparse.OptionParser.add_option_group(self, 314 OptionGroup(self, *args, **kwargs)) 315 return 316 elif len(args) == 1 and not kwargs: 317 if isinstance(args[0], OptionGroup): 318 if not args[0].parser: 319 args[0].parser = self 320 if args[0].parser is not self: 321 raise ValueError("invalid OptionGroup (wrong parser)") 322 if isinstance(args[0], _gi.OptionGroup): 323 self.option_groups.append(args[0]) 324 return 325 optparse.OptionParser.add_option_group(self, *args, **kwargs) 326 327 def _get_all_options(self): 328 options = self.option_list[:] 329 for group in self.option_groups: 330 if isinstance(group, optparse.OptionGroup): 331 options.extend(group.option_list) 332 return options 333 334 def _process_args(self, largs, rargs, values): 335 context = self._to_goptioncontext(values) 336 337 # _process_args() returns the remaining parameters in rargs. 338 # The prepended program name is used to all g_set_prgname() 339 # The program name is cut away so it doesn't appear in the result. 340 rargs[:] = context.parse([sys.argv[0]] + rargs)[1:] 341 342 def parse_args(self, args=None, values=None): 343 try: 344 options, args = optparse.OptionParser.parse_args( 345 self, args, values) 346 except GError: 347 error = sys.exc_info()[1] 348 if error.domain != OPTION_CONTEXT_ERROR_QUARK: 349 raise 350 if error.code == GLib.OptionError.BAD_VALUE: 351 raise OptionValueError(error.message) 352 elif error.code == GLib.OptionError.UNKNOWN_OPTION: 353 raise BadOptionError(error.message) 354 elif error.code == GLib.OptionError.FAILED: 355 raise OptParseError(error.message) 356 else: 357 raise 358 359 for group in self.option_groups: 360 for key, value in group.values.__dict__.items(): 361 options.ensure_value(key, value) 362 363 return options, args 364 365 366make_option = Option 367