##// END OF EJS Templates
fancyopts: add early-options parser compatible with getopt()...
Yuya Nishihara -
r35179:898c6f81 stable
parent child Browse files
Show More
@@ -7,6 +7,8 b''
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import functools
11
10 from .i18n import _
12 from .i18n import _
11 from . import (
13 from . import (
12 error,
14 error,
@@ -24,6 +26,179 b' nevernegate = {'
24 'version',
26 'version',
25 }
27 }
26
28
29 def _earlyoptarg(arg, shortlist, namelist):
30 """Check if the given arg is a valid unabbreviated option
31
32 Returns (flag_str, has_embedded_value?, embedded_value, takes_value?)
33
34 >>> def opt(arg):
35 ... return _earlyoptarg(arg, b'R:q', [b'cwd=', b'debugger'])
36
37 long form:
38
39 >>> opt(b'--cwd')
40 ('--cwd', False, '', True)
41 >>> opt(b'--cwd=')
42 ('--cwd', True, '', True)
43 >>> opt(b'--cwd=foo')
44 ('--cwd', True, 'foo', True)
45 >>> opt(b'--debugger')
46 ('--debugger', False, '', False)
47 >>> opt(b'--debugger=') # invalid but parsable
48 ('--debugger', True, '', False)
49
50 short form:
51
52 >>> opt(b'-R')
53 ('-R', False, '', True)
54 >>> opt(b'-Rfoo')
55 ('-R', True, 'foo', True)
56 >>> opt(b'-q')
57 ('-q', False, '', False)
58 >>> opt(b'-qfoo') # invalid but parsable
59 ('-q', True, 'foo', False)
60
61 unknown or invalid:
62
63 >>> opt(b'--unknown')
64 ('', False, '', False)
65 >>> opt(b'-u')
66 ('', False, '', False)
67 >>> opt(b'-ufoo')
68 ('', False, '', False)
69 >>> opt(b'--')
70 ('', False, '', False)
71 >>> opt(b'-')
72 ('', False, '', False)
73 >>> opt(b'-:')
74 ('', False, '', False)
75 >>> opt(b'-:foo')
76 ('', False, '', False)
77 """
78 if arg.startswith('--'):
79 flag, eq, val = arg.partition('=')
80 if flag[2:] in namelist:
81 return flag, bool(eq), val, False
82 if flag[2:] + '=' in namelist:
83 return flag, bool(eq), val, True
84 elif arg.startswith('-') and arg != '-' and not arg.startswith('-:'):
85 flag, val = arg[:2], arg[2:]
86 i = shortlist.find(flag[1:])
87 if i >= 0:
88 return flag, bool(val), val, shortlist.startswith(':', i + 1)
89 return '', False, '', False
90
91 def earlygetopt(args, shortlist, namelist, gnu=False, keepsep=False):
92 """Parse options like getopt, but ignores unknown options and abbreviated
93 forms
94
95 If gnu=False, this stops processing options as soon as a non/unknown-option
96 argument is encountered. Otherwise, option and non-option arguments may be
97 intermixed, and unknown-option arguments are taken as non-option.
98
99 If keepsep=True, '--' won't be removed from the list of arguments left.
100 This is useful for stripping early options from a full command arguments.
101
102 >>> def get(args, gnu=False, keepsep=False):
103 ... return earlygetopt(args, b'R:q', [b'cwd=', b'debugger'],
104 ... gnu=gnu, keepsep=keepsep)
105
106 default parsing rules for early options:
107
108 >>> get([b'x', b'--cwd', b'foo', b'-Rbar', b'-q', b'y'], gnu=True)
109 ([('--cwd', 'foo'), ('-R', 'bar'), ('-q', '')], ['x', 'y'])
110 >>> get([b'x', b'--cwd=foo', b'y', b'-R', b'bar', b'--debugger'], gnu=True)
111 ([('--cwd', 'foo'), ('-R', 'bar'), ('--debugger', '')], ['x', 'y'])
112 >>> get([b'--unknown', b'--cwd=foo', b'--', '--debugger'], gnu=True)
113 ([('--cwd', 'foo')], ['--unknown', '--debugger'])
114
115 restricted parsing rules (early options must come first):
116
117 >>> get([b'--cwd', b'foo', b'-Rbar', b'x', b'-q', b'y'], gnu=False)
118 ([('--cwd', 'foo'), ('-R', 'bar')], ['x', '-q', 'y'])
119 >>> get([b'--cwd=foo', b'x', b'y', b'-R', b'bar', b'--debugger'], gnu=False)
120 ([('--cwd', 'foo')], ['x', 'y', '-R', 'bar', '--debugger'])
121 >>> get([b'--unknown', b'--cwd=foo', b'--', '--debugger'], gnu=False)
122 ([], ['--unknown', '--cwd=foo', '--debugger'])
123
124 stripping early options (without loosing '--'):
125
126 >>> get([b'x', b'-Rbar', b'--', '--debugger'], gnu=True, keepsep=True)[1]
127 ['x', '--', '--debugger']
128
129 last argument:
130
131 >>> get([b'--cwd'])
132 ([], ['--cwd'])
133 >>> get([b'--cwd=foo'])
134 ([('--cwd', 'foo')], [])
135 >>> get([b'-R'])
136 ([], ['-R'])
137 >>> get([b'-Rbar'])
138 ([('-R', 'bar')], [])
139 >>> get([b'-q'])
140 ([('-q', '')], [])
141 >>> get([b'-q', b'--'])
142 ([('-q', '')], [])
143
144 value passed to bool options:
145
146 >>> get([b'--debugger=foo', b'x'])
147 ([], ['--debugger=foo', 'x'])
148 >>> get([b'-qfoo', b'x'])
149 ([], ['-qfoo', 'x'])
150
151 short option isn't separated with '=':
152
153 >>> get([b'-R=bar'])
154 ([('-R', '=bar')], [])
155
156 ':' may be in shortlist, but shouldn't be taken as an option letter:
157
158 >>> get([b'-:', b'y'])
159 ([], ['-:', 'y'])
160
161 '-' is a valid non-option argument:
162
163 >>> get([b'-', b'y'])
164 ([], ['-', 'y'])
165 """
166 # ignoring everything just after '--' isn't correct as '--' may be an
167 # option value (e.g. ['-R', '--']), but we do that consistently.
168 try:
169 argcount = args.index('--')
170 except ValueError:
171 argcount = len(args)
172
173 parsedopts = []
174 parsedargs = []
175 pos = 0
176 while pos < argcount:
177 arg = args[pos]
178 flag, hasval, val, takeval = _earlyoptarg(arg, shortlist, namelist)
179 if not hasval and takeval and pos + 1 >= argcount:
180 # missing last argument
181 break
182 if not flag or hasval and not takeval:
183 # non-option argument or -b/--bool=INVALID_VALUE
184 if gnu:
185 parsedargs.append(arg)
186 pos += 1
187 else:
188 break
189 elif hasval == takeval:
190 # -b/--bool or -s/--str=VALUE
191 parsedopts.append((flag, val))
192 pos += 1
193 else:
194 # -s/--str VALUE
195 parsedopts.append((flag, args[pos + 1]))
196 pos += 2
197
198 parsedargs.extend(args[pos:argcount])
199 parsedargs.extend(args[argcount + (not keepsep):])
200 return parsedopts, parsedargs
201
27 def gnugetopt(args, options, longoptions):
202 def gnugetopt(args, options, longoptions):
28 """Parse options mostly like getopt.gnu_getopt.
203 """Parse options mostly like getopt.gnu_getopt.
29
204
@@ -51,7 +226,7 b' def gnugetopt(args, options, longoptions'
51 return opts, args
226 return opts, args
52
227
53
228
54 def fancyopts(args, options, state, gnu=False):
229 def fancyopts(args, options, state, gnu=False, early=False):
55 """
230 """
56 read args, parse options, and store options in state
231 read args, parse options, and store options in state
57
232
@@ -124,7 +299,9 b' def fancyopts(args, options, state, gnu='
124 namelist.append(oname)
299 namelist.append(oname)
125
300
126 # parse arguments
301 # parse arguments
127 if gnu:
302 if early:
303 parse = functools.partial(earlygetopt, gnu=gnu)
304 elif gnu:
128 parse = gnugetopt
305 parse = gnugetopt
129 else:
306 else:
130 parse = pycompat.getoptb
307 parse = pycompat.getoptb
@@ -48,6 +48,7 b" testmod('mercurial.context')"
48 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
48 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
49 testmod('mercurial.dispatch')
49 testmod('mercurial.dispatch')
50 testmod('mercurial.encoding')
50 testmod('mercurial.encoding')
51 testmod('mercurial.fancyopts')
51 testmod('mercurial.formatter')
52 testmod('mercurial.formatter')
52 testmod('mercurial.hg')
53 testmod('mercurial.hg')
53 testmod('mercurial.hgweb.hgwebdir_mod')
54 testmod('mercurial.hgweb.hgwebdir_mod')
General Comments 0
You need to be logged in to leave comments. Login now