import os from jwutils.stree.StringTree import * from jwutils.log import * def _cleanup_line(line: str) -> str: line = line.strip() r = '' in_quote = None for c in line: # slog(DEBUG, "in_quote = >" + str(in_quote) + "<") if in_quote is not None: if c == in_quote: in_quote = None else: if c in [ '"', "'" ]: in_quote = c elif in_quote is None and c == '#': return r.strip() r += c if len(r) >= 2 and r[0] in [ '"', "'" ] and r[-1] == r[0]: return r[1:-1] return r def parse(s: str, allow_full_lines: bool=True, root_content: str='root') -> StringTree: # export slog_m(DEBUG, "--->--- parsing --->---\n" + s + "\n---<--- parsing ---<---\n") root = StringTree('', content=root_content) sec = '' for line in s.splitlines(): slog(DEBUG, "line=", line) line = _cleanup_line(line) #slog(DEBUG, "cleaned line=", line) if not len(line): continue if line[0] == '[': if line[-1] == ']': sec = line[1:-1] elif line[-1] == '[': if len(sec): sec += '.' sec += line[1:-1] else: raise Exception("failed to parse section line", line) if root.get(sec) is None: root.add(sec) continue elif line[0] == ']': assert(len(sec) > 0) sec = '.'.join(sec.split('.')[0:-1]) continue lhs = '' rhs = None for c in line: if rhs is None: if c == '=': rhs = '' continue lhs += c continue rhs += c split = True if rhs is None: if not allow_full_lines: raise Exception("failed to parse assignment", line) rhs = 'empty' split = False root.add(sec + '.' + cleanup_string(lhs), cleanup_string(rhs), split=split) return root def _read_lines(path: str, throw=True, level=0, log_prio=INFO, paths_buf=None): try: with open(path, 'r') as infile: slog(log_prio, 'Reading {}"{}"'.format(' ' * level * 2, path)) if paths_buf is not None: paths_buf.append(path) ret = [] for line in infile: # lines are all trailed by \n m = re.search(r'^\s*(-)*include\s+(\S+)', line) if m: optional = m.group(1) == '-' include_path = m.group(2) if include_path[0] != '/': dir_name = os.path.dirname(path) if len(dir_name): include_path = dir_name + '/' + include_path include_lines = _read_lines(include_path, throw=(not optional), level=level+1, paths_buf=paths_buf) if include_lines is None: msg = f'{path}: Failed to process "{line}"' slog(DEBUG, line) continue ret.extend(include_lines) continue ret.append(line) return ret except Exception as e: msg = f'Failed to read file "{path}": {e}' if throw: raise Exception(msg) slog(DEBUG, msg) return None def read(path: str, root_content: str='root', log_prio=INFO, paths_buf=None) -> StringTree: # export lines = _read_lines(path, log_prio=log_prio, paths_buf=paths_buf) s = ''.join(lines) return parse(s, root_content=root_content)