sebastienros / fluid

Fluid is an open-source .NET template engine based on the Liquid template language.
MIT License
1.44k stars 178 forks source link

How to convert ArgumentsTag #535

Open SebastianStehle opened 1 year ago

SebastianStehle commented 1 year ago

Hi,

I am trying to convert the following arguments tag to v2:

https://github.com/Squidex/squidex/blob/master/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs#L27

I have had a look to Orchard (https://github.com/OrchardCMS/OrchardCore/blob/339d74b251b53393dd510f8ad3027aa17d78500d/src/OrchardCore/OrchardCore.DisplayManagement.Liquid/LiquidViewParser.cs) and after that I came up with something like:

parser.RegisterParserTag("reference", primary.And(primary),
            async (ValueTuple<Expression, Expression> arguments, TextWriter writer, TextEncoder encoder, TemplateContext context) =>
            {
                if (context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent)
                {
                    var contentId = await arguments.Item2.EvaluateAsync(context);
                    var content = await ResolveContentAsync(serviceProvider, enrichedEvent.AppId.Id, contentId);

                    if (content != null)
                    {
                        var name = (await arguments.Item1.EvaluateAsync(context)).ToStringValue();

                        context.SetValue(name, content);
                    }
                }

                return Fluid.Ast.Completion.Normal;
            });

The tag works like assign:

{% for id in event.data.references.iv %}
    {% reference 'ref' id %}
    Text: {{ ref.data.field1.iv }} {{ ref.data.field2.iv }} {{ ref.id }}
{% endfor %}

there is also a filter

{% for id in event.data.references.iv %}
    {% assign ref = id | reference %}
    Text: {{ ref.data.field1.iv }} {{ ref.data.field2.iv }} {{ ref.id }}
{% endfor %}

But in my tests I always get the following error from the parse method:

Object reference not set to an instance of an object.
SebastianStehle commented 1 year ago

Okay, got it. It is basically the same problem like this: https://github.com/sebastienros/fluid/issues/307#issuecomment-818001822

I derived from FluidParser and exposed the parsers I needed:

public sealed class CustomFluidParser : FluidParser
{
    public Deferred<Expression> PrimaryParser => Primary;

    public Parser<char> CommaParser => Comma;
}

I guess I could just have used ArgumentsList, but I decided to use this syntax:

parser.PrimaryParser.And(ZeroOrOne(parser.CommaParser)).And(parser.PrimaryParser);

It makes the comma optional and allows these statements:

{% asset 'ref' id %}
{% asset 'ref', id %}
sebastienros commented 1 year ago

You can even use AndSkip on the comma so it doesn't show up as a consumable token.

SebastianStehle commented 1 year ago

Good to know. I tried that first, but without the ZeroOrOne, so:

Primary.AndSkip(comma).And(Primary).

SebastianStehle commented 1 year ago

Can the parsers not be made public? It is "weird" that you can add custom tags from outside but not access the parsers that you need to register them.

sebastienros commented 1 year ago

I agree most of them should be public. But maybe through a custom property such that the public API is not drowning in these. A solution could be to introduce a class that will return the protected instances. This way it's not a breaking change, and there is a public property that exposes all of them.