import re
import itertools
import glob
from . import filetree
[docs]def resolve(template, variables):
"""
Resolves the template given a set of variables
:param template: template
:param variables: mapping of variable names to values
:return: cleaned string
"""
filled = fill_known(template, variables)
filename = resolve_optionals(filled)
remaining = find_variables(filename)
if len(remaining) > 0:
raise filetree.MissingVariable('Variables %s not defined' % set(remaining))
return filename
[docs]def get_all(template, variables, glob_vars=()):
"""
Gets all variables matching the templates given the variables
:param template: template
:param variables: (incomplete) mapping of variable names to values
:param glob_vars: sequence of undefined variables that can take any possible values when looking for matches on the disk
If `glob_vars` contains any defined variables, it will be ignored.
:return: sequence of filenames
"""
filled = fill_known(template, variables)
remaining = set(find_variables(filled))
optional = optional_variables(filled)
res = set()
if glob_vars == 'all':
glob_vars = remaining
glob_vars = set(glob_vars).difference(variables.keys())
undefined_vars = remaining.difference(glob_vars).difference(optional)
if len(undefined_vars) > 0:
raise KeyError("Required variables {} were not defined".format(undefined_vars))
for keep in itertools.product(*[(True, False) for _ in optional.intersection(glob_vars)]):
sub_variables = {var: '*' for k, var in zip(keep, optional) if k}
for var in remaining.difference(optional).intersection(glob_vars):
sub_variables[var] = '*'
sub_filled = fill_known(filled, sub_variables)
pattern = resolve_optionals(sub_filled)
assert len(find_variables(pattern)) == 0
for filename in glob.glob(pattern):
try:
extract_variables(filled, filename)
except ValueError:
continue
res.add(filename)
return sorted(res)
[docs]def fill_known(template, variables):
"""
Fills in the known variables filling the other variables with {<variable_name>}
:param template: template
:param variables: mapping of variable names to values (ignoring any None)
:return: cleaned string
"""
prev = ''
while prev != template:
prev = template
settings = {}
for name in set(find_variables(template)):
if name in variables and variables[name] is not None:
settings[name] = variables[name]
else:
settings[name] = '{' + name + '}'
template = template.format(**settings)
return template
[docs]def resolve_optionals(text):
"""
Resolves the optional sections
:param text: template after filling in the known variables
:return: cleaned string
"""
def resolve_single_optional(part):
if len(part) == 0:
return part
if part[0] != '[' or part[-1] != ']':
return part
elif len(find_variables(part)) == 0:
return part[1:-1]
else:
return ''
res = [resolve_single_optional(text) for text in re.split(r'(\[.*?\])', text)]
return ''.join(res)
[docs]def find_variables(template):
"""
Finds all the variables in the template
:param template: full template
:return: sequence of variables
"""
return tuple(var.split(':')[0] for var in re.findall(r"\{(.*?)\}", template))
[docs]def optional_variables(template):
"""
Finds the variables that can be skipped
:param template: full template
:return: set of variables that are only present in optional parts of the string
"""
include = set()
exclude = set()
for text in re.split(r'(\[.*?\])', template):
if len(text) == 0:
continue
variables = find_variables(text)
if text[0] == '[' and text[-1] == ']':
include.update(variables)
else:
exclude.update(variables)
return include.difference(exclude)