"""
Execute code with captured values.
"""
from typing import Dict
from typing import Iterator
from typing import List
from typing import Optional
from typing import Tuple
from .capture import Capture
from .capture import globs
from .value import Value
from .value import value_as_list
from .value import values_dict
__all__ = ["foreach", "expand"]
[docs]def foreach( # pylint: disable=too-many-branches
pattern: Optional[Value] = None, **kwargs: Value
) -> Iterator[Optional[Capture]]:
"""
Execute code with captured values.
The ``pattern`` should be a capture pattern (see :py:class:`ningen.capture.Capture` for details) or a list of such
patterns (which presumably capture the same set of named parts of the file name). The ``foreach`` code will loop
on the (sorted, unique, non-``None``) such patterns.
Additional named parameters (``kwargs``) are supported. If one or more of these has a list of values, then a capture
will be generated for every (sorted, unique, non-``None``) combination of the values for each of the captured paths,
or just once if no ``pattern`` was specified.
For example:
.. code-block:: python
for c in foreach("foo/{*name}.cc"):
assert c.path == f"foo/{name}.cc"
print(f"foo/{c.name}.cc exists on disk")
for c in foreach("foo/{*name}.cc", mode=["debug", "release"], compiler=["gcc", "clang"]):
print(f"compile foo/{c.name}.cc using the {c.compiler} compiler in {c.mode} mode")
for c in foreach(mode=["debug", "release"], compiler=["gcc", "clang"]):
print(f"final link step using the {c.compiler} compiler in {c.mode} mode")
"""
if pattern is None:
if not kwargs:
return
captured = [Capture()] # type: ignore
else:
patterns = sorted(set(value_as_list(pattern)))
if not patterns:
return
captured = []
for capture_pattern in patterns:
captured += globs(capture_pattern)
if not captured:
return
variables = values_dict(kwargs or {})
for capture in sorted(captured, key=lambda capture: capture.__dict__.get("path", "none")):
for name in variables:
if name in capture.__dict__:
raise ValueError(f"overriding the value of the captured: {name}")
yield from _foreach(capture.__dict__, list(variables.items()))
def _foreach(data: Dict[str, str], items: List[Tuple[str, Value]]) -> Iterator[Capture]:
if not items:
yield Capture(**data)
else:
name, values = items[0]
for value in sorted(set(value_as_list(values))):
data[name] = value
yield from _foreach(data, items[1:])
[docs]def expand(template: Value, **kwargs: Value) -> List[str]:
"""
Generate multiple formatted strings using all the combinations of the provided named values
(``kwargs``).
The ``template`` should be a normal Python format (using ``{name}``). If this is a list, then the result will
contain the strings generated from all the (sorted, unique, non-``None``) templates in the list.
Additional named parameters (``kwargs``) are expected. If one or more of these has a list of values, then a string
will be generated for every (sorted, unique, non-``None``) combination of the values
For example:
.. code-block:: python
assert expand('src/{name}.cc', name='foo') == ['src/foo.cc']
assert expand('src/{name}.cc', name=['foo', 'bar']) == ['src/foo.cc', 'src/bar.cc']
assert expand('obj/{mode}/{name}.o', name=['foo', 'bar'], mode=['debug', 'release']) \
== ['obj/debug/foo.o', 'obj/debug/bar.o', 'obj/release/foo.o', 'obj/release/bar.o']
"""
results: List[str] = []
templates = sorted(set(value_as_list(template)))
if templates and kwargs:
_collect(templates, {}, list(values_dict(kwargs).items()), results)
return results
def _collect(templates: List[str], data: Dict[str, str], items: List[Tuple[str, Value]], results: List[str]) -> None:
if not items:
for template in templates:
results.append(template.format(**data))
return
name, values = items[0]
for value in sorted(set(value_as_list(values))):
data[name] = value
_collect(templates, data, items[1:], results)