001package io.jstach.jstache;
002
003import java.lang.annotation.Documented;
004import java.lang.annotation.ElementType;
005import java.lang.annotation.Retention;
006import java.lang.annotation.RetentionPolicy;
007import java.lang.annotation.Target;
008
009/**
010 * Tag a method to be used as a mustache lambda section for custom logic.
011 * <p>
012 * Lambda sections look like regular mustache sections but execute code and is one of the
013 * only ways to add custom logic to mustache templates.
014 *
015 * Lambda sections look something like: <pre><code class="language-hbs">
016 * {{#context}}
017 * {{#lambda}}body{{/lambda}}
018 * {{/context}}
019 * </code></pre> Where in the above example a lambda is named "lambda" and optionally has
020 * access to the object called "context" and the raw body passed to the lambda is "body".
021 * A nonexhaustive example of the above lambda in Java assuming the context type is
022 * <code>SomeType</code> <em>might</em> look like:
023 *
024 * <pre><code class="language-java">
025 * &#64;JStacheLambda
026 * &#64;JStacheLambda.Raw
027 * public String lambda(SomeType context, &#64;JStacheLambda.Raw String body) {
028 *     return "Hello" + body "!";
029 * }
030 * </code> </pre>
031 *
032 * The lambda could also return a new model and use the section body as a template instead
033 * of raw content:
034 *
035 * <pre><code class="language-hbs">
036 * {{#context}}
037 * {{#lambda}}{{message}}{{/lambda}}
038 * {{/context}}
039 * </code> </pre>
040 *
041 *
042 * <pre><code class="language-java">
043 * record Model(String message){}
044 *
045 * &#64;JStacheLambda
046 * public Model lambda(SomeType context) {
047 *     return new Model("Hello " + context.name() + "!");
048 * }
049 * </code> </pre>
050 *
051 * <strong>TIP:</strong> A nice feature of mustache when using lambdas is leveraging
052 * dotted path support for reduced syntax noise:
053 *
054 * <pre><code class="language-hbs">
055 * {{#context.lambda}}{{message}}{{/context.lambda}}
056 * </code> </pre>
057 *
058 * Notice how this sort of resembles OOP method calls. We can even pass virtual keys like
059 * <code>-index</code>.
060 *
061 * <pre><code class="language-hbs">
062 * {{#someList}}
063 * {{.}} is {{#-index.isEven}}{{#.}}even{{/.}}{{^.}}odd{{/.}}{{/-index.isEven}}
064 * {{/someList}}
065 * </code> </pre>
066 *
067 * <pre><code class="language-java">
068 * &#64;JStacheLambda
069 * public boolean isEven(int index) {
070 *     return index % 2 == 0;
071 * }
072 * </code> </pre>
073 *
074 * Output of <code>someList = List.of("a", "b", "c")</code>: <pre>
075 * a is odd
076 * b is even
077 * c is odd
078 * </pre>
079 *
080 * If we want to duplicate or wrap the results of the lambda we can use lambda templates:
081 *
082 * <pre><code class="language-java">
083 * &#64;JStacheLambda(template="&lt;span class="{{>@section}}"&gt;{{>@section}}&lt;span&gt;")
084 * public boolean isEven(int index) {
085 *     return index % 2 == 0;
086 * }
087 * </code> </pre>
088 *
089 * Output of <code>someList = List.of("a", "b", "c")</code>: <pre>
090 * a is &lt;span class="odd"&gt;odd&lt;span&gt;
091 * b is &lt;span class="even"&gt;even&lt;span&gt;
092 * c is &lt;span class="odd"&gt;odd&lt;span&gt;
093 * </pre>
094 *
095 * <p>
096 * JStachio lambdas just like normal method calls do not have to be directly enclosed on
097 * the context objects but can be on implemented interfaces or inherited and thus models
098 * can be "mixed in" with interfaces to achieve sharing of lambdas. However there is
099 * currently no support for static methods to be used as lambdas.
100 * <p>
101 * JStachio lambdas work in basically two modes for <strong>parameters</strong>:
102 * <ol>
103 * <li><strong>Context aware:</strong> The default. The top of the stack is passed if an
104 * argument is present and is not annotated with {@link Raw}.
105 * <li><strong>Raw:</strong> If a string parameter is annoated with {@link Raw} it will be
106 * passed the contents of the lambda section call. <strong>Some caveats:</strong>
107 * <ul>
108 * <li><em>While this mode appears to be the default for the spec it is not for
109 * JStachio.</em></li>
110 * <li><em>The contents may not be valid mustache as the spec does not define that it has
111 * to be.</em></li>
112 * <li><em>If the lambda start tag is standalone the space and newline following the tag
113 * will not be passed to the lambda.</em></li>
114 * </ul>
115 * </ol>
116 * <p>
117 * Similarly JStachio works in two modes for <strong>return types</strong>:
118 * <ol>
119 * <li><strong>Model: </strong> The default. The returned model is pushed onto the context
120 * stack and the contents of the lambda section call are used as an inline template and
121 * rendered against it.
122 * <li><strong>Raw: </strong> If the return type is a {@link String} and the method is
123 * annotated with {@link Raw} the contents of the string are directly written
124 * <em>unescaped</em>.
125 * </ol>
126 * Regardless of parameter and return annotations the method must always be annotated with
127 * this annotation to be discovered.
128 * <p>
129 * Due to the static nature of JStachio, JStachio does not support returning
130 * <strong>truly</strong> dynamic templates which is the optional lambda spec default if a
131 * {@link String} is returned. That is you cannot construct a string as a template at
132 * runtime.
133 * <p>
134 * That being said the lambda can ostensibly return a template (and a model that the
135 * template uses) that then references the section body as a partial by using
136 * {@link #template()} and then referencing the section body with the partial named
137 * {@value #SECTION_PARTIAL_NAME}. This allows repeating or wrapping the passed in section
138 * body. In some other mustache implementations this accomplished with a render callback
139 * but because templates are compiled statically this is a powerful declaritive
140 * workaround.
141 * <p>
142 * For those that are coming from other Mustache implementations the JStachio's lambda
143 * model is very similar to the
144 * <a href="https://github.com/samskivert/jmustache">JMustache</a> model and does not have
145 * a direct analog to
146 * <a href="https://github.com/spullara/mustache.java">mustache.java</a> of returning
147 * {@code Function<String,String> } where the function will automatically be called.
148 *
149 * @see Raw
150 * @see JStacheInterfaces
151 * @author agentgt
152 *
153 */
154@Retention(RetentionPolicy.RUNTIME)
155@Target(ElementType.METHOD)
156@Documented
157public @interface JStacheLambda {
158
159        /**
160         * Name of the partial to render the section body of a lambda call.
161         * @see #template()
162         */
163        public static String SECTION_PARTIAL_NAME = "@section";
164
165        /**
166         * The logical name of the lambda. If blank the method name will be used.
167         * @return lambda name
168         */
169        String name() default "";
170
171        /**
172         * An inline template used for rendering the returned model that has access to the
173         * lambda section body as a partial. The section body contents can be accessed as a
174         * partial with the name <code>&#64;section</code>. <strong>This effectively allows
175         * you render the section body and wrap or repeat it.</strong> Below is an example:
176         *
177         * <pre><code class="language-hbs">
178         * {{! template call lambda }}
179         * {{#context}}
180         * {{#lambda}}Use the force {{name}}{{/lambda}} {{! "name" will come from the returned model }}
181         * {{/context}}
182         * </code> </pre>
183         *
184         * <pre><code class="language-java">
185         * record Model(String name){}
186         *
187         * public record LambdaModel(List&lt;Model&gt; list) {
188         * }
189         *
190         * &#64;JStacheLambda(template="""
191         *      {{#list}}
192         *         {{>@section}}
193         *      {{/list}}
194         *      """)
195         * public LambdaModel lambda(SomeType context) {
196         *     return new LambdaModel(List.of(new Model("Luke"), new Model("Leia"), new Model("Anakin")));
197         * }
198         * </code> </pre>
199         *
200         * Output:
201         *
202         * <pre>
203         * Use the force Luke
204         * Use the force Leia
205         * Use the force Anakin
206         * </pre>
207         * @return the inline template and if empty is ignored. By default it is empty and
208         * ignored.
209         */
210        String template() default "";
211
212        /**
213         * Tag a method return type of String or parameter of String to be used as a raw
214         * unprocessed string.
215         * @author agentgt
216         * @see JStacheLambda
217         */
218        @Retention(RetentionPolicy.RUNTIME)
219        @Target({ ElementType.PARAMETER, ElementType.METHOD })
220        @Documented
221        public @interface Raw {
222
223        }
224
225}