fennol.utils.input_parser

  1import re
  2from typing import Dict, Any
  3from .atomic_units import AtomicUnits as au
  4
  5_separators = " |,|=|\t|\n"
  6_comment_chars = ["#", "!"]
  7_true_repr = ["true", "yes", ".true."]
  8_false_repr = ["false", "no", ".false."]
  9
 10
 11class InputFile(dict):
 12    case_insensitive = True
 13
 14    def __init__(self, *args, **kwargs):
 15        super(InputFile, self).__init__(*args, **kwargs)
 16        if InputFile.case_insensitive:
 17            for key in list(self.keys()):
 18                dict.__setitem__(self, key.lower(), dict.get(self, key))
 19        for key in list(self.keys()):
 20            if isinstance(self[key], dict):
 21                dict.__setitem__(self, key, InputFile(**self[key]))
 22        
 23
 24    def get(self, path, default=None):
 25        if not isinstance(path, str):
 26            raise TypeError("Path must be a string")
 27        if InputFile.case_insensitive:
 28            path = path.lower()
 29        keys = path.split("/")
 30        val = None
 31        for key in keys:
 32            if isinstance(val, InputFile):
 33                val = val.get(key, default=None)
 34            else:
 35                val = dict.get(self, key, None)
 36
 37            if val is None:
 38                return default
 39
 40        return val
 41
 42    def store(self, path, value):
 43        if not isinstance(path, str):
 44            raise TypeError("Path must be a string")
 45        if isinstance(value, dict):
 46            value = InputFile(**value)
 47        if InputFile.case_insensitive:
 48            path = path.lower()
 49        keys = path.split("/")
 50        child = self.get(keys[0], default=None)
 51        if isinstance(child, InputFile):
 52            if len(keys) == 1:
 53                print("Warning: overriding a sub-dictionary!")
 54                dict.__setitem__(self, keys[0], value)
 55                # self[keys[0]] = value
 56                return 1
 57            else:
 58                child.store("/".join(keys[1:]), value)
 59        else:
 60            if len(keys) == 1:
 61                dict.__setitem__(self, keys[0], value)
 62                # self[keys[0]] = value
 63                return 0
 64            else:
 65                if child is None:
 66                    sub_dict = InputFile()
 67                    sub_dict.store("/".join(keys[1:]), value)
 68                    dict.__setitem__(self, keys[0], sub_dict)
 69                else:
 70                    print("Error: hit a leaf before the end of path!")
 71                    return -1
 72    
 73    def __getitem__(self, path):
 74        return self.get(path)
 75    
 76    def __setitem__(self, path, value):
 77        return self.store(path, value)
 78
 79    def print(self, tab=""):
 80        string = ""
 81        for p_id, p_info in self.items():
 82            string += tab + p_id
 83            val = self.get(p_id)
 84            if isinstance(val, InputFile):
 85                string += "{\n" + val.print(tab=tab + "  ") + "\n" + tab + "}\n\n"
 86            else:
 87                string += " = " + str(val) + "\n"
 88        return string[:-1]
 89
 90    def save(self, filename):
 91        with open(filename, "w") as f:
 92            f.write(self.print())
 93
 94    def __str__(self):
 95        return self.print()
 96
 97
 98def parse_input(input_file):
 99    # parse an input file and return a nested dictionary
