6 from pathlib import Path
7 from abc import ABC, abstractmethod
9 class ControlAction(ABC):
10 def __init__(self, record) -> None:
11 self.__record = record
15 def create(field, value):
17 return RangeAction(value)
22 def _parse(self, record):
26 def action(self, data, param):
32 class RangeAction(ControlAction):
33 def __init__(self, record) -> None:
34 self.__ranged_component = re.compile(r"^(?P<index>[0-9]+)$|^(?P<start>[0-9]+)..(?P<end>[0-9]+)$")
35 super().__init__(record)
37 def _parse(self, record):
38 if not (record.startswith('[') and record.endswith(']')):
39 raise Exception(f"'{record}' is not valid range expression")
40 record = record.strip('[]')
43 for component in record.split(','):
44 component = component.strip()
45 mo = self.__ranged_component.match(component)
47 raise Exception(f"value '{component}' is not valid range component")
50 if mo["index"] is not None:
51 self.__value.append(int(mo['index']))
53 start = int(mo['start'])
55 self.__value += [x for x in range(start, end + 1)]
57 self.__value = sorted(self.__value)
59 def action(self, data, param):
60 return super().action(data, param)
66 class DataObject(ABC):
67 def __init__(self, name, record):
75 return DataObject.create("", record)
78 def create(key, record):
79 if not isinstance(record, dict):
83 t = name if "$type" not in record else record['$type']
86 name = record["$name"].strip()
88 if not key.startswith('@') and "$type" not in record:
89 return PlainOldObject(name, record)
93 return RangedObject(name, record)
95 return ForEachIndexObject(name, record)
96 elif t == "case_range_index":
97 return Condition(record)
99 return PlainOldObject(name, record)
101 return RawObject(name, record)
104 def _parse(self, record):
105 for k, v in record.items():
106 if k.startswith("$"):
107 self.ctrl_field[k.strip("$")] = ControlAction.create(k, v)
108 elif k.startswith("@"):
109 self.ctrl_field[k.strip("@")] = DataObject.create(k, v)
111 self.user_field[k] = DataObject.create(k, v)
114 def expand(self, param={}):
115 obj = { **self.user_field }
116 for f in self.ctrl_field.values():
117 if not isinstance(f, DataObject):
119 obj.update(**f.expand(param))
122 class Condition(DataObject):
123 def __init__(self, record):
124 super().__init__("", record)
126 def _parse(self, record):
127 super()._parse(record)
128 if "range" not in self.ctrl_field:
129 raise Exception("condition body must contains valid range case")
130 if "true" not in self.ctrl_field:
131 raise Exception("condition body must contains 'True' handling case")
133 self.__range_lst = self.ctrl_field["range"].get()
135 def expand(self, param={}):
136 if param["index"] in self.__range_lst:
137 return self.ctrl_field["true"].expand(param)
138 elif "else" in self.ctrl_field:
139 return self.ctrl_field["else"].expand(param)
142 class ForEachIndexObject(DataObject):
143 def __init__(self, name, record):
144 super().__init__(name, record)
146 for k, v in record.items():
147 self.conditions.append(DataObject.create(k, v))
149 def _parse(self, record):
150 super()._parse(record)
152 def expand(self, param={}):
153 if "index" not in param:
154 raise Exception(f"'{type(self).__name__}' require parameter 'index'.")
156 for cond in self.conditions:
157 obj.update(**cond.expand(param))
160 class RawObject(DataObject):
161 def __init__(self, name, record):
162 super().__init__(name, record)
164 def _parse(self, record):
165 return super()._parse(record)
167 def expand(self, param={}):
168 return super().expand(param)
170 class PlainOldObject(DataObject):
171 def __init__(self, name, record):
172 super().__init__(name, record)
174 def _parse(self, record):
175 return super()._parse(record)
177 def expand(self, param={}):
179 self.key: super().expand(param)
182 class RangedObject(DataObject):
183 def __init__(self, name, record):
184 super().__init__(name, record)
186 def _parse(self, record):
187 super()._parse(record)
189 def expand(self, param={}):
190 if "range" not in self.ctrl_field:
191 raise Exception("RangedObject with ranged type must have 'range' field defined")
194 indices = self.ctrl_field["range"].get()
197 self.user_field["index"] = i
198 out_lst.append(super().expand(param))
204 class TemplateExpander:
205 def __init__(self, template_path, project_path, arch) -> None:
207 self.tbase_path = template_path.joinpath(arch)
208 self.pbase_path = project_path
211 self.__load_mappings()
213 def __load_config(self):
215 cfg_file: Path = self.tbase_path.joinpath("config.json")
216 if not cfg_file.is_file():
217 raise Exception(f"config not found. ({cfg_file})")
219 obj = json.loads(cfg_file.read_text())
220 for k, v in obj.items():
221 o = DataObject.create(k, v).expand()
222 self.data.update(**o)
224 def __load_mappings(self):
226 mapping_file: Path = self.tbase_path.joinpath("mappings")
227 if not mapping_file.is_file():
228 raise Exception(f"config not found. ({mapping_file})")
230 with mapping_file.open() as f:
232 src, dest = l.split("->")
235 if src in self.mapping:
236 raise Exception(f"repeating entry ({src})")
238 self.mapping[src] = dest.strip()
241 for k, v in self.mapping.items():
242 src: Path = self.tbase_path.joinpath(k)
243 dest: Path = self.pbase_path.joinpath(v)
244 if not src.is_file():
247 template = jinja2.Template(src.read_text(), trim_blocks=True)
248 out = template.render(data=self.data)
252 print(f"rendered: {k} -> {v}")
256 parser = argparse.ArgumentParser()
257 parser.add_argument("--arch", default='i386')
258 parser.add_argument("-twd", "--template_dir", default=str(Path.cwd()))
259 parser.add_argument("-pwd", "--project_dir", default=str(Path.cwd()))
261 args = parser.parse_args()
263 expander = TemplateExpander(Path(args.template_dir), Path(args.project_dir), args.arch)
266 if __name__ == "__main__":