Make control directives generic and add @ifSet
This commit is contained in:
parent
619cae3f45
commit
8c0957c6f1
@ -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'
|
||||
;
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user