100    # containing the categories, keys and values
101    f = open(input_file, "r")
102    struct = InputFile()
103    path = []
104    for line in f:
105        # remove all after comment character
106        for comment_char in _comment_chars:
107            index = line.find(comment_char)
108            if index >= 0:
109                line = line[:index]
110
111        # split line using defined separators
112        parsed_line = re.split(_separators, line.strip())
113
114        # remove empty strings
115        parsed_line = [x for x in parsed_line if x]
116        # skip blank lines
117        if not parsed_line:
118            continue
119        # print(parsed_line)
120
121        word0 = parsed_line[0].lower()
122        cat_fields = "".join(parsed_line)
123        # check if beginning of a category
124        if cat_fields.endswith("{"):
125            path.append(cat_fields[:-1])
126            continue
127        if cat_fields.startswith("&"):
128            path.append(cat_fields[1:])
129            continue
130        if cat_fields.endswith("{}"):
131            struct.store("/".join("path") + "/" + cat_fields[1:-2], InputFile())
132            continue
133
134        # print(current_category)
135        # if not path:
136        # 	print("Error: line not recognized!")
137        # 	return None
138        # else: #check if end of a category
139        if (cat_fields[0] in "}/") or ("&end" in cat_fields):
140            del path[-1]
141            continue
142
143        word0, unit = _get_unit_from_key(word0)
144        val = None
145        if len(parsed_line) == 1:
146            val = True  # keyword only => store True
147        elif len(parsed_line) == 2:
148            val = string_to_true_type(parsed_line[1], unit)
149        else:
150            # analyze parsed line
151            val = []
152            for word in parsed_line[1:]:
153                val.append(string_to_true_type(word, unit))
154        struct.store("/".join(path + [word0]), val)
155
156    f.close()
157    return struct
158
159
160def string_to_true_type(word, unit=None):
161    if unit is not None:
162        return float(word) / unit
163
164    try:
165        val = int(word)
166    except ValueError:
167        try:
168            val = float(word)
169        except ValueError:
170            if word.lower() in _true_repr:
171                val = True
172            elif word.lower() in _false_repr:
173                val = False
174            else:
175                val = word
176    return val
177
178
179def _get_unit_from_key(word):
180    unit_start = max(word.find("{"), word.find("["))
181    n = len(word)
182    if unit_start < 0:
183        key = word
184        unit = None
185    elif unit_start == 0:
186        print("Error: Field '" + str(word) + "' must not start with '{' or '[' !")
187        raise ValueError
188    else:
189        if word[unit_start] == "{":
190            end_bracket = "}"
191        else:
192            end_bracket = "]"
193        key = word[:unit_start]
194        if word[n - 1] != end_bracket:
195            print("Error: wrong unit specification in field '" + str(word) + "' !")
196            raise ValueError
197
198        if n - unit_start - 2 < 0:
199            unit = 1.0
200        else:
201            unit = au.get_multiplier(word[unit_start + 1 : -1])
202            # print(key+" unit= "+str(unit))
203    return key, unit
204
205
206def convert_dict_units(d: Dict[str, Any]) -> Dict[str, Any]:
207    """
208    Convert all values in a dictionary from specified units to atomic units.
209    """
210
211    d2 = {}
212    for k,v in d.items():
213        if isinstance(v, dict):
214            d2[k] = convert_dict_units(v)
215            continue
216        key, unit = _get_unit_from_key(k)
217        if unit is None:
218            d2[k] = v
219            continue
220        try:
221            if isinstance(v, list):
222                d2[key] = [x / unit for x in v]
223            elif isinstance(v, tuple):
224                d2[key] = tuple(x / unit for x in v)
225            else:
226                d2[key] = v / unit
227        except TypeError:
228            raise ValueError(f"Error: cannot convert value '{v}' of type to atomic units.")
229        except Exception as e:
230            print(f"Error: unexpected error in unit conversion for key '{k}': {e}")
231            raise e
232
233    return d2
class InputFile(builtins.dict):
12class InputFile(dict):
13    case_insensitive = True
14
15    def __init__(self, *args, **kwargs):
16        super(InputFile, self).__init__(*args, **kwargs)
17        if InputFile.case_insensitive:
18            for key in list(self.keys()):
19                dict.__setitem__(self, key.lower(), dict.get(self, key))
20        for key in list(self.keys()):
21            if isinstance(self[key], dict):
22                dict.__setitem__(self, key, InputFile(**self[key]))
23        
24
25    def get(self, path, default=None):
26        if not isinstance(path, str):
27            raise TypeError("Path must be a string")
28        if InputFile.case_insensitive:
29            path = path.lower()
30        keys = path.split("/")
31        val = None
32        for key in keys:
33            if isinstance(val, InputFile):
34                val = val.get(key, default=None)
35            else:
36                val = dict.get(self, key, None)
37
38            if val is None:
39                return default
40
41        return val
42
43    def store(self, path, value):
44        if not isinstance(path, str):
45            raise TypeError("Path must be a string")
46        if isinstance(value, dict):
47            value = InputFile(**value)
48        if InputFile.case_insensitive:
49            path = path.lower()
50        keys = path.split("/")
51        child = self.get(keys[0], default=None)
52        if isinstance(child, InputFile):
53            if len(keys) == 1:
54                print("Warning: overriding a sub-dictionary!")
55                dict.__setitem__(self, keys[0], value)
56                # self[keys[0]] = value
57                return 1
58            else:
59                child.store("/".join(keys[1:]), value)
60        else:
61            if len(keys) == 1:
62                dict.__setitem__(self, keys[0], value)
63                # self[keys[0]] = value
64                return 0
65            else:
66                if child is None:
67                    sub_dict = InputFile()
68                    sub_dict.store("/".join(keys[1:]), value)
69                    dict.__setitem__(self, keys[0], sub_dict)
70                else:
71                    print("Error: hit a leaf before the end of path!")
72                    return -1
73    
74    def __getitem__(self, path):
75        return self.get(path)
76    
77    def __setitem__(self, path, value):
78        return self.store(path, value)
79
80    def print(self, tab=""):
81        string = ""
82        for p_id, p_info in self.items():
83            string += tab + p_id
84            val = self.get(p_id)
85            if isinstance(val, InputFile):
86                string += "{\n" + val.print(tab=tab + "  ") + "\n" + tab + "}\n\n"
87            else:
88                string += " = " + str(val) + "\n"
89        return string[:-1]
90
91    def save(self, filename):
92        with open(filename, "w") as f:
93            f.write(self.print())
94
95    def __str__(self):
96        return self.print()
case_insensitive = True
def get(self, path, default=None):
25    def get(self, path, default=None):
26        if not isinstance(path, str):
27            raise TypeError("Path must be a string")
28        if InputFile.case_insensitive:
29            path = path.lower()
30        keys = path.split("/")
31        val = None
32        for key in keys:
33            if isinstance(val, InputFile):
34                val = val.get(key, default=None)
35            else:
36                val = dict.get(self, key, None)
37
38            if val is None:
39                return default
40
41        return val

