Loading .gitignore +2 −0 Original line number Diff line number Diff line Loading @@ -2,3 +2,5 @@ __pycache__ build dist namelist.egg-info *.pyc .coverage README.md +1 −1 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ Parse Fortran Namelists to dict-like objects and back ## Download To get the latest git version do ``` $ git clone https://github.com/martinclaus/py-namelist.git $ git clone https://git.geomar.de:martin-claus/py-namelist.git ``` ## Usage Loading namelist/__init__.py +4 −1 Original line number Diff line number Diff line Loading @@ -3,4 +3,7 @@ __name__ = "namelist" __version__ = "0.0.1" from .namelist import Namelist, parse_namelist_file from .namelist import (Namelist, parse_namelist_file, parse_namelist_string, ) namelist/namelist.py +113 −46 Original line number Diff line number Diff line Loading @@ -4,56 +4,60 @@ Created on Tue Sep 23 10:27:57 2014 @author: mclaus """ import sys import re if sys.version_info[:2] < (2, 7): DictClass = dict else: # If Python 2.7 or higher use OrderedDict to preserve # the order of the Namelist try: from collections import OrderedDict as DictClass except ImportError: # pragma: no cover # Python < 2.7 DictClass = dict MODULE_NAME = "namelist" NML_LINE_LENGTH = 70 # Config file parser, called from the class initialization varname = r'[a-zA-Z][a-zA-Z0-9_]*' valueBool = re.compile(r"(\.(true|false|t|f)\.)", re.I) quote = re.compile(r"([\'\"]{1}.*[\'\"]{1})") quote = re.compile(r"([\']{1}[^\']*[\']{1}|[\"]{1}[^\"]*[\"]{1})", re.MULTILINE) namelistname = re.compile(r"&(" + varname + r")") paramname = re.compile(r"^(" + varname + r")") namlistend = re.compile(r"^\$(end)?", re.I) comment = re.compile(r"#.*") namlistend = re.compile(r'^(&(end)?|/)$', re.I) comment = re.compile(r"!.*$", re.MULTILINE) equalsign = re.compile(r"^=$") computation = re.compile(r"^([0-9\.e]+\s*[\*\+\-/]{1}\s*)+[0-9\.e]+", re.I) computation = re.compile( r"([\(]*[0-9\.e]+[\)\s]*([\*\+\-/]{1}|[\*]{2})\s*)+[0-9\.e]+[\)]*", re.I ) class Namelist(DictClass): """ Class to handle Fortran Namelists Namelist(string) -> new namelist with fortran nml identifier string Namelist(string, init_val) -> new initialized namelist with nml identifier string and init_val beeing a valid initialisation object for the parent """Class to handle Fortran Namelists. Namelist(string) -> new namelist with fortran namelist group name Namelist(string, init_val) -> new initialized namelist with namelist group name and init_val beeing a valid initialisation object for the parent class (either OrderedDict for Python >= 2.7 or else dict). A fortran readable string representation of the namelist can be generated via str() build-in function. A string representation of the Python object that can be used with eval or string. Template substitution can be obtained by repr() build-in function. """ @property def name(self): """ Read only property name, representing the fortran namelist identifier. """ """Namelist group name.""" return self._name def __init__(self, name, init_val=()): """x.__init__(...) initializes x; see help(type(x)) for signature""" """Create a `Namelist` instance. See help(type(x)) for signature. """ self._name = name super(self.__class__, self).__init__(init_val) def __str__(self): """x.__str__(self) -> Fortran readable string representation of the namelist. If a value v is a sequence, an 1D fortran array representation """Fortran readable string representation of the namelist. If a value v is a sequence, an 1D fortran array representation is created using iter(v). """ retstr = "&%s\n" % str(self.name) Loading Loading @@ -85,13 +89,11 @@ class Namelist(DictClass): else: rv = repr(v) retstr += "%s = %s\n" % (str(k), rv) retstr += "&end\n" retstr += "/\n" return retstr def __repr__(self): """x.__repr__(self) -> string that can be used by eval to create a copy of x. """ """Return a string that can be used by eval to create a copy.""" retstr = "%s.%s(%s, (" % (MODULE_NAME, self.__class__.__name__, repr(self.name)) for k, v in self.items(): Loading @@ -100,17 +102,55 @@ class Namelist(DictClass): return retstr def has_name(self, name): """x.hasname(self, name) <==> name==x.name""" """Return `True` if `name` matches the namelist group name. Parameters ---------- name : str name to test against. Returns ------- bool : `True` if `name` matches the namelist group name. """ return name == self.name def parse_namelist_file(in_file): """ parse_namelist_file(fobj) -> list of Namelist instances. fobj can be any object that implements pythons file object API, i.e. that offers a read() method. """Parse namelists from file object. Parameters ---------- in_file : :obj: Any object that implements pythons file object API, i.e. that offers a `read` and `seek` method. Returns ------- :obj:`List` of :obj:`Namelist` """ retlist = [] content = _tokenize(in_file.read()) namelist_string = in_file.read() in_file.seek(0, 0) return parse_namelist_string(namelist_string) def parse_namelist_string(in_string): """Parse namelists from string. Parameters ---------- in_string : str String containing one or more namelist definitions. Returns ------- :obj:`List` of :obj:`Namelist` """ retlist = [] content = _tokenize(in_string) for item in content: match = re.match(namelistname, item) if match: Loading @@ -123,9 +163,9 @@ def parse_namelist_file(in_file): pname = match.group(1) nml[pname] = [] continue for pattern in (namlistend, equalsign): match = re.match(pattern, item) if match: if re.match(namlistend, item): continue if re.match(equalsign, item): continue match = re.match(valueBool, item) if match: Loading @@ -140,26 +180,53 @@ def parse_namelist_file(in_file): except ValueError: pass else: continue continue # pragma: no cover try: nml[pname].append(float(item)) except ValueError: pass else: continue continue # pragma: no cover match = re.match(computation, item) if match: nml[pname].append(eval(item)) for nml in retlist: for k, v in nml.iteritems(): for k, v in nml.items(): if len(v) == 1: nml[k] = v[0] return retlist def _tokenize(text): fs = "$FS$" """Extract syntax tokens.""" fs = "$$$FS$$$" # remove comments text = re.sub(comment, '', text) for char, rep in zip(('\n', r',', ' ', '=', ), (fs, fs, fs, fs+'='+fs)): hashed_tokens = {} # replace quoted strings by hash text = _hash_token(text, quote, hashed_tokens, fs) # replace numerical computations by hash text = _hash_token(text, computation, hashed_tokens, fs) for char, rep in zip(('\n', r',', ' ', '=', '(/', '/)'), (fs, fs, fs, fs+'='+fs, fs, fs)): text = text.replace(char, rep) text = text.split(fs) return [token.strip() for token in text if token.strip() != ''] tokens = [token.strip() for token in text if token.strip() != ''] return [hashed_tokens[t] if t in hashed_tokens else t for t in tokens] def _hash_token(text, pattern, hashed_tokens, fs): while True: match = re.search(pattern, text) if not match: break matched_str = match.group(0) hashed = str(hash(matched_str)) hashed_tokens[hashed] = matched_str text = text.replace(matched_str, fs+hashed+fs, 1) return text tests/context.py 0 → 100644 +14 −0 Original line number Diff line number Diff line """Context for tests.""" import os import sys sys.path.insert( 0, os.path.abspath( os.path.join(os.path.dirname(__file__), '..') ) ) import namelist import io Loading
.gitignore +2 −0 Original line number Diff line number Diff line Loading @@ -2,3 +2,5 @@ __pycache__ build dist namelist.egg-info *.pyc .coverage
README.md +1 −1 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ Parse Fortran Namelists to dict-like objects and back ## Download To get the latest git version do ``` $ git clone https://github.com/martinclaus/py-namelist.git $ git clone https://git.geomar.de:martin-claus/py-namelist.git ``` ## Usage Loading
namelist/__init__.py +4 −1 Original line number Diff line number Diff line Loading @@ -3,4 +3,7 @@ __name__ = "namelist" __version__ = "0.0.1" from .namelist import Namelist, parse_namelist_file from .namelist import (Namelist, parse_namelist_file, parse_namelist_string, )
namelist/namelist.py +113 −46 Original line number Diff line number Diff line Loading @@ -4,56 +4,60 @@ Created on Tue Sep 23 10:27:57 2014 @author: mclaus """ import sys import re if sys.version_info[:2] < (2, 7): DictClass = dict else: # If Python 2.7 or higher use OrderedDict to preserve # the order of the Namelist try: from collections import OrderedDict as DictClass except ImportError: # pragma: no cover # Python < 2.7 DictClass = dict MODULE_NAME = "namelist" NML_LINE_LENGTH = 70 # Config file parser, called from the class initialization varname = r'[a-zA-Z][a-zA-Z0-9_]*' valueBool = re.compile(r"(\.(true|false|t|f)\.)", re.I) quote = re.compile(r"([\'\"]{1}.*[\'\"]{1})") quote = re.compile(r"([\']{1}[^\']*[\']{1}|[\"]{1}[^\"]*[\"]{1})", re.MULTILINE) namelistname = re.compile(r"&(" + varname + r")") paramname = re.compile(r"^(" + varname + r")") namlistend = re.compile(r"^\$(end)?", re.I) comment = re.compile(r"#.*") namlistend = re.compile(r'^(&(end)?|/)$', re.I) comment = re.compile(r"!.*$", re.MULTILINE) equalsign = re.compile(r"^=$") computation = re.compile(r"^([0-9\.e]+\s*[\*\+\-/]{1}\s*)+[0-9\.e]+", re.I) computation = re.compile( r"([\(]*[0-9\.e]+[\)\s]*([\*\+\-/]{1}|[\*]{2})\s*)+[0-9\.e]+[\)]*", re.I ) class Namelist(DictClass): """ Class to handle Fortran Namelists Namelist(string) -> new namelist with fortran nml identifier string Namelist(string, init_val) -> new initialized namelist with nml identifier string and init_val beeing a valid initialisation object for the parent """Class to handle Fortran Namelists. Namelist(string) -> new namelist with fortran namelist group name Namelist(string, init_val) -> new initialized namelist with namelist group name and init_val beeing a valid initialisation object for the parent class (either OrderedDict for Python >= 2.7 or else dict). A fortran readable string representation of the namelist can be generated via str() build-in function. A string representation of the Python object that can be used with eval or string. Template substitution can be obtained by repr() build-in function. """ @property def name(self): """ Read only property name, representing the fortran namelist identifier. """ """Namelist group name.""" return self._name def __init__(self, name, init_val=()): """x.__init__(...) initializes x; see help(type(x)) for signature""" """Create a `Namelist` instance. See help(type(x)) for signature. """ self._name = name super(self.__class__, self).__init__(init_val) def __str__(self): """x.__str__(self) -> Fortran readable string representation of the namelist. If a value v is a sequence, an 1D fortran array representation """Fortran readable string representation of the namelist. If a value v is a sequence, an 1D fortran array representation is created using iter(v). """ retstr = "&%s\n" % str(self.name) Loading Loading @@ -85,13 +89,11 @@ class Namelist(DictClass): else: rv = repr(v) retstr += "%s = %s\n" % (str(k), rv) retstr += "&end\n" retstr += "/\n" return retstr def __repr__(self): """x.__repr__(self) -> string that can be used by eval to create a copy of x. """ """Return a string that can be used by eval to create a copy.""" retstr = "%s.%s(%s, (" % (MODULE_NAME, self.__class__.__name__, repr(self.name)) for k, v in self.items(): Loading @@ -100,17 +102,55 @@ class Namelist(DictClass): return retstr def has_name(self, name): """x.hasname(self, name) <==> name==x.name""" """Return `True` if `name` matches the namelist group name. Parameters ---------- name : str name to test against. Returns ------- bool : `True` if `name` matches the namelist group name. """ return name == self.name def parse_namelist_file(in_file): """ parse_namelist_file(fobj) -> list of Namelist instances. fobj can be any object that implements pythons file object API, i.e. that offers a read() method. """Parse namelists from file object. Parameters ---------- in_file : :obj: Any object that implements pythons file object API, i.e. that offers a `read` and `seek` method. Returns ------- :obj:`List` of :obj:`Namelist` """ retlist = [] content = _tokenize(in_file.read()) namelist_string = in_file.read() in_file.seek(0, 0) return parse_namelist_string(namelist_string) def parse_namelist_string(in_string): """Parse namelists from string. Parameters ---------- in_string : str String containing one or more namelist definitions. Returns ------- :obj:`List` of :obj:`Namelist` """ retlist = [] content = _tokenize(in_string) for item in content: match = re.match(namelistname, item) if match: Loading @@ -123,9 +163,9 @@ def parse_namelist_file(in_file): pname = match.group(1) nml[pname] = [] continue for pattern in (namlistend, equalsign): match = re.match(pattern, item) if match: if re.match(namlistend, item): continue if re.match(equalsign, item): continue match = re.match(valueBool, item) if match: Loading @@ -140,26 +180,53 @@ def parse_namelist_file(in_file): except ValueError: pass else: continue continue # pragma: no cover try: nml[pname].append(float(item)) except ValueError: pass else: continue continue # pragma: no cover match = re.match(computation, item) if match: nml[pname].append(eval(item)) for nml in retlist: for k, v in nml.iteritems(): for k, v in nml.items(): if len(v) == 1: nml[k] = v[0] return retlist def _tokenize(text): fs = "$FS$" """Extract syntax tokens.""" fs = "$$$FS$$$" # remove comments text = re.sub(comment, '', text) for char, rep in zip(('\n', r',', ' ', '=', ), (fs, fs, fs, fs+'='+fs)): hashed_tokens = {} # replace quoted strings by hash text = _hash_token(text, quote, hashed_tokens, fs) # replace numerical computations by hash text = _hash_token(text, computation, hashed_tokens, fs) for char, rep in zip(('\n', r',', ' ', '=', '(/', '/)'), (fs, fs, fs, fs+'='+fs, fs, fs)): text = text.replace(char, rep) text = text.split(fs) return [token.strip() for token in text if token.strip() != ''] tokens = [token.strip() for token in text if token.strip() != ''] return [hashed_tokens[t] if t in hashed_tokens else t for t in tokens] def _hash_token(text, pattern, hashed_tokens, fs): while True: match = re.search(pattern, text) if not match: break matched_str = match.group(0) hashed = str(hash(matched_str)) hashed_tokens[hashed] = matched_str text = text.replace(matched_str, fs+hashed+fs, 1) return text
tests/context.py 0 → 100644 +14 −0 Original line number Diff line number Diff line """Context for tests.""" import os import sys sys.path.insert( 0, os.path.abspath( os.path.join(os.path.dirname(__file__), '..') ) ) import namelist import io