2022-02-07 17:28:09 +01:00
|
|
|
from typing import List, Optional, Dict, Tuple, Union
|
|
|
|
import json
|
|
|
|
import yaml
|
2022-02-06 15:10:28 +01:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
2022-02-07 17:28:09 +01:00
|
|
|
def clean_dict(dictionary: Dict) -> Optional[Dict]:
|
|
|
|
aux = {
|
|
|
|
k: clean_dict(v) for k, v in dictionary.items() if type(v) == dict
|
|
|
|
} | {
|
|
|
|
k: v for k, v in dictionary.items() if type(v) != dict
|
|
|
|
}
|
|
|
|
aux2 = {
|
|
|
|
k: v for k, v in aux.items() if v is not None
|
|
|
|
}
|
|
|
|
return aux2 if aux2 != {} else None
|
|
|
|
|
|
|
|
|
|
|
|
def recursive_merge_dictionaries(dict1: Dict, dict2: Dict) -> Dict:
|
|
|
|
|
|
|
|
aux1 = {
|
|
|
|
k: v for k, v in dict1.items() if type(v) == dict
|
|
|
|
}
|
|
|
|
aux2 = {
|
|
|
|
k: v for k, v in dict2.items() if type(v) == dict
|
|
|
|
}
|
|
|
|
merged = {
|
|
|
|
k: recursive_merge_dictionaries(v, aux2[k]) for k, v in aux1.items() if k in aux2.keys()
|
|
|
|
}
|
|
|
|
return dict1 | dict2 | merged
|
|
|
|
|
|
|
|
|
2022-02-06 15:10:28 +01:00
|
|
|
class Config:
|
2022-02-06 21:26:55 +01:00
|
|
|
def merge_with(self, other, strict: bool = False):
|
|
|
|
"""
|
|
|
|
Merges the other config into this one
|
|
|
|
:param other:
|
|
|
|
:param strict: whether conflicting options are allowed or not
|
|
|
|
:return: self
|
|
|
|
"""
|
|
|
|
for var in vars(self):
|
|
|
|
if not getattr(self, var):
|
|
|
|
setattr(self, var, getattr(other, var))
|
|
|
|
else:
|
|
|
|
if strict and getattr(other, var) is not None and getattr(self, var) != getattr(other, var):
|
|
|
|
raise NotImplementedError
|
|
|
|
return self
|
2022-02-06 15:10:28 +01:00
|
|
|
|
2022-02-07 17:28:09 +01:00
|
|
|
@classmethod
|
|
|
|
def from_json(cls, content: Union[Path, Dict]):
|
|
|
|
if isinstance(content, Path):
|
|
|
|
with open(content, 'r') as config:
|
|
|
|
content: Dict = json.load(config)
|
|
|
|
config = cls()
|
|
|
|
config.set_from_json(content)
|
|
|
|
return config
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_yaml(cls, path: Path):
|
|
|
|
with open(path, 'r') as config:
|
|
|
|
path: Dict = yaml.safe_load(config)
|
|
|
|
return cls.from_json(path)
|
|
|
|
|
|
|
|
def set_from_json(self, content: Dict):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def to_json(self) -> Dict:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def dump_as_yaml(self, filename: Path, clean_none_entries: bool = True):
|
|
|
|
with filename.open('w') as file:
|
|
|
|
if clean_none_entries:
|
|
|
|
simple_dict = clean_dict(self.to_json())
|
|
|
|
else:
|
|
|
|
simple_dict = self.to_json()
|
|
|
|
if simple_dict is not None:
|
|
|
|
yaml.dump(simple_dict, file)
|
|
|
|
else:
|
|
|
|
pass # TODO
|
|
|
|
|
|
|
|
def dump_as_json(self, filename: Path, clean_none_entries: bool = True):
|
|
|
|
with open(filename, 'w') as config:
|
|
|
|
if clean_none_entries:
|
|
|
|
simple_dict = clean_dict(self.to_json())
|
|
|
|
else:
|
|
|
|
simple_dict = self.to_json()
|
|
|
|
if simple_dict is not None:
|
|
|
|
json.dump(simple_dict, config)
|
|
|
|
else:
|
|
|
|
pass # TODO
|
|
|
|
|
2022-02-06 15:10:28 +01:00
|
|
|
|
|
|
|
class FormatterIF:
|
|
|
|
"""
|
|
|
|
A formatter is bound to a specific input file with some
|
|
|
|
building configuration.
|
|
|
|
"""
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
input_file: Optional[Path] = None,
|
|
|
|
config: Optional[Config] = None,
|
|
|
|
):
|
|
|
|
self._input_file: Optional[Path] = input_file
|
|
|
|
self._config: Optional[Config] = config
|
|
|
|
|
2022-02-06 16:50:09 +01:00
|
|
|
def format(self, build_dir: Path, overwrite: bool = False) -> Optional[List[Tuple[str, Dict]]]:
|
2022-02-06 15:10:28 +01:00
|
|
|
"""
|
|
|
|
:param build_dir: Directory where output files are written to
|
2022-02-06 16:50:09 +01:00
|
|
|
:param overwrite: overwrite existing files
|
2022-02-06 15:10:28 +01:00
|
|
|
:return: When configuration files are needed for a future
|
|
|
|
build of the output files, a list of the file names and their
|
|
|
|
needed configurations. Else None.
|
|
|
|
"""
|
2022-02-06 16:50:09 +01:00
|
|
|
raise NotImplementedError
|
2022-02-06 15:10:28 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def output_files(self) -> List[str]:
|
|
|
|
"""
|
|
|
|
|
|
|
|
:return: List of files that will be built when the formatter is invoked
|
|
|
|
"""
|
2022-02-06 16:50:09 +01:00
|
|
|
raise NotImplementedError
|
2022-02-06 15:10:28 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def input_file(self) -> Path:
|
|
|
|
if self._input_file is None:
|
|
|
|
raise NotImplementedError
|
|
|
|
return self._input_file
|
|
|
|
|
|
|
|
@input_file.setter
|
|
|
|
def input_file(self, input_file: Path) -> None:
|
|
|
|
self._input_file = input_file
|
|
|
|
|
|
|
|
@property
|
|
|
|
def config(self) -> Config:
|
|
|
|
if self._config is None:
|
|
|
|
raise NotImplementedError
|
|
|
|
return self._config
|
|
|
|
|
|
|
|
@config.setter
|
|
|
|
def config(self, config: Config):
|
|
|
|
self._config = config
|