def write(nml, nml_path, force=False): """Save a namelist to disk using either a file object or its file path. File object usage: >>> with open(nml_path, 'w') as nml_file: >>> f90nml.write(nml, nml_file) File path usage: >>> f90nml.write(nml, 'data.nml') This function is equivalent to the ``write`` function of the ``Namelist`` object ``nml``. >>> nml.write('data.nml') By default, ``write`` will not overwrite an existing file. To override this, use the ``force`` flag. >>> nml.write('data.nml', force=True) """ # Promote dicts to Namelists if not isinstance(nml, Namelist) and isinstance(nml, dict): nml_in = Namelist(nml) else: nml_in = nml nml_in.write(nml_path, force=force)
def test_f90repr(self): nml = Namelist() self.assertEqual(nml._f90repr(1), '1') self.assertEqual(nml._f90repr(1.), '1.0') self.assertEqual(nml._f90repr(1+2j), '(1.0, 2.0)') self.assertEqual(nml._f90repr(True), '.true.') self.assertEqual(nml._f90repr(False), '.false.') self.assertEqual(nml._f90repr('abc'), "'abc'") for ptype in ({}, [], set()): self.assertRaises(ValueError, nml._f90repr, ptype)
def parse_variable(self, parent, patch_nml=None): """Parse a variable and return its name and values.""" if not patch_nml: patch_nml = Namelist() v_name = self.prior_token v_values = [] # Patch state patch_values = None if self.token == '(': v_idx_bounds = self.parse_indices() v_idx = FIndex(v_idx_bounds) # Update starting index against namelist record if v_name in parent.first_index: p_idx = parent.first_index[v_name] v_idx.first = [min(p_i, v_i) for p_i, v_i in zip(p_idx, v_idx.first)] # Resize vector based on starting index for i_p, i_v in zip(p_idx, v_idx.first): if i_v < i_p: pad = [None for _ in range(i_p - i_v)] parent[v_name] = pad + parent[v_name] parent.first_index[v_name] = v_idx.first self.update_tokens() else: v_idx = None if self.token == '%': # Resolve the derived type if parent and v_name in parent: v_parent = parent[v_name] else: v_parent = Namelist() self.update_tokens() self.update_tokens() v_att, v_att_vals = self.parse_variable(v_parent) next_value = Namelist() next_value[v_att] = v_att_vals self.append_value(v_values, next_value, v_idx) else: # Construct the variable array assert self.token == '=' n_vals = None prior_ws_sep = ws_sep = False self.update_tokens() # Check if value is in the namelist patch # TODO: Edit `Namelist` to support case-insensitive `pop` calls # (Currently only a problem in PyPy2) if v_name in patch_nml: patch_values = patch_nml.pop(v_name.lower()) if not isinstance(patch_values, list): patch_values = [patch_values] p_idx = 0 # Add variables until next variable trigger while (self.token not in ('=', '(', '%') or (self.prior_token, self.token) == ('=', '(')): # Check for repeated values if self.token == '*': n_vals = self.parse_value() assert isinstance(n_vals, int) self.update_tokens() elif not n_vals: n_vals = 1 # First check for implicit null values if self.prior_token in ('=', '%', ','): if (self.token in (',', '/', '&', '$') and not (self.prior_token == ',' and self.token in ('/', '&', '$'))): self.append_value(v_values, None, v_idx, n_vals) elif self.prior_token == '*': if self.token not in ('/', '&', '$'): self.update_tokens() if (self.token == '=' or (self.token in ('/', '&', '$') and self.prior_token == '*')): next_value = None else: next_value = self.parse_value() self.append_value(v_values, next_value, v_idx, n_vals) else: next_value = self.parse_value() # Check for escaped strings if (v_values and isinstance(v_values[-1], str) and isinstance(next_value, str) and not prior_ws_sep): quote_char = self.prior_token[0] v_values[-1] = quote_char.join([v_values[-1], next_value]) else: self.append_value(v_values, next_value, v_idx, n_vals) # Exit for end of nml group (/, &, $) or null broadcast (=) if self.token in ('/', '&', '$', '='): break else: prior_ws_sep = ws_sep if (patch_values and p_idx < len(patch_values) and len(patch_values) > 0 and self.token != ','): p_val = patch_values[p_idx] p_repr = patch_nml.f90repr(patch_values[p_idx]) p_idx += 1 ws_sep = self.update_tokens(override=p_repr) if isinstance(p_val, complex): # Skip over the complex content # NOTE: Assumes input and patch are complex self.update_tokens(write_token=False) self.update_tokens(write_token=False) self.update_tokens(write_token=False) self.update_tokens(write_token=False) else: ws_sep = self.update_tokens() if patch_values: v_values = patch_values if not v_idx: v_values = delist(v_values) return v_name, v_values
def readstream(self, nml_file, nml_patch): """Parse an input stream containing a Fortran namelist.""" f90lex = shlex.shlex(nml_file) f90lex.whitespace = '' f90lex.wordchars += '.-+' # Include floating point tokens if nml_patch: f90lex.commenters = '' else: f90lex.commenters = self.comment_tokens self.tokens = iter(f90lex) nmls = Namelist() # TODO: Replace "while True" with an update_token() iterator self.update_tokens(write_token=False) while True: try: # Check for classic group terminator if self.token == 'end': self.update_tokens() # Ignore tokens outside of namelist groups while self.token not in ('&', '$'): self.update_tokens() except StopIteration: break # Create the next namelist self.update_tokens() g_name = self.token g_vars = Namelist() v_name = None # TODO: Edit `Namelist` to support case-insensitive `get` calls grp_patch = nml_patch.get(g_name.lower(), {}) # Populate the namelist group while g_name: if self.token not in ('=', '%', '('): self.update_tokens() # Set the next active variable if self.token in ('=', '(', '%'): v_name, v_values = self.parse_variable(g_vars, patch_nml=grp_patch) if v_name in g_vars: v_prior_values = g_vars[v_name] v_values = merge_values(v_prior_values, v_values) if v_name in g_vars and isinstance(g_vars[v_name], dict): g_vars[v_name].update(v_values) else: g_vars[v_name] = v_values # Deselect variable v_name = None v_values = [] # Finalise namelist group if self.token in ('/', '&', '$'): # Append any remaining patched variables for v_name, v_val in grp_patch.items(): g_vars[v_name] = v_val v_strs = nmls.var_strings(v_name, v_val) for v_str in v_strs: self.pfile.write(' {0}\n'.format(v_str)) # Append the grouplist to the namelist if g_name in nmls: g_update = nmls[g_name] # Update to list of groups if not isinstance(g_update, list): g_update = [g_update] g_update.append(g_vars) else: g_update = g_vars nmls[g_name] = g_update # Reset state g_name, g_vars = None, None try: self.update_tokens() except StopIteration: break return nmls
def _parse_variable(self, parent, patch_nml=None): """Parse a variable and return its name and values.""" if not patch_nml: patch_nml = Namelist() v_name = self.prior_token v_values = [] # Patch state patch_values = None # Derived type parent index (see notes below) dt_idx = None if self.token == '(': v_idx_bounds = self._parse_indices() v_idx = FIndex(v_idx_bounds, self.global_start_index) # Update starting index against namelist record if v_name.lower() in parent.start_index: p_idx = parent.start_index[v_name.lower()] for idx, pv in enumerate(zip(p_idx, v_idx.first)): if all(i is None for i in pv): i_first = None else: i_first = min(i for i in pv if i is not None) v_idx.first[idx] = i_first # Resize vector based on starting index parent[v_name] = prepad_array(parent[v_name], p_idx, v_idx.first) else: # If variable already existed without an index, then assume a # 1-based index # FIXME: Need to respect undefined `None` starting indexes? if v_name in parent: v_idx.first = [self.default_start_index for _ in v_idx.first] parent.start_index[v_name.lower()] = v_idx.first self._update_tokens() # Derived type parent check # NOTE: This assumes single-dimension derived type vectors # (which I think is the only case supported in Fortran) if self.token == '%': assert v_idx_bounds[0][1] - v_idx_bounds[0][0] == 1 dt_idx = v_idx_bounds[0][0] - v_idx.first[0] # NOTE: This is the sensible play to call `parse_variable` # but not yet sure how to implement it, so we currently pass # along `dt_idx` to the `%` handler. else: v_idx = None # If indexed variable already exists, then re-index this new # non-indexed variable using the global start index if v_name in parent.start_index: p_start = parent.start_index[v_name.lower()] v_start = [self.default_start_index for _ in p_start] # Resize vector based on new starting index for i_p, i_v in zip(p_start, v_start): if i_v < i_p: pad = [None for _ in range(i_p - i_v)] parent[v_name] = pad + parent[v_name] parent.start_index[v_name.lower()] = v_start if self.token == '%': # Resolve the derived type # Check for value in patch v_patch_nml = None if v_name in patch_nml: v_patch_nml = patch_nml.pop(v_name.lower()) if parent: vpar = parent.get(v_name.lower()) if vpar and isinstance(vpar, list): # If new element is not a list, then assume it's the first # element of the list. if dt_idx is None: dt_idx = self.default_start_index try: v_parent = vpar[dt_idx] except IndexError: v_parent = Namelist() elif vpar: v_parent = vpar else: v_parent = Namelist() else: v_parent = Namelist() parent[v_name] = v_parent self._update_tokens() self._update_tokens() v_att, v_att_vals = self._parse_variable( v_parent, patch_nml=v_patch_nml ) next_value = Namelist() next_value[v_att] = v_att_vals self._append_value(v_values, next_value, v_idx) else: # Construct the variable array assert self.token == '=' n_vals = None self._update_tokens() # Check if value is in the namelist patch # TODO: Edit `Namelist` to support case-insensitive `pop` calls # (Currently only a problem in PyPy2) if v_name in patch_nml: patch_values = patch_nml.pop(v_name.lower()) if not isinstance(patch_values, list): patch_values = [patch_values] p_idx = 0 # Add variables until next variable trigger while (self.token not in ('=', '(', '%') or (self.prior_token, self.token) in (('=', '('), (',', '('))): # Check for repeated values if self.token == '*': n_vals = self._parse_value() assert isinstance(n_vals, int) self._update_tokens() elif not n_vals: n_vals = 1 # First check for implicit null values if self.prior_token in ('=', '%', ','): if (self.token in (',', '/', '&', '$') and not (self.prior_token == ',' and self.token in ('/', '&', '$'))): self._append_value(v_values, None, v_idx, n_vals) elif self.prior_token == '*': if self.token not in ('/', '&', '$'): self._update_tokens() if (self.token == '=' or (self.token in ('/', '&', '$') and self.prior_token == '*')): next_value = None else: next_value = self._parse_value() self._append_value(v_values, next_value, v_idx, n_vals) else: next_value = self._parse_value() self._append_value(v_values, next_value, v_idx, n_vals) # Reset default repeat factor for subsequent values n_vals = 1 # Exit for end of nml group (/, &, $) or null broadcast (=) if self.token in ('/', '&', '$', '='): break else: # Get the remaining length of the unpatched vector? # NOTE: it is probably very inefficient to keep re-creating # iterators upon every element; this solution reflects the # absence of mature lookahead in the script. # # This is a temporary fix to address errors caused by # patches of different length from the original value, and # represents a direction to fully rewrite the parser using # `tee`. self.tokens, lookahead = itertools.tee(self.tokens) n_vals_remain = count_values(lookahead) if patch_values: # XXX: The (p_idx - 1) <= n_vals_remain test is dodgy # and does not really make sense to me, but it appears # to work. # TODO: Patch indices that are not set in the namelist if (p_idx < len(patch_values) and (p_idx - 1) <= n_vals_remain and len(patch_values) > 0 and self.token != ','): p_val = patch_values[p_idx] p_repr = patch_nml._f90repr(patch_values[p_idx]) p_idx += 1 self._update_tokens(override=p_repr) if isinstance(p_val, complex): # Skip over the complex content # NOTE: Assumes input and patch are complex self._update_tokens(write_token=False) self._update_tokens(write_token=False) self._update_tokens(write_token=False) self._update_tokens(write_token=False) else: # Skip any values beyond the patch size skip = (p_idx >= len(patch_values)) self._update_tokens(patch_skip=skip) else: self._update_tokens() if patch_values: v_values = patch_values if not v_idx: v_values = delist(v_values) return v_name, v_values
def _readstream(self, nml_file, nml_patch_in=None): """Parse an input stream containing a Fortran namelist.""" nml_patch = nml_patch_in if nml_patch_in is not None else Namelist() tokenizer = Tokenizer() f90lex = [] for line in nml_file: toks = tokenizer.parse(line) while tokenizer.prior_delim: new_toks = tokenizer.parse(next(nml_file)) # Skip empty lines if not new_toks: continue # The tokenizer always pre-tokenizes the whitespace (leftover # behaviour from Fortran source parsing) so this must be added # manually. if new_toks[0].isspace(): toks[-1] += new_toks.pop(0) # Append the rest of the string (if present) if new_toks: toks[-1] += new_toks[0] # Attach the rest of the tokens toks.extend(new_toks[1:]) toks.append('\n') f90lex.extend(toks) self.tokens = iter(f90lex) nmls = Namelist() # Attempt to get first token; abort on empty file try: self._update_tokens(write_token=False) except StopIteration: return nmls # TODO: Replace "while True" with an update_token() iterator while True: try: # Check for classic group terminator if self.token == 'end': self._update_tokens() # Ignore tokens outside of namelist groups while self.token not in ('&', '$'): self._update_tokens() except StopIteration: break # Create the next namelist self._update_tokens() g_name = self.token g_vars = Namelist() v_name = None # TODO: Edit `Namelist` to support case-insensitive `get` calls grp_patch = nml_patch.get(g_name.lower(), Namelist()) # Populate the namelist group while g_name: if self.token not in ('=', '%', '('): self._update_tokens() # Set the next active variable if self.token in ('=', '(', '%'): v_name, v_values = self._parse_variable( g_vars, patch_nml=grp_patch ) if v_name in g_vars: v_prior_values = g_vars[v_name] v_values = merge_values(v_prior_values, v_values) g_vars[v_name] = v_values # Deselect variable v_name = None v_values = [] # Finalise namelist group if self.token in ('/', '&', '$'): # Append any remaining patched variables for v_name, v_val in grp_patch.items(): g_vars[v_name] = v_val v_strs = nmls._var_strings(v_name, v_val) for v_str in v_strs: self.pfile.write( '{0}{1}\n'.format(nml_patch.indent, v_str) ) # Append the grouplist to the namelist if g_name in nmls: g_update = nmls[g_name] # Update to list of groups if not isinstance(g_update, list): g_update = [g_update] g_update.append(g_vars) else: g_update = g_vars nmls[g_name] = g_update # Reset state g_name, g_vars = None, None try: self._update_tokens() except StopIteration: break return nmls
def parse_variable(self, parent, patch_nml=None): """Parse a variable and return its name and values.""" if not patch_nml: patch_nml = Namelist() v_name = self.prior_token v_values = [] # Patch state patch_values = None if self.token == '(': v_idx_bounds = self.parse_indices() v_idx = FIndex(v_idx_bounds, self.global_start_index) # Update starting index against namelist record if v_name in parent.start_index: p_idx = parent.start_index[v_name] for idx, pv in enumerate(zip(p_idx, v_idx.first)): if all(i is None for i in pv): i_first = None else: i_first = min(i for i in pv if i is not None) v_idx.first[idx] = i_first # Resize vector based on starting index for i_p, i_v in zip(p_idx, v_idx.first): if i_p is not None and i_v is not None and i_v < i_p: pad = [None for _ in range(i_p - i_v)] parent[v_name] = pad + parent[v_name] else: # If variable already existed without an index, then assume a # 1-based index # FIXME: Need to respect undefined `None` starting indexes? if v_name in parent: v_idx.first = [self.default_start_index for _ in v_idx.first] parent.start_index[v_name] = v_idx.first self.update_tokens() else: v_idx = None # If indexed variable already exists, then re-index this new # non-indexed variable using the global start index # FIXME: The `hasattr` check is a hack to deal with the "list of # parents" bug metioned in the `%` parsing block below. The real # solution here is to prevent the list of parents being set as # parent. if hasattr(parent, 'start_index') and v_name in parent.start_index: p_start = parent.start_index[v_name] v_start = [self.default_start_index for _ in p_start] # Resize vector based on new starting index for i_p, i_v in zip(p_start, v_start): if i_v < i_p: pad = [None for _ in range(i_p - i_v)] parent[v_name] = pad + parent[v_name] parent.start_index[v_name] = v_start if self.token == '%': # Resolve the derived type # FIXME: If we were in an index (v_idx != None) then v_parent is # incorrectly set as the list of dicts rather than the respective # dict (which must somehow be deduced from v_idx at some later # stage) # This is not causing any errors, but the parent value is nonsense # and could break something in the future. # Check for value in patch v_patch_nml = None if v_name in patch_nml: v_patch_nml = patch_nml.pop(v_name.lower()) if parent and v_name in parent: v_parent = parent[v_name] else: v_parent = Namelist() parent[v_name] = v_parent self.update_tokens() self.update_tokens() v_att, v_att_vals = self.parse_variable(v_parent, patch_nml=v_patch_nml) next_value = Namelist() next_value[v_att] = v_att_vals self.append_value(v_values, next_value, v_idx) else: # Construct the variable array assert self.token == '=' n_vals = None prior_ws_sep = ws_sep = False self.update_tokens() # Check if value is in the namelist patch # TODO: Edit `Namelist` to support case-insensitive `pop` calls # (Currently only a problem in PyPy2) if v_name in patch_nml: patch_values = patch_nml.pop(v_name.lower()) if not isinstance(patch_values, list): patch_values = [patch_values] p_idx = 0 # Add variables until next variable trigger while (self.token not in ('=', '(', '%') or (self.prior_token, self.token) == ('=', '(')): # Check for repeated values if self.token == '*': n_vals = self.parse_value() assert isinstance(n_vals, int) self.update_tokens() elif not n_vals: n_vals = 1 # First check for implicit null values if self.prior_token in ('=', '%', ','): if (self.token in (',', '/', '&', '$') and not (self.prior_token == ',' and self.token in ('/', '&', '$'))): self.append_value(v_values, None, v_idx, n_vals) elif self.prior_token == '*': if self.token not in ('/', '&', '$'): self.update_tokens() if (self.token == '=' or (self.token in ('/', '&', '$') and self.prior_token == '*')): next_value = None else: next_value = self.parse_value() self.append_value(v_values, next_value, v_idx, n_vals) else: next_value = self.parse_value() # Check for escaped strings if (v_values and isinstance(v_values[-1], str) and isinstance(next_value, str) and not prior_ws_sep): quote_char = self.prior_token[0] v_values[-1] = quote_char.join([v_values[-1], next_value]) else: self.append_value(v_values, next_value, v_idx, n_vals) # Exit for end of nml group (/, &, $) or null broadcast (=) if self.token in ('/', '&', '$', '='): break else: prior_ws_sep = ws_sep if patch_values: if (p_idx < len(patch_values) and len(patch_values) > 0 and self.token != ','): p_val = patch_values[p_idx] p_repr = patch_nml.f90repr(patch_values[p_idx]) p_idx += 1 ws_sep = self.update_tokens(override=p_repr) if isinstance(p_val, complex): # Skip over the complex content # NOTE: Assumes input and patch are complex self.update_tokens(write_token=False) self.update_tokens(write_token=False) self.update_tokens(write_token=False) self.update_tokens(write_token=False) else: # Skip any values beyond the patch size skip = (p_idx >= len(patch_values)) self.update_tokens(patch_skip=skip) else: ws_sep = self.update_tokens() if patch_values: v_values = patch_values if not v_idx: v_values = delist(v_values) return v_name, v_values
def test_groups(self): d = {'a': {'b': 1.}} nml = Namelist(d) key, value = next(nml.groups()) self.assertEqual(key, ('a', 'b')) self.assertEqual(value, 1.)
def parse_variable(self, parent, patch_nml=None): """Parse a variable and return its name and values.""" if not patch_nml: patch_nml = Namelist() v_name = self.prior_token v_values = [] # Patch state patch_values = None # Derived type parent index (see notes below) dt_idx = None if self.token == '(': v_idx_bounds = self.parse_indices() v_idx = FIndex(v_idx_bounds, self.global_start_index) # Update starting index against namelist record if v_name.lower() in parent.start_index: p_idx = parent.start_index[v_name.lower()] for idx, pv in enumerate(zip(p_idx, v_idx.first)): if all(i is None for i in pv): i_first = None else: i_first = min(i for i in pv if i is not None) v_idx.first[idx] = i_first # Resize vector based on starting index for i_p, i_v in zip(p_idx, v_idx.first): if i_p is not None and i_v is not None and i_v < i_p: pad = [None for _ in range(i_p - i_v)] parent[v_name] = pad + parent[v_name] else: # If variable already existed without an index, then assume a # 1-based index # FIXME: Need to respect undefined `None` starting indexes? if v_name in parent: v_idx.first = [self.default_start_index for _ in v_idx.first] parent.start_index[v_name.lower()] = v_idx.first self.update_tokens() # Derived type parent check # NOTE: This assumes single-dimension derived type vectors # (which I think is the only case supported in Fortran) if self.token == '%': assert(v_idx_bounds[0][1] - v_idx_bounds[0][0] == 1) dt_idx = v_idx_bounds[0][0] - v_idx.first[0] # NOTE: This is the sensible play to call `parse_variable` # but not yet sure how to implement it, so we currently pass # along `dt_idx` to the `%` handler. else: v_idx = None # If indexed variable already exists, then re-index this new # non-indexed variable using the global start index if v_name in parent.start_index: p_start = parent.start_index[v_name.lower()] v_start = [self.default_start_index for _ in p_start] # Resize vector based on new starting index for i_p, i_v in zip(p_start, v_start): if i_v < i_p: pad = [None for _ in range(i_p - i_v)] parent[v_name] = pad + parent[v_name] parent.start_index[v_name.lower()] = v_start if self.token == '%': # Resolve the derived type # Check for value in patch v_patch_nml = None if v_name in patch_nml: v_patch_nml = patch_nml.pop(v_name.lower()) if parent: vpar = parent.get(v_name.lower()) if vpar and isinstance(vpar, list): assert dt_idx is not None try: v_parent = vpar[dt_idx] except IndexError: v_parent = Namelist() elif vpar: v_parent = vpar else: v_parent = Namelist() else: v_parent = Namelist() parent[v_name] = v_parent self.update_tokens() self.update_tokens() v_att, v_att_vals = self.parse_variable(v_parent, patch_nml=v_patch_nml) next_value = Namelist() next_value[v_att] = v_att_vals self.append_value(v_values, next_value, v_idx) else: # Construct the variable array assert self.token == '=' n_vals = None self.update_tokens() # Check if value is in the namelist patch # TODO: Edit `Namelist` to support case-insensitive `pop` calls # (Currently only a problem in PyPy2) if v_name in patch_nml: patch_values = patch_nml.pop(v_name.lower()) if not isinstance(patch_values, list): patch_values = [patch_values] p_idx = 0 # Add variables until next variable trigger while (self.token not in ('=', '(', '%') or (self.prior_token, self.token) == ('=', '(')): # Check for repeated values if self.token == '*': n_vals = self.parse_value() assert isinstance(n_vals, int) self.update_tokens() elif not n_vals: n_vals = 1 # First check for implicit null values if self.prior_token in ('=', '%', ','): if (self.token in (',', '/', '&', '$') and not (self.prior_token == ',' and self.token in ('/', '&', '$'))): self.append_value(v_values, None, v_idx, n_vals) elif self.prior_token == '*': if self.token not in ('/', '&', '$'): self.update_tokens() if (self.token == '=' or (self.token in ('/', '&', '$') and self.prior_token == '*')): next_value = None else: next_value = self.parse_value() self.append_value(v_values, next_value, v_idx, n_vals) else: next_value = self.parse_value() self.append_value(v_values, next_value, v_idx, n_vals) # Reset default repeat factor for subsequent values n_vals = 1 # Exit for end of nml group (/, &, $) or null broadcast (=) if self.token in ('/', '&', '$', '='): break else: if patch_values: if (p_idx < len(patch_values) and len(patch_values) > 0 and self.token != ','): p_val = patch_values[p_idx] p_repr = patch_nml.f90repr(patch_values[p_idx]) p_idx += 1 self.update_tokens(override=p_repr) if isinstance(p_val, complex): # Skip over the complex content # NOTE: Assumes input and patch are complex self.update_tokens(write_token=False) self.update_tokens(write_token=False) self.update_tokens(write_token=False) self.update_tokens(write_token=False) else: # Skip any values beyond the patch size skip = (p_idx >= len(patch_values)) self.update_tokens(patch_skip=skip) else: self.update_tokens() if patch_values: v_values = patch_values if not v_idx: v_values = delist(v_values) return v_name, v_values
def readstream(self, nml_file, nml_patch): """Parse an input stream containing a Fortran namelist.""" tokenizer = Tokenizer() f90lex = [] for line in nml_file: toks = tokenizer.parse(line) toks.append('\n') f90lex.extend(toks) self.tokens = iter(f90lex) nmls = Namelist() # Attempt to get first token; abort on empty file try: self.update_tokens(write_token=False) except StopIteration: return nmls # TODO: Replace "while True" with an update_token() iterator while True: try: # Check for classic group terminator if self.token == 'end': self.update_tokens() # Ignore tokens outside of namelist groups while self.token not in ('&', '$'): self.update_tokens() except StopIteration: break # Create the next namelist self.update_tokens() g_name = self.token g_vars = Namelist() v_name = None # TODO: Edit `Namelist` to support case-insensitive `get` calls grp_patch = nml_patch.get(g_name.lower(), {}) # Populate the namelist group while g_name: if self.token not in ('=', '%', '('): self.update_tokens() # Set the next active variable if self.token in ('=', '(', '%'): v_name, v_values = self.parse_variable(g_vars, patch_nml=grp_patch) if v_name in g_vars: v_prior_values = g_vars[v_name] v_values = merge_values(v_prior_values, v_values) g_vars[v_name] = v_values # Deselect variable v_name = None v_values = [] # Finalise namelist group if self.token in ('/', '&', '$'): # Append any remaining patched variables for v_name, v_val in grp_patch.items(): g_vars[v_name] = v_val v_strs = nmls.var_strings(v_name, v_val) for v_str in v_strs: self.pfile.write(' {0}\n'.format(v_str)) # Append the grouplist to the namelist if g_name in nmls: g_update = nmls[g_name] # Update to list of groups if not isinstance(g_update, list): g_update = [g_update] g_update.append(g_vars) else: g_update = g_vars nmls[g_name] = g_update # Reset state g_name, g_vars = None, None try: self.update_tokens() except StopIteration: break return nmls
def read(self, nml_fname, nml_patch_in=None, patch_fname=None, row_major=None, strict_logical=None): """Parse a Fortran 90 namelist file and store the contents. >>> from f90nml.parser import Parser >>> parser = Parser() >>> data_nml = parser.read('data.nml')""" if row_major is not None: if not isinstance(row_major, bool): raise ValueError("f90nml: error: row_major must be a logical " "value.") else: self.row_major = row_major if strict_logical is not None: if not isinstance(strict_logical, bool): raise ValueError("f90nml: error: strict_logical must be a " "logical value.") else: self.strict_logical = strict_logical nml_file = open(nml_fname, "r") if nml_patch_in: if not isinstance(nml_patch_in, dict): nml_file.close() raise ValueError("Input patch must be a dict or an Namelist.") nml_patch = copy.deepcopy(Namelist(nml_patch_in)) if not patch_fname: patch_fname = nml_fname + "~" elif nml_fname == patch_fname: nml_file.close() raise ValueError("f90nml: error: Patch filepath cannot be the " "same as the original filepath.") self.pfile = open(patch_fname, "w") else: nml_patch = Namelist() f90lex = shlex.shlex(nml_file) f90lex.whitespace = "" f90lex.wordchars += ".-+" # Include floating point tokens if nml_patch: f90lex.commenters = "" else: f90lex.commenters = "!" self.tokens = iter(f90lex) nmls = Namelist() # TODO: Replace "while True" with an update_token() iterator self.update_tokens(write_token=False) while True: try: # Check for classic group terminator if self.token == "end": self.update_tokens() # Ignore tokens outside of namelist groups while self.token not in ("&", "$"): self.update_tokens() except StopIteration: break # Create the next namelist self.update_tokens() g_name = self.token g_vars = Namelist() v_name = None grp_patch = nml_patch.get(g_name, {}) # Populate the namelist group while g_name: if self.token not in ("=", "%", "("): self.update_tokens() # Set the next active variable if self.token in ("=", "(", "%"): try: v_name, v_values = self.parse_variable(g_vars, patch_nml=grp_patch) except ValueError: nml_file.close() if self.pfile: self.pfile.close() raise if v_name in g_vars: v_prior_values = g_vars[v_name] v_values = merge_values(v_prior_values, v_values) if v_name in g_vars and isinstance(g_vars[v_name], dict): g_vars[v_name].update(v_values) else: g_vars[v_name] = v_values # Deselect variable v_name = None v_values = [] # Finalise namelist group if self.token in ("/", "&", "$"): # Append any remaining patched variables for v_name, v_val in grp_patch.items(): g_vars[v_name] = v_val v_strs = nmls.var_strings(v_name, v_val) for v_str in v_strs: self.pfile.write(" {0}\n".format(v_str)) # Append the grouplist to the namelist if g_name in nmls: g_update = nmls[g_name] # Update to list of groups if not isinstance(g_update, list): g_update = [g_update] g_update.append(g_vars) else: g_update = g_vars nmls[g_name] = g_update # Reset state g_name, g_vars = None, None try: self.update_tokens() except StopIteration: break nml_file.close() if self.pfile: self.pfile.close() return nmls
def parse_variable(self, parent, patch_nml=None): """Parse a variable and return its name and values.""" if not patch_nml: patch_nml = Namelist() v_name = self.prior_token v_values = [] # Patch state patch_values = None write_token = v_name not in patch_nml if self.token == "(": v_idx_bounds = self.parse_indices() v_idx = FIndex(v_idx_bounds) self.update_tokens() else: v_idx = None if self.token == "%": # Resolve the derived type if parent and v_name in parent: v_parent = parent[v_name] else: v_parent = [] self.update_tokens() self.update_tokens() v_att, v_att_vals = self.parse_variable(v_parent) next_value = Namelist() next_value[v_att] = v_att_vals self.append_value(v_values, next_value, v_idx) else: # Construct the variable array assert self.token == "=" n_vals = None prior_ws_sep = ws_sep = False self.update_tokens() # Check if value is in the namelist patch if v_name in patch_nml: patch_values = patch_nml.f90repr(patch_nml.pop(v_name)) if not isinstance(patch_values, list): patch_values = [patch_values] for p_val in patch_values: self.pfile.write(p_val) # Add variables until next variable trigger while self.token not in ("=", "(", "%") or (self.prior_token, self.token) == ("=", "("): # Check for repeated values if self.token == "*": n_vals = self.parse_value(write_token) assert isinstance(n_vals, int) self.update_tokens(write_token) elif not n_vals: n_vals = 1 # First check for implicit null values if self.prior_token in ("=", "%", ","): if self.token in (",", "/", "&", "$") and not ( self.prior_token == "," and self.token in ("/", "&", "$") ): self.append_value(v_values, None, v_idx, n_vals) elif self.prior_token == "*": if self.token not in ("/", "&", "$"): self.update_tokens(write_token) if self.token == "=" or (self.token in ("/", "&", "$") and self.prior_token == "*"): next_value = None else: next_value = self.parse_value(write_token) self.append_value(v_values, next_value, v_idx, n_vals) else: next_value = self.parse_value(write_token) # Finished reading old value, we can again write tokens write_token = True # Check for escaped strings if v_values and isinstance(v_values[-1], str) and isinstance(next_value, str) and not prior_ws_sep: quote_char = self.prior_token[0] v_values[-1] = quote_char.join([v_values[-1], next_value]) else: self.append_value(v_values, next_value, v_idx, n_vals) # Exit for end of nml group (/, &, $) or null broadcast (=) if self.token in ("/", "&", "$", "="): break else: prior_ws_sep = ws_sep ws_sep = self.update_tokens(write_token) if patch_values: v_values = patch_values if not v_idx: v_values = delist(v_values) return v_name, v_values
def pull_cfg_from_parameters_out(parameters_out, namelist_to_read="nml_allcfgs"): """ Pull out a single config set from a parameters_out namelist. This function returns a single file with the config that needs to be passed to MAGICC in order to do the same run as is represented by the values in ``parameters_out``. Parameters ---------- parameters_out : dict, f90nml.Namelist The parameters to dump namelist_to_read : str The namelist to read from the file. Returns ------- :obj:`f90nml.Namelist` An f90nml object with the cleaned, read out config. Examples -------- >>> cfg = pull_cfg_from_parameters_out(magicc.metadata["parameters"]) >>> cfg.write("/somewhere/else/ANOTHERNAME.cfg") """ single_cfg = Namelist({namelist_to_read: {}}) for key, value in parameters_out[namelist_to_read].items(): if "file_tuning" in key: single_cfg[namelist_to_read][key] = "" else: try: if isinstance(value, str): single_cfg[namelist_to_read][key] = value.strip( " \t\n\r").replace("\x00", "") elif isinstance(value, list): clean_list = [ v.strip(" \t\n\r").replace("\x00", "") for v in value ] single_cfg[namelist_to_read][key] = [ v for v in clean_list if v ] else: if not isinstance(value, Number): raise AssertionError( "value is not a number: {}".format(value)) single_cfg[namelist_to_read][key] = value except AttributeError: if isinstance(value, list): if not all([isinstance(v, Number) for v in value]): raise AssertionError( "List where not all values are numbers? " "{}".format(value)) single_cfg[namelist_to_read][key] = value else: raise AssertionError( "Unexpected cause in out parameters conversion") return single_cfg