2 from pathlib import Path
3 from typing import Any, List
5 def join_path(stem, path):
6 if os.path.isabs(path):
8 return os.path.join(stem, path)
23 def __init__(self, *args):
27 strs = [Schema.get_type_str(t) for t in self.union]
28 return f"{' | '.join(strs)}"
34 def __init__(self, el_type):
35 self.el_type = el_type
38 strs = Schema.get_type_str(self.el_type)
44 def __init__(self, type, **kwargs):
46 self.__fields = kwargs
48 def __match_list(self, actual, expect):
49 if len(actual) != len(expect):
52 for a, b in zip(actual, expect):
53 if not self.__match(a, b):
58 def __match_list_member(self, actual, expect):
59 if not isinstance(actual, List):
63 if not self.__match(a, expect.el_type):
68 def __match_union(self, actual, union):
70 if self.__match(actual, s):
74 def __match(self, val, scheme):
78 if isinstance(scheme, Schema):
79 return scheme.match(val)
81 if isinstance(scheme, Schema.List):
82 return self.__match_list_member(val, scheme)
84 if isinstance(scheme, list) and isinstance(val, list):
85 return self.__match_list(val, scheme)
87 if isinstance(scheme, Schema.Union):
88 return self.__match_union(val, scheme)
90 if not isinstance(scheme, type):
93 return isinstance(val, scheme)
95 def match(self, instance):
96 if not self.__match(instance, self.__type):
99 for field, t in self.__fields.items():
100 if not hasattr(instance, field):
103 field_val = getattr(instance, field)
105 if not self.__match(field_val, t):
110 def __eq__(self, value):
111 if isinstance(value, Schema):
112 return super().__eq__(value)
113 return self.match(value)
115 def __ne__(self, value):
116 return not self.__eq__(value)
120 if len(self.__fields) > 0:
122 f"{name} :: {Schema.get_type_str(t)}"
123 for name, t in self.__fields.items()])
124 fields = "{%s}"%(fields)
126 return f"{Schema.get_type_str(self.__type)} {fields}"
129 return self.__str__()
132 def get_type_str(maybe_type):
133 if isinstance(maybe_type, type):
134 return maybe_type.__name__
135 if isinstance(maybe_type, str):
136 return f'"{maybe_type}"'
137 return str(maybe_type)
140 def __init__(self, visitor):
141 self.__visitor = visitor
143 def warn(self, node, fmt, *args):
144 fname = self.__visitor.current_file()
146 coln = node.col_offset
148 print(SourceLogger.fmt_warning(fname, line, coln, fmt%args))
151 def fmt_message(fname, line, col, level, msg):
152 return "%s:%d:%d: %s: %s"%(fname, line, col, level, msg)
155 def fmt_warning(fname, line, col, msg):
156 return SourceLogger.fmt_message(fname, line, col, "warning", msg)
159 def fmt_info(fname, line, col, msg):
160 return SourceLogger.fmt_message(fname, line, col, "info", msg)
163 def fmt_error(fname, line, col, msg):
164 return SourceLogger.fmt_message(fname, line, col, "error", msg)
167 def log(level, node, token, msg):
168 fname = node._filename
170 col = token.col_offset
172 print( SourceLogger.fmt_message(fname, line, col, level, msg))
173 print(f" at... {ast.unparse(token)}")
176 def warn(node, token, msg):
177 return SourceLogger.log("warning", node, token, msg)
180 ConfigImport = Schema(ast.ImportFrom, level=1)
182 class EnterFileMarker:
183 def __init__(self, filename = None):
186 class LeaveFileMarker:
190 def __init__(self, root_file, cfg_name = "LConfig"):
191 self.__tree = ast.Module([])
192 self.__cfg_name = cfg_name
193 self.__rootfile = Path(root_file)
195 self.__load(self.__rootfile)
197 def __load(self, file: Path):
200 with file.open('r') as f:
201 nodes = ast.parse(f.read()).body
203 relfile = str(file.relative_to(self.__rootfile.parent))
204 marker = ConfigAST.EnterFileMarker(relfile)
205 self.__append_tree(marker)
208 if ConfigAST.ConfigImport != node:
209 self.__append_tree(node)
213 module = "" if not module else module
214 path = parent.joinpath(*module.split('.'))
215 for alia in node.names:
216 p = path / alia.name / self.__cfg_name
219 self.__append_tree(ConfigAST.LeaveFileMarker())
221 def __append_tree(self, node):
222 self.__tree.body.append(node)
224 def visit(self, visitor):
225 visitor.visit(self.__tree)
227 class ConfigASTVisitor:
231 def _visit_fndef(self, node : ast.FunctionDef):
232 self._visit_subtree(node)
234 def _visit_leaf(self, node):
237 def _visit_subtree(self, node):
241 def _visit_expr(self, node : ast.Expr):
244 def current_file(self):
245 if len(self.__markers) == 0:
247 return self.__markers[-1].name
249 def visit(self, node):
250 if isinstance(node, ConfigAST.EnterFileMarker):
251 self.__markers.append(node)
254 if isinstance(node, ConfigAST.LeaveFileMarker):
258 if isinstance(node, ast.FunctionDef):
259 self._visit_fndef(node)
261 elif isinstance(node, ast.Expr):
262 self._visit_expr(node)
264 elif hasattr(node, "body"):
265 self._visit_subtree(node)
268 self._visit_leaf(node)