~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/option.py

- help for global options

- test for this

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2006 by Canonical Ltd
2
 
#
 
1
# Copyright (C) 2004, 2005 by Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
# TODO: For things like --diff-prefix, we want a way to customize the display
18
 
# of the option argument.
19
17
 
20
 
import optparse
21
18
import re
22
19
 
23
 
from bzrlib.trace import warning
 
20
import bzrlib.commands
 
21
from bzrlib.trace import warning, mutter
24
22
from bzrlib.revisionspec import RevisionSpec
25
 
from bzrlib.errors import BzrCommandError
26
23
 
27
24
 
28
25
def _parse_revision_str(revstr):
32
29
    each revision specifier supplied.
33
30
 
34
31
    >>> _parse_revision_str('234')
35
 
    [<RevisionSpec_revno 234>]
 
32
    [<RevisionSpec_int 234>]
36
33
    >>> _parse_revision_str('234..567')
37
 
    [<RevisionSpec_revno 234>, <RevisionSpec_revno 567>]
 
34
    [<RevisionSpec_int 234>, <RevisionSpec_int 567>]
38
35
    >>> _parse_revision_str('..')
39
36
    [<RevisionSpec None>, <RevisionSpec None>]
40
37
    >>> _parse_revision_str('..234')
41
 
    [<RevisionSpec None>, <RevisionSpec_revno 234>]
 
38
    [<RevisionSpec None>, <RevisionSpec_int 234>]
42
39
    >>> _parse_revision_str('234..')
43
 
    [<RevisionSpec_revno 234>, <RevisionSpec None>]
 
40
    [<RevisionSpec_int 234>, <RevisionSpec None>]
44
41
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
45
 
    [<RevisionSpec_revno 234>, <RevisionSpec_revno 456>, <RevisionSpec_revno 789>]
 
42
    [<RevisionSpec_int 234>, <RevisionSpec_int 456>, <RevisionSpec_int 789>]
46
43
    >>> _parse_revision_str('234....789') #Error ?
47
 
    [<RevisionSpec_revno 234>, <RevisionSpec None>, <RevisionSpec_revno 789>]
 
44
    [<RevisionSpec_int 234>, <RevisionSpec None>, <RevisionSpec_int 789>]
48
45
    >>> _parse_revision_str('revid:test@other.com-234234')
49
46
    [<RevisionSpec_revid revid:test@other.com-234234>]
50
47
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
51
48
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revid revid:test@other.com-234235>]
52
49
    >>> _parse_revision_str('revid:test@other.com-234234..23')
53
 
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revno 23>]
 
50
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_int 23>]
54
51
    >>> _parse_revision_str('date:2005-04-12')
55
52
    [<RevisionSpec_date date:2005-04-12>]
56
53
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
60
57
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
61
58
    [<RevisionSpec_date date:2005-04-12,12:24:33>]
62
59
    >>> _parse_revision_str('-5..23')
63
 
    [<RevisionSpec_revno -5>, <RevisionSpec_revno 23>]
 
60
    [<RevisionSpec_int -5>, <RevisionSpec_int 23>]
64
61
    >>> _parse_revision_str('-5')
65
 
    [<RevisionSpec_revno -5>]
 
62
    [<RevisionSpec_int -5>]
66
63
    >>> _parse_revision_str('123a')
67
64
    Traceback (most recent call last):
68
65
      ...
69
 
    NoSuchRevisionSpec: No namespace registered for string: '123a'
 
66
    BzrError: No namespace registered for string: '123a'
70
67
    >>> _parse_revision_str('abc')
71
68
    Traceback (most recent call last):
72
69
      ...
73
 
    NoSuchRevisionSpec: No namespace registered for string: 'abc'
 
70
    BzrError: No namespace registered for string: 'abc'
74
71
    >>> _parse_revision_str('branch:../branch2')
75
72
    [<RevisionSpec_branch branch:../branch2>]
76
 
    >>> _parse_revision_str('branch:../../branch2')
77
 
    [<RevisionSpec_branch branch:../../branch2>]
78
 
    >>> _parse_revision_str('branch:../../branch2..23')
79
 
    [<RevisionSpec_branch branch:../../branch2>, <RevisionSpec_revno 23>]
