diff --git a/bob/bio/base/tools/command_line.py b/bob/bio/base/tools/command_line.py index b3a8bfcf6c932ae21deeb73ba2c23dc92a04365c..cf8010c159094aed99a1de3d29e0bc2c44e64d3c 100644 --- a/bob/bio/base/tools/command_line.py +++ b/bob/bio/base/tools/command_line.py @@ -3,6 +3,8 @@ import os import sys import socket +import bob.extension + import bob.core logger = bob.core.log.setup("bob.bio.base") @@ -47,6 +49,7 @@ def command_line_parser(description=__doc__, exclude_resources_from=[]): ############## options that are required to be specified ####################### config_group = parser.add_argument_group('\nParameters defining the experiment. Most of these parameters can be a registered resource, a configuration file, or even a string that defines a newly created object') config_group.add_argument('configuration_file', metavar='PATH', nargs='*', help = 'A configuration file containing one or more of "database", "preprocessor", "extractor", "algorithm" and/or "grid"') + config_group.add_argument('-H', '--create-configuration-file', metavar='PATH', help = 'If selected, an empty configuration file will be created') config_group.add_argument('-d', '--database', metavar = 'x', nargs = '+', help = 'Database and the protocol; registered databases are: %s' % utils.resource_keys('database', exclude_resources_from)) config_group.add_argument('-p', '--preprocessor', metavar = 'x', nargs = '+', @@ -173,6 +176,18 @@ def _take_from_config_or_command_line(args, config, keyword, default, required=T elif required: raise ValueError("Please specify a %s either on command line (via --%s) or in a configuration file" %(keyword, keyword)) + if config is not None and hasattr(config, keyword): + setattr(config, keyword, None) + +def _check_config_consumed(config): + if config is not None: + import inspect + for keyword in dir(config): + if not keyword.startswith('_') and not keyword.isupper(): + attr = getattr(config,keyword) + if attr is not None and not inspect.isclass(attr) and not inspect.ismodule(attr): + logger.warn("The variable '%s' in a configuration file is not known or not supported; use all uppercase variable names (e.g., '%s') to suppress this warning.", keyword, keyword.upper()) + def initialize(parsers, command_line_parameters = None, skips = []): """initialize(parsers, command_line_parameters = None, skips = []) -> args @@ -219,6 +234,18 @@ def initialize(parsers, command_line_parameters = None, skips = []): parser = parsers['main'] args = parser.parse_args(command_line_parameters) + + # check if the "create_configuration_file" function was requested + if args.create_configuration_file is not None: + # update list of options to be written into the config file + set_required_common_optional_arguments( + required = ['database', 'preprocessor', 'extractor', 'algorithm', 'sub_directory'], + common = ['protocol', 'grid', 'parallel', 'verbose', 'groups', 'temp_directory', 'result_directory', 'zt_norm', 'allow_missing_files', 'dry_run', 'force'], + optional = ['preprocessed_directory', 'extracted_directory', 'projected_directory', 'model_directories', 'extractor_file', 'projector_file', 'enroller_file'] + ) + # this will exit at the end + _create_configuration_file(parsers, args) + # first, read the configuration file and set everything from the config file to the args -- as long as not overwritten on command line config = utils.read_config_file(args.configuration_file) if args.configuration_file else None for keyword in ("database", "preprocessor", "extractor", "algorithm"): @@ -271,6 +298,9 @@ def initialize(parsers, command_line_parameters = None, skips = []): _take_from_config_or_command_line(args, config, keyword, parser.get_default(keyword), required=False, is_resource=False) + # check that all variables in the config file are consumed by the above options + _check_config_consumed(config) + # evaluate skips if skips is not None and args.execute_only is not None: for skip in skips: @@ -417,3 +447,62 @@ def write_info(args, command_line_parameters, executable): f.write("Algorithm:\n%s\n\n" % args.algorithm) except IOError: logger.error("Could not write the experimental setup into file '%s'", args.info_file) + +global _required_list, _common_list, _optional_list +_required_list = set() +_common_list = set() +_optional_list = set() + +def set_required_common_optional_arguments(required = [], common = [], optional = []): + _required_list.update(required) + _common_list.update(common) + _optional_list.update(optional) + +def _create_configuration_file(parsers, args): + """This function writes an empty configuration file with all possible options.""" + logger.info("Writing configuration file %s", args.create_configuration_file) + import datetime + executables = bob.extension.find_executable(os.path.basename(sys.argv[0]), prefixes = [os.path.dirname(sys.argv[0]), 'bin']) + if not executables: + executables = [sys.argv[0]] + + parser = parsers['main'] + + bob.io.base.create_directories_safe(os.path.dirname(args.create_configuration_file)) + + required = "# Configuration file automatically generated at %s for %s.\n\n" % (datetime.date.today(), executables[0]) + required += "##################################################\n############### REQUIRED ARGUMENTS ###############\n##################################################\n\n" + required += "# These arguments need to be set.\n\n\n" + common = "##################################################\n################ COMMON ARGUMENTS ################\n##################################################\n\n" + common += "# These arguments are commonly changed.\n\n\n" + optional = "##################################################\n############### OPTIONAL ARGUMENTS ###############\n##################################################\n\n" + optional += "# Files and directories might commonly be specified with absolute paths or relative to the temp_directory.\n# Change these options, e.g., to reuse parts of other experiments.\n\n\n" + rare = "##################################################\n############ RARELY CHANGED ARGUMENTS ############\n##################################################\n\n\n" + + with open(args.create_configuration_file, 'w') as f: + + for action in parser._actions[3:]: + if action.help == "==SUPPRESS==": + continue + + tmp = "# %s\n\n" % action.help + if action.nargs is None and action.type is None and action.default is not None: + tmp += "#%s = '%s'\n\n\n" % (action.dest, action.default) + else: + tmp += "#%s = %s\n\n\n" % (action.dest, action.default) + + if action.dest in _required_list: + required += tmp + elif action.dest in _common_list: + common += tmp + elif action.dest in _optional_list: + optional += tmp + else: + rare += tmp + + f.write(required) + f.write(common) + f.write(optional) + f.write(rare) + + parser.exit(1, "Configuration file '%s' was written; exiting\n" % args.create_configuration_file)