import repository from arizona
[raven.git] / lib / arizona-lib / arizonaconfig.py
1 #! /usr/bin/env python
2 """
3 <Program Name>
4    arizonaconfig.py
5
6 <Started>
7    June 6, 2005
8
9 <Author>
10    Programmed by Jeffry Johnston under the direction of Justin Cappos.
11
12 <Purpose>
13    Configuration file, command-line, and run-time options manager.  Finds
14    used source files in a tree to only include the necessary options.
15 """
16
17 #---------------------------------------------------------------------
18 # Imports
19 #---------------------------------------------------------------------
20 import os
21 import sys
22 import glob
23 import optparse
24 import arizonageneral
25 import traceback
26
27 #---------------------------------------------------------------------
28 # Globals
29 #---------------------------------------------------------------------
30 # Storage for found modules during recursive search in __build_options()
31 # Although the list of modules could be passed and returned with each
32 # recursive call, storing it globally should be much faster.
33 found_modules = []
34
35 # A list of all the options found so far (during the recursive search).
36 # After the program (command line) options are parsed, a copy is made as
37 # program_options.  Then, it is further expanded to include all possible
38 # options (for successful parsing of the config file options).
39 found_options = []
40
41 # A dictionary containing all found program variables (from found
42 # arizonaconfig blocks in Python files), and their set values (from the
43 # command line program, config file, or the default value).
44 program_variables = None
45
46 # The version string passed in by init_options, or None.
47 version_string = None
48
49 # To store option parser for print_help()
50 glo_option_parser = None
51
52 glo_section = None
53
54 # List of parsed modules
55 parsed_modules = []
56
57 # Debugging mode (0 = off)
58 glo_debug = 0
59
60 # Config filename that was read or attempted to be read
61 glo_config_filename = None
62 glo_append_override_default = False
63
64 # Some options that appear in the stork config file but aren't used by all
65 # stork programs. It's not very clean to hardcode them here, but it's the simplest
66 # way to eliminate the nuisance warnings. Some of these may be deprecated.
67 no_warn_options = ["bittorrenttrackerhost", "bittorrenttrackerport",
68                    "bittorrentuploadrate", "bittorrentseedlookuptimeout"]
69
70 class SectionableOption (optparse.Option):
71    ACTIONS = optparse.Option.ACTIONS + ("sectionstart", "sectionstop", )
72    STORE_ACTIONS = optparse.Option.STORE_ACTIONS + ("sectionstart", )
73    TYPED_ACTIONS = optparse.Option.TYPED_ACTIONS + ("sectionstart", )
74
75    def take_action(self, action, dest, opt, value, values, parser):
76       global glo_append_override_default
77
78       # specified_options is a list of options that were specified by the
79       # user/config file. i.e. it is options that are not defaults.
80       specified_options = values.ensure_value("specified_options", [])
81
82       if action == "sectionstart":
83          setattr(values, "cursection", value)
84          optparse.Option.take_action(self, "append", dest, opt, value, values, parser)
85       elif action == "sectionstop":
86          setattr(values, "cursection", None)
87       elif action == "append" and glo_append_override_default:
88          # when processing config files, we have this unusual semantics where
89          # specifying and option that uses 'append' causes the default to be
90          # dropped, rather than appending to the default.
91          if not dest in specified_options:
92              setattr(values, dest, [])
93          optparse.Option.take_action(self, action, dest, opt, value, values, parser)
94       else:
95          if hasattr(values, "cursection"):
96             section = getattr(values, "cursection")
97             if section and dest:
98                dest = section + "." + dest
99          optparse.Option.take_action(self, action, dest, opt, value, values, parser)
100
101       # for append actions, allow the item "$$reset$$" to cause the list to be
102       # cleared. This allows a list to be reset from the command line.
103
104       if action == "append":
105          val_list = getattr(values, dest)
106          if "##reset##" in val_list:
107             pos = val_list.index("##reset##")
108             if pos >= 0:
109                val_list = val_list[(pos+1):]
110                setattr(values, dest, val_list)
111
112       specified_options.append(dest)
113
114 def is_storable(x):
115    return x == "store" or x == "append" or x == "sectionstart"
116
117 def is_append(x):
118    return (x == "append") or (x == "sectionstart")
119
120 def init_options(module="*", path=".", alt_paths=[], usage=None, version=None, configfile_optvar=None, debug=0):
121    """
122    <Purpose>
123       Returns all options for the specified module, including imported
124       modules (recursively).  Looks for an options block in the following
125       format:
126
127       ""'arizonaconfig
128          options=[
129                   [option, long option, variable, action, data, default,
130                    metavar, description],
131                   ...
132                  ]
133          includes=[
134                    "path/module"
135                    ...
136                   ]
137       ""'
138
139       The code within the triple quoted string will be loaded with exec,
140       so it must consist of valid Python statements.  The first line must
141       start with "arizonaconfig" (no quotes), for this function to
142       recognize it as an arizonaconfig options block.
143
144       Description of options list:
145       option:
146               Short option string, example: "-f".  If a short option is
147               not desired, use "".
148       long option:
149               Long option string, example: "--file".  If a long option is
150               not desired, use "".
151       variable:
152               Variable name for the option, will be used in the function
153               get_option("variable").
154       action:
155               One of the following strings:
156               "store"        Store this option's argument in 'variable'.
157                              See 'data' and 'metavar'.
158               "append"       Append this option's argument to the list
159                              'variable'.  See 'data' and 'metavar'.
160               "store_const"  Store the integer value 'data' in 'variable'.
161                              The string 'metavar' must be None.
162               "store_true"   Store True in 'variable'.  Both 'metavar' and
163                              'data' must be None.
164               "store_false"  Store False in 'variable'.  Both 'metavar'
165                              and 'data' must be None.
166       data:
167               * If the action is "store" or "append", use one of the
168                 following strings:
169                 "string"       Take any string as the option.
170                 "int"          Accept an integer value as the option.
171                 "long"         As "int", but for a long integer.
172                 "float"        As "int", but for a float.
173                 "complex"      As "int", but for a complex number.
174               * If the action is "store_const", specify the integer
175                 constant to store into 'variable' if the option is
176                 given.
177               * Otherwise, use None.
178       default:
179               The default value to store if the option is not given.  If
180               the action is "help", must be None.  If the action is
181               "append" this must either be a list or None.
182       metavar:
183               * If the action is "store" or "append", specify an alternate
184                 variable name to show in --help, or None to use 'variable'
185                 for the metavar.
186               * Otherwise, must be None.
187       description:
188               Helpful description of the option, for use in --help.
189               Specify None to hide the option from the --help listing.
190
191       Decription of includes list:
192              See module in Arguments section for a description of this.
193              If this string contains $MAIN, it will be replaced by the
194              path where the main (starting module) existed on disk.  This
195              is helpful to not have to hardcode an absolute path to a
196              module, if it is always in the same place, relative to where
197              the main module is.
198              Note that init_options scans the module code for import
199              statements and adds discovered modules automatically, so most
200              modules do not need to be specified in this way.  These
201              includes can be useful for dynamically importing any found
202              modules in a certain directory.
203
204    <Arguments>
205       module:
206              (default="*")
207              Specifies the module in which to recursively find all valid
208              program options.  This is usually just the filename of the
209              module calling init_options.  It may contain a path to the
210              module.  Include the .py filename extension if there is one.
211              The standard *nix glob characters *, ?, and [ ] may be used
212              to specify multiple modules at once.
213       path:
214              (default=".")
215              Path to the module.
216       alt_paths:
217              (default=[])
218              A list of alternate paths that will be searched if a module
219              could not be found via the default path.  These paths must be
220              absolute (must start with `/').
221       usage:
222              (default=None)
223              Command-line usage string for module --help.  The default
224              None will generate a generic usage statement.
225       version:
226              (default=None)
227              The program version, as a string.  If None is given, the
228              --version option will not be accepted on the command line, so
229              no version information will be available to the user.
230       configfile_optvar:
231              (default=None)
232              If None, no configuration file will be parsed.  Otherwise,
233              should be a string containing the name of a configuration
234              option variable (see "variable" in the Purpose section).
235              The contents of the given variable will be examined to
236              determine the configuration file filename.
237
238              For example, consider the following example arizonaconfig
239              option line:
240
241              ["-c", "--configfile", "conffile", "store", "string",
242               "/etc/myconfig.txt", "FILE",
243               "Configuration file to use (default: /etc/myconfig.txt)"]
244
245              The string to be passed for configfile_optvar is "conffile".
246              The default config file read by arizonaconfig will be
247              "/etc/myconfig.txt".  But, if the user specifies the -c or
248              --configfile options, the configuration file they specified
249              will be read instead.
250       debug:
251              (default=0)
252              If set to 1, will print a debug listing for arizonaconfig.
253              This listing will give the name of the file containing the
254              arizonaconfig options block where the option was first found.
255
256              If set to 2, will print an alternate debug listing, showing 
257              the recursive steps taken to find the modules.
258              
259              If set to 3, shows output for both 1 and 2.
260
261    <Exceptions>
262       TypeError: options having a type other than expected.
263
264    <Side Effects>
265       Sets up (modifies) program_variables.
266
267    <Returns>
268       A list of non-option command line arguments that remained after
269       option parsing.
270    """
271    # check params
272    arizonageneral.check_type_simple(module, "module", str, "init_options")
273    arizonageneral.check_type_simple(path, "path", str, "init_options")
274    arizonageneral.check_type_stringlist(alt_paths, "alt_paths", "init_options")
275    arizonageneral.check_type_simple(usage, "usage", str, "init_options", noneok=True)
276    arizonageneral.check_type_simple(version, "version", str, "init_options", noneok=True)
277    arizonageneral.check_type_simple(configfile_optvar, "configfile_optvar", str, "init_options", noneok=True)
278    arizonageneral.check_type_simple(debug, "debug", int, "init_options")
279
280    global program_variables
281    global version_string
282    global glo_option_parser                                                                                  
283    global glo_debug
284    global glo_section
285    global glo_append_override_default
286
287    # SMB: if the user (i.e. one of us developers) set a PYTHONPATH environment
288    # variable, then make sure it is added to the alt_paths list
289    pythonpath = os.environ.get("PYTHONPATH", None)
290    if pythonpath:
291       search_paths = pythonpath.split(":")
292       for search_path in search_paths:
293          if not search_path in alt_paths:
294             alt_paths.append(search_path)
295
296    # SMB: because arizonalib is now in a site-packages directory, ensure
297    # that we can find the modules.
298    for search_path in sys.path:
299        if search_path.find("site-packages") >= 0:
300            if not search_path in alt_paths:
301                alt_paths.append(search_path)
302
303    # clear any existing state
304    reset()
305    glo_debug = debug
306    glo_section = None
307
308    # build generic usage line, if needed
309    if usage == None:
310       usage = "usage: " + module + " [options]"
311
312    # initialize options parsers
313    if version == None:
314       program_parser = OptionParser(usage=usage, option_class=SectionableOption)
315    else:
316       program_parser = OptionParser(usage=usage, version="%prog " + version, option_class=SectionableOption)
317       version_string = version
318
319    # display debugging output, if requested
320    if glo_debug & 2:
321       print "<Recursive module import listing for the program>"
322
323    # build an options list for the program
324    __build_options(path, alt_paths, module)
325
326    # display debugging output, if requested
327    if glo_debug & 2:
328       print "-" * 70
329
330    program_options = found_options[:]
331    for option in program_options:
332       __add_option(program_parser, option)
333
334    # display debugging output, if requested
335    if glo_debug & 1:
336       __debug_output(program_options)
337
338    # Store option parser for print_help()
339    glo_option_parser = program_parser
340
341    # process command line for program
342    (program_variables, args) = program_parser.parse_args()
343
344    # process config file options
345    config_parser = OptionParser(option_class=SectionableOption)
346    if configfile_optvar != None:
347       # get the option variable name for the config file filename
348       try:
349          configfile = getattr(program_variables, configfile_optvar)
350       except AttributeError:
351          raise AttributeError, "Did not find variable '" + configfile_optvar + \
352                                "' needed for the config file filename."
353          sys.exit(1)
354
355       # display debugging output, if requested
356       if glo_debug & 2:
357          print "<Recursive module import listing for all files in the current directory>"
358
359       # get ALL options for the config file (not just the run module, this
360       # will grab options for all files in the module's run directory
361
362       # SMB: Set ignore_duplicates to True, to prevent needless error messages
363       # when two modules use the same option (but both modules are not used by
364       # the run module)
365
366       __build_options(path, alt_paths, ignore_duplicates=True)
367       for option in found_options:
368          __add_option(config_parser, option)
369
370       # if configfile option was given on the command line, parse the
371       # specified config file, otherwise use the default config file
372       # filename.
373       if configfile == None:
374          configfile = __default(configfile_optvar)
375
376       glo_append_override_default = True
377
378       # parse the config file and get any set options
379       config_variables = __read_config_file(configfile, config_parser)
380
381       # includes= is a special variable that specifies what addition config
382       # files to read. If it is set, then expand it using glob and read all of
383       # those config files
384       if config_variables:
385          includes = getattr(config_variables, "include", [])
386          if includes:
387             for include in includes:
388                files = glob.glob(include)
389                for file in files:
390                  config_variables = __read_config_file(file, config_parser, config_variables)
391
392       glo_append_override_default = False
393
394    else:
395       config_variables = None
396
397    # display debugging output, if requested
398    if glo_debug & 2:
399       print "-" * 70
400
401    # Merge config_variables into program_variables.
402    # Situations:
403    # 1) Option was specified on the command line, and is already included
404    #    in program_variables at this point.  Result: Already included.
405    # 2) Option was specified both on command line and config file, and is
406    #    an append action.  Result: maintain previous setting and append
407    #    newly found option to the end.
408    # 3) Option was not specified on the command line, but was in the
409    #    config file.  Result: use config file setting.
410    # 4) Option was not specified on the command line or the config file,
411    #    but is used by the program.  Result: use default value.
412
413    if config_variables:
414       # at this point, we have the config file variables in config_variables.
415       # we need to re-parse the command line, so that command-line variables can
416       # override the config file variables
417       (program_variables, args) = config_parser.parse_args(None, config_variables)
418
419    if glo_debug & 1:
420       __debug_variable_output()
421
422    # return program non-option args to caller
423    return args
424
425
426
427
428
429 def get_option(variable, section = None):
430    """
431    <Purpose>
432       Returns the value associated with the given variable.  The variables
433       are set by options found by init_options, and are specified in the
434       arizonaconfig options blocks.
435
436    <Arguments>
437       variable:
438               The name of the variable to retrieve a value from, as a
439               string.
440       section:
441               The name of the section containing the variable. If section.variable
442               does not exist, then this func will try to default back to
443               variable.
444
445    <Exceptions>
446       TypeError: variable missing or not a string.
447
448    <Side Effects>
449       None.
450
451    <Returns>
452       The value associated with the specified variable.  Returns None if
453       the variable could not be found.
454    """
455    # check params
456    arizonageneral.check_type_simple(variable, "variable", str, "get_option")
457
458    if section:
459       result = getattr(program_variables, section + "." + variable, None)
460       if result:
461          return result
462
463    return getattr(program_variables, variable, None)
464
465
466
467
468
469 # TODO: deprecated, use get_option instead
470 def get_option_section(variable, section = None):
471    return get_option(variable, section)
472
473
474
475
476
477 def get_sections(variable):
478    # check params
479    arizonageneral.check_type_simple(variable, "variable", str, "get_sections")
480
481    if not program_variables:
482       return []
483
484    dict = getattr(program_variables, "specified_options", None)
485    if not dict:
486       return []
487
488    list = []
489    for var in dict:
490       dot = var.find(".")
491       if (dot >= 0) and (var[dot+1:] == variable):
492          list.append(var[:dot])
493
494    return list
495
496
497          
498
499
500 def set_option(variable, value):
501    """
502    <Purpose>
503       Sets the value associated with the given variable.
504
505    <Arguments>
506       variable:
507               The name of the variable to set the value of, as a string.
508       value:
509               The new value for the variable.
510
511    <Exceptions>
512       TypeError: variable missing or not a string.
513
514    <Side Effects>
515       Updates program_variables by setting variable to the desired value.
516
517    <Returns>
518       None.
519    """
520    # check params
521    arizonageneral.check_type_simple(variable, "variable", str, "set_option")
522
523    if not program_variables:
524       raise UnboundLocalError, "Must call init_options before set_option"
525
526    setattr(program_variables, variable, value)
527    return
528
529
530
531
532
533 def get_version():
534    """
535    <Purpose>
536       Returns the program version as set by the init_options function.
537
538    <Arguments>
539       None.
540
541    <Exceptions>
542       None.
543
544    <Side Effects>
545       None.
546
547    <Returns>
548       Returns the program version as set by the init_options function, or
549       None if it has not been set.
550    """
551    return version_string
552
553
554
555
556
557 def print_help(stream=sys.stdout):
558    """
559    <Purpose>
560       Prints the program help to the specified file, or defaults to 
561       sys.stdout.
562
563    <Arguments>
564       None.
565
566    <Exceptions>
567       None.
568
569    <Side Effects>
570       None.
571
572    <Returns>
573       None.
574    """
575    glo_option_parser.print_help(stream)
576
577
578
579
580
581 def reset():
582    """
583    <Purpose>
584       Resets all internal state of arizonaconfig.  Only needed for testing
585       purposes.
586
587    <Arguments>
588       Mone.
589
590    <Exceptions>
591       None.
592
593    <Side Effects>
594       Resets all globals to their defaults
595
596    <Returns>
597       None.
598    """   
599    global found_modules
600    global found_options
601    global program_variables
602    global version_string
603    global glo_option_parser
604    global parsed_modules
605    global glo_debug
606
607    # clear any existing state
608    found_modules = []
609    found_options = []
610    program_variables = None
611    version_string = None
612    glo_option_parser = None
613    parsed_modules = []
614    glo_debug = 0
615
616
617
618
619
620 def __debug_output(program_options):
621    """
622    <Purpose>
623       Prints debug output for the given program options.
624
625    <Arguments>
626       program_options:
627               List of program options to display.
628
629    <Exceptions>
630       TypeError: variable missing or not a string.
631
632    <Side Effects>
633       Exits the program with status 0.
634
635    <Returns>
636       None.
637    """
638    global program_variables
639
640    # check params
641    arizonageneral.check_type_simple(program_options, "program_options", list, "__debug_output")
642
643    print "<Module Locations of arizonaconfig Configuration Options>"
644
645    # find the longest long option to make things line up
646    longest_optname = 0
647    for option in program_options:
648       if len(option.longoption) > longest_optname:
649          longest_optname = len(option.longoption)
650
651    # print column headings
652    if longest_optname < 10:
653       longest_optname = 10
654    print "Opt Long Option" + " " * (longest_optname - 9) + "From File"
655    print "--- -----------" + " " * (longest_optname - 9) + "---------"
656
657    # display the help lines and exit
658    for option in program_options:
659       if option.option != "":
660          print option.option + " ",
661       if option.longoption != "":
662          if option.option == "":
663             print "   ",
664          print option.longoption,
665       print " " * (longest_optname - len(option.longoption)) + " " + option.module
666    print "-" * 70
667
668
669 def __debug_variable_output():
670    if program_variables:
671       longest_var = 0
672       for var in program_variables.__dict__:
673          if len(var) > longest_var:
674             longest_var = len(var)
675
676       for var in program_variables.__dict__:
677          print var,
678          print " " * (longest_var - len(var)) + " " + str(program_variables.__dict__[var])
679
680
681
682
683
684 def __default(variable):
685    """
686    <Purpose>
687       Returns the default value for an option.  If the option is not
688       found, raises a TypeError.
689
690    <Arguments>
691       variable:
692               Name of the option variable with the desired default value.
693
694    <Exceptions>
695       TypeError, if there are bad parameters or if the option is not 
696       found.
697
698    <Side Effects>
699       None.
700
701    <Returns>
702       Default value for the option variable.  Can be None.
703    """
704    # check params
705    arizonageneral.check_type_simple(variable, "variable", str, "__default")
706
707    global found_options
708
709    found = False
710    for option in found_options:
711       if option.variable == variable:
712          found = True
713          if option.default != None:
714             return option.default
715    if not found:
716       raise TypeError, "Asked for default of unknown option variable: `" + str(variable) + "'" 
717    return None
718
719
720
721
722
723 def __read_config_file(filename, config_parser, values = None):
724    """
725    <Purpose>
726       Parses the configuration file specified by filename.  Adds any
727       previously missing options to the config_parser.  Returns a list of
728       set options.
729
730    <Arguments>
731       filename:
732               Filename of the configuration file.
733       config_parser:
734               OptionParser for the configuration file.
735
736    <Exceptions>
737       TypeError, if there are bad parameters.
738
739    <Side Effects>
740       Modifies config_parser by adding additional found options.
741       Exits the program if the config file could not be found.
742
743    <Returns>
744       A dictionary of variables, with values either None or the value
745       set in the config file.
746    """
747    global glo_config_filename
748    # check params
749    arizonageneral.check_type_simple(filename, "filename", str, "__read_config_file")
750    filename = filename.strip()
751    if filename[:2] == '~/':
752       envHome = ''
753       if 'HOME' in os.environ:
754         envHome = os.environ['HOME']
755       elif 'HOMEDRIVE' in os.environ and 'HOMEPATH' in os.environ:
756         envHome = os.path.join(os.environ['HOMEDRIVE'],os.environ['HOMEPATH'])
757       filename = envHome + filename[1:]
758    if not isinstance(config_parser, OptionParser):
759       raise TypeError, "The parameter 'config_parser' of the function '__read_config_file' must be an OptionParser."
760
761    # this global is used by pacman/storktrackusage to track what configuration
762    # file was read (or was attempted to be read). It should be set regardless
763    # of whether or not the file was opened and read.
764    glo_config_filename = filename
765
766    try:
767       config_file = file(filename,"r")
768    except IOError:
769       print >> sys.stderr, "WARNING: cannot open configuration file: " + filename
770       return {}
771
772    # dictionary to hold set configuration file options
773    config_variables = {}
774
775    # a list of all the arguments in the config file
776    config_args = []
777
778    # process the config file one line at a time
779    for origline in config_file:
780       line = origline
781
782       # Ignore anything after #, it is a comment
783       linebr = line.split('#')
784       line = linebr[0].strip()
785       if not line:
786          # If the line is empty continue
787          continue
788
789       # set up the usage message
790       error_msg = "The config file " + filename + " has an invalid line:\n" + origline
791       config_parser.set_error(error_msg)
792
793       # Prepend '--' and remove '='s to make a long command line option
794       line = "--" + line
795
796       # JAC: Bug fix.   All '=' were changed to ' '.   This should be more
797       # selective.   There are four cases when this should happen:
798       # a=b
799       # a = b
800       # a= b
801       # a =b
802
803       if line.split()[0].find('=')>0:
804          # case a=b and a= b
805          line = line.replace('=', ' ',1)
806       else:
807          # case a = b and a =b
808          if len(line.split()) > 1 and line.split()[1][0] == '=':
809             line = line.replace('=', ' ',1)
810
811
812       # Detect options that do not exist and issue a warning, rather than
813       # letting config_parser error and exit.
814       optname = line.split()
815       if optname:
816          optname = optname[0]
817          if not config_parser.has_option(optname):
818              if not (optname[2:] in no_warn_options):
819                  print >> sys.stderr, "WARNING: Unknown option: '" + optname[2:] + "' in " + filename
820              continue
821
822       # Handle the comma case in a config file.  For example, expand
823       # an option such as --x a,b,c into --x a --x b --x c.
824       linelist = line.split(',')
825       if len(linelist) > 1:
826          front = line.split()[0]
827          parselist = linelist[0].strip().split(None, 1)
828          for item in linelist[1:]:
829             parselist.append(front.strip())
830             parselist.append(item.strip())
831
832          # JAC: I added the parselist code in this area to fix a bug.
833          # The code used to assume that everything that looked like an option
834          # was an option (i.e. if you had an option "--sshopts" that might
835          # be set to: "-n -i ./id_rsa_ebay -o StrictHostKeyChecking=no" the
836          # program would actually treat most of these as separate options
837          # I think it actually was broken for any arguments that would be
838          # broken in two by "split".
839          #
840          # Even now I think the "strip()" commands above are unnecessary and
841          # may even cause problems, but I'm not certain so I left them in
842          # rather than potentially break compatibility with the prior
843          # implementation.
844       else:
845          parselist = line.split(None,1)
846
847       config_args.extend(parselist)
848
849    # The option line has now been prepared.  Parse it.
850    (val, args) = config_parser.parse_args(args=config_args, values=values)
851
852    # If there are args, then this was a mistyped or invalid option
853    # line in the config file.  Report the error.
854
855    # JRP - 11/5/06
856    # the print_usage() message (as defined in the python
857    # OptionParser class should print the error message
858    # that has been set instead of the usage message. Even
859    # Though we are setting the error message a few lines
860    # up it is defaulting to printing the default usage
861    # message. So we will just print our error_msg
862    # instead.
863    if args:
864       #config_parser.print_usage()
865       print error_msg
866       sys.exit(1)
867
868    return val
869
870
871
872
873
874 def __build_options(path=None, alt_paths=[], module="*", depth=0, ignore_duplicates=False):
875    """
876    <Purpose>
877       Recursively finds and gets the options for any imported modules.
878
879    <Arguments>
880       path:
881               Path to the module.
882       alt_paths:
883               A list of alternate absolute paths to modules.
884       module:
885               The name of the module to start with.  Can contain wildcards
886               to specify multiple modules. Cannot contain a path. 
887
888    <Exceptions>
889       TypeError, if there are bad parameters.
890
891    <Side Effects>
892       Modifies found_options and found_modules.
893
894    <Returns>
895       None.
896    """
897    # check params
898    arizonageneral.check_type_simple(module, "module", str, "__build_options")
899
900    global found_options
901    global found_modules
902    global parsed_modules
903
904    # print "XXX __build_options", path
905
906    # if the caller specified a pathname as the module parameter, then split
907    # it into path and filename parts
908    if os.path.dirname(module):
909       # it is an error to pass in a path and a pathname 
910       assert(path=="." or path==None)
911       path = os.path.dirname(module)
912       module = os.path.basename(module)
913
914    if path == None or path == '.':
915       path = arizonageneral.get_main_module_path()
916
917    path = os.path.abspath(path)
918
919    paths = [path]
920    paths.extend(alt_paths)
921
922    # print "XXX __build_options", path, alt_paths, module, depth
923
924    # get a list of modules
925    if ("*" in module):
926       globpath = os.path.join(path, module)
927       modules = [os.path.basename(fn) for fn in glob.glob(globpath)]
928    else:
929       # SMB: If there were not glob characters in the module name, then don't
930       # try to glob it (if we glob it, and it's in one of the alt_paths, then
931       # we won't find it)
932       modules = [module]
933
934    # print "XXX modules", modules
935
936    # display debugging output, if requested
937    if glo_debug & 2:
938       print " " * depth + globpath
939
940    # process each module
941    for filename in modules:
942       # skip this module if it has already been parsed
943       if filename in parsed_modules:
944          continue
945
946       # smbaker: skip backup files
947       if filename.endswith("~") or filename.endswith(".~py") or filename.endswith(".pyc"):
948          continue
949
950       # display debugging output, if requested
951       if glo_debug & 2:
952          print " " * (depth + 2) + filename
953
954       # open the module
955       # print "XXX looking for ", filename
956       for prefix in paths:
957          pathname = os.path.join(prefix, filename)
958          # print "XXX trying",pathname
959          try:
960             module_file = file(pathname)
961             break
962          except IOError:
963             pass
964       else:
965          # ignore errors (there will be some, because modules like sys
966          # won't be found in the current directory.
967          continue
968       # print "XXX found ", module_file
969
970       # build the options_block string
971       options_block = __build_options_block(module_file)
972
973       includes = []
974
975       # process the options block
976       if (options_block != None):
977          # exec the options string to build the options / includes lists
978          options = None
979          includes = None
980          try:
981             exec(options_block)
982          except:
983             raise TypeError, "\nModule " + filename + ": The arizonaconfig options block is invalid.\n" + \
984                              "It must be of the form:\n" + \
985                              "\"\"\"arizonaconfig\n" + \
986                              "   options=[]\n" + \
987                              "   includes=[]\n" + \
988                              "\"\"\"\n\n" + \
989                              "".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])) + \
990                              "Check for mismatched brackets, missing commas, or unclosed strings."
991
992          # check for the options and includes lists
993          if options == None:
994             raise TypeError, "\nModule " + filename + ": The arizonaconfig options block is missing an options list."
995          if includes == None:
996             raise TypeError, "\nModule " + filename + ": The arizonaconfig options block is missing an includes list."
997
998          # check the validity of the options list
999          try:
1000             option = None
1001             try:
1002                arizonageneral.check_type_simple(options, "", list, "")
1003             except TypeError:
1004                raise TypeError, "Options block must be a list"
1005             for option in options:
1006                __check_option(option)
1007          except TypeError, error:
1008             error_str = "\nModule " + filename + ": The arizonaconfig options block has an invalid options list\n\n"
1009             if (option == None):
1010                error_str += "Options list: " + str(options)
1011             else:
1012                error_str += "Option: " + str(option)
1013             error_str += "\n\nError: " + str(error)
1014             raise TypeError, error_str
1015
1016          # check the validity of the includes list
1017          try:
1018             arizonageneral.check_type_stringlist(includes, "", "arizonaconfig.__build_options")
1019          except TypeError, error:
1020             raise TypeError, "\nModule " + filename + ": The arizonaconfig options block has an invalid includes list\n\n" + \
1021                              "Includes list: " + str(includes) + "\n\n" + \
1022                              "Error: " + str(error)
1023
1024          # process options= line
1025          # add each option to the master list
1026          for option in options:
1027             # store the filename with the option for the debug listing
1028             option = OptionName(option, filename)
1029
1030             if option.description == None:
1031                option.description = optparse.SUPPRESS_HELP
1032
1033             # see if the short or long option already exists
1034             for found_option in found_options:
1035                if found_option == "":
1036                   continue
1037                if (option.option != "" and option.option == found_option.option) \
1038                   or (option.longoption != "" and option.longoption == found_option.longoption):
1039                   # an option name matched.  Does everything else match? (except module name)
1040                   if option.option == found_option.option \
1041                      and option.longoption == found_option.longoption \
1042                      and option.variable == found_option.variable \
1043                      and option.action == found_option.action \
1044                      and option.data == found_option.data \
1045                      and option.default == found_option.default \
1046                      and option.metavar == found_option.metavar \
1047                      and option.description == found_option.description or \
1048                          (option.description == None and found_option == optparse.SUPPRESS_HELP):
1049                      # yes, just skip this option (it already exists)
1050                      break
1051                   else:
1052                      # SMB: if ignore_duplicates is set, then ignore the duplicate
1053                      # option. This is used when __build_options is called when
1054                      # processing the config file (which reads all modules, even
1055                      # those that are not referenced by the current program)
1056                      if ignore_duplicates:
1057                         break
1058                      error_str = "File " + filename + ": Tried to redefine existing option (" + \
1059                                  str(found_option.option) + ", " + str(found_option.longoption) + "), from " + \
1060                                  str(found_option.module) + ", but options were not identical:\n\n"
1061                      if option.option != found_option.option:
1062                         error_str += "Existing 'option': " + str(option.option) + "  Redefined as:" + str(found_option.option) + "\n"
1063                      if option.longoption != found_option.longoption:
1064                         error_str += "Existing 'long option': " + str(option.longoption) + "  Redefined as:" + str(found_option.longoption) + "\n"
1065                      if option.variable != found_option.variable:
1066                         error_str += "Existing 'variable': " + str(option.variable) + "  Redefined as:" + str(found_option.variable) + "\n"
1067                      if option.action != found_option.action:
1068                         error_str += "Existing 'action': " + str(option.action) + "  Redefined as:" + str(found_option.action) + "\n"
1069                      if option.data != found_option.data:
1070                         error_str += "Existing 'data': " + str(option.data) + "  Redefined as:" + str(found_option.data) + "\n"
1071                      if option.default != found_option.default:
1072                         error_str += "Existing 'default': " + str(option.default) + "  Redefined as:" + str(found_option.default) + "\n"
1073                      if option.metavar != found_option.metavar:
1074                         error_str += "Existing 'metavar': " + str(option.metavar) + "  Redefined as:" + str(found_option.metavar) + "\n"
1075                      if option.description != found_option.description or \
1076                         (found_option == optparse.SUPPRESS_HELP and option.description != None):
1077                         error_str +=  "Existing 'description': " + str(option.description) + "  Redefined as:" + str(found_option.description) + "\n"
1078
1079                      raise TypeError, error_str
1080             else:
1081                # nothing matched, add the option
1082                found_options.append(option)
1083
1084          raw_includes = includes
1085          includes = []
1086
1087          # print "XXX raw_includes=", raw_includes
1088          # process includes= line
1089          for include in raw_includes:
1090             # replace $MAIN with the path to the main run module
1091             include = include.replace("$MAIN", arizonageneral.get_main_module_path())
1092             include = include.replace("$SELF", os.path.dirname(pathname))
1093
1094             # add the module to the list if it wasn't already included
1095             # before in the master list
1096             if not (include in found_modules):
1097                includes.append(include)
1098                found_modules.append(include)
1099
1100          # print "XXX includes=", includes
1101
1102       # find import statements in the module
1103       module_file.seek(0)
1104       for line in module_file:
1105          line = line.lstrip()
1106          if line.startswith("import"):
1107             # find the name of the imported module(s), ignore the rest of the line
1108             include_line = line.split(" ", 1)[1].split(",")
1109             for include in include_line:
1110                include = include.strip()
1111
1112                # remove comments
1113                i = include.find("#")
1114                if i != -1:
1115                   include = include[:i].rstrip()
1116
1117                # convert special case "x.y" to "x/y"
1118                # Note: * means pass a list as individual arguments
1119                include = os.path.join(*include.split("."))
1120
1121                # convert special case "x as y" to "x"
1122                include = include.split(" ")[0]
1123
1124                # add the module to the list if it wasn't already included
1125                # before in the master list
1126                if not (include in found_modules):
1127                   includes.append(include + ".py")
1128                   found_modules.append(include)
1129
1130       # done searching the module, close it
1131       module_file.close()
1132
1133       # this module is done.. add it to the list of parsed modules
1134       parsed_modules.append(filename)
1135
1136       # print "XXX module", module, "includes", includes
1137
1138       # call __build_options() recursively for each import
1139       # base case: if includes = [], no recursive call is made
1140       for include in includes:
1141          # general case: handle each included/imported module
1142          if os.path.dirname(include):
1143             # if we have something with an absolute path, then break it into
1144             # path and filename parts
1145             __build_options(os.path.dirname(include), alt_paths, os.path.basename(include), depth + 4, ignore_duplicates=ignore_duplicates)
1146          else:
1147             __build_options(path, alt_paths, include, depth + 4, ignore_duplicates=ignore_duplicates)
1148
1149    # done with this module
1150    return
1151
1152
1153
1154
1155
1156 def __check_option(option):
1157    """
1158    <Purpose>
1159       Checks the validity of an option line.
1160
1161    <Arguments>
1162       option:
1163               List containing option items that will be checked.
1164
1165    <Exceptions>
1166       TypeError, if there was a problem with the option.
1167
1168    <Side Effects>
1169       None.
1170
1171    <Returns>
1172       None.
1173    """
1174    try:
1175       arizonageneral.check_type_simple(option, "", list, "")
1176    except TypeError:
1177       raise TypeError, "Option line must be a list"
1178       
1179    if len(option) != 8:
1180       raise TypeError, "Option line must contain exactly 8 items, only detected " + str(len(option))
1181    arizonageneral.check_type_simple(option[0], "option", str, "arizonaconfig.__check_option")
1182    arizonageneral.check_type_simple(option[1], "long option", str, "arizonaconfig.__check_option")
1183    arizonageneral.check_type_simple(option[2], "variable", str, "arizonaconfig.__check_option")
1184    arizonageneral.check_type_simple(option[3], "action", str, "arizonaconfig.__check_option")
1185    arizonageneral.check_type(option[4], "data", [str, None, int], "arizonaconfig.__check_option")
1186    arizonageneral.check_type_simple(option[6], "metavar", str, "arizonaconfig.__check_option", noneok=True)
1187    arizonageneral.check_type_simple(option[7], "description", str, "arizonaconfig.__check_option", noneok=True)
1188    if option[2].strip() == "" or option[2].strip() != option[2]:
1189       raise TypeError, "Invalid variable: '" + str(option[2]) + "'\nShould either be None, or a non-empty string with no leading or trailing spaces"
1190    if option[3] != "store" and option[3] != "append" and option[3] != "store_const" \
1191       and option[3] != "store_true" and option[3] != "store_false" \
1192       and option[3] != "sectionstart" and option[3] != "sectionstop":
1193       raise TypeError, "action must be one of: 'store', 'append', 'store_const', 'store_true', 'store_false'"
1194    if option[3] == "help" and option[5] != None:
1195       raise TypeError, "default must be None when action is 'help'"
1196    if option[3] == "store":
1197       if option[4] == "string":
1198          arizonageneral.check_type_simple(option[5], "default", str, "arizonaconfig.__check_option", noneok=True)
1199       elif option[4] == "int":
1200          arizonageneral.check_type_simple(option[5], "default", int, "arizonaconfig.__check_option")
1201       elif option[4] == "long":
1202          arizonageneral.check_type_simple(option[5], "default", long, "arizonaconfig.__check_option")
1203       elif option[4] == "float":
1204          arizonageneral.check_type_simple(option[5], "default", float, "arizonaconfig.__check_option")
1205       elif option[4] == "complex":
1206          arizonageneral.check_type_simple(option[5], "default", complex, "arizonaconfig.__check_option")
1207       else:
1208          raise TypeError, "data must be one of 'string', 'int', 'long', 'float', 'complex' when action is either 'store' or 'append'"
1209    elif option[3] == "append" or option[3] == "sectionstart":
1210       if option[4] == "string":
1211          arizonageneral.check_type(option[5], "default", [[list, str], None], "arizonaconfig.__check_option")
1212       elif option[4] == "int":
1213          arizonageneral.check_type(option[5], "default", [[list, int], None], "arizonaconfig.__check_option")
1214       elif option[4] == "long":
1215          arizonageneral.check_type(option[5], "default", [[list, long], None], "arizonaconfig.__check_option")
1216       elif option[4] == "float":
1217          arizonageneral.check_type(option[5], "default", [[list, float], None], "arizonaconfig.__check_option")
1218       elif option[4] == "complex":
1219          arizonageneral.check_type(option[5], "default", [[list, complex], None], "arizonaconfig.__check_option")
1220       else:
1221          raise TypeError, "data must be one of 'string', 'int', 'long', 'float', 'complex' when action is either 'store' or 'append'"
1222    elif option[3] == "store_const":
1223       arizonageneral.check_type_simple(option[4], "data", int, "arizonaconfig.__check_option")
1224       arizonageneral.check_type_simple(option[5], "default", int, "arizonaconfig.__check_option")
1225    elif option[3] == "store_true" or option[3] == "store_false":
1226       arizonageneral.check_type_simple(option[5], "default", bool, "arizonaconfig.__check_option")
1227    else:
1228       if option[4] != None:
1229          raise TypeError, "data must be None, unless action is one of 'store', 'append', 'store_const'"
1230    if option[6] != None and option[3] != "store" and option[3] != "append" and option[3] != "sectionstart":
1231       raise TypeError, "metavar must be None unless action is either 'store' or 'append'"  
1232    if option[6] != None and (option[6].strip() == "" or option[6].strip() != option[6]):
1233       raise TypeError, "Invalid metavar: '" + option[2] + "'\nShould either be None, or a non-empty string with no leading or trailing spaces"
1234    
1235
1236
1237
1238
1239 def __build_options_block(module_file):
1240    """
1241    <Purpose>
1242       Scans a file for the arizonaconfig options block and returns it as
1243       a string.
1244
1245    <Arguments>
1246       module_file:
1247               Open file that will be scanned for triple quoted strings
1248               starting as "" "arizonaconfig (no spaces).  This file is
1249               expected to be open and ready to read.
1250
1251    <Exceptions>
1252       TypeError, if there are bad parameters.
1253
1254    <Side Effects>
1255       None.
1256
1257    <Returns>
1258       The found options block string, or None if an option block could
1259       not be found.
1260    """
1261    # check params
1262    arizonageneral.check_type_simple(module_file, "module_file", file, "__build_options_block")
1263
1264    # build the options_block string
1265    options_block = None
1266    append_block = False
1267    for line in module_file:
1268       # clean up the line for easier processing
1269       line = line.strip()
1270
1271       # the options block must start with (3 quotes)arizonaconfig
1272       # if this is found, set to start adding lines to the block
1273       if line.startswith("\"\"\"arizonaconfig"):
1274          # set to add lines to the block. Doesn't add the first line,
1275          # because it is just the block identifier.
1276          append_block = True
1277          options_block = ""
1278       # if it's set to add options, add each line to the block
1279       elif append_block:
1280          # if the trailing triple quote was found, the block is done.
1281          if line.endswith("\"\"\""):
1282             # stop adding lines to the block
1283             append_block = False
1284
1285             # strip off trailing triple quotes
1286             line = line[0:len(line) - 3]
1287
1288          # add the line to the options block
1289          options_block += line + "\n"
1290
1291    return options_block
1292
1293
1294
1295
1296
1297 def __add_option(parser, option):
1298    """
1299    <Purpose>
1300       Add an option to the option parser.
1301
1302    <Arguments>
1303       parser:
1304               OptionParser to add an option to.
1305       option:
1306               OptionName with the option information.
1307
1308    <Exceptions>
1309       None.
1310
1311    <Side Effects>
1312       OptionParser is changed to include a new option.
1313
1314    <Returns>
1315       None.
1316    """
1317    # check params
1318    if not isinstance(parser, OptionParser):
1319       raise TypeError, "The parameter 'parser' of the function '__add_option' must be an OptionParser."
1320    if not isinstance(option, OptionName):
1321       raise TypeError, "The parameter 'option' of the function '__add_option' must be an OptionName."
1322
1323    # clear unused option line entries.
1324    option_type = None
1325    option_const = None
1326
1327    if is_storable(option.action):
1328       # only 'store' and 'append' use 'type='
1329       option_type = option.data
1330    else:
1331       option.metavar = None
1332    if option.action == "store_const":
1333       # only 'store_const' uses 'const='
1334       option_const = option.data
1335
1336    # add option line to parser
1337    # Note: "default=option.default" is not added.
1338    # Reason: We first must check the config file for any specified
1339    #         options.  Then, only if it's not in the config file can the
1340    #         default be used.
1341    parser.add_option(option.option, option.longoption, dest=option.variable,
1342                      action=option.action, type=option_type,
1343                      const=option_const, metavar=option.metavar,
1344                      help=option.description, default=option.default)
1345    return
1346
1347
1348
1349
1350
1351 class OptionName:
1352    """
1353    <Purpose>
1354       Data structure for holding all the date for a single option line
1355
1356    <Variables>
1357       See the init_options functions for more details on these variables.
1358       option:
1359               Short option string.
1360       long option:
1361               Long option string.
1362       variable:
1363               Variable name for the option.
1364       action:
1365               "store", "append", "store_const", "store_true",
1366               "store_false".
1367       data:
1368               "string", "int", "long", "float", "complex", None.
1369       default:
1370               The default value to store if the option is not given.
1371       metavar:
1372               An alternate variable name to show in --help, or None.
1373       description:
1374               Helpful description of the option, for use in --help.
1375       module:
1376               Name of the module that the option was in.
1377
1378    <Functions>
1379       None.
1380    """
1381    option = None
1382    longoption = None
1383    variable = None
1384    action = None
1385    data = None
1386    default = None
1387    metavar = None
1388    description = None
1389    module = None
1390
1391    def __init__(self, options, module_name):
1392       """
1393       <Purpose>
1394          OptionName constructor.  Sets initial values for the internal
1395          variables.
1396
1397       <Arguments>
1398          options:
1399                  List of options.  Must have 8 elements.
1400          module:
1401                  Name of the module the options came from.
1402
1403       <Exceptions>
1404          None.
1405
1406       <Side Effects>
1407          Sets variables: option, longoption, variable, action, data,
1408          default, metavar, description, module.
1409
1410       <Returns>
1411          None.
1412       """
1413       # check params
1414       arizonageneral.check_type_simple(options, "options", list, "OptionName.__init__")
1415       arizonageneral.check_type_simple(module_name, "module_name", str, "OptionName.__init__")
1416
1417       self.option = options[0]
1418       self.longoption = options[1]
1419       self.variable = options[2]
1420       self.action = options[3]
1421       self.data = options[4]
1422       self.default = options[5]
1423       self.metavar = options[6]
1424       self.description = options[7]
1425       self.module = module_name
1426       return
1427
1428
1429
1430
1431
1432 class OptionParser(optparse.OptionParser):
1433    """
1434    <Purpose>
1435       Overrides optparse.OptionParser.error to extend its functionality.
1436
1437    <Parent>
1438       optparse.OptionParser
1439    """
1440
1441    custom_error = None
1442
1443    def error(self, msg):
1444       """
1445       <Purpose>
1446          Overrides optparse.OptionParser.error to print an extra line
1447          offering --help.
1448
1449       <Arguments>
1450          msg:
1451                  The error message to be displayed.
1452
1453       <Exceptions>
1454          None.
1455
1456       <Side Effects>
1457          None.
1458
1459       <Returns>
1460          None.
1461       """
1462       if self.custom_error != None:
1463          print >> sys.stderr, self.custom_error
1464       else:
1465          self.print_usage()
1466
1467       print >> sys.stderr, msg
1468
1469       if self.custom_error == None:
1470          print >> sys.stderr, "Try '--help' for an informative help message"
1471
1472       sys.exit(1)
1473       return
1474
1475
1476    def set_error(self, error_message):
1477       """
1478       <Purpose>
1479          Saves the given error message, which will be displayed instead of
1480          a usage line.  Also, suppresses printing of the "Try --help"
1481          text.  This is used to display parsing errors in the config file.
1482
1483       <Arguments>
1484          error_message:
1485                  The error message to be displayed.
1486
1487       <Exceptions>
1488          None.
1489
1490       <Side Effects>
1491          The class variable custom_error is set.
1492
1493       <Returns>
1494          None.
1495       """
1496       self.custom_error = error_message
1497       return
1498
1499