def toResolvedSequence(self, resolver: "Resolver", varArgs: bool, onlyTypes: bool = False, onlyValues: bool = False) -> Sequence: """ Build the resolved sequence of those parameters. Must be called after mergeDefaults. """ sequence = SequenceBuilder() entries = self.itemsValuesOrTypes(resolver=resolver, varArgs=varArgs) if self.isNamed: entries = sorted(entries, key=lambda k: k[2].order) # Build the sequence from tools.bdl.entities.impl.fragment.type import Type for key, item, metadata in entries: if onlyTypes: Error.assertTrue(element=self.element, condition=isinstance(item, Type), message="Parameter '{}' is not a type.".format(key)) if onlyValues: Error.assertTrue(element=self.element, condition=not isinstance(item, Type), message="Parameter '{}' is not a value.".format(key)) if isinstance(item, str): element = ElementBuilder().setAttr(key="value", value=item) else: element = item.element.copy() if self.isNamed: ElementBuilder.cast(element, ElementBuilder).setAttr(key="key", value=key) sequence.pushBackElement(element) return sequence
def visitInclude(self, element: Element) -> ResultType: """ Handle include. """ includePathStr = self.readValue(element=element) Error.assertTrue( element=element, condition=isinstance(includePathStr, str), message="The include path must resolve into a string, instead: '{}'." .format(includePathStr)) path = pathlib.Path(includePathStr) paths = [(base / path) for base in self.includeDirs if (base / path).is_file()] Error.assertTrue(element=element, condition=len(paths) > 0, message="No valid file '{}' within {}".format( includePathStr, str([f.as_posix() for f in self.includeDirs]))) template = bzd.template.template.Template( template=paths[0].read_text(), includeDirs=self.includeDirs, indent=self.indent) result, substitutions = template._render( substitutions=self.substitutions) # Update the current substitution object self.substitutions.update(substitutions) return result
def processMacro(self, element: Element, *args: typing.Any) -> str: """ Process a macro already defined. """ sequence = element.getNestedSequence("nested") assert sequence # Build the argument list argList = [] argument = element.getNestedSequence("argument") assert argument is not None for arg in argument: Error.assertHasAttr(element=arg, attr="name") argList.append(arg.getAttr("name").value) # Sanity check Error.assertTrue( element=element, condition=len(argList) == len(args), message="Wrong number of argument(s), expected {}: {}".format( len(argList), ", ".join(argList))) for i, name in enumerate(argList): self.substitutions.register(element=element, key=name, value=args[i]) # Process the template result = self._visit(sequence=sequence) for name in argList: self.substitutions.unregister(name) return "".join(result)
def mergeDefaults(self, parameters: "Parameters") -> None: """ Merge default parameters with this. """ if not bool(parameters): return if bool(self): if self.isNamed != parameters.isNamed: Error.assertTrue(element=self.element, condition=bool(self.isNamed), message="Requires named parameters.") Error.assertTrue(element=self.element, condition=not bool(self.isNamed), message="Requires unnamed parameters.") # Merge the values order: int = 0 for name, expression, metadata in parameters.itemsMetadata(): if not self.contains(name): expression.assertTrue(condition=not expression.contracts.has("mandatory"), message="Missing mandatory parameter: '{}'.".format(name)) self.append(expression, Metadata(default=True)) # Merge the metadata self.atKeyMetadata(name).template = metadata.template self.atKeyMetadata(name).order = order order += 1
def resolve(self, resolver: "Resolver") -> "EntityType": """ Resolve the types and nested templates by updating their symbol to fqn. """ fqns = resolver.resolveFQN(name=self.kind).assertValue(element=self.element, attr=self.kindAttr) ElementBuilder.cast(self.element, ElementBuilder).updateAttr(self.kindAttr, ";".join(fqns)) # Resolve the templates if available self.templates.resolve(resolver=resolver) templates = self.templates # Get and save the underlying type underlying = self.getEntityResolved(resolver=resolver) # Resolve the entity, this is needed only if the entity is defined after the one holding this type. underlying.resolveMemoized(resolver=resolver) if self.underlyingTypeAttr is not None and underlying.underlyingType is not None: ElementBuilder.cast(self.element, ElementBuilder).setAttr(self.underlyingTypeAttr, underlying.underlyingType) # Validate template arguments configTypes = underlying.getConfigTemplateTypes(resolver=resolver) if not configTypes: Error.assertTrue(element=self.element, condition=(not bool(self.templates)), attr=self.kindAttr, message="Type '{}' does not support template type arguments.".format(self.kind)) else: templates.mergeDefaults(configTypes) # Validate the template arguments values = templates.getValuesOrTypesAsDict(resolver=resolver, varArgs=False) validation = underlying.makeValidationForTemplate(resolver=resolver, parameters=configTypes) assert validation, "Cannot be empty, already checked by the condition." resultValidate = validation.validate(values, output="return") Error.assertTrue(element=self.element, attr=self.kindAttr, condition=bool(resultValidate), message=str(resultValidate)) # Save the resolved template only after the validation is completed. sequence = templates.toResolvedSequence(resolver=resolver, varArgs=False, onlyTypes=True) ElementBuilder.cast(self.element, ElementBuilder).setNestedSequence("{}_resolved".format(self.templateAttr), sequence) # Resolve contract self.contracts.resolve(underlying.contracts) return underlying
def assertTrue(self, condition: bool, message: str, throw: bool = True) -> typing.Optional[str]: return Error.assertTrue(condition=condition, element=self.element, message=message, throw=throw)
def visitMacro(self, element: Element) -> None: """ Handle macro definition block. """ Error.assertHasSequence(element=element, sequence="argument") Error.assertHasSequence(element=element, sequence="nested") Error.assertHasAttr(element=element, attr="name") name = element.getAttr("name").value Error.assertTrue( element=element, attr="name", condition=(name not in self.substitutions), message= "Name conflict with macro and an already existing name: '{}'.". format(name)) # Register the macro self.substitutions.register( element=element, key=name, value=lambda *args: self.processMacro(element, *args))
def visitElement(self, element: Element, result: typing.List[str], nested: typing.Optional[typing.List[str]]) -> typing.List[str]: if element.isAttr("type"): entity = Type(element=element, kind="type", underlyingType="fqn_type", template="template_resolved" if self.isResolved else "template", argumentTemplate="argument_template_resolved" if self.isResolved else None, const="const") output = self.visitType(entity=entity, nested=[] if nested is None else nested, parameters=entity.parametersTemplateResolved) else: Error.assertHasAttr(element=element, attr="value") Error.assertTrue(element=element, condition=not nested, message="Value cannot have nested entities.") output = self.visitValue(value=element.getAttr("value").value, comment=element.getAttrValue("comment")) result.append(output) return result
def visitElement(self, element: Element, result: T) -> T: """ Main visitor, called each time a new element is discovered. """ entity = elementToEntity(element=element, extension=self.elementToEntityExtenstion) # Handle nested object if isinstance(entity, Nested): self.level += 1 for category in CATEGORIES: sequence = element.getNestedSequence(category) if sequence is not None: self.parents.append( Parent(entity=entity, category=category)) nestedResult = self._visit(sequence, result) self.entityToNested(category, entity, nestedResult, result) self.parents.pop() self.level -= 1 self.visitNestedEntities(entity, result) # Handle expression elif isinstance(entity, Expression): self.visitExpression(entity, result) # Handle method elif isinstance(entity, Method): self.visitMethod(entity, result) # Handle using elif isinstance(entity, Using): self.visitUsing(entity, result) # Handle using elif isinstance(entity, Enum): self.visitEnum(entity, result) # Handle namespace elif isinstance(entity, Namespace): Error.assertTrue( element=element, condition=(self.level == 0), message="Namespaces can only be declared at top level.") self.visitNamespace(entity, result) # Update the current namespace self.parents.append(Parent(entity=entity)) # Handle use elif isinstance(entity, Use): self.visitUse(entity, result) # Should never go here else: Error.handleFromElement(element=element, message="Unexpected entity: {}".format( type(entity))) return result
def resolve(self, resolver: "Resolver") -> None: """ Resolve entities. """ if self.isValue: return entity = self.type.resolve(resolver=resolver) # Set the underlying value if self.isParameters: # The type must represent a type (not a value) and have a valid FQN. self.assertTrue( condition=entity.isRoleType, message="Cannot instantiate a value from another value.") self.assertTrue(condition=self.isFQN, message="The value must have a FQN.") # TODO: it is possible that the expression does not have a FQN, in case the expression is resolved through another entity. self._setUnderlyingValue(entity=self, fqn=self.fqn) else: self._setUnderlyingValue(entity=entity) # Generate the argument list and resolve it if self.isParameters: parameters = self.parameters assert parameters is not None parameters.resolve(resolver=resolver) else: parameters = Parameters(element=self.element) # Merge its default values defaults = self.getConfigValues(resolver=resolver) parameters.mergeDefaults(defaults) # Read the validation for the value. it comes in part from the direct underlying type, contract information # directly associated with this expression do not apply to the current validation. validation = self._makeValueValidation(resolver=resolver, parameters=defaults, contracts=self.contracts) if validation is not None: arguments = parameters.getValuesOrTypesAsDict(resolver=resolver, varArgs=False) result = validation.validate(arguments, output="return") Error.assertTrue(element=self.element, attr="type", condition=bool(result), message=str(result)) # Save the resolved parameters (values and templates), only after the validation is completed. argumentValues = parameters.copy(template=False) sequence = argumentValues.toResolvedSequence(resolver=resolver, varArgs=False, onlyValues=True) ElementBuilder.cast(self.element, ElementBuilder).setNestedSequence( "argument_resolved", sequence) argumentTemplates = parameters.copy(template=True) sequence = argumentTemplates.toResolvedSequence(resolver=resolver, varArgs=False, onlyValues=True) ElementBuilder.cast(self.element, ElementBuilder).setNestedSequence( "argument_template_resolved", sequence)