Return the value for key if key is in the dictionary, else default.

def store(self, path, value):
43    def store(self, path, value):
44        if not isinstance(path, str):
45            raise TypeError("Path must be a string")
46        if isinstance(value, dict):
47            value = InputFile(**value)
48        if InputFile.case_insensitive:
49            path = path.lower()
50        keys = path.split("/")
51        child = self.get(keys[0], default=None)
52        if isinstance(child, InputFile):
53            if len(keys) == 1:
54                print("Warning: overriding a sub-dictionary!")
55                dict.__setitem__(self, keys[0], value)
56                # self[keys[0]] = value
57                return 1
58            else:
59                child.store("/".join(keys[1:]), value)
60        else:
61            if len(keys) == 1:
62                dict.__setitem__(self, keys[0], value)
63                # self[keys[0]] = value
64                return 0
65            else:
66                if child is None:
67                    sub_dict = InputFile()
68                    sub_dict.store("/".join(keys[1:]), value)
69                    dict.__setitem__(self, keys[0], sub_dict)
70                else:
71                    print("Error: hit a leaf before the end of path!")
72                    return -1
def print(self, tab=''):
80    def print(self, tab=""):
81        string = ""
82        for p_id, p_info in self.items():
83            string += tab + p_id
84            val = self.get(p_id)
85            if isinstance(val, InputFile):
86                string += "{\n" + val.print(tab=tab + "  ") + "\n" + tab + "}\n\n"
87            else:
88                string += " = " + str(val) + "\n"
89        return string[:-1]
def save(self, filename):
91    def save(self, filename):
92        with open(filename, "w") as f:
93            f.write(self.print())
def parse_input(input_file):
 99def parse_input(input_file):
100    # parse an input file and return a nested dictionary
101    # containing the categories, keys and values
102    f = open(input_file, "r")
103    struct = InputFile()
104    path = []
105    for line in f:
106        # remove all after comment character
107        for comment_char in _comment_chars:
108            index = line.find(comment_char)
109            if index >= 0:
110                line = line[:index]
111
112        # split line using defined separators
113        parsed_line = re.split(_separators, line.strip())
114
115        # remove empty strings
116        parsed_line = [x for x in parsed_line if x]
117        # skip blank lines
118        if not parsed_line:
119            continue
120        # print(parsed_line)
121
122        word0 = parsed_line[0].lower()
123        cat_fields = "".join(parsed_line)
124        # check if beginning of a category
125        if cat_fields.endswith("{"):
126            path.append(cat_fields[:-1])
127            continue
128        if cat_fields.startswith("&"):
129            path.append(cat_fields[1:])
130            continue
131        if cat_fields.endswith("{}"):
132            struct.store("/".join("path") + "/" + cat_fields[1:-2], InputFile())
133            continue
134
135        # print(current_category)
136        # if not path:
137        # 	print("Error: line not recognized!")
138        # 	return None
139        # else: #check if end of a category
140        if (cat_fields[0] in "}/") or ("&end" in cat_fields):
141            del path[-1]
142            continue
143
144        word0, unit = _get_unit_from_key(word0)
145        val = None
146        if len(parsed_line) == 1:
147            val = True  # keyword only => store True
148        elif len(parsed_line) == 2:
149            val = string_to_true_type(parsed_line[1], unit)
150        else:
151            # analyze parsed line
152            val = []
153            for word in parsed_line[1:]:
154                val.append(string_to_true_type(word, unit))
155        struct.store("/".join(path + [word0]), val)
156
157    f.close()
158    return struct
def string_to_true_type(word, unit=None):
161def string_to_true_type(word, unit=None):
162    if unit is not None:
163        return float(word) / unit
164
165    try:
166        val = int(word)
167    except ValueError:
168        try:
169            val = float(word)
170        except ValueError:
171            if word.lower() in _true_repr:
172                val = True
173            elif word.lower() in _false_repr:
174                val = False
175            else:
176                val = word
177    return val
def convert_dict_units(d: Dict[str, Any]) -> Dict[str, Any]:
207def convert_dict_units(d: Dict[str, Any]) -> Dict[str, Any]:
208    """
209    Convert all values in a dictionary from specified units to atomic units.
210    """
211
212    d2 = {}
213    for k,v in d.items():
214        if isinstance(v, dict):
215            d2[k] = convert_dict_units(v)
216            continue
217        key, unit = _get_unit_from_key(k)
218        if unit is None:
219            d2[k] = v
220            continue
221        try:
222            if isinstance(v, list):
223                d2[key] = [x / unit for x in v]
224            elif isinstance(v, tuple):
225                d2[key] = tuple(x / unit for x in v)
226            else:
227                d2[key] = v / unit
228        except TypeError:
229            raise ValueError(f"Error: cannot convert value '{v}' of type to atomic units.")
230        except Exception as e:
231            print(f"Error: unexpected error in unit conversion for key '{k}': {e}")
232            raise e
233
234    return d2

Convert all values in a dictionary from specified units to atomic units.