def is_valid_value(self, name, value): """Determine whether a value is valid for the named variable. The `value` argument must be a list of strings formatted as they would appear in the namelist (even for scalar variables, in which case the length of the list is always 1). """ name = name.lower() # Separate into a type, optional length, and optional size. type_, max_len, size = self.split_type_string(name) invalid = [] # Check value against type. for scalar in value: if not is_valid_fortran_namelist_literal(type_, scalar): invalid.append(scalar) if len(invalid) > 0: logger.warn("Invalid values %s" % invalid) return False # Now that we know that the strings as input are valid Fortran, do some # canonicalization for further checks. canonical_value = self._canonicalize_value(type_, value) # Check maximum length (if applicable). if max_len is not None: for scalar in canonical_value: if len(scalar) > max_len: return False # Check valid value constraints (if applicable). valid_values = self._valid_values[name] if valid_values is not None: expect( type_ in ('integer', 'character'), "Found valid_values attribute for variable %s with " "type %s, but valid_values only allowed for character " "and integer variables." % (name, type_)) if type_ == 'integer': compare_list = [int(vv) for vv in valid_values] else: compare_list = valid_values for scalar in canonical_value: if scalar not in compare_list: invalid.append(scalar) if len(invalid) > 0: logger.warn("Invalid values %s" % invalid) return False # Check size of input array. if len(expand_literal_list(value)) > size: expect( False, "Value index exceeds variable size for variable %s, allowed array length is %s value array size is %s" % (name, size, len(expand_literal_list(value)))) return True
def add_default(self, name, value=None): """Add a value for the specified variable to the namelist. If the specified variable is already defined in the object, the existing value is preserved. Otherwise, the `value` argument, if provided, will be used to set the value. If no such value is found, the defaults file will be consulted. If null values are present in any of the above, the result will be a merged array of values. If no value for the variable is found via any of the above, this method will raise an exception. """ group = self._definition.get_node_element_info(name, "group") # Use this to see if we need to raise an error when nothing is found. have_value = False # Check for existing value. current_literals = self._namelist.get_variable_value(group, name) if current_literals != [""]: have_value = True # Check for input argument. if value is not None: have_value = True literals = self._to_namelist_literals( name, value ) #FIXME - this is where an array is compressed into a 3*value current_literals = merge_literal_lists(literals, current_literals) # Check for default value. default = self.get_default(name, allow_none=True) if default is not None: have_value = True default_literals = self._to_namelist_literals(name, default) current_literals = merge_literal_lists(default_literals, current_literals) expect(have_value, "No default value found for %s." % name) # Go through file names and prepend input data root directory for # absolute pathnames. var_input_pathname = self._definition.get_input_pathname(name) if var_input_pathname == 'abs': current_literals = expand_literal_list(current_literals) for i, literal in enumerate(current_literals): if literal == '': continue file_path = character_literal_to_string(literal) if file_path == 'UNSET' or file_path == 'idmap': continue if file_path == 'null': continue file_path = self.set_abs_file_path(file_path) expect(os.path.exists(file_path), "File not found: %s = %s" % (name, literal)) current_literals[i] = string_to_character_literal(file_path) current_literals = compress_literal_list(current_literals) # Set the new value. self._namelist.set_variable_value(group, name, current_literals)
def add_default(self, name, value=None, ignore_abs_path=None): """Add a value for the specified variable to the namelist. If the specified variable is already defined in the object, the existing value is preserved. Otherwise, the `value` argument, if provided, will be used to set the value. If no such value is found, the defaults file will be consulted. If null values are present in any of the above, the result will be a merged array of values. If no value for the variable is found via any of the above, this method will raise an exception. """ # pylint: disable=protected-access group = self._definition.get_group(name) # Use this to see if we need to raise an error when nothing is found. have_value = False # Check for existing value. current_literals = self._namelist.get_variable_value(group, name) # Check for input argument. if value is not None: have_value = True # if compression were to occur, this is where it does literals = self._to_namelist_literals(name, value) current_literals = merge_literal_lists(literals, current_literals) # Check for default value. default = self.get_default(name, allow_none=True) if default is not None: have_value = True default_literals = self._to_namelist_literals(name, default) current_literals = merge_literal_lists(default_literals, current_literals) expect(have_value, "No default value found for {}.".format(name)) # Go through file names and prepend input data root directory for # absolute pathnames. var_type, _, var_size = self._definition.split_type_string(name) if var_type == "character" and ignore_abs_path is None: var_input_pathname = self._definition.get_input_pathname(name) if var_input_pathname == 'abs': current_literals = expand_literal_list(current_literals) for i, literal in enumerate(current_literals): if literal == '': continue file_path = character_literal_to_string(literal) # NOTE - these are hard-coded here and a better way is to make these extensible if file_path == 'UNSET' or file_path == 'unset' or file_path == 'idmap': continue if file_path == 'null': continue file_path = self.set_abs_file_path(file_path) if not os.path.exists(file_path): logger.warning("File not found: {} = {}, will attempt to download in check_input_data phase".format(name, literal)) current_literals[i] = string_to_character_literal(file_path) current_literals = compress_literal_list(current_literals) # Set the new value. self._namelist.set_variable_value(group, name, current_literals, var_size)
def add_default(self, name, value=None): """Add a value for the specified variable to the namelist. If the specified variable is already defined in the object, the existing value is preserved. Otherwise, the `value` argument, if provided, will be used to set the value. If no such value is found, the defaults file will be consulted. If null values are present in any of the above, the result will be a merged array of values. If no value for the variable is found via any of the above, this method will raise an exception. """ group = self._definition.get_node_element_info(name, "group") # Use this to see if we need to raise an error when nothing is found. have_value = False # Check for existing value. current_literals = self._namelist.get_variable_value(group, name) if current_literals != [""]: have_value = True # Check for input argument. if value is not None: have_value = True literals = self._to_namelist_literals(name, value) #FIXME - this is where an array is compressed into a 3*value current_literals = merge_literal_lists(literals, current_literals) # Check for default value. default = self.get_default(name, allow_none=True) if default is not None: have_value = True default_literals = self._to_namelist_literals(name, default) current_literals = merge_literal_lists(default_literals, current_literals) expect(have_value, "No default value found for %s." % name) # Go through file names and prepend input data root directory for # absolute pathnames. var_input_pathname = self._definition.get_input_pathname(name) if var_input_pathname == 'abs': current_literals = expand_literal_list(current_literals) for i, literal in enumerate(current_literals): if literal == '': continue file_path = character_literal_to_string(literal) if file_path == 'UNSET' or file_path == 'idmap': continue if file_path == 'null': continue file_path = self.set_abs_file_path(file_path) expect(os.path.exists(file_path), "File not found: %s = %s" % (name, literal)) current_literals[i] = string_to_character_literal(file_path) current_literals = compress_literal_list(current_literals) # Set the new value. self._namelist.set_variable_value(group, name, current_literals)
def is_valid_value(self, name, value): """Determine whether a value is valid for the named variable. The `value` argument must be a list of strings formatted as they would appear in the namelist (even for scalar variables, in which case the length of the list is always 1). """ name = name.lower() # Separate into a type, optional length, and optional size. type_, max_len, size = self.split_type_string(name, self.get_type_info(name)) invalid = [] # Check value against type. for scalar in value: if not is_valid_fortran_namelist_literal(type_, scalar): invalid.append(scalar) if len(invalid) > 0: logger.warn("Invalid values %s"%invalid) return False # Now that we know that the strings as input are valid Fortran, do some # canonicalization for further checks. canonical_value = self._canonicalize_value(type_, value) # Check maximum length (if applicable). if max_len is not None: for scalar in canonical_value: if len(scalar) > max_len: return False # Check valid value constraints (if applicable). valid_values = self.get_valid_values(name) if valid_values is not None: expect(type_ in ('integer', 'character'), "Found valid_values attribute for variable %s with " "type %s, but valid_values only allowed for character " "and integer variables." % (name, type_)) if type_ == 'integer': compare_list = [int(vv) for vv in valid_values] else: compare_list = valid_values for scalar in canonical_value: if scalar not in compare_list: invalid.append(scalar) if len(invalid) > 0: logger.warn("Invalid values %s"%invalid) return False # Check size of input array. if len(expand_literal_list(value)) > size: return False return True
def _to_python_value(self, name, literals): """Transform a literal list as needed for `get_value`.""" var_type, _, var_size, = self._definition.split_type_string(name, self._definition.get_type_info(name)) if len(literals) > 0: value = expand_literal_list(literals) else: value = '' return value for i, scalar in enumerate(value): if scalar == '': value[i] = None elif var_type == 'character': value[i] = character_literal_to_string(scalar) if var_size == 1: return value[0] else: return value
def _to_python_value(self, name, literals): """Transform a literal list as needed for `get_value`.""" var_type, _, var_size, = self._definition.split_type_string(name) if len(literals) > 0: value = expand_literal_list(literals) else: value = '' return value for i, scalar in enumerate(value): if scalar == '': value[i] = None elif var_type == 'character': value[i] = character_literal_to_string(scalar) if var_size == 1: return value[0] else: return value
def get_default(self, name, config=None, allow_none=False): """Get the value of a variable from the namelist definition file. The `config` argument is passed through to the underlying `NamelistDefaults.get_value` call as the `attribute` argument. The return value of this function is a list of values that were found in the defaults file. If there is no matching default, this function returns `None` if `allow_none=True` is passed, otherwise an error is raised. Note that we perform some translation of the values, since there are a few differences between Fortran namelist literals and values in the defaults file: 1) In the defaults file, whitespace is ignored except within strings, so the output of this function strips out most whitespace. (This implies that commas are the only way to separate array elements in the defaults file.) 2) In the defaults file, quotes around character literals (strings) are optional, as long as the literal does not contain whitespace, commas, or (single or double) quotes. If a setting for a character variable does not seem to have quotes (and is not a null value), this function will add them. 3) Default values may refer to variables in a case's `env_*.xml` files. This function replaces references of the form `$VAR` or `${VAR}` with the value of the variable `VAR` in an env file, if that variable exists. This behavior is suppressed within single-quoted strings (similar to parameter expansion in shell scripts). """ default = self._definition.get_value_match(name, attributes=config, exact_match=True) if default is None: expect(allow_none, "No default value found for %s." % name) return None default = expand_literal_list(default) var_type,_,_ = self._definition.split_type_string(name, self._definition.get_type_info(name)) for i, scalar in enumerate(default): # Skip single-quoted strings. if var_type == 'character' and scalar != '' and \ scalar[0] == scalar[-1] == "'": continue match = _var_ref_re.search(scalar) while match: env_val = self._case.get_value(match.group('name')) expect(env_val is not None, "Namelist default for variable %s refers to unknown XML " "variable %s." % (name, match.group('name'))) scalar = scalar.replace(match.group(0), str(env_val), 1) match = _var_ref_re.search(scalar) default[i] = scalar # Deal with missing quotes. if var_type == 'character': for i, scalar in enumerate(default): # Preserve null values. if scalar != '': default[i] = self.quote_string(scalar) default = self._to_python_value(name, default) return default
def add_default(self, name, value=None, ignore_abs_path=None): """Add a value for the specified variable to the namelist. If the specified variable is already defined in the object, the existing value is preserved. Otherwise, the `value` argument, if provided, will be used to set the value. If no such value is found, the defaults file will be consulted. If null values are present in any of the above, the result will be a merged array of values. If no value for the variable is found via any of the above, this method will raise an exception. """ # pylint: disable=protected-access group = self._definition.get_group(name) # Use this to see if we need to raise an error when nothing is found. have_value = False # Check for existing value. current_literals = self._namelist.get_variable_value(group, name) if current_literals != [""]: have_value = True # Do not proceed further since this has been obtained the -infile contents return # Check for input argument. if value is not None: have_value = True # if compression were to occur, this is where it does literals = self._to_namelist_literals(name, value) current_literals = merge_literal_lists(literals, current_literals) # Check for default value. default = self.get_default(name, allow_none=True) if default is not None: have_value = True default_literals = self._to_namelist_literals(name, default) current_literals = merge_literal_lists(default_literals, current_literals) expect(have_value, "No default value found for %s." % name) # Go through file names and prepend input data root directory for # absolute pathnames. if ignore_abs_path is None: var_input_pathname = self._definition.get_input_pathname(name) if var_input_pathname == 'abs': current_literals = expand_literal_list(current_literals) for i, literal in enumerate(current_literals): if literal == '': continue file_path = character_literal_to_string(literal) # NOTE - these are hard-coded here and a better way is to make these extensible if file_path == 'UNSET' or file_path == 'idmap': continue if file_path == 'null': continue file_path = self.set_abs_file_path(file_path) if not os.path.exists(file_path): logger.warn( "File not found: %s = %s, will attempt to download in check_input_data phase" % (name, literal)) current_literals[i] = string_to_character_literal( file_path) current_literals = compress_literal_list(current_literals) # Set the new value. self._namelist.set_variable_value(group, name, current_literals)
def get_default(self, name, config=None, allow_none=False): """Get the value of a variable from the namelist definition file. The `config` argument is passed through to the underlying `NamelistDefaults.get_value` call as the `attribute` argument. The return value of this function is a list of values that were found in the defaults file. If there is no matching default, this function returns `None` if `allow_none=True` is passed, otherwise an error is raised. Note that we perform some translation of the values, since there are a few differences between Fortran namelist literals and values in the defaults file: 1) In the defaults file, whitespace is ignored except within strings, so the output of this function strips out most whitespace. (This implies that commas are the only way to separate array elements in the defaults file.) 2) In the defaults file, quotes around character literals (strings) are optional, as long as the literal does not contain whitespace, commas, or (single or double) quotes. If a setting for a character variable does not seem to have quotes (and is not a null value), this function will add them. 3) Default values may refer to variables in a case's `env_*.xml` files. This function replaces references of the form `$VAR` or `${VAR}` with the value of the variable `VAR` in an env file, if that variable exists. This behavior is suppressed within single-quoted strings (similar to parameter expansion in shell scripts). """ default = self._definition.get_value_match(name, attributes=config, exact_match=False) if default is None: expect(allow_none, "No default value found for %s." % name) return None default = expand_literal_list(default) var_type, _, _ = self._definition.split_type_string(name) for i, scalar in enumerate(default): # Skip single-quoted strings. if var_type == 'character' and scalar != '' and \ scalar[0] == scalar[-1] == "'": continue match = _var_ref_re.search(scalar) while match: env_val = self._case.get_value(match.group('name')) expect( env_val is not None, "Namelist default for variable %s refers to unknown XML " "variable %s." % (name, match.group('name'))) scalar = scalar.replace(match.group(0), str(env_val), 1) match = _var_ref_re.search(scalar) default[i] = scalar # Deal with missing quotes. if var_type == 'character': for i, scalar in enumerate(default): # Preserve null values. if scalar != '': default[i] = self.quote_string(scalar) default = self._to_python_value(name, default) return default