Skip to content

Commit

Permalink
Merge pull request #59 from EvoEsports/feature/fallthrough-attributes
Browse files Browse the repository at this point in the history
Feature/fallthrough attributes
  • Loading branch information
araszka authored Mar 6, 2024
2 parents db746f7 + 1be2b0b commit 148754a
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 52 deletions.
1 change: 0 additions & 1 deletion src/ManiaTemplates/Components/MtComponent.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Security;
using System.Xml;
using ManiaTemplates.Exceptions;
using ManiaTemplates.Lib;
Expand Down
17 changes: 0 additions & 17 deletions src/ManiaTemplates/ControlElements/MtContextAlias.cs

This file was deleted.

115 changes: 84 additions & 31 deletions src/ManiaTemplates/Lib/MtTransformer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.CodeDom;
using System.Dynamic;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using ManiaTemplates.Components;
using ManiaTemplates.ControlElements;
Expand Down Expand Up @@ -146,7 +147,8 @@ private string CreateTemplatePropertiesBlock(MtComponent mtComponent)
/// Process a ManiaTemplate node.
/// </summary>
private string ProcessNode(XmlNode node, MtComponentMap componentMap, MtDataContext oldContext,
MtComponent rootComponent, MtComponent parentComponent)
MtComponent rootComponent, MtComponent parentComponent,
Dictionary<string, string>? fallthroughAttributesMap = null)
{
Snippet snippet = [];

Expand All @@ -166,13 +168,13 @@ private string ProcessNode(XmlNode node, MtComponentMap componentMap, MtDataCont
currentContext = forEachCondition.Context;
_loopDepth++;
}

if (componentMap.TryGetValue(tag, out var importedComponent))
{
//Node is a component
var component = engine.GetComponent(importedComponent.TemplateKey);
var slotContents = GetSlotContentsGroupedBySlotName(childNode, component, componentMap, currentContext,
parentComponent, rootComponent);
parentComponent, rootComponent);

var oldLoopDepth = _loopDepth;
_loopDepth = 0;
Expand All @@ -181,16 +183,11 @@ private string ProcessNode(XmlNode node, MtComponentMap componentMap, MtDataCont
component,
parentComponent,
currentContext,
oldContext,
attributeList,
ProcessNode(
IXmlMethods.NodeFromString(component.TemplateContent),
componentMap.Overload(component.ImportedComponents),
oldContext,
rootComponent: rootComponent,
parentComponent: component
),
slotContents,
rootComponent: rootComponent
rootComponent,
componentMap
);
_loopDepth = oldLoopDepth;

Expand Down Expand Up @@ -219,6 +216,26 @@ private string ProcessNode(XmlNode node, MtComponentMap componentMap, MtDataCont
default:
{
var hasChildren = childNode.HasChildNodes;

if (fallthroughAttributesMap != null && node.ChildNodes.Count == 1)
{
foreach (var (originalAttributeName, aliasAttributeName) in fallthroughAttributesMap)
{
if (attributeList.ContainsKey(originalAttributeName))
{
//Overwrite existing attribute with fallthrough value
attributeList[originalAttributeName] =
maniaTemplateLanguage.InsertResult(aliasAttributeName);
}
else
{
//Resolve alias on node
attributeList.Add(originalAttributeName,
maniaTemplateLanguage.InsertResult(aliasAttributeName));
}
}
}

subSnippet.AppendLine(IXmlMethods.CreateOpeningTag(tag, attributeList, hasChildren,
curlyContentWrapper: maniaTemplateLanguage.InsertResult));

Expand Down Expand Up @@ -313,10 +330,11 @@ private string ProcessComponentNode(
MtComponent component,
MtComponent parentComponent,
MtDataContext currentContext,
MtDataContext oldContext,
MtComponentAttributes attributeList,
string componentBody,
IReadOnlyDictionary<string, string> slotContents,
MtComponent rootComponent
Dictionary<string, string> slotContents,
MtComponent rootComponent,
MtComponentMap componentMap
)
{
foreach (var slotName in component.Slots)
Expand All @@ -342,15 +360,9 @@ MtComponent rootComponent
});
}

var templateContentNode = IXmlMethods.NodeFromString(component.TemplateContent);
var renderMethodName = GetComponentRenderMethodName(component, currentContext);
var contextAliasMap = new MtContextAlias(currentContext);
if (!_renderMethods.ContainsKey(renderMethodName))
{
_renderMethods.Add(
renderMethodName,
CreateComponentRenderMethod(component, renderMethodName, componentBody, contextAliasMap)
);
}
var fallthroughAttributesAliasMap = new Dictionary<string, string>();

//Create render call
var renderComponentCall = new StringBuilder(renderMethodName + "(");
Expand All @@ -361,15 +373,34 @@ MtComponent rootComponent
//Attach attributes to render method call
foreach (var (attributeName, attributeValue) in attributeList)
{
bool isStringType;
string attributeNameAlias;

//Skip attributes that don't match component property name
if (!component.Properties.TryGetValue(attributeName, out var componentProperty)) continue;
if (component.Properties.TryGetValue(attributeName, out var componentProperty))
{
isStringType = componentProperty.IsStringType();
attributeNameAlias = attributeName;
}
else
{
if (templateContentNode.ChildNodes.Count != 1)
{
//Only add fallthrough attributes if the component template has only one root element
continue;
}

isStringType = true;
attributeNameAlias = GetFallthroughAttributeAlias(attributeName);
fallthroughAttributesAliasMap[attributeName] = attributeNameAlias;
}

var methodArgument = componentProperty.IsStringType()
var methodArgument = isStringType
? IStringMethods.WrapStringInQuotes(
ICurlyBraceMethods.ReplaceCurlyBraces(attributeValue, s => $"{{({s})}}"))
: ICurlyBraceMethods.ReplaceCurlyBraces(attributeValue, s => $"({s})");

componentRenderArguments.Add(CreateMethodCallArgument(attributeName, methodArgument));
componentRenderArguments.Add(CreateMethodCallArgument(attributeNameAlias, methodArgument));
}

renderComponentCall.Append(string.Join(", ", componentRenderArguments));
Expand Down Expand Up @@ -423,24 +454,38 @@ MtComponent rootComponent

_namespaces.AddRange(component.Namespaces);

if (!_renderMethods.ContainsKey(renderMethodName))
{
var componentBody = ProcessNode(
templateContentNode,
componentMap.Overload(component.ImportedComponents),
oldContext,
rootComponent: rootComponent,
parentComponent: component,
fallthroughAttributesMap: fallthroughAttributesAliasMap
);

_renderMethods.Add(
renderMethodName,
CreateComponentRenderMethod(component, renderMethodName, componentBody, fallthroughAttributesAliasMap)
);
}

return renderComponentCall.ToString();
}

