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()
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
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.