80
73
    """
81
74
    # TODO: Maybe move this into revisionspec.py
 
75
    old_format_re = re.compile('\d*:\d*')
 
76
    m = old_format_re.match(revstr)
82
77
    revs = []
83
 
    sep = re.compile("\\.\\.(?!/)")
84
 
    for x in sep.split(revstr):
85
 
        revs.append(RevisionSpec.from_string(x or None))
 
78
    if m:
 
79
        warning('Colon separator for revision numbers is deprecated.'
 
80
                ' Use .. instead')
 
81
        for rev in revstr.split(':'):
 
82
            if rev:
 
83
                revs.append(RevisionSpec(int(rev)))
 
84
            else:
 
85
                revs.append(RevisionSpec(None))
 
86
    else:
 
87
        next_prefix = None
 
88
        for x in revstr.split('..'):
 
89
            if not x:
 
90
                revs.append(RevisionSpec(None))
 
91
            elif x[-1] == ':':
 
92
                # looks like a namespace:.. has happened
 
93
                next_prefix = x + '..'
 
94
            else:
 
95
                if next_prefix is not None:
 
96
                    x = next_prefix + x
 
97
                revs.append(RevisionSpec(x))
 
98
                next_prefix = None
 
99
        if next_prefix is not None:
 
100
            revs.append(RevisionSpec(next_prefix))
86
101
    return revs
87
102
 
88
103
 
89
104
def _parse_merge_type(typestring):
90
 
    return get_merge_type(typestring)
 
105
    return bzrlib.commands.get_merge_type(typestring)
91
106
 
92
 
def get_merge_type(typestring):
93
 
    """Attempt to find the merge class/factory associated with a string."""
94
 
    from merge import merge_types
95
 
    try:
96
 
        return merge_types[typestring][0]
97
 
    except KeyError:
98
 
        templ = '%s%%7s: %%s' % (' '*12)
99
 
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
100
 
        type_list = '\n'.join(lines)
101
 
        msg = "No known merge type %s. Supported types are:\n%s" %\
102
 
            (typestring, type_list)
103
 
        raise BzrCommandError(msg)
104
107
 
105
108
class Option(object):
106
109
    """Description of a command line option"""
109
112
    OPTIONS = {}
110
113
    SHORT_OPTIONS = {}
111
114
 
112
 
    def __init__(self, name, help='', type=None, argname=None):
 
115
    def __init__(self, name, help='', type=None):
113
116
        """Make a new command option.
114
117
 
115
118
        name -- regular name of the command, used in the double-dash
120
123
 
121
124
        type -- function called to parse the option argument, or 
122
125
            None (default) if this option doesn't take an argument.
123
 
 
124
 
        argname -- name of option argument, if any
125
126
        """
126
127
        # TODO: perhaps a subclass that automatically does 
127
 
        # --option, --no-option for reversible booleans
 
128
        # --option, --no-option for reversable booleans
128
129
        self.name = name
129
130
        self.help = help
130
131
        self.type = type
131
 
        if type is None:
132
 
            assert argname is None
133
 
        elif argname is None:
134
 
            argname = 'ARG'
135
 
        self.argname = argname
136
132
 
137
133
    def short_name(self):
138
134
        """Return the single character option for this command, if any.
139
135
 
140
136
        Short options are globally registered.
141
137
        """
142
 
        for short, option in Option.SHORT_OPTIONS.iteritems():
143
 
            if option is self:
144
 
                return short
145
 
 
146
 
    def get_negation_name(self):
147
 
        if self.name.startswith('no-'):
148
 
            return self.name[3:]
149
 
        else:
150
 
            return 'no-' + self.name
151
 
 
152
 
    def add_option(self, parser, short_name):
153
 
        """Add this option to an Optparse parser"""
154
 
        option_strings = ['--%s' % self.name]
155
 
        if short_name is not None:
156
 
            option_strings.append('-%s' % short_name)
157
 
        optargfn = self.type
158
 
        if optargfn is None:
159
 
            parser.add_option(action='store_true', dest=self.name, 
160
 
                              help=self.help,
161
 
                              default=OptionParser.DEFAULT_VALUE,
162
 
                              *option_strings)
163
 
            negation_strings = ['--%s' % self.get_negation_name()]
164
 
            parser.add_option(action='store_false', dest=self.name, 
165
 
                              help=optparse.SUPPRESS_HELP, *negation_strings)
166
 
        else:
167
 
            parser.add_option(action='callback', 
168
 
                              callback=self._optparse_callback, 
169
 
                              type='string', metavar=self.argname.upper(),
170
 
                              help=self.help,
171
 
                              default=OptionParser.DEFAULT_VALUE, 
172
 
                              *option_strings)
173
 
 
174
 
    def _optparse_callback(self, option, opt, value, parser):
175
 
        setattr(parser.values, self.name, self.type(value))
176
 
 
177
 
    def iter_switches(self):
178
 
        """Iterate through the list of switches provided by the option
