import re import os from pathlib import Path from fnmatch import fnmatch from glob import glob import string class TemplateFormatter(string.Formatter): def format_field(self, value, format_spec): if isinstance(value, str): spec, sep = [*format_spec.split(":"), None][:2] if sep: value = value.replace("_", " ") value = value = re.sub(r"([a-z])([A-Z])", rf"\1{sep}\2", value) value = value.replace(" ", sep) if spec == "u": value = value.upper() elif spec == "l": value = value.lower() elif spec == "t": value = value.title() return super().format(value, format_spec) class Template: field_pattern = re.compile(r"{(\w+)\*{0,2}}") field_pattern_recursive = re.compile(r"{(\w+)\*{2}}") def __init__(self, template): # asset_data_path = Path(lib_path) / ASSETLIB_FILENAME self.raw = template self.formatter = TemplateFormatter() @property def glob_pattern(self): pattern = self.field_pattern_recursive.sub("**", self.raw) pattern = self.field_pattern.sub("*", pattern) return pattern @property def re_pattern(self): pattern = self.field_pattern_recursive.sub("([\\\w -_.\/]+)", self.raw) pattern = self.field_pattern.sub("([\\\w -_.]+)", pattern) pattern = pattern.replace("?", ".") pattern = pattern.replace("*", ".*") return re.compile(pattern) @property def fields(self): return self.field_pattern.findall(self.raw) # return [f or '0' for f in fields] def parse(self, path): path = Path(path).as_posix() res = self.re_pattern.findall(path) if not res: print("Could not parse {path} with {self.re_pattern}") return {} fields = self.fields if len(fields) == 1: field_values = res else: field_values = res[0] return {k: v for k, v in zip(fields, field_values)} def norm_data(self, data): norm_data = {} for k, v in data.items(): if isinstance(v, Path): v = v.as_posix() norm_data[k] = v return norm_data def format(self, data=None, **kargs): data = {**(data or {}), **kargs} try: # print('FORMAT', self.raw, data) path = self.formatter.format(self.raw, **self.norm_data(data)) except KeyError as e: print(f"Cannot format {self.raw} with {data}, field {e} is missing") return path = os.path.expandvars(path) return Path(path) def glob(self, directory, pattern=None): """If pattern is given it need to be absolute""" if pattern is None: pattern = Path(directory, self.glob_pattern).as_posix() for entry in os.scandir(directory): entry_path = Path(entry.path) if entry.is_file() and fnmatch(entry_path.as_posix(), pattern): yield entry_path elif entry.is_dir(): yield from self.glob(entry.path, pattern) def find(self, data, **kargs): pattern = self.format(data, **kargs) pattern_str = str(pattern) if "*" not in pattern_str and "?" not in pattern_str: return pattern paths = glob(pattern.as_posix()) if paths: return Path(paths[0]) # return pattern def __repr__(self): return f"Template({self.raw})"