Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/fallthrough attributes #59

Merged
merged 2 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading