jw-python/src/python/jw/util/stree/serdes.py

136 lines
4.4 KiB
Python
Raw Normal View History

import glob
import os
import re
from ..log import DEBUG, ERR, INFO, slog, slog_m
from .StringTree import StringTree, cleanup_string
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( # export
s: str,
allow_full_lines: bool = True,
root_content: str = 'root'
) -> StringTree:
slog_m(DEBUG, "--->--- parsing --->---\n" + s + "\n---<--- parsing ---<---\n")
root = StringTree('', content = root_content)
sec = ''
for line in s.splitlines():
slog(DEBUG, f'Parsing: "{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_from_one_path(
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:
slog(DEBUG, f'{path}: Failed to process "{line}"')
continue
ret.extend(include_lines)
continue
ret.append(line)
return ret
except Exception as e:
slog(ERR, f'Failed to read file "{path}": {str(e)}')
if throw or not isinstance(e, FileNotFoundError):
raise
return None
def _read_lines(path: str, throw = True, level = 0, log_prio = INFO, paths_buf = None):
paths = glob.glob(path)
ret = []
for p in paths:
rr = _read_lines_from_one_path(
p, throw = throw, level = level, log_prio = log_prio, paths_buf = paths_buf
)
if rr is None:
return None
ret.extend(rr)
return ret
def read( # export
path: str,
root_content: str = 'root',
log_prio = INFO,
paths_buf = None
) -> StringTree:
lines = _read_lines_from_one_path(path, log_prio = log_prio, paths_buf = paths_buf)
if lines is None:
raise Exception(f'Could not read ini file from "{path}"')
s = ''.join(lines)
return parse(s, root_content = root_content)