r3c / cottle

Fast, light & extensible template engine for C#

Home Page:https://r3c.github.io/cottle/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is there support for nested echo commands?

MichaelSimons opened this issue · comments

I want to do something like the following - {test-{who}}. Is this supported?

            var template = "Hello {test-{who}}, stay awhile and listen!";

            var documentResult = Document.CreateDefault(template);
            var document = documentResult.DocumentOrThrow;

            var context = Context.CreateBuiltin(new Dictionary<Value, Value>
            {
                ["who"] = "me",
                ["test-me"] = "my friend"
            });

            Console.Write(document.Render(context));

Hello @MichaelSimons, it doesn't exist in the form you suggested, but there may be an equivalent suitable for your need. Just for my understanding, could you please tell me what you would like this {test-{who}} syntax to provide compared to {test}-{who}? (I don't really see what the nesting would do here)

@r3c, Thanks for the quick response. My sample was rather contrived for simplicity. In my real world usage the values are dynamic based on the context in which the template is used in. {test-{who}} and {test}-{who} would not be equivalent. I want {test-{who}} to result in {who} being evaluated first and then the resulting command {test-me} to be evaluated resulting in "my friend".

Oh OK I get it now, you want to be able to do some indirection on variable names.

This won't work with top-level variables because Cottle detect referenced names at compile-time (when calling Document.Create*) for performance reason. However you could achieve something similar with Cottle "map" values where field subscripts can be dynamic (similar to what you would do in JavaScript):

var template = "Hello {vars[cat("test-", who)]}, stay awhile and listen!";
// ...
var context = Context.CreateBuiltin(new Dictionary<Value, Value>
{
    ["vars"] = new Dictionary<Value, Value>
    {
        ["test-me"] = "my-friend"
    },
    ["who"] = "me"
});

I don't know if that could be a possible solution to your use case?

That would work in my use case. A little more verbose than I was hoping but is acceptable. Thanks for the help!

You're welcome :)

Just a second thought about your last comment on verbosity: I agree the {vars[cat("test-", who)]} doesn't read nicely. Maybe injecting a dedicated function for this usage could help here? I'm thinking about something like this:

var template = "Hello {indirect(vars, \"test-\", who)}, stay awhile and listen!";

// Example: `indirect(vars, "test-", who)` to read subscript `cat("test-", who)` from `vars` fields
var indirect =
    Function.CreatePure(
        (state, arguments) =>
            arguments[0].Fields[string.Join(string.Empty, arguments.Skip(1).Select(v => v.AsString))], 2,
        int.MaxValue);

var context = Context.CreateBuiltin(new Dictionary<Value, Value>
{
    ["indirect"] = Value.FromFunction(indirect),
    ["vars"] = new Dictionary<Value, Value>
    {
        ["test-me"] = "my-friend"
    },
    ["who"] = "me"
});

Template code doesn't get any shorter but could be considered as more readable, especially if you plan to use this feature multiple times in your template. If you declare the indirect method in the template instead of C# code, you could even reference the global vars variable directly and therefore remove it from arguments.

Hope that helps!
Rémi

I had considered an approach similar to what your suggest. I also thought about a more specialized function specific to my scenario. In the end, I decided to use your original suggestion. I find it to be the most readable/simplest to understand. I'm also thinking it is pretty flexible.