179
 
        
180
 
        :return: an iterator of (name, short_name, argname, help)
181
 
        """
182
 
        argname =  self.argname
183
 
        if argname is not None:
184
 
            argname = argname.upper()
185
 
        yield self.name, self.short_name(), argname, self.help
186
 
 
187
 
 
188
 
class OptionParser(optparse.OptionParser):
189
 
    """OptionParser that raises exceptions instead of exiting"""
190
 
 
191
 
    DEFAULT_VALUE = object()
192
 
 
193
 
    def error(self, message):
194
 
        raise BzrCommandError(message)
195
 
 
196
 
 
197
 
def get_optparser(options):
198
 
    """Generate an optparse parser for bzrlib-style options"""
199
 
 
200
 
    parser = OptionParser()
201
 
    parser.remove_option('--help')
202
 
    short_options = dict((k.name, v) for v, k in 
203
 
                         Option.SHORT_OPTIONS.iteritems())
204
 
    for option in options.itervalues():
205
 
        option.add_option(parser, short_options.get(option.name))
206
 
    return parser
 
138
        return Option.SHORT_OPTIONS.get(self.name)
207
139
 
208
140
 
209
141
def _global_option(name, **kwargs):
211
143
    Option.OPTIONS[name] = Option(name, **kwargs)
212
144
 
213
145
_global_option('all')
214
 
_global_option('overwrite', help='Ignore differences between branches and '
215
 
               'overwrite unconditionally')
216
146
_global_option('basis', type=str)
217
 
_global_option('bound')
218
147
_global_option('diff-options', type=str)
219
 
_global_option('help',
220
 
               help='show help message')
 
148
_global_option('help')
221
149
_global_option('file', type=unicode)
222
150
_global_option('force')
223
151
_global_option('format', type=unicode)
224
152
_global_option('forward')
225
153
_global_option('message', type=unicode)
226
154
_global_option('no-recurse')
227
 
_global_option('prefix', type=str, 
228
 
               help='Set prefixes to added to old and new filenames, as '
229
 
                    'two values separated by a colon.')
230
 
_global_option('profile',
231
 
               help='show performance profiling information')
 
155
_global_option('profile')
232
156
_global_option('revision', type=_parse_revision_str)
 
157
_global_option('short')
233
158
_global_option('show-ids', 
234
159
               help='show internal object ids')
235
 
_global_option('timezone', 
236
 
               type=str,
237
 
               help='display timezone as local, original, or utc')
238
 
_global_option('unbound')
239
 
_global_option('verbose',
240
 
               help='display more information')
 
160
_global_option('timezone', type=str)
 
161
_global_option('verbose',)
 
162
##               help='display more information')
241
163
_global_option('version')
242
164
_global_option('email')
 
165
_global_option('unchanged')
243
166
_global_option('update')
244
 
_global_option('log-format', type=str, help="Use this log format")
245
 
_global_option('long', help='Use detailed log format. Same as --log-format long')
246
 
_global_option('short', help='Use moderately short log format. Same as --log-format short')
247
 
_global_option('line', help='Use log format with one line per revision. Same as --log-format line')
 
167
_global_option('long')
248
168
_global_option('root', type=str)
249
169
_global_option('no-backup')
250
 
_global_option('merge-type', type=_parse_merge_type, 
251
 
               help='Select a particular merge algorithm')
 
170
_global_option('merge-type', type=_parse_merge_type)
252
171
_global_option('pattern', type=str)
253
172
_global_option('quiet')
254
 
_global_option('remember', help='Remember the specified location as a'
255
 
               ' default.')
256
 
_global_option('reprocess', help='Reprocess to reduce spurious conflicts')
257
 
_global_option('kind', type=str)
258
 
_global_option('dry-run',
259
 
               help="show what would be done, but don't actually do anything")
 
173
_global_option('remember')
260
174
 
261
175
 
262
176
def _global_short(short_name, long_name):
271
185
Option.SHORT_OPTIONS['v'] = Option.OPTIONS['verbose']
272
186
Option.SHORT_OPTIONS['l'] = Option.OPTIONS['long']
273
187
Option.SHORT_OPTIONS['q'] = Option.OPTIONS['quiet']
274
 
Option.SHORT_OPTIONS['p'] = Option.OPTIONS['prefix']