@@ -74,7 +74,7 @@ def set_columns(cols=None):
74
74
75
75
def what_str_list_tuple (name ):
76
76
"""Given name, return separator, class and helptext wrt separator.
77
- (Currently supports strlist, strtuple, pathlist, pathtuple)
77
+ (Currently supports strlist, strtuple, pathlist, pathtuple)
78
78
"""
79
79
sep = ','
80
80
helpsep = 'comma'
@@ -106,6 +106,21 @@ def check_str_list_tuple(option, opt, value):
106
106
return klass (split )
107
107
108
108
109
+ def get_empty_add_flex (allvalues ):
110
+ """Return the empty element for add_flex action for allvalues"""
111
+ empty = None
112
+
113
+ if isinstance (allvalues , (list , tuple )):
114
+ if isinstance (allvalues [0 ], basestring ):
115
+ empty = ''
116
+
117
+ if empty is None :
118
+ msg = "get_empty_add_flex cannot determine empty element for type %s (%s)"
119
+ self .log .raiseException (msg % (type (allvalues ), allvalues ))
120
+
121
+ return empty
122
+
123
+
109
124
class ExtOption (CompleterOption ):
110
125
"""Extended options class
111
126
- enable/disable support
@@ -119,16 +134,25 @@ class ExtOption(CompleterOption):
119
134
- add_first : add default to value (result is value + default)
120
135
- extend : alias for add with strlist type
121
136
- type must support + (__add__) and one of negate (__neg__) or slicing (__getslice__)
137
+ - add_flex : similar to add / add_first, but replaces the first "empty" element with the default
138
+ - the empty element is dependent of the type
139
+ - for {str,path}{list,tuple} this is the empty string
140
+ - types must support the index method to determine the location of the "empty" element
141
+ - the replacement uses +
142
+ - e.g. a strlist type with value "0,,1"` and default [3,4] and action add_flex will
143
+ use the empty string '' as "empty" element, and will result in [0,3,4,1] (not [0,[3,4],1])
144
+ (but also a strlist with value "" and default [3,4] will result in [3,4];
145
+ so you can't set an empty list with add_flex)
122
146
- date : convert into datetime.date
123
147
- datetime : convert into datetime.datetime
124
148
- regex: compile str in regexp
125
149
- store_or_None
126
150
- set default to None if no option passed,
127
151
- set to default if option without value passed,
128
152
- set to value if option with value passed
129
-
153
+
130
154
Types:
131
- - strlist, strtuple : convert comma-separated string in a list resp. tuple of strings
155
+ - strlist, strtuple : convert comma-separated string in a list resp. tuple of strings
132
156
- pathlist, pathtuple : using os.pathsep, convert pathsep-separated string in a list resp. tuple of strings
133
157
- the path separator is OS-dependent
134
158
"""
@@ -137,7 +161,7 @@ class ExtOption(CompleterOption):
137
161
ENABLE = 'enable' # do nothing
138
162
DISABLE = 'disable' # inverse action
139
163
140
- EXTOPTION_EXTRA_OPTIONS = ('date' , 'datetime' , 'regex' , 'add' , 'add_first' ,)
164
+ EXTOPTION_EXTRA_OPTIONS = ('date' , 'datetime' , 'regex' , 'add' , 'add_first' , 'add_flex' , )
141
165
EXTOPTION_STORE_OR = ('store_or_None' ,) # callback type
142
166
EXTOPTION_LOG = ('store_debuglog' , 'store_infolog' , 'store_warninglog' ,)
143
167
EXTOPTION_HELP = ('shorthelp' , 'confighelp' ,)
@@ -230,19 +254,33 @@ def take_action(self, action, dest, opt, value, values, parser):
230
254
Option .take_action (self , action , dest , opt , value , values , parser )
231
255
232
256
elif action in self .EXTOPTION_EXTRA_OPTIONS :
233
- if action in ("add" , "add_first" ,):
257
+ if action in ("add" , "add_first" , "add_flex" , ):
234
258
# determine type from lvalue
235
259
# set default first
236
- values .ensure_value (dest , type (value )())
237
- default = getattr (values , dest )
260
+ default = getattr (parser .get_default_values (), dest , None )
261
+ if default is None :
262
+ default = type (value )()
238
263
if not (hasattr (default , '__add__' ) and
239
264
(hasattr (default , '__neg__' ) or hasattr (default , '__getslice__' ))):
240
265
msg = "Unsupported type %s for action %s (requires + and one of negate or slice)"
241
266
self .log .raiseException (msg % (type (default ), action ))
242
- if action == 'add' :
267
+ if action in ( 'add' , 'add_flex' ) :
243
268
lvalue = default + value
244
269
elif action == 'add_first' :
245
270
lvalue = value + default
271
+
272
+ if action == 'add_flex' and lvalue :
273
+ # use lvalue here rather than default to make sure there is 1 element
274
+ # to determine the type
275
+ if not hasattr (lvalue , 'index' ):
276
+ msg = "Unsupported type %s for action %s (requires index method)"
277
+ self .log .raiseException (msg % (type (lvalue ), action ))
278
+ empty = get_empty_add_flex (lvalue )
279
+ if empty in value :
280
+ ind = value .index (empty )
281
+ lvalue = value [:ind ] + default + value [ind + 1 :]
282
+ else :
283
+ lvalue = value
246
284
elif action == "date" :
247
285
lvalue = date_parser (value )
248
286
elif action == "datetime" :
@@ -350,7 +388,7 @@ def add_option(self, *args, **kwargs):
350
388
351
389
class ExtOptionParser (OptionParser ):
352
390
"""
353
- Make an option parser that limits the C{-h} / C{--shorthelp} to short opts only,
391
+ Make an option parser that limits the C{-h} / C{--shorthelp} to short opts only,
354
392
C{-H} / C{--help} for all options.
355
393
356
394
Pass options through environment. Like:
@@ -369,11 +407,34 @@ class ExtOptionParser(OptionParser):
369
407
DESCRIPTION_DOCSTRING = False
370
408
371
409
def __init__ (self , * args , ** kwargs ):
410
+ """
411
+ Following named arguments are specific to ExtOptionParser
412
+ (the remaining ones are passed to the parent OptionParser class)
413
+
414
+ :param help_to_string: boolean, if True, the help is written
415
+ to a newly created StingIO instance
416
+ :param help_to_file: filehandle, help is written to this filehandle
417
+ :param envvar_prefix: string, specify the environment variable prefix
418
+ to use (if you don't want the default one)
419
+ :param process_env_options: boolean, if False, don't check the
420
+ environment for options (default: True)
421
+ :param error_env_options: boolean, if True, use error_env_options_method
422
+ if an environment variable with correct envvar_prefix
423
+ exists but does not correspond to an existing option
424
+ (default: False)
425
+ :param error_env_options_method: callable; method to use to report error
426
+ in used environment variables (see error_env_options);
427
+ accepts string value + additional
428
+ string arguments for formatting the message
429
+ (default: own log.error method)
430
+ """
372
431
self .log = getLogger (self .__class__ .__name__ )
373
432
self .help_to_string = kwargs .pop ('help_to_string' , None )
374
433
self .help_to_file = kwargs .pop ('help_to_file' , None )
375
434
self .envvar_prefix = kwargs .pop ('envvar_prefix' , None )
376
435
self .process_env_options = kwargs .pop ('process_env_options' , True )
436
+ self .error_env_options = kwargs .pop ('error_env_options' , False )
437
+ self .error_env_option_method = kwargs .pop ('error_env_option_method' , self .log .error )
377
438
378
439
# py2.4 epilog compatibilty with py2.7 / optparse 1.5.3
379
440
self .epilog = kwargs .pop ('epilog' , None )
@@ -453,7 +514,7 @@ def set_usage(self, usage):
453
514
def get_default_values (self ):
454
515
"""Introduce the ExtValues class with class constant
455
516
- make it dynamic, otherwise the class constant is shared between multiple instances
456
- - class constant is used to avoid _action_taken as option in the __dict__
517
+ - class constant is used to avoid _action_taken as option in the __dict__
457
518
- only works by using reference to object
458
519
- same for _logaction_taken
459
520
"""
@@ -607,14 +668,16 @@ def get_env_options(self):
607
668
epilogprefixtxt += "eg. --some-opt is same as setting %(prefix)s_SOME_OPT in the environment."
608
669
self .epilog .append (epilogprefixtxt % {'prefix' : self .envvar_prefix })
609
670
671
+ candidates = dict ([(k , v ) for k , v in os .environ .items () if k .startswith ("%s_" % self .envvar_prefix )])
672
+
610
673
for opt in self ._get_all_options ():
611
674
if opt ._long_opts is None :
612
675
continue
613
676
for lo in opt ._long_opts :
614
677
if len (lo ) == 0 :
615
678
continue
616
679
env_opt_name = "%s_%s" % (self .envvar_prefix , lo .lstrip ('-' ).replace ('-' , '_' ).upper ())
617
- val = os . environ . get (env_opt_name , None )
680
+ val = candidates . pop (env_opt_name , None )
618
681
if not val is None :
619
682
if opt .action in opt .TYPED_ACTIONS : # not all typed actions are mandatory, but let's assume so
620
683
self .environment_arguments .append ("%s=%s" % (lo , val ))
@@ -625,6 +688,14 @@ def get_env_options(self):
625
688
else :
626
689
self .log .debug ("Environment variable %s is not set" % env_opt_name )
627
690
691
+ if candidates :
692
+ msg = "Found %s environment variable(s) that are prefixed with %s but do not match valid option(s): %s"
693
+ if self .error_env_options :
694
+ logmethod = self .error_env_option_method
695
+ else :
696
+ logmethod = self .log .debug
697
+ logmethod (msg , len (candidates ), self .envvar_prefix , ',' .join (candidates ))
698
+
628
699
self .log .debug ("Environment variable options with prefix %s: %s" % (self .envvar_prefix , self .environment_arguments ))
629
700
return self .environment_arguments
630
701
@@ -654,7 +725,7 @@ class GeneralOption(object):
654
725
if True, an option --configfiles will be added
655
726
- go_configfiles : list of configfiles to parse. Uses ConfigParser.read; last file wins
656
727
- go_configfiles_initenv : section dict of key/value dict; inserted before configfileparsing
657
- As a special case, using all uppercase key in DEFAULT section with a case-sensitive
728
+ As a special case, using all uppercase key in DEFAULT section with a case-sensitive
658
729
configparser can be used to set "constants" for easy interpolation in all sections.
659
730
- go_loggername : name of logger, default classname
660
731
- go_mainbeforedefault : set the main options before the default ones
@@ -1046,11 +1117,11 @@ def parseoptions(self, options_list=None):
1046
1117
1047
1118
def configfile_parser_init (self , initenv = None ):
1048
1119
"""
1049
- Initialise the confgiparser to use.
1050
-
1051
- @params initenv: insert initial environment into the configparser.
1052
- It is a dict of dicts; the first level key is the section name;
1053
- the 2nd level key,value is the key=value.
1120
+ Initialise the configparser to use.
1121
+
1122
+ @params initenv: insert initial environment into the configparser.
1123
+ It is a dict of dicts; the first level key is the section name;
1124
+ the 2nd level key,value is the key=value.
1054
1125
All section names, keys and values are converted to strings.
1055
1126
"""
1056
1127
self .configfile_parser = self .CONFIGFILE_PARSER ()
@@ -1419,9 +1490,17 @@ def generate_cmd_line(self, ignore=None, add_default=None):
1419
1490
(opt_name , action , default ))
1420
1491
else :
1421
1492
args .append ("--%s" % opt_name )
1422
- elif action in ("add" , "add_first" ):
1493
+ elif action in ("add" , "add_first" , "add_flex" ):
1423
1494
if default is not None :
1424
- if hasattr (opt_value , '__neg__' ):
1495
+ if action == 'add_flex' and default :
1496
+ for ind , elem in enumerate (opt_value ):
1497
+ if elem == default [0 ] and opt_value [ind :ind + len (default )] == default :
1498
+ empty = get_empty_add_flex (opt_value )
1499
+ # TODO: this will only work for tuples and lists
1500
+ opt_value = opt_value [:ind ] + type (opt_value )([empty ]) + opt_value [ind + len (default ):]
1501
+ # only the first occurence
1502
+ break
1503
+ elif hasattr (opt_value , '__neg__' ):
1425
1504
if action == 'add_first' :
1426
1505
opt_value = opt_value + - default
1427
1506
else :
0 commit comments