class HostModel(VarDictionary): """Class to hold the data from a host model""" def __init__(self, meta_tables, name_in, logger): self.__name = name_in self.__var_locations = {} # Local name to module map self.__loop_vars = None # Loop control vars in interface calls self.__used_variables = None # Local names which have been requested self.__deferred_finds = None # Used variables that were missed at first # First, process DDT headers meta_headers = list() for sect in [x.sections() for x in meta_tables.values()]: meta_headers.extend(sect) # end for # Initialize our dictionaries # Initialize variable dictionary super(HostModel, self).__init__(self.name, logger=logger) self.__ddt_lib = DDTLibrary( '{}_ddts'.format(self.name), ddts=[d for d in meta_headers if d.header_type == 'ddt'], logger=logger) self.__ddt_dict = VarDictionary("{}_ddt_vars".format(self.name), parent_dict=self, logger=logger) # Now, process the code headers by type self.__metadata_tables = meta_tables for header in [h for h in meta_headers if h.header_type != 'ddt']: title = header.title if logger is not None: msg = 'Adding {} {} to host model' logger.debug(msg.format(header.header_type, title)) # End if if header.header_type == 'module': # Set the variable modules modname = header.title for var in header.variable_list(): self.add_variable(var) lname = var.get_prop_value('local_name') self.__var_locations[lname] = modname self.ddt_lib.check_ddt_type(var, header, lname=lname) if var.is_ddt(): self.ddt_lib.collect_ddt_fields(self.__ddt_dict, var) # End if # End for elif header.header_type == 'host': if self.__name is None: # Grab the first host name we see self.__name = header.name # End if for var in header.variable_list(): self.add_variable(var) self.ddt_lib.check_ddt_type(var, header) if var.is_ddt(): self.ddt_lib.collect_ddt_fields(self.__ddt_dict, var) # End if # End for loop_vars = header.variable_list(std_vars=False, loop_vars=True, consts=False) if loop_vars: # loop_vars are part of the host-model interface call # at run time. As such, they override the host-model # array dimensions. self.__loop_vars = VarDictionary(self.name) # End if for hvar in loop_vars: std_name = hvar.get_prop_value('standard_name') if std_name not in self.__loop_vars: self.__loop_vars.add_variable(hvar) else: ovar = self.__loop_vars[std_name] ctx1 = context_string(ovar.context) ctx2 = context_string(hvar.context) lname1 = ovar.get_prop_value('local_name') lname2 = hvar.get_prop_value('local_name') errmsg = ("Duplicate host loop var for {n}:\n" " Dup: {l1}{c1}\n Orig: {l2}{c2}") raise CCPPError( errmsg.format(n=self.name, l1=lname1, c1=ctx1, l2=lname2, c2=ctx2)) # End if # End for else: errmsg = "Invalid host model metadata header type, {} ({}){}" errmsg += "\nType must be 'module' or 'host'" ctx = context_string(header.context) raise CCPPError( errmsg.format(header.title, header.header_type, ctx)) # End if # End while if self.name is None: errmsg = 'No name found for host model, add a host metadata entry' raise CCPPError(errmsg) # End if # Finally, turn on the use meter so we know which module variables # to 'use' in a host cap. self.__used_variables = set() # Local names which have been requested self.__deferred_finds = set( ) # Used variables that were missed at first @property def name(self): """Return the host model name""" return self.__name @property def loop_vars(self): """Return this host model's loop variables""" return self.__loop_vars @property def ddt_lib(self): """Return this host model's DDT library""" return self.__ddt_lib # XXgoldyXX: v needed? @property def constituent_module(self): """Return the name of host model constituent module""" return "{}_ccpp_constituents".format(self.name) # XXgoldyXX: ^ needed? def argument_list(self, loop_vars=True): """Return a string representing the host model variable arg list""" args = [ v.call_string(self) for v in self.variable_list(loop_vars=loop_vars, consts=False) ] return ', '.join(args) def metadata_tables(self): """Return a copy of this host models metadata tables""" return dict(self.__metadata_tables) def host_variable_module(self, local_name): """Return the module name for a host variable""" if local_name in self.__var_locations: return self.__var_locations[local_name] # End if return None def variable_locations(self): """Return a set of module-variable and module-type pairs. These represent the locations of all host model data with a listed source location (variables with no <module> source are omitted).""" varset = set() lnames = self.prop_list('local_name') # Attempt to realize deferred lookups if self.__deferred_finds is not None: for std_name in list(self.__deferred_finds): var = self.find_variable(standard_name=std_name) if var is not None: self.__deferred_finds.remove(std_name) # End if # End for # End if # Now, find all the used module variables for name in lnames: module = self.host_variable_module(name) used = self.__used_variables and (name in self.__used_variables) if module and used: varset.add((module, name)) # No else, either no module or a zero-length module name # End if # End for return varset def find_variable(self, standard_name=None, source_var=None, any_scope=False, clone=None, search_call_list=False, loop_subst=False): """Return the host model variable matching <standard_name> or None If <loop_subst> is True, substitute a begin:end range for an extent. """ my_var = super(HostModel, self).find_variable(standard_name=standard_name, source_var=source_var, any_scope=any_scope, clone=clone, search_call_list=search_call_list, loop_subst=loop_subst) if my_var is None: # Check our DDT library if standard_name is None: if source_var is None: emsg = ("One of <standard_name> or <source_var> " + "must be passed.") raise ParseInternalError(emsg) # end if standard_name = source_var.get_prop_value('standard_name') # end if # Since we are the parent of the DDT library, only check that dict my_var = self.__ddt_dict.find_variable(standard_name=standard_name, any_scope=False) # End if if loop_subst: if my_var is None: my_var = self.find_loop_subst(standard_name) # End if if my_var is not None: # If we get here, the host does not have the requested # variable but does have a replacement set. Create a new # variable to use to send to suites. ##XXgoldyXX: This cannot be working since find_loop_subst ## returns a tuple new_name = self.new_internal_variable_name(prefix=self.name) ctx = ParseContext(filename='host_model.py') new_var = my_var.clone(new_name, source_name=self.name, source_type="HOST", context=ctx) self.add_variable(new_var) my_var = new_var # End if # End if if my_var is None: if self.__deferred_finds is not None: self.__deferred_finds.add(standard_name) # End if elif self.__used_variables is not None: lname = my_var.get_prop_value('local_name') # Try to add any index references (should be method?) imatch = FORTRAN_SCALAR_REF_RE.match(lname) if imatch is not None: vdims = [ x.strip() for x in imatch.group(2).split(',') if ':' not in x ] for vname in vdims: _ = self.find_variable(standard_name=vname) # End for # End if if isinstance(my_var, VarDDT): lname = my_var.get_parent_prop('local_name') # End if self.__used_variables.add(lname) # End if return my_var def add_variable(self, newvar, exists_ok=False, gen_unique=False, adjust_intent=False): """Add <newvar> if it does not conflict with existing entries. For the host model, this includes entries in used DDT variables. If <exists_ok> is True, attempting to add an identical copy is okay. If <gen_unique> is True, a new local_name will be created if a local_name collision is detected. if <adjust_intent> is True, adjust conflicting intents to inout.""" standard_name = newvar.get_prop_value('standard_name') cvar = self.find_variable(standard_name=standard_name, any_scope=False) if cvar is None: # Check the DDT dictionary cvar = self.__ddt_dict.find_variable(standard_name=standard_name, any_scope=False) # end if if cvar and (not exists_ok): emsg = "Attempt to add duplicate host model variable, {}{}." emsg += "\nVariable originally defined{}" ntx = context_string(newvar.context) ctx = context_string(cvar.context) raise CCPPError(emsg.format(standard_name, ntx, ctx)) # end if # No collision, proceed normally super(HostModel, self).add_variable(newvar=newvar, exists_ok=exists_ok, gen_unique=gen_unique, adjust_intent=False) def add_host_variable_module(self, local_name, module, logger=None): """Add a module name location for a host variable""" if local_name not in self.__var_locations: if logger is not None: emsg = 'Adding variable, {}, from module, {}' logger.debug(emsg.format(local_name, module)) # End if self.__var_locations[local_name] = module else: emsg = "Host variable, {}, already located in module" raise CCPPError(emsg.format(self.__var_locations[local_name])) # End if def call_list(self, phase): "Return the list of variables passed by the host model to the host cap" hdvars = list() loop_vars = phase == 'run' for hvar in self.variable_list(loop_vars=loop_vars, consts=False): lname = hvar.get_prop_value('local_name') if self.host_variable_module(lname) is None: hdvars.append(hvar) # End if # End for return hdvars
def add_constituent_vars(cap, host_model, suite_list, logger): ############################################################################### """Create a DDT library containing array reference variables for each constituent field for all suites in <suite_list>. Create and return a dictionary containing an index variable for each of the constituents as well as the variables from the DDT object. Also, write declarations for these variables to <cap>. Since the constituents are in a DDT (ccpp_constituent_properties_t), create a metadata table with the required information, then parse it to create the dictionary. """ # First create a MetadataTable for the constituents DDT stdname_layer = "ccpp_constituents_num_layer_consts" stdname_interface = "ccpp_constituents_num_interface_consts" stdname_2d = "ccpp_constituents_num_2d_consts" horiz_dim = "horizontal_dimension" vert_layer_dim = "vertical_layer_dimension" vert_interface_dim = "vertical_interface_dimension" array_layer = "vars_layer" array_interface = "vars_interface" array_2d = "vars_2d" # Table preamble (leave off ccpp-table-properties header) ddt_mdata = [ #"[ccpp-table-properties]", " name = {}".format(CONST_DDT_NAME), " type = ddt", "[ccpp-arg-table]", " name = {}".format(CONST_DDT_NAME), " type = ddt", "[ num_layer_vars ]", " standard_name = {}".format(stdname_layer), " units = count", " dimensions = ()", " type = integer", "[ num_interface_vars ]", " standard_name = {}".format(stdname_interface), " units = count", " dimensions = ()", " type = integer", "[ num_2d_vars ]", " standard_name = {}".format(stdname_2d), " units = count", " dimensions = ()", " type = integer", "[ {} ]".format(array_layer), " standard_name = ccpp_constituents_array_of_layer_consts", " units = none", " dimensions = ({}, {}, {})".format(horiz_dim, vert_layer_dim, stdname_layer), " type = real", " kind = kind_phys", "[ {} ]".format(array_interface), " standard_name = ccpp_constituents_array_of_interface_consts", " units = none", " dimensions = ({}, {}, {})".format(horiz_dim, vert_interface_dim, stdname_interface), " type = real", " kind = kind_phys", "[ {} ]".format(array_2d), " standard_name = ccpp_constituents_array_of_2d_consts", " units = none", " dimensions = ({}, {})".format(horiz_dim, stdname_2d), " type = real", " kind = kind_phys"] # Add entries for each constituent (once per standard name) const_stdnames = set() for suite in suite_list: if logger is not None: lmsg = "Adding constituents from {} to {}" logger.debug(lmsg.format(suite.name, host_model.name)) # end if scdict = suite.constituent_dictionary() for cvar in scdict.variable_list(): std_name = cvar.get_prop_value('standard_name') if std_name not in const_stdnames: # Add a metadata entry for this constituent # Check dimensions and figure vertical dimension # Currently, we only support variables with first dimension, # horizontal_dimension, and second (optional) dimension, # vertical_layer_dimension or vertical_interface_dimension dims = cvar.get_dimensions() if (len(dims) < 1) or (len(dims) > 2): emsg = "Unsupported constituent dimensions, '{}'" dimstr = "({})".format(", ".join(dims)) raise CCPPError(emsg.format(dimstr)) # end if hdim = dims[0].split(':')[-1] if hdim != 'horizontal_dimension': emsg = "Unsupported first constituent dimension, '{}', " emsg += "must be 'horizontal_dimension'" raise CCPPError(emsg.format(hdim)) # end if if len(dims) > 1: vdim = dims[1].split(':')[-1] if vdim == vert_layer_dim: cvar_array_name = array_layer elif vdim == vert_interface_dim: cvar_array_name = array_interface else: emsg = "Unsupported vertical constituent dimension, " emsg += "'{}', must be '{}' or '{}'" raise CCPPError(emsg.format(vdim, vert_layer_dim, vert_interface_dim)) # end if else: cvar_array_name = array_2d # end if # First, create an index variable for <cvar> ind_std_name = "index_of_{}".format(std_name) loc_name = "{}(:,:,{})".format(cvar_array_name, ind_std_name) ddt_mdata.append("[ {} ]".format(loc_name)) ddt_mdata.append(" standard_name = {}".format(std_name)) units = cvar.get_prop_value('units') ddt_mdata.append(" units = {}".format(units)) dimstr = "({})".format(", ".join(dims)) ddt_mdata.append(" dimensions = {}".format(dimstr)) vtype = cvar.get_prop_value('type') vkind = cvar.get_prop_value('kind') ddt_mdata.append(" type = {} | kind = {}".format(vtype, vkind)) const_stdnames.add(std_name) # end if # end for # end for # Parse this table using a fake filename parse_obj = ParseObject("{}_constituent_mod.meta".format(host_model.name), ddt_mdata) ddt_table = MetadataTable(parse_object=parse_obj, logger=logger) ddt_name = ddt_table.sections()[0].title ddt_lib = DDTLibrary('{}_constituent_ddtlib'.format(host_model.name), ddts=ddt_table.sections(), logger=logger) # A bit of cleanup del parse_obj del ddt_mdata # Now, create the "host constituent module" dictionary const_dict = VarDictionary("{}_constituents".format(host_model.name), parent_dict=host_model) # Add in the constituents object prop_dict = {'standard_name' : "ccpp_model_constituents_object", 'local_name' : constituent_model_object_name(host_model), 'dimensions' : '()', 'units' : "None", 'ddt_type' : ddt_name} const_var = Var(prop_dict, _API_SOURCE) const_var.write_def(cap, 1, const_dict) ddt_lib.collect_ddt_fields(const_dict, const_var) # Declare variable for the constituent standard names array max_csname = max([len(x) for x in const_stdnames]) if const_stdnames else 0 num_const_fields = len(const_stdnames) cs_stdname = constituent_model_const_stdnames(host_model) const_list = sorted(const_stdnames) if const_list: const_strs = ['"{}{}"'.format(x, ' '*(max_csname - len(x))) for x in const_list] cs_stdame_initstr = " = (/ " + ", ".join(const_strs) + " /)" else: cs_stdame_initstr = "" # end if cap.write("character(len={}) :: {}({}){}".format(max_csname, cs_stdname, num_const_fields, cs_stdame_initstr), 1) # Declare variable for the constituent standard names array array_name = constituent_model_const_indices(host_model) cap.write("integer :: {}({}) = -1".format(array_name, num_const_fields), 1) # Add individual variables for each index var to the const_dict for index, std_name in enumerate(const_list): ind_std_name = "index_of_{}".format(std_name) ind_loc_name = "{}({})".format(array_name, index + 1) prop_dict = {'standard_name' : ind_std_name, 'local_name' : ind_loc_name, 'dimensions' : '()', 'units' : 'index', 'protected' : "True", 'type' : 'integer', 'kind' : ''} ind_var = Var(prop_dict, _API_SOURCE) const_dict.add_variable(ind_var) # end for # Add vertical dimensions for DDT call strings pver = host_model.find_variable(standard_name=vert_layer_dim, any_scope=False) if pver is not None: prop_dict = {'standard_name' : vert_layer_dim, 'local_name' : pver.get_prop_value('local_name'), 'units' : 'count', 'type' : 'integer', 'protected' : 'True', 'dimensions' : '()'} if const_dict.find_variable(standard_name=vert_layer_dim, any_scope=False) is None: ind_var = Var(prop_dict, _API_SOURCE) const_dict.add_variable(ind_var) # end if # end if pver = host_model.find_variable(standard_name=vert_interface_dim, any_scope=False) if pver is not None: prop_dict = {'standard_name' : vert_interface_dim, 'local_name' : pver.get_prop_value('local_name'), 'units' : 'count', 'type' : 'integer', 'protected' : 'True', 'dimensions' : '()'} if const_dict.find_variable(standard_name=vert_interface_dim, any_scope=False) is None: ind_var = Var(prop_dict, _API_SOURCE) const_dict.add_variable(ind_var) # end if # end if return const_dict
class MetadataHeader(ParseSource): """Class to hold all information from a metadata header >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "type = scheme", "module = foo", \ "[ im ]", "standard_name = horizontal_loop_extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in"])) #doctest: +ELLIPSIS <__main__.MetadataHeader foo / foobar at 0x...> >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "type = scheme", "module = foobar", \ "[ im ]", "standard_name = horizontal_loop_extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in"])).get_var(standard_name='horizontal_loop_extent') #doctest: +ELLIPSIS <metavar.Var horizontal_loop_extent: im at 0x...> >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "module = foo", \ "[ im ]", "standard_name = horizontal_loop_extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ " subroutine foo()"])).get_var(standard_name='horizontal_loop_extent') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Missing metadata header type, at foobar.txt:7 >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "type = scheme", "module=foobar", \ "[ im ]", "standard_name = horizontal_loop_extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ ""], line_start=0)).get_var(standard_name='horizontal_loop_extent').get_prop_value('local_name') 'im' >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "type = scheme" \ "[ im ]", "standard_name = horizontal loop extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ ""], line_start=0)).get_var(standard_name='horizontal_loop_extent') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Invalid variable property value, 'horizontal loop extent', at foobar.txt:2 >>> MetadataHeader(ParseObject("foobar.txt", \ ["[ccpp-arg-table]", "name = foobar", "type = scheme" \ "[ im ]", "standard_name = horizontal loop extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ ""], line_start=0)).get_var(standard_name='horizontal_loop_extent') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Invalid property syntax, '[ccpp-arg-table]', at foobar.txt:1 >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "module = foo" \ "[ im ]", "standard_name = horizontal loop extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ ""], line_start=0)).get_var(standard_name='horizontal_loop_extent') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Invalid metadata header start, no table type, at foobar.txt:2 >>> MetadataHeader.__var_start__.match('[ qval ]') #doctest: +ELLIPSIS <_sre.SRE_Match object at 0x...> >>> MetadataHeader.__var_start__.match('[ qval(hi_mom) ]') #doctest: +ELLIPSIS <_sre.SRE_Match object at 0x...> """ __header_start__ = re.compile(r"(?i)\s*\[\s*ccpp-arg-table\s*\]") __var_start__ = re.compile(r"^\[\s*(" + FORTRAN_ID + r"|" + LITERAL_INT + r"|" + FORTRAN_SCALAR_REF + r")\s*\]$") __blank_line__ = re.compile(r"\s*[#;]") __html_template__ = """ <html> <head> <title>{title}</title> <meta charset="UTF-8"> </head> <body> <table border="1"> {header}{contents}</table> </body> </html> """ def __init__(self, parse_object=None, title=None, type_in=None, module=None, var_dict=None, logger=None): self._pobj = parse_object """If <parse_object> is not None, initialize from the current file and location in <parse_object>. If <parse_object> is None, initialize from <title>, <type>, <module>, and <var_dict>. Note that if <parse_object> is not None, <title>, <type>, <module>, and <var_dict> are ignored. """ if parse_object is None: if title is None: raise ParseInternalError('MetadataHeader requires a title') else: self._table_title = title # End if if type_in is None: raise ParseInternalError( 'MetadataHeader requires a header type') else: self._header_type = type # End if if module is None: raise ParseInternalError( 'MetadataHeader requires a module name') else: self._module_name = module # End if # Initialize our ParseSource parent super(MetadataHeader, self).__init__(self.title, self.header_type, self._pobj) self._variables = VarDictionary(self.title, logger=logger) for var in var_dict.variable_list(): # Let this crash if no dict self._variables.add_variable(var) # End for else: self.__init_from_file__(parse_object, logger) # End if # Categorize the variables self._var_intents = {'in': list(), 'out': list(), 'inout': list()} for var in self.variable_list(): intent = var.get_prop_value('intent') if intent is not None: self._var_intents[intent].append(var) # End if # End for def __init_from_file__(self, parse_object, logger): # Read the table preamble, assume the caller already figured out # the first line of the header using the table_start method. curr_line, curr_line_num = self._pobj.next_line() self._table_title = None self._header_type = None self._module_name = None while (curr_line is not None) and (not self.variable_start(curr_line)) and ( not MetadataHeader.table_start(curr_line)): for property in self.parse_config_line(curr_line): # Manually parse name, type, and module properties key = property[0].strip().lower() value = property[1].strip() if key == 'name': self._table_title = value elif key == 'type': if value not in ['module', 'scheme', 'ddt']: raise ParseSyntaxError("metadata table type", token=value, context=self._pobj) # End if self._header_type = value elif key == 'module': if value == "None": raise ParseSyntaxError("metadata table, no module", context=self._pobj) else: self._module_name = value # End if else: raise ParseSyntaxError("metadata table start property", token=value, context=self._pobj) # End if # End for curr_line, curr_line_num = self._pobj.next_line() # End while if self.title is None: raise ParseSyntaxError("metadata header start, no table name", token=curr_line, context=self._pobj) elif self.header_type is None: raise ParseSyntaxError("metadata header start, no table type", token=curr_line, context=self._pobj) elif self.header_type == "ddt": register_fortran_ddt_name(self.title) # End if # Initialize our ParseSource parent super(MetadataHeader, self).__init__(self.title, self.header_type, self._pobj) # Read the variables valid_lines = True self._variables = VarDictionary(self.title, logger=logger) while valid_lines: newvar, curr_line = self.parse_variable(curr_line) valid_lines = newvar is not None if valid_lines: self._variables.add_variable(newvar) # Check to see if we hit the end of the table valid_lines = not MetadataHeader.table_start(curr_line) # No else, we just run off the end of the table # End if # End while def parse_config_line(self, line): "Parse a config line and return a list of keyword value pairs." parse_items = list() if line is None: pass # No properties on this line elif MetadataHeader.is_blank(line): pass # No properties on this line else: properties = line.strip().split('|') for property in properties: pitems = property.split('=', 1) if len(pitems) < 2: raise ParseSyntaxError("variable property syntax", token=property, context=self._pobj) else: parse_items.append(pitems) # End if # End for # End if return parse_items def parse_variable(self, curr_line): # The header line has the format [ <valid_fortran_symbol> ] # Parse header valid_line = (curr_line is not None) and ( not MetadataHeader.table_start(curr_line)) if valid_line: local_name = self.variable_start( curr_line) # caller handles exception else: local_name = None # End if if local_name is None: # This is not a valid variable line, punt (should be end of table) valid_line = False # End if # Parse lines until invalid line is found # NB: Header variables cannot have embedded blank lines if valid_line: var_props = {} var_props['local_name'] = local_name else: var_props = None # End if while valid_line: curr_line, curr_line_num = self._pobj.next_line() valid_line = ((curr_line is not None) and (not MetadataHeader.is_blank(curr_line)) and (not MetadataHeader.table_start(curr_line)) and (self.variable_start(curr_line) is None)) # A valid line may have multiple properties (separated by '|') if valid_line: properties = self.parse_config_line(curr_line) for property in properties: try: pname = property[0].strip() pval_str = property[1].strip() # Make sure this is a match hp = Var.get_prop(pname) if hp is not None: pval = hp.valid_value(pval_str) else: raise ParseSyntaxError("variable property name", token=pname, context=self._pobj) # End if if pval is None: raise ParseSyntaxError( "'{}' property value".format(pname), token=pval_str, context=self._pobj) # End if except ParseSyntaxError as p: raise p # If we get this far, we have a valid property. var_props[pname] = pval # End for # End if # End while if var_props is None: return None, curr_line else: try: newvar = Var(var_props, source=self) except CCPPError as ve: raise ParseSyntaxError(ve, context=self._pobj) return newvar, curr_line # End if def variable_list(self): "Return an ordered list of the header's variables" return self._variables.variable_list() def to_html(self, outdir, props): """Write html file for metadata table and return filename. Skip metadata headers without variables""" if not self._variables.variable_list(): return None # Write table header header = "<tr>" for prop in props: header += "<th>{}</th>".format(prop) header += "</tr>\n" # Write table contents, one row per variable contents = "" for var in self._variables.variable_list(): row = "<tr>" for prop in props: value = var.get_prop_value(prop) # Pretty-print for dimensions if prop == 'dimensions': value = '(' + ', '.join(value) + ')' elif value is None: value = "n/a" row += "<td>{}</td>".format(value) row += "</tr>\n" contents += row filename = os.path.join(outdir, self.title + '.html') with open(filename, "w") as f: f.writelines( self.__html_template__.format(title=self.title + ' argument table', header=header, contents=contents)) return filename def get_var(self, standard_name=None, intent=None): if standard_name is not None: var = self._variables.find_variable(standard_name) return var elif intent is not None: if intent not in self._var_intents: raise ParseInternalError( "Illegal intent type, '{}', in {}".format( intent, self.title), context=self._pobj) # End if return self._var_intents[intent] else: return None def prop_list(self, prop_name): "Return list of <prop_name> values for this scheme's arguments" return self._variables.prop_list(prop_name) def variable_start(self, line): """Return variable name if <line> is an interface metadata table header """ if line is None: match = None else: match = MetadataHeader.__var_start__.match(line) # End if if match is not None: name = match.group(1) if not MetadataHeader.is_scalar_reference(name): raise ParseSyntaxError("local variable name", token=name, context=self._pobj) # End if else: name = None # End if return name def __repr__(self): base = super(MetadataHeader, self).__repr__() pind = base.find(' object ') if pind >= 0: pre = base[0:pind] else: pre = '<MetadataHeader' # End if bind = base.find('at 0x') if bind >= 0: post = base[bind:] else: post = '>' # End if return '{} {} / {} {}'.format(pre, self.module, self.title, post) def __del__(self): try: del self._variables super(MetadataHeader, self).__del__() except Exception as e: pass # Python does not guarantee much about __del__ conditions # End try @property def title(self): 'Return the name of the metadata arg_table' return self._table_title @property def module(self): 'Return the module name for this header (if it exists)' return self._module_name @property def header_type(self): 'Return the type of structure this header documents' return self._header_type @classmethod def is_blank(cls, line): "Return True iff <line> is a valid config format blank or comment line" return (len(line) == 0) or (cls.__blank_line__.match(line) is not None) @classmethod def table_start(cls, line): """Return variable name if <line> is an interface metadata table header """ if (line is None) or cls.is_blank(line): match = None else: match = MetadataHeader.__header_start__.match(line) # End if return match is not None @classmethod def is_scalar_reference(cls, test_val): return check_fortran_ref(test_val) is not None @classmethod def parse_metadata_file(cls, filename): "Parse <filename> and return list of parsed metadata headers" # Read all lines of the file at once mheaders = list() with open(filename, 'r') as file: fin_lines = file.readlines() for index in range(len(fin_lines)): fin_lines[index] = fin_lines[index].rstrip('\n') # End for # End with # Look for a header start parse_obj = ParseObject(filename, fin_lines) curr_line, curr_line_num = parse_obj.curr_line() while curr_line is not None: if MetadataHeader.table_start(curr_line): mheaders.append(MetadataHeader(parse_obj)) curr_line, curr_line_num = parse_obj.curr_line() else: curr_line, curr_line_num = parse_obj.next_line() # End if # End while return mheaders