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

View File

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