Make control directives generic and add @ifSet

This commit is contained in:
Olivier 'reivilibre' 2021-01-15 21:36:48 +00:00
parent 619cae3f45
commit 8c0957c6f1
2 changed files with 84 additions and 30 deletions

View File

@ -27,7 +27,8 @@ SubBlock[ws=' \t']:
Directive:
UserDirective | SousDirective | ForDirective | ImportDirective |
RecipeEdgeDirective | ResourceEdgeDirective | ListenEdgeDirective
RecipeEdgeDirective | ResourceEdgeDirective | ListenEdgeDirective |
IfSetDirective
;
UserDirective[ws=' \t']:
@ -53,6 +54,10 @@ ForDirective[ws=' \t']:
)
;
IfSetDirective[ws=' \t']:
'@ifSet' variable=DottedIdString /\n/+
;
ResourceEdgeDirectiveKind:
'@needs' | '@wants' | '@provides'
;

View File

@ -45,8 +45,13 @@ scoml_classes = scoml_grammar.namespaces["scoml"]
logger = logging.getLogger(__name__)
class ControlDirective:
def iter_over(self, vars: Variables) -> Iterable[Variables]:
raise NotImplementedError("Abstract.")
@attr.s(auto_attribs=True)
class ForDirective:
class ForDirective(ControlDirective):
"""
For loop_variable in collection
"""
@ -57,6 +62,41 @@ class ForDirective:
# List of literals or str for a variable (by name)
collection: Union[str, List[Any]]
def iter_over(self, vars: Variables):
to_iter = self.collection
if isinstance(to_iter, str):
to_iter = vars.get_dotted(to_iter)
if not isinstance(to_iter, list):
raise ValueError(f"to_iter = {to_iter!r} not a list")
for item in to_iter:
new_vars = Variables(vars)
new_vars.set_dotted(self.loop_variable, item)
yield new_vars
@attr.s(auto_attribs=True)
class IfDirective(ControlDirective):
def condition_true(self, vars: Variables) -> bool:
return False
def iter_over(self, vars: Variables) -> Iterable[Variables]:
if self.condition_true(vars):
yield vars
else:
yield from ()
@attr.s(auto_attribs=True)
class IfSetDirective(IfDirective):
# Name of the variable to check for existence.
check_variable: str
def condition_true(self, vars: Variables) -> bool:
print(f"isset? {self.check_variable} {vars.has_dotted(self.check_variable)}")
return vars.has_dotted(self.check_variable)
@attr.s(auto_attribs=True)
class RecipeEdgeDirective:
@ -94,7 +134,7 @@ class MenuBlock:
user_directive: Optional[str] = None
sous_directive: Optional[str] = None
for_directives: List[ForDirective] = attr.ib(factory=list)
control_directives: List[ForDirective] = attr.ib(factory=list)
import_directives: List[str] = attr.ib(factory=list)
recipe_edges: List[RecipeEdgeDirective] = attr.ib(factory=list)
resource_edges: List[ResourceEdgeDirective] = attr.ib(factory=list)
@ -114,7 +154,7 @@ class MenuRecipe:
user_directive: Optional[str] = None
sous_directive: Optional[str] = None
for_directives: List[ForDirective] = attr.ib(factory=list)
control_directives: List[ForDirective] = attr.ib(factory=list)
recipe_edges: List[RecipeEdgeDirective] = attr.ib(factory=list)
resource_edges: List[ResourceEdgeDirective] = attr.ib(factory=list)
listen_edges: List[ListenEdgeDirective] = attr.ib(factory=list)
@ -183,6 +223,18 @@ def convert_textx_recipe(txrecipe_or_subblock, parent: Optional[MenuBlock]):
or convert_textx_resource(directive.resource),
)
)
elif isinstance(directive, scoml_classes["ForDirective"]):
for_list = directive.collection or convert_textx_value(directive.list)
assert isinstance(for_list, list) or isinstance(for_list, str)
recipe.control_directives.append(
ForDirective(directive.loop_variable, for_list)
)
elif isinstance(directive, scoml_classes["IfSetDirective"]):
var = directive.variable
assert isinstance(var, str)
recipe.control_directives.append(
IfSetDirective(var)
)
else:
raise ValueError(f"Unknown directive {directive}")
@ -224,8 +276,14 @@ def convert_textx_block(txblock, parent: Optional[MenuBlock]) -> MenuBlock:
elif isinstance(directive, scoml_classes["ForDirective"]):
for_list = directive.collection or convert_textx_value(directive.list)
assert isinstance(for_list, list) or isinstance(for_list, str)
block.for_directives.append(
ForDirective(directive.loop_variable, for_list,)
block.control_directives.append(
ForDirective(directive.loop_variable, for_list)
)
elif isinstance(directive, scoml_classes["IfSetDirective"]):
var = directive.variable
assert isinstance(var, str)
block.control_directives.append(
IfSetDirective(var)
)
elif isinstance(directive, scoml_classes["ImportDirective"]):
block.import_directives.append(directive.importee)
@ -342,7 +400,7 @@ class MenuLoader:
a: Union[MenuBlock, MenuRecipe] = referrer
strip = 0
while a != first_common_ancestor:
strip += len(a.for_directives)
strip += len(a.control_directives)
parent = a.parent
assert parent is not None
a = parent
@ -350,7 +408,7 @@ class MenuLoader:
a = menu_recipe
extra = 0
while a != first_common_ancestor:
extra += len(a.for_directives)
extra += len(a.control_directives)
parent = a.parent
assert parent is not None
a = parent
@ -394,7 +452,7 @@ class MenuLoader:
if recipe_class is None:
raise ValueError(f"No recipe class found for {recipe.kind!r}")
fors = fors + tuple(recipe.for_directives)
fors = fors + tuple(recipe.control_directives)
if recipe.user_directive:
applicable_user = recipe.user_directive
@ -411,7 +469,7 @@ class MenuLoader:
assert applicable_user is not None
sous_vars = self._head.variables[sous]
for context_vars, for_indices in self._for_apply(fors, sous_vars, tuple()):
for context_vars, for_indices in self._control_apply(fors, sous_vars, tuple()):
context = RecipeContext(
sous=sous,
user=applicable_user,
@ -439,7 +497,7 @@ class MenuLoader:
sous_mask: Optional[Set[str]],
applicable_user: Optional[str],
):
fors = fors + tuple(block.for_directives)
fors = fors + tuple(block.control_directives)
if block.user_directive:
applicable_user = block.user_directive
@ -483,7 +541,7 @@ class MenuLoader:
# TODO(feature): add edges
# add fors
fors = fors + tuple(recipe.for_directives)
fors = fors + tuple(recipe.control_directives)
if recipe.sous_directive:
applicable_souss = self._head.get_souss_for_hostspec(recipe.sous_directive)
@ -493,7 +551,7 @@ class MenuLoader:
for sous in applicable_souss:
sous_vars = self._head.variables[sous]
for _vars, for_indices in self._for_apply(fors, sous_vars, tuple()):
for _vars, for_indices in self._control_apply(fors, sous_vars, tuple()):
instance = self._recipes[recipe][(sous, for_indices)] # noqa
for recipe_edge in recipe.recipe_edges:
@ -573,7 +631,7 @@ class MenuLoader:
# TODO(feature): add edges
fors = fors + tuple(block.for_directives)
fors = fors + tuple(block.control_directives)
if block.sous_directive:
applicable_souss = self._head.get_souss_for_hostspec(block.sous_directive)
@ -604,27 +662,18 @@ class MenuLoader:
unit, tuple(), self._head.get_souss_for_hostspec("all"), sous_subset
)
def _for_apply(
self, fors: Tuple[ForDirective, ...], vars: "Variables", accum: Tuple[int, ...]
def _control_apply(
self, controls: Tuple[ControlDirective, ...], vars: "Variables", accum: Tuple[int, ...]
) -> Iterable[Tuple["Variables", Tuple[int, ...]]]:
if not fors:
if not controls:
yield vars, accum
return
head = fors[0]
tail = fors[1:]
head = controls[0]
tail = controls[1:]
to_iter = head.collection
if isinstance(to_iter, str):
to_iter = vars.get_dotted(to_iter)
if not isinstance(to_iter, list):
raise ValueError(f"to_iter = {to_iter!r} not a list")
for idx, item in enumerate(to_iter):
new_vars = Variables(vars)
new_vars.set_dotted(head.loop_variable, item)
yield from self._for_apply(tail, new_vars, accum + (idx,))
for idx, new_vars in enumerate(head.iter_over(vars)):
yield from self._control_apply(tail, new_vars, accum + (idx,))
def load_menus_in_dir(self) -> RecipeDag:
dag = RecipeDag()