/// <summary>
/// Creates the method which renders the contents of a component.
/// </summary>
private string CreateComponentRenderMethod(MtComponent component, string renderMethodName, string componentBody,
MtContextAlias contextAliasMap)
Dictionary<string, string> aliasMap)
{
//open method arguments
var arguments = new List<string>();
var body = new StringBuilder(componentBody);

//Add local variables to component render method call (loop index, fallthrough vars, ...)
// foreach (var (localVariableName, localVariableType) in contextAliasMap.Context)
// {
// arguments.Add($"{localVariableType} {contextAliasMap.Aliases[localVariableName]}");
// }
//Add fallthrough variables to component render method call
arguments.AddRange(aliasMap.Values.Select(aliasAttributeName => $"string {aliasAttributeName}"));

//add slot render methods
AppendSlotRenderArgumentsToList(component, arguments);
Expand Down Expand Up @@ -663,6 +708,14 @@ private static string GetComponentRenderMethodName(MtComponent component, MtData
return $"Render_Component_{component.Id()}{context}";
}

/// <summary>
/// Returns a valid variable name alias.
/// </summary>
private static string GetFallthroughAttributeAlias(string variableName)
{
return Regex.Replace(variableName, @"\W", "") + new Random().Next();
}

/// <summary>
/// Returns the method name that renders the scripts of a given component.
/// </summary>
Expand Down
17 changes: 17 additions & 0 deletions tests/ManiaTemplates.Tests/IntegrationTests/ManialinkEngineTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,21 @@ public async void Component_Properties_Should_Not_Collide_With_Loop_Variables()
var template = _maniaTemplateEngine.RenderAsync("LoopTest", new { }, assemblies).Result;
Assert.Equal(expected, template, ignoreLineEndingDifferences: true);
}

[Fact]
public async void Should_Pass_Unmapped_Node_Attributes_To_Component_Root_Element()
{
var wrapperComponent = await File.ReadAllTextAsync("IntegrationTests/templates/wrapper.mt");
var fallthroughAttributesTestComponent = await File.ReadAllTextAsync("IntegrationTests/templates/fallthrough-attributes.mt");
var multiChildComponent = await File.ReadAllTextAsync("IntegrationTests/templates/component-multiple-elements.mt");
var expected = await File.ReadAllTextAsync("IntegrationTests/expected/fallthrough-test.xml");
var assemblies = new[] { typeof(ManiaTemplateEngine).Assembly, typeof(ComplexDataType).Assembly };

_maniaTemplateEngine.AddTemplateFromString("Wrapper", wrapperComponent);
_maniaTemplateEngine.AddTemplateFromString("MultiChild", multiChildComponent);
_maniaTemplateEngine.AddTemplateFromString("FallthroughTest", fallthroughAttributesTestComponent);

var template = _maniaTemplateEngine.RenderAsync("FallthroughTest", new { }, assemblies).Result;
Assert.Equal(expected, template, ignoreLineEndingDifferences: true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<manialink version="3" id="MtFallthroughTest" name="EvoSC#-MtFallthroughTest">
<frame size="20 11" data-test="unit0" datatest="1">
</frame>
<frame size="20 11" data-test="unit1" datatest="2">
</frame>
<frame size="20 11" data-test="unit2" datatest="3">
</frame>
<frame />
<frame />
</manialink>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<component>
<template>
<frame>
</frame>
<frame>
</frame>
</template>
</component>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<component>
<using namespace="System.Linq" />
<import component="Wrapper" as="Wrapper" />
<import component="MultiChild" as="MultiChild" />

<template>
<Wrapper foreach="int i in Enumerable.Range(1, 3)"
data-test="unit{{ __index }}"
datatest="{{ i }}" />
<MultiChild data-test="nope" />
</template>
</component>
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
y="-{{ padding }}"
width="{{ width }}"
height="{{ height }}"

pos="{{ padding }} -{{ padding }}"
size="{{ width - padding * 2 }} {{ height - padding * 2 }}"
>
<label text="Next element should be TextInput" />
<slot />
Expand Down

0 comments on commit 148754a

Please sign in to comment.