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