#!/usr/bin/env python
"""
Allows querying variables from env_*xml files and listing all available variables.
There are two usage modes:
1) Querying variables:
- You can query a variable, or a list of variables via
./xmlquery var1
or, for multiple variables (either comma or space separated)
./xmlquery var1,var2,var3 ....
./xmlquery var1 var2 var3 ....
where var1, var2 and var3 are variables that appear in a CIME case xml file
Several xml variables that have settings for each component have somewhat special treatment
The variables that this currently applies to are
NTASKS, NTHRDS, ROOTPE, PIO_TYPENAME, PIO_STRIDE, PIO_NUMTASKS
As examples:
- to show the number of tasks for each component, issue
./xmlquery NTASKS
- to show the number of tasks just for the atm component, issue
./xmlquery NTASKS_ATM
- The CIME case xml variables are grouped together in xml elements .
This is done to associate together xml variables with common features.
Most variables are only associated with one group. However, in env_batch.xml,
there are also xml variables that are associated with each potential batch job.
For these variables, the '--subgroup' option may be used to query the variable's
value for a particular group.
As an example, in env_batch.xml, the xml variable JOB_QUEUE appears in each of
the batch job groups (defined in config_batch.xml):
To query the variable JOB_QUEUE only for one group in case.run, you need
to specify a sub-group argument to xmlquery.
./xmlquery JOB_QUEUE --subgroup case.run
JOB_QUEUE: regular
./xmlquery JOB_QUEUE
Results in group case.run
JOB_QUEUE: regular
Results in group case.st_archive
JOB_QUEUE: caldera
Results in group case.test
JOB_QUEUE: regular
- You can tailor the query by adding ONE of the following possible qualifier arguments:
[--full --fileonly --value --raw --description --get-group --type --valid-values ]
as examples:
./xmlquery var1,var2 --full
./xmlquery var1,var2 --fileonly
- You can query variables via a partial-match, using --partial-match or -p
as examples:
./xmlquery STOP --partial-match
Results in group run_begin_stop_restart
STOP_DATE: -999
STOP_N: 5
STOP_OPTION: ndays
./xmlquery STOP_N
STOP_N: 5
- By default variable values are resolved prior to output. If you want to see the unresolved
value(s), use the --no-resolve qualifier
as examples:
./xmlquery RUNDIR
RUNDIR: /glade/scratch/mvertens/atest/run
./xmlquery RUNDIR --no-resolve
RUNDIR: $CIME_OUTPUT_ROOT/$CASE/run
2) Listing all groups and variables in those groups
./xmlquery --listall
- You can list a subset of variables by adding one of the following qualifier arguments:
[--subgroup GROUP --file FILE]
As examples:
If you want to see the all of the variables in group 'case.run' issue
./xmlquery --listall --subgroup case.run
If you want to see all of the variables in 'env_run.xml' issue
./xmlquery --listall --file env_run.xml
If you want to see all of the variables in LockedFiles/env_build.xml issue
./xmlquery --listall --file LockedFiles/env_build.xml
- You can tailor the query by adding ONE of the following possible qualifier arguments:
[--full --fileonly --raw --description --get-group --type --valid-values]
- The env_mach_specific.xml and env_archive.xml files are not supported by this tool.
"""
from standard_script_setup import *
from CIME.case import Case
from CIME.utils import expect, convert_to_string
import textwrap, sys, re
logger = logging.getLogger("xmlquery")
unsupported_files = ["env_mach_specific.xml", "env_archive.xml"]
###############################################################################
def parse_command_line(args, description):
###############################################################################
parser = argparse.ArgumentParser(
description=description,
formatter_class=argparse.RawTextHelpFormatter)
CIME.utils.setup_standard_logging_options(parser)
# Set command line options
parser.add_argument("variables", nargs="*" ,
help="Variable name(s) to query from env_*.xml file(s)\n"
"( 'variable_name' from value ).\n"
"Multiple variables can be given, separated by commas or spaces.\n")
parser.add_argument("--caseroot" , "-caseroot", default=os.getcwd(),
help="Case directory to reference.\n"
"Default is current directory.")
parser.add_argument("--listall", "-listall" , default=False , action="store_true" ,
help="List all variables and their values.")
parser.add_argument("--file" , "-file",
help="The file you want to query. If not given, queries all files.\n"
"Typically used with the --listall option.")
parser.add_argument("--subgroup","-subgroup",
help="Apply to this subgroup only.")
parser.add_argument("-p", "--partial-match", action="store_true",
help="Allow partial matches of variable names, treats args as regex.")
parser.add_argument("--no-resolve", "-no-resolve", action="store_true",
help="Do not resolve variable values.")
group = parser.add_mutually_exclusive_group()
group.add_argument("--full", default=False, action="store_true",
help="Print a full listing for each variable, including value, type,\n"
"valid values, description and file.")
group.add_argument("--fileonly", "-fileonly", default=False, action="store_true",
help="Only print the filename that each variable is defined in.")
group.add_argument("--value", "-value", default=False, action="store_true",
help="Only print one value without newline character.\n"
"If more than one has been found print first value in list.")
group.add_argument("--raw", default=False, action="store_true",
help="Print the complete raw record associated with each variable.")
group.add_argument("--description", default=False, action="store_true",
help="Print the description associated with each variable.")
group.add_argument("--get-group", default=False, action="store_true",
help="Print the group associated with each variable.")
group.add_argument("--type", default=False, action="store_true",
help="Print the data type associated with each variable.")
group.add_argument("--valid-values", default=False, action="store_true",
help="Print the valid values associated with each variable, if defined.")
args = CIME.utils.parse_args_and_handle_standard_logging_options(args, parser)
if (len(sys.argv) == 1) :
parser.print_help()
exit()
if len(args.variables) == 1:
variables = args.variables[0].split(',')
else:
variables = args.variables
return variables, args.subgroup, args.caseroot, args.listall, args.fileonly, \
args.value, args.no_resolve, args.raw, args.description, args.get_group, args.full, \
args.type, args.valid_values, args.partial_match, args.file
def get_value_as_string(case, var, attribute=None, resolved=False, subgroup=None):
if var in ["THREAD_COUNT", "TOTAL_TASKS", "TASKS_PER_NODE", "NUM_NODES", "SPARE_NODES", "TASKS_PER_NUMA", "CORES_PER_TASK"]:
value = str(getattr(case, var.lower()))
else:
thistype = case.get_type_info(var)
value = case.get_value(var, attribute=attribute, resolved=resolved, subgroup=subgroup)
if value is not None and thistype:
value = convert_to_string(value, thistype, var)
return value
def xmlquery_sub(case, variables, subgroup=None, fileonly=False,
resolved=True, raw=False, description=False, get_group=False,
full=False, dtype=False, valid_values=False, xmlfile=None):
"""
Return list of attributes and their values, print formatted
"""
results = {}
comp_classes = case.get_values("COMP_CLASSES")
if xmlfile:
case.set_file(xmlfile)
# Loop over variables
for var in variables:
if subgroup is not None:
groups = [subgroup]
else:
groups = case.get_record_fields(var, "group")
if not groups:
groups = ['none']
if xmlfile:
expect(xmlfile not in unsupported_files,
"XML file {} is unsupported by this tool."
.format(xmlfile))
if not groups:
value = case.get_value(var, resolved=resolved)
results['none'] = {}
results['none'][var] = {}
results['none'][var]['value'] = value
elif not groups:
results['none'] = {}
results['none'][var] = {}
for group in groups:
if not group in results:
results[group] = {}
if not var in results[group]:
results[group][var] = {}
expect(group, "No group found for var {}".format(var))
if get_group:
results[group][var]['get_group'] = group
value = get_value_as_string(case, var, resolved=resolved, subgroup=group)
if value is None:
var, comp, iscompvar = case.check_if_comp_var(var)
if iscompvar:
value = []
for comp in comp_classes:
try:
nextval = get_value_as_string(case,var, attribute={"compclass" : comp}, resolved=resolved, subgroup=group)
except:
nextval = get_value_as_string(case,var, attribute={"compclass" : comp}, resolved=False, subgroup=group)
if nextval is not None:
value.append(comp + ":" + "{}".format(nextval))
else:
value = get_value_as_string(case, var, resolved=resolved, subgroup=group)
if value is None:
if xmlfile:
expect(False, " No results found for variable {} in file {}".format(var, xmlfile))
else:
expect(False, " No results found for variable {}".format(var))
results[group][var]['value'] = value
if raw:
results[group][var]['raw'] = case.get_record_fields(var, "raw")
if description or full:
results[group][var]['desc'] = case.get_record_fields(var, "desc")
if fileonly or full:
results[group][var]['file'] = case.get_record_fields(var, "file")
if dtype or full:
results[group][var]['type'] = case.get_type_info(var)
if valid_values or full:
results[group][var]['valid_values'] = case.get_record_fields(var, "valid_values") #*** this is the problem ***
return results
def _main_func(description):
# Initialize command line parser and get command line options
variables, subgroup, caseroot, listall, fileonly, \
value, no_resolve, raw, description, get_group, full, dtype, \
valid_values, partial_match, xmlfile = parse_command_line(sys.argv, description)
expect(xmlfile not in unsupported_files,
"XML file {} is unsupported by this tool."
.format(xmlfile))
# Initialize case ; read in all xml files from caseroot
with Case(caseroot) as case:
if listall or partial_match:
if xmlfile:
case.set_file(xmlfile)
all_variables = sorted(case.get_record_fields(None, "varid"))
logger.debug("all_variables: {}".format(all_variables))
if partial_match:
all_matching_vars = []
for variable in variables:
regex = re.compile(variable)
for all_variable in all_variables:
if regex.search(all_variable):
if subgroup is not None:
vargroups = case.get_record_fields(all_variable, "group")
if subgroup not in vargroups:
continue
all_matching_vars.append(all_variable)
variables = all_matching_vars
else:
if subgroup is not None:
all_matching_vars = []
for all_variable in all_variables:
vargroups = case.get_record_fields(all_variable, "group")
if subgroup not in vargroups:
continue
else:
all_matching_vars.append(all_variable)
variables = all_matching_vars
else:
variables = all_variables
expect(variables, "No variables found")
results = xmlquery_sub(case, variables, subgroup, fileonly, resolved=not no_resolve,
raw=raw, description=description, get_group=get_group, full=full,
dtype=dtype, valid_values=valid_values, xmlfile=xmlfile)
if full or description:
wrapper=textwrap.TextWrapper()
wrapper.subsequent_indent = "\t\t\t"
wrapper.fix_sentence_endings = True
for group in sorted(iter(results)):
if (len(variables) > 1 or len(results) > 1 or full) and not get_group and not value:
print("\nResults in group {}".format(group))
cnt = 0
for var in variables:
if var in results[group]:
if raw:
print(results[group][var]['raw'])
elif get_group:
print("\t{}: {}".format(var, results[group][var]['get_group']))
elif value:
if cnt > 0:
sys.stdout.write(",")
sys.stdout.write("{}".format(results[group][var]['value']))
elif description:
if results[group][var]['desc'][0] is not None:
desc_text = ' '.join(results[group][var]['desc'][0].split())
print("\t{}: {}".format(var, wrapper.fill(desc_text)))
elif fileonly:
print("\t{}: {}".format(var, results[group][var]['file']))
elif dtype:
print("\t{}: {}".format(var, results[group][var]['type']))
elif valid_values:
if 'valid_values' in results[group][var]:
print("\t{}: {}".format(var, results[group][var]["valid_values"]))
elif full:
if results[group][var]['desc'][0] is not None:
desc_text = ' '.join(results[group][var]['desc'][0].split())
print("\t{}: value={}".format(var, results[group][var]['value']))
print("\t\ttype: {}".format(results[group][var]['type'][0]))
if 'valid_values' in results[group][var]:
print("\t\tvalid_values: {}".format(results[group][var]["valid_values"]))
print("\t\tdescription: {}".format(wrapper.fill(desc_text)))
print("\t\tfile: {}".format(results[group][var]['file'][0]))
else:
print("\t{}: {}".format(var, results[group][var]['value']))
cnt += 1
if (__name__ == "__main__"):
_main_func(__doc__)