-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmdopt.py
More file actions
377 lines (235 loc) · 11.1 KB
/
cmdopt.py
File metadata and controls
377 lines (235 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
#!/usr/bin/python
# -*- coding: ISO-8859-1 -*-
import sys
# class _Structure: pass # convenient syntax to pass arguments
class _KeyData :
def __init__( self ) :
self.key = None
self.value = None
def _get_key( arg ) :
""" returns None or Structure ; special cases: for '--' key is None, for '-' it is '' """
if arg is None : return None
# else ...
# ret = None
ret = _KeyData() # most frequent case _in the code_ )
# data = _KeyData()
#
# the checks order makes them a bit ineffective -- but the code is easier to understand :
#
# a special case
if arg == '-' :
ret.key = ''
# ret.value = None
# another one
elif arg == '--' :
ret.key = None
# ret.value = None
# "--option " and "--option=value"
elif arg.startswith( '--' ) :
short = arg[2:]
# "--option=value"
valstart = short.find('=')
if valstart >= 0 :
ret.key = short[:valstart]
ret.value = short[(valstart+1):]
# "--option"
else : # no '=' in the argument string
ret.key = short
# ret.value = None
# "-o ", "-oValue"
elif arg.startswith( '-' ) : # we have checked for '--' above -- so it is a single '-'
short = arg[1:]
ret.key = short[0]
if len( short ) > 1 :
ret.value = short[1:]
else : # just an argument
# may be we'd better put this as a first "if" check -- but at the moment I think this order reads better )
ret = None
return ret
# -----------------------------------------------------------------------------
#
# evaluate the expression by Python rules ( in an empty context, so that no local namespace (except the built-in one, obviously) can affect it )
#
# we might want to pass 'None' as a value, so we need "own None" :
class _MyNone :
""" while the class itself is evaluated to True, its instances would be "untrue" by design! """
def __nonzero__(self):
return 0
NONE = _MyNone()
def _try_eval( expr ) :
""" try: eval( expr, {} ); except: return None """
ret = NONE
try:
ret = eval( expr, {} )
except:
pass
return ret
def _eval_or_leave( expr ):
""" try to evaluate the expression, else return it as is """
value = _try_eval( expr )
if value is NONE :
value = expr
return value
# -----------------------------------------------------------------------------
#
# want to do things like "if opts['k'] is None and opts[keys] is not None : ... "
#
class _Dict( dict ):
def __getitem__(self, key) :
return self.get(key)
class _OptionsDict(_Dict) :
""" a _Dict( dict ) with some additional fields -- e.g. 'args' (a list of all "ordered / non-named" arguments) """
# for "__init__", see _get_keys() below
def try_eval( self ) :
"""
try to convert the values following Python rules ;
in code:
try:
return eval( expr, {} )
except:
return expr
"""
# same could be achieved via " .convert( { '*' : < myeval(expr) or expr >} ) "
result = self.__class__()
result.args = self.args[:] # copy positional arguments
for key, value in self.iteritems() :
result[ key ] = _eval_or_leave( value )
return result
"""
for key, raw_value in self.iteritems() :
evaluated = _try_eval( raw_value )
if evaluated is not NONE :
result[ key ] = evaluated
else :
result[ key ] = raw_value
return result
"""
def convert( self, table ) :
""" the "conversion table" is a dict of a form:
{
"k,key" : lambda arg : int(arg) # or just 'int'
...
'*' : default handler # optional
}
i.e. the table 'key' is a string containing a comma-separated list of arguments (may be just one: "key" or "k" ) ,
and the table 'value' is a conversion function that need to be applied .
on exception there is a warning message printed to stderr, and then the exception is re-raised .
"""
# result = _OptionsDict()
result = self.__class__()
result.args = self.args[:] # copy positional arguments
# the internal lookup table
# functions = {}
functions = _Dict() # __getitem__ = get
# functions.default = lambda arg: arg # " '*': id() "
functions.default = None
# fill it
for key, value in table.iteritems() :
keys = key.split(',')
for k in keys :
functions[ k.strip() ] = value
# the 'default' handler
if k == '*' :
functions.default = value
# now apply it
for key, value in self.iteritems() :
func = functions[key] or functions.default
if func :
try:
## self[ key ] = func( value )
result[ key ] = func( value )
# except Exception as e :
# want to be backward-compatible with 2.5 :
except:
print >>sys.stderr, "failed to convert the value '%s' for the key '%s' " % ( value, key )
# too wordy
"""
print >>sys.stderr, "| failed to convert the value '%s' for the key '%s' " % ( value, key )
etype, evalue = sys.exc_info()[0:2]
exception_string = "%s: %s" % ( etype, evalue )
print >>sys.stderr, "| exception:\n|\t", exception_string
if isinstance( func, type(lambda x:x) ) :
func_name = "%s function from %s:%d" % ( func.func_name, func.func_code.co_filename, func.func_code.co_firstlineno )
else :
func_name = "function: %s" % (func, )
print >>sys.stderr, "| caused by:\n|\t", func_name
"""
# better, but still in doubt if we need it )
"""
if isinstance( func, type(lambda x:x) ) :
func_name = "function %s from %s:%d" % ( func.func_name, func.func_code.co_filename, func.func_code.co_firstlineno )
else :
func_name = "function: %s" % (func, )
print >>sys.stderr, "caused by", func_name
"""
# re-raise finally
raise
else: # no conversion function ( even no default one )
# -- then leave it :
result[ key ] = value
return result
def _get_keys( argv ) :
""" the actual constructor for the _OptionsDict class """
# ret = {}
# ret = _Dict()
ret = _OptionsDict()
i, N = 0, len( argv )
ret.args = []
# a synonym
args = ret.args
# for re-evaluation, if we'll need it :
ret._argv = argv
while i < N :
arg = argv[i]
i += 1
keydata = _get_key( arg )
# "if not an option: "
if keydata is None :
args.append( arg )
continue
# special case : '--'
elif keydata.key is None :
# stop parsing options
args.extend( argv[ i : ] )
break
# special case : '-'
elif keydata.key == '' :
ret[ '-' ] = True
continue
# simple option : "--option=value" or "-oValue"
elif keydata.value is not None :
ret[ keydata.key ] = keydata.value
continue
# got a boolean option, "-o --other-option" , or a key/value pair, "-o Value" or "--option value"
else :
nextarg = None
if i < N:
nextarg = argv[ i ]
if nextarg.startswith( '-' ) :
nextarg = None
else: # skip this next argument on the next iteration
i += 1
if nextarg is None :
ret[ keydata.key ] = True
else :
ret[ keydata.key ] = nextarg
continue # for uniformness
return ret
# -----------------------------------------------------------------------------
## # debug
## print sys.argv
std_eval = eval
# try_eval = lambda expr : _try_eval( expr ) or expr
try_eval = _eval_or_leave
## options = _get_keys( sys.argv[1:] )
# options as strings
string_options = _get_keys( sys.argv[1:] )
# trying to evaluate them as Python expressions, if possible
options = string_options.try_eval()
# # local rename ( for better safety from errors, this line should be the last one in the module )
# eval = try_eval
if __name__ == "__main__" :
print __doc__
print "\n === \n"
# print "module dir() listing: ", __dict__.keys()
print "module dir() listing: ", dir()