def read_xml_file(filename, logger=None): ############################################################################### """Read the XML file, <filename>, and return its tree and root""" if os.path.isfile(filename) and os.access(filename, os.R_OK): if PY3: file_open = (lambda x: open(x, 'r', encoding='utf-8')) else: file_open = (lambda x: open(x, 'r')) # End if with file_open(filename) as file_: try: tree = ET.parse(file_) root = tree.getroot() except ET.ParseError as perr: emsg = "read_xml_file: Cannot read {}, {}" raise CCPPError(emsg.format(filename, perr)) elif not os.access(filename, os.R_OK): raise CCPPError("read_xml_file: Cannot open '{}'".format(filename)) else: emsg = "read_xml_file: Filename, '{}', does not exist" raise CCPPError(emsg.format(filename)) # End if if logger: logger.debug("Read XML file, '{}'".format(filename)) # End if return tree, root
def check_diagnostic_fixed(test_val, prop_dict, error): """Return <test_val> if a valid descriptor for a CCPP diagnostic, otherwise, None. If <error> is True, raise an Exception if <value> is not valid. A fixed diagnostic name is any Fortran identifier, however, it is an error to specify both 'diagnostic_name' and 'diagnostic_name_fixed'. >>> check_diagnostic_fixed("foo", {'diagnostic_name_fixed' : 'foo'}, False) 'foo' >>> check_diagnostic_fixed("foo", {'diagnostic_name_fixed' : 'foo'}, True) 'foo' >>> check_diagnostic_fixed("foo", {'diagnostic_name' : 'foo'}, False) >>> check_diagnostic_fixed("foo", {'diagnostic_name':'','local_name':'hi','standard_name':'mom'}, True) 'foo' >>> check_diagnostic_fixed("foo", {'diagnostic_name':'foo','local_name':'hi','standard_name':'mom'}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: hi (mom) cannot have both 'diagnostic_name' and 'diagnostic_name_fixed' attributes >>> check_diagnostic_fixed("2foo", {'diagnostic_name_fixed':'foo','local_name':'hi','standard_name':'mom'}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: '2foo' (hi) is not a valid fixed diagnostic name """ valid = test_val if (prop_dict and ('diagnostic_name' in prop_dict) and prop_dict['diagnostic_name']): valid = None if error: emsg = "{} ({}) cannot have both 'diagnostic_name' and " emsg += "'diagnostic_name_fixed' attributes" if 'local_name' in prop_dict: lname = prop_dict['local_name'] else: lname = 'UNKNOWN' # end if if 'standard_name' in prop_dict: sname = prop_dict['standard_name'] else: sname = 'UNKNOWN' # end if raise CCPPError(emsg.format(lname, sname)) # end if elif check_fortran_id(test_val, prop_dict, False) is None: valid = None if error: emsg = "'{}' ({}) is not a valid fixed diagnostic name" if 'local_name' in prop_dict: lname = prop_dict['local_name'] else: lname = 'UNKNOWN' # end if raise CCPPError(emsg.format(test_val, lname)) # end if # end if return valid
def validate_xml_file(filename, schema_root, version, logger, schema_path=None, error_on_noxmllint=False): ############################################################################### """ Find the appropriate schema and validate the XML file, <filename>, against it using xmllint """ # Check the filename if not os.path.isfile(filename): raise CCPPError( "validate_xml_file: Filename, '{}', does not exist".format( filename)) # End if if not os.access(filename, os.R_OK): raise CCPPError("validate_xml_file: Cannot open '{}'".format(filename)) # End if if not schema_path: # Find the schema, based on the model version thispath = os.path.abspath(__file__) pdir = os.path.dirname(os.path.dirname(os.path.dirname(thispath))) schema_path = os.path.join(pdir, 'schema') # End if schema_file = find_schema_file(schema_root, version, schema_path) if not (schema_file and os.path.isfile(schema_file)): verstring = '.'.join([str(x) for x in version]) emsg = """validate_xml_file: Cannot find schema for version {}, {} does not exist""" raise CCPPError(emsg.format(verstring, schema_file)) # End if if not os.access(schema_file, os.R_OK): emsg = "validate_xml_file: Cannot open schema, '{}'" raise CCPPError(emsg.format(schema_file)) # End if if _XMLLINT is not None: logger.debug("Checking file {} against schema {}".format( filename, schema_file)) cmd = [_XMLLINT, '--noout', '--schema', schema_file, filename] result = call_command(cmd, logger) return result # End if lmsg = "xmllint not found, could not validate file {}" if error_on_noxmllint: raise CCPPError("validate_xml_file: " + lmsg.format(filename)) # End if logger.warning(lmsg.format(filename)) return True # We could not check but still need to proceed
def call_command(commands, logger, silent=False): ############################################################################### """ Try a command line and return the output on success (None on failure) >>> call_command(['ls', 'really__improbable_fffilename.foo'], _LOGGER) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Execution of 'ls really__improbable_fffilename.foo' failed: [Errno 2] No such file or directory >>> call_command(['ls', 'really__improbable_fffilename.foo'], _LOGGER, silent=True) False >>> call_command(['ls'], _LOGGER) True """ result = False outstr = '' try: if PY3: if PYSUBVER > 6: cproc = subprocess.run(commands, check=True, capture_output=True) if not silent: logger.debug(cproc.stdout) # End if result = cproc.returncode == 0 elif PYSUBVER >= 5: cproc = subprocess.run(commands, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if not silent: logger.debug(cproc.stdout) # End if result = cproc.returncode == 0 else: raise ValueError("Python 3 must be at least version 3.5") # End if else: pproc = subprocess.Popen(commands, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, _ = pproc.communicate() if not silent: logger.debug(output) # End if result = pproc.returncode == 0 # End if except (OSError, CCPPError, subprocess.CalledProcessError) as err: if silent: result = False else: cmd = ' '.join(commands) emsg = "Execution of '{}' failed with code:\n" outstr = emsg.format(cmd, err.returncode) outstr += "{}".format(err.output) raise CCPPError(outstr) # End if # End of try return result
def add_syntax_err(self, token_type, token=None, skip_context=False): """Add a ParseSyntaxError-type message to this object's error log, separating it from any previous messages with a newline.""" if self.__error_message: if self.__num_errors == self._max_errors: self.__error_message += '\nMaximum number of errors exceeded' self.line_num = self.__num_lines # Intentionally walk off end self.__line_next = self.line_num elif self.__num_errors > self._max_errors: # Oops, something went wrong, panic! raise CCPPError(self.error_message) # end if self.__error_message += '\n' # end if if self.__num_errors < self._max_errors: if skip_context: cstr = "" else: cstr = context_string(self) # end if if token is None: self.__error_message += "{}{}".format(token_type, cstr) else: self.__error_message += "Invalid {}, '{}'{}".format( token_type, token, cstr) # end if # end if self.__num_errors += 1
def write_line(self, line_num, line): """Overwrite line, <line_num> with <line>. If <line_start> is out of bounds, raise an exception.""" if (line_num < 0) or (line_num >= len(self.__lines)): emsg = 'Attempt to write non-existent line, {}' raise CCPPError(emsg.format(line_num)) # end if self.__lines[line_num] = line
def check_units(test_val, prop_dict, error): """Return <test_val> if a valid unit, otherwise, None if <error> is True, raise an Exception if <test_val> is not valid. >>> check_units('m/s', None, True) 'm/s' >>> check_units('kg m-3', None, True) 'kg m-3' >>> check_units('1', None, True) '1' >>> check_units('', None, False) >>> check_units('', None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: '' is not a valid unit >>> check_units(' ', None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: '' is not a valid unit >>> check_units(['foo'], None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: ['foo'] is invalid; not a string """ if not isinstance(test_val, str): if error: raise CCPPError("'{}' is invalid; not a string".format(test_val)) else: test_val = None # end if else: if not test_val.strip(): if error: raise CCPPError("'{}' is not a valid unit".format(test_val)) else: test_val = None # end if # end if # end if # DH* 20210812 # Temporary workaround to convert unit 'none' (used for # dimensionless quantities in ccpp-physics/UFS/SCM) to '1' if test_val.lower() == 'none': test_val = '1' # *DH 20210812 return test_val
def reset_pos(self, line_start=0): """Attempt to set the current file position to <line_start>. If <line_start> is out of bounds, raise an exception.""" if (line_start < 0) or (line_start >= self.__num_lines): emsg = 'Attempt to reset_pos to non-existent line, {}' raise CCPPError(emsg.format(line_start)) # end if self.line_num = line_start self.__line_next = line_start
def check_fortran_id(test_val, prop_dict, error, max_len=0): """Return <test_val> if a valid Fortran identifier, otherwise, None If <max_len> > 0, <test_val> must not be longer than <max_len>. if <error> is True, raise an Exception if <test_val> is not valid. >>> check_fortran_id("hi_mom", None, False) 'hi_mom' >>> check_fortran_id("hi_mom", None, False, max_len=5) >>> check_fortran_id("hi_mom", None, True, max_len=5) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'hi_mom' is too long (> 5 chars) >>> check_fortran_id("hi mom", None, False) >>> check_fortran_id("hi mom", None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'hi_mom' is not a valid Fortran identifier >>> check_fortran_id("", None, False) >>> check_fortran_id("_hi_mom", None, False) >>> check_fortran_id("2pac", None, False) >>> check_fortran_id("Agood4tranID", None, False) 'Agood4tranID' """ match = __FID_RE.match(test_val) if match is None: if error: raise CCPPError( "'{}' is not a valid Fortran identifier".format(test_val)) else: test_val = None # end if elif (max_len > 0) and (len(test_val) > max_len): if error: raise CCPPError("'{}' is too long (> {} chars)".format( test_val, max_len)) else: test_val = None # end if # end if return test_val
def check_cf_standard_name(test_val, prop_dict, error): """Return <test_val> if a valid CF Standard Name, otherwise, None http://cfconventions.org/Data/cf-standard-names/docs/guidelines.html if <error> is True, raise an Exception if <test_val> is not valid. >>> check_cf_standard_name("hi_mom", None, False) 'hi_mom' >>> check_cf_standard_name("hi mom", None, False) >>> check_cf_standard_name("hi mom", None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'hi_mom' is not a valid CF Standard Name >>> check_cf_standard_name("", None, False) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: CCPP Standard Name cannot be blank >>> check_cf_standard_name("_hi_mom", None, False) >>> check_cf_standard_name("2pac", None, False) >>> check_cf_standard_name("Agood4tranID", None, False) 'agood4tranid' >>> check_cf_standard_name("agoodcfid", None, False) 'agoodcfid' """ if len(test_val) == 0: raise CCPPError("CCPP Standard Name cannot be blank") else: match = __CFID_RE.match(test_val) # end if if match is None: if error: errmsg = "'{}' is not a valid CCPP Standard Name" raise CCPPError(errmsg.format(test_val)) else: test_val = None # end if else: test_val = test_val.lower() # end if return test_val
def check_fortran_type(typestr, prop_dict, error): """Return <typestr> if a valid Fortran type, otherwise, None if <error> is True, raise an Exception if <typestr> is not valid. >>> check_fortran_type("real", None, False) 'real' >>> check_fortran_type("integer", None, False) 'integer' >>> check_fortran_type("InteGer", None, False) 'InteGer' >>> check_fortran_type("character", None, False) 'character' >>> check_fortran_type("double precision", None, False) 'double precision' >>> check_fortran_type("double precision", None, False) 'double precision' >>> check_fortran_type("doubleprecision", None, False) 'doubleprecision' >>> check_fortran_type("complex", None, False) 'complex' >>> check_fortran_type("char", {}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'char' is not a valid Fortran type >>> check_fortran_type("int", None, False) >>> check_fortran_type("char", {}, False) >>> check_fortran_type("type", None, False) >>> check_fortran_type("type", {}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'type' is not a valid derived Fortran type >>> check_fortran_type("type(hi mom)", {}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'type(hi mom)' is not a valid derived Fortran type """ dt = "" match = check_fortran_intrinsic(typestr, error=False) if match is None: match = registered_fortran_ddt_name(typestr) dt = " derived" # end if if match is None: if error: emsg = "'{}' is not a valid{} Fortran type" raise CCPPError(emsg.format(typestr, dt)) else: typestr = None # end if # end if return typestr
def check_fortran_intrinsic(typestr, error=False): """Return <test_val> if a valid Fortran intrinsic type, otherwise, None if <error> is True, raise an Exception if <test_val> is not valid. >>> check_fortran_intrinsic("real", error=False) 'real' >>> check_fortran_intrinsic("complex") 'complex' >>> check_fortran_intrinsic("integer") 'integer' >>> check_fortran_intrinsic("InteGer") 'InteGer' >>> check_fortran_intrinsic("logical") 'logical' >>> check_fortran_intrinsic("character") 'character' >>> check_fortran_intrinsic("double precision") 'double precision' >>> check_fortran_intrinsic("double precision") 'double precision' >>> check_fortran_intrinsic("doubleprecision") 'doubleprecision' >>> check_fortran_intrinsic("char", error=True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'char' is not a valid Fortran type >>> check_fortran_intrinsic("int") >>> check_fortran_intrinsic("char", error=False) >>> check_fortran_intrinsic("type") >>> check_fortran_intrinsic("complex(kind=r8)") """ chk_type = typestr.strip().lower() match = chk_type in FORTRAN_INTRINSIC_TYPES if (not match) and (chk_type[0:6] == 'double'): # Special case for double precision match = FORTRAN_DP_RE.match(chk_type) is not None # End if if not match: if error: raise CCPPError("'{}' is not a valid Fortran type".format(typestr)) else: typestr = None # end if # end if return typestr
def find_schema_version(root): ############################################################################### """ Find the version of the host registry file represented by root >>> find_schema_version(ET.fromstring('<model name="CAM" version="1.0"></model>')) [1, 0] >>> find_schema_version(ET.fromstring('<model name="CAM" version="1.a"></model>')) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Illegal version string, '1.a' Format must be <integer>.<integer> >>> find_schema_version(ET.fromstring('<model name="CAM" version="0.0"></model>')) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Illegal version string, '0.0' Major version must be at least 1 >>> find_schema_version(ET.fromstring('<model name="CAM" version="0.-1"></model>')) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Illegal version string, '0.0' Minor version must be at least 0 """ verbits = None if 'version' not in root.attrib: raise CCPPError("version attribute required") # End if version = root.attrib['version'] versplit = version.split('.') try: if len(versplit) != 2: raise CCPPError('oops') # End if (no else needed) try: verbits = [int(x) for x in versplit] except ValueError as verr: raise CCPPError(verr) # End try if verbits[0] < 1: raise CCPPError('Major version must be at least 1') # End if if verbits[1] < 0: raise CCPPError('Minor version must be non-negative') # End if except CCPPError as verr: errstr = """Illegal version string, '{}' Format must be <integer>.<integer>""" ve_str = str(verr) if ve_str: errstr = ve_str + '\n' + errstr # End if raise CCPPError(errstr.format(version)) # End try return verbits
def check_dimensions(test_val, max_len=0, error=False): """Return <test_val> if a valid dimensions list, otherwise, None If <max_len> > 0, each string in <test_val> must not be longer than <max_len>. if <error> is True, raise an Exception if <test_val> is not valid. >>> check_dimensions(["dim1", "dim2name"]) ['dim1', 'dim2name'] >>> check_dimensions([":", ":"]) [':', ':'] >>> check_dimensions([":", "dim2"]) [':', 'dim2'] >>> check_dimensions(["dim1", ":"]) ['dim1', ':'] >>> check_dimensions(["8", "::"]) ['8', '::'] >>> check_dimensions(['start1:end1', 'start2:end2']) ['start1:end1', 'start2:end2'] >>> check_dimensions(['start1:', 'start2:end2']) ['start1:', 'start2:end2'] >>> check_dimensions(["dim1", "dim2name"], max_len=5) >>> check_dimensions(["dim1", "dim2name"], error=True, max_len=5) Traceback (most recent call last): CCPPError: 'dim2name' is too long (> 5 chars) >>> check_dimensions("hi_mom", error=True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'hi_mom' is invalid; not a list """ if type(test_val) != list: if error: raise CCPPError("'{}' is invalid; not a list".format(test_val)) else: test_val = None # End if else: for item in test_val: isplit = item.split(':') # Check for too many colons if (len(isplit) > 3): if error: raise CCPPError( "'{}' is an invalid dimension range".format(item)) else: test_val = None # End if break # End if # Check possible dim styles (a, a:b, a:, :b, :, ::, a:b:c, a::c) tdims = [x for x in isplit if len(x) > 0] for tdim in tdims: # Check numeric value first try: valid = isinstance(int(tdim), int) except ValueError as ve: # Not an integer, try a Fortran ID valid = check_fortran_id(tdim, max_len=max_len, error=error) is not None # End try if not valid: if error: raise CCPPError( "'{}' is an invalid dimension name".format(item)) else: test_val = None # End if break # End if # End for # End for # End if return test_val
def reset_pos(self, line_start=0): if (line_start < 0) or (line_start >= len(self._lines)): raise CCPPError('Attempt to reset_pos to non-existent line, {}'.format(line_start)) else: self.line_num = line_start self._line_next = line_start
def write_line(self, line_num, line): "Overwrite line, <line_num> with <line>" if (line_num < 0) or (line_num >= len(self._lines)): raise CCPPError('Attempt to write non-existent line, {}'.format(line_num)) else: self._lines[line_num] = line
def check_balanced_paren(string, start=0, error=False): """Return <string> indices delineating a balance set of parentheses. Parentheses in character context do not count. Left parenthesis search begins at <start>. Return start and end indices if found If no parentheses are found, return (-1, -1). If a left parenthesis is found but no balancing right, return (begin, -1) where begin is the index where the left parenthesis was found. If error is True, raise a CCPPError. >>> check_balanced_paren("foo") (-1, -1) >>> check_balanced_paren("(foo, bar)") (0, 9) >>> check_balanced_paren("( (foo, bar) )", start=1) (2, 11) >>> check_balanced_paren("(size(foo,1), qux)") (0, 17) >>> check_balanced_paren("(foo('bar()'))") (0, 13) >>> check_balanced_paren("(foo('bar()')") (0, -1) >>> check_balanced_paren("(foo('bar()')", error=True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: ERROR: Unbalanced parenthesis in '(foo('bar()')' """ index = start begin = -1 end = -1 depth = 0 inchar = None str_len = len(string) while index < str_len: if (string[index] == '"') or (string[index] == "'"): if inchar == string[index]: inchar = None elif inchar is None: inchar = string[index] # else in character context, keep going # end if elif inchar is not None: # In character context, keep going pass elif string[index] == '(': if depth == 0: begin = index # end if depth = depth + 1 if depth == 0: break # end if elif string[index] == ')': depth = depth - 1 if depth == 0: end = index break # end if # else just keep going # end if index = index + 1 # End while if (begin >= 0) and (end < 0) and error: raise CCPPError("ERROR: Unbalanced parenthesis in '{}'".format(string)) # end if return begin, end
def check_diagnostic_id(test_val, prop_dict, error): """Return <test_val> if a valid descriptor for a CCPP diagnostic, otherwise, None. If <error> is True, raise an Exception if <value> is not valid. A diagnostic name is a Fortran identifier with the optional addition of one variable substitution. A variable substitution is a substring of the form of either: ${process}: The scheme process name will be substituted for this substring. If this substring is included, it is an error for there to be no process specified by the scheme (although this error cannot be detected by this routine). ${scheme_name}: The scheme name will be substituted for this substring. It is an error to specify both 'diagnostic_name' and 'diagnostic_name_fixed'. >>> check_diagnostic_id("foo", {'diagnostic_name' : 'foo'}, False) 'foo' >>> check_diagnostic_id("foo", {'diagnostic_name' : 'foo'}, True) 'foo' >>> check_diagnostic_id("foo", {'diagnostic_name_fixed' : 'foo'}, False) >>> check_diagnostic_id("foo_${process}", {}, False) 'foo_${process}' >>> check_diagnostic_id("foo_${process}_2bad", {}, False) 'foo_${process}_2bad' >>> check_diagnostic_id("${process}_2bad", {}, False) '${process}_2bad' >>> check_diagnostic_id("foo_${scheme_name}", {}, False) 'foo_${scheme_name}' >>> check_diagnostic_id("foo_${scheme_name}_2bad", {}, False) 'foo_${scheme_name}_2bad' >>> check_diagnostic_id("${scheme_name}_suff", {}, False) '${scheme_name}_suff' >>> check_diagnostic_id("pref_${scheme}_suff", {}, False) >>> check_diagnostic_id("pref_${scheme_name_suff", {}, False) >>> check_diagnostic_id("pref_$scheme_name}_suff", {}, False) >>> check_diagnostic_id("pref_{scheme_name}_suff", {}, False) >>> check_diagnostic_id("foo", {'diagnostic_name_fixed':'','local_name':'hi','standard_name':'mom'}, True) 'foo' >>> check_diagnostic_id("foo", {'diagnostic_name_fixed':'foo','local_name':'hi','standard_name':'mom'}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: hi (mom) cannot have both 'diagnostic_name' and 'diagnostic_name_fixed' attributes >>> check_diagnostic_id("2foo", {'diagnostic_name':'foo','local_name':'hi','standard_name':'mom'}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: '2foo' (hi) is not a valid diagnostic name """ if (prop_dict and ('diagnostic_name_fixed' in prop_dict) and prop_dict['diagnostic_name_fixed']): valid = None if error: emsg = "{} ({}) cannot have both 'diagnostic_name' and " emsg += "'diagnostic_name_fixed' attributes" if 'local_name' in prop_dict: lname = prop_dict['local_name'] else: lname = 'UNKNOWN' # end if if 'standard_name' in prop_dict: sname = prop_dict['standard_name'] else: sname = 'UNKNOWN' # end if raise CCPPError(emsg.format(lname, sname)) # end if else: match = _DIAG_RE.match(test_val) if match is None: valid = None if error: emsg = "'{}' is not a valid diagnostic_name value" raise CCPPError(emsg.format(test_val)) # end if else: valid = test_val # end if # end if return valid
def check_fortran_ref(test_val, prop_dict, error, max_len=0): """Return <test_val> if a valid simple Fortran variable reference, otherwise, None. A simple Fortran variable reference is defined as a scalar id or a scalar array reference. if <error> is True, raise an Exception if <test_val> is not valid. >>> FORTRAN_SCALAR_REF_RE.match("foo( bar, baz )").group(1) 'foo' >>> FORTRAN_SCALAR_REF_RE.match("foo( bar, baz )").group(2) 'bar, baz ' >>> FORTRAN_SCALAR_REF_RE.match("foo( bar, baz )").group(2).split(',')[0].strip() 'bar' >>> FORTRAN_SCALAR_REF_RE.match("foo( :, baz )").group(2).split(',')[0].strip() ':' >>> FORTRAN_SCALAR_REF_RE.match("foo( bar, baz )").group(2).split(',')[1].strip() 'baz' >>> check_fortran_ref("hi_mom", None, False) 'hi_mom' >>> check_fortran_ref("hi_mom", None, False, max_len=5) >>> check_fortran_ref("hi_mom", None, True, max_len=5) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'hi_mom' is too long (> 5 chars) >>> check_fortran_ref("hi mom", None, False) >>> check_fortran_ref("hi mom", None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'hi_mom' is not a valid Fortran identifier >>> check_fortran_ref("", None, False) >>> check_fortran_ref("_hi_mom", None, False) >>> check_fortran_ref("2pac", None, False) >>> check_fortran_ref("Agood4tranID", None, False) 'Agood4tranID' >>> check_fortran_ref("foo(bar)", None, False) 'foo(bar)' >>> check_fortran_ref("foo( bar, baz )", None, False) 'foo( bar, baz )' >>> check_fortran_ref("foo( :, baz )", None, False) 'foo( :, baz )' >>> check_fortran_ref("foo( bar, )", None, False) >>> check_fortran_ref("foo( bar, )", None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'foo( bar, )' is not a valid Fortran scalar reference >>> check_fortran_ref("foo()", None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'foo()' is not a valid Fortran scalar reference >>> check_fortran_ref("foo(bar, bazz)", None, True, max_len=3) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'bazz' is too long (> 3 chars) in foo(bar, bazz) >>> check_fortran_ref("foo(barr, baz)", None, True, max_len=3) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'bazr' is too long (> 3 chars) in foo(barr, baz) >>> check_fortran_ref("fooo(bar, baz)", None, True, max_len=3) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'foo' is too long (> 3 chars) in fooo(bar, baz) """ idval = check_fortran_id(test_val, prop_dict, False, max_len=max_len) if idval is None: match = FORTRAN_SCALAR_REF_RE.match(test_val) if match is None: if error: emsg = "'{}' is not a valid Fortran scalar reference" raise CCPPError(emsg.format(test_val)) else: test_val = None # end if elif max_len > 0: tokens = test_val.strip().rstrip(')').split('(') tokens = [tokens[0].strip() ] + [x.strip() for x in tokens[1].split(',')] for token in tokens: if len(token) > max_len: if error: emsg = "'{}' is too long (> {} chars) in {}" raise CCPPError(emsg.format(token, max_len, test_val)) else: test_val = None break # end if # end if # end for # end if # end if return test_val
def check_default_value(test_val, prop_dict, error): """Return <test_val> if a valid default value for a CCPP field, otherwise, None. If <error> is True, raise an Exception if <value> is not valid. A valid value is determined by the 'type' of the variable. It is an error for there to be no 'type' property in <prop_dict>. >>> check_default_value('314', {'type':'integer'}, False) '314' >>> check_default_value('314', {'type':'integer'}, True) '314' >>> check_default_value('314', {'type':'integer', 'kind':'ikind'}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 314 is not a valid Fortran integer of kind, ikind >>> check_default_value('314_ikind', {'type':'integer', 'kind':'ikind'}, True) '314_ikind' >>> check_default_value('314', {'type':'real'}, False) >>> check_default_value('314', {'type':'real'}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 314 is not a valid Fortran real >>> check_default_value('3.14', {'type':'real'}, False) '3.14' >>> check_default_value('314', {'tipe':'integer'}, False) >>> check_default_value('314', {'local_name':'foo'}, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: foo does not have a 'type' attribute >>> check_default_value('314', {'tipe':'integer'}, False) >>> check_default_value('314', None, True) '314' """ valid = None if prop_dict and ('type' in prop_dict): valid = test_val var_type = prop_dict['type'].lower().strip() if 'kind' in prop_dict: vkind = prop_dict['kind'].lower().strip() else: vkind = '' # end if if not check_fortran_literal(test_val, var_type, vkind): valid = None if error: emsg = '{} is not a valid Fortran {}' if vkind: emsg += ' of kind, {}' raise CCPPError(emsg.format(test_val, var_type, vkind)) # end if # end if (no else, <test_val> is okay) elif prop_dict is None: # Special case for checks during parsing, always pass valid = test_val elif error: emsg = "{} does not have a 'type' attribute" if 'local_name' in prop_dict: lname = prop_dict['local_name'] else: lname = 'UNKNOWN' # end if raise CCPPError(emsg.format(lname)) # end if return valid
def check_dimensions(test_val, prop_dict, error, max_len=0): """Return <test_val> if a valid dimensions list, otherwise, None If <max_len> > 0, each string in <test_val> must not be longer than <max_len>. if <error> is True, raise an Exception if <test_val> is not valid. >>> check_dimensions(["dim1", "dim2name"], None, False) ['dim1', 'dim2name'] >>> check_dimensions([":", ":"], None, False) [':', ':'] >>> check_dimensions([":", "dim2"], None, False) [':', 'dim2'] >>> check_dimensions(["dim1", ":"], None, False) ['dim1', ':'] >>> check_dimensions(["8", "::"], None, False) ['8', '::'] >>> check_dimensions(['start1:end1', 'start2:end2'], None, False) ['start1:end1', 'start2:end2'] >>> check_dimensions(['start1:', 'start2:end2'], None, False) ['start1:', 'start2:end2'] >>> check_dimensions(['start1 :end1', 'start2: end2'], None, False) ['start1 :end1', 'start2: end2'] >>> check_dimensions(['size(foo)'], None, False) ['size(foo)'] >>> check_dimensions(['size(foo,1) '], None, False) ['size(foo,1) '] >>> check_dimensions(['size(foo,1'], None, False) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Invalid dimension component, size(foo,1 >>> check_dimensions(["dim1", "dim2name"], None, False, max_len=5) >>> check_dimensions(["dim1", "dim2name"], None, True, max_len=5) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'dim2name' is too long (> 5 chars) >>> check_dimensions("hi_mom", None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'hi_mom' is invalid; not a list """ if not isinstance(test_val, list): if error: raise CCPPError("'{}' is invalid; not a list".format(test_val)) else: test_val = None # end if else: for item in test_val: isplit = item.split(':') # Check for too many colons if (len(isplit) > 3): if error: errmsg = "'{}' is an invalid dimension range" raise CCPPError(errmsg.format(item)) else: test_val = None # end if break # end if # Check possible dim styles (a, a:b, a:, :b, :, ::, a:b:c, a::c) tdims = [x.strip() for x in isplit if len(x) > 0] for tdim in tdims: # Check numeric value first try: valid = isinstance(int(tdim), int) except ValueError as ve: # Not an integer, try a Fortran ID valid = check_fortran_id(tdim, None, error, max_len=max_len) is not None if not valid: # Check for size entry -- simple check tcheck = tdim.strip().lower() if tcheck[0:4] == 'size': ploc = check_balanced_paren(tdim[4:]) if -1 in ploc: emsg = 'Invalid dimension component, {}' raise CCPPError(emsg.format(tdim)) else: valid = tdim # end if # end if # end if # End try if not valid: if error: errmsg = "'{}' is an invalid dimension name" raise CCPPError(errmsg.format(item)) else: test_val = None # end if break # end if # end for # end for # end if return test_val