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 * @JStacheLambda 026 * @JStacheLambda.Raw 027 * public String lambda(SomeType context, @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 * @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 * @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 * @JStacheLambda(template="<span class="{{>@section}}">{{>@section}}<span>") 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 <span class="odd">odd<span> 091 * b is <span class="even">even<span> 092 * c is <span class="odd">odd<span> 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>@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<Model> list) { 188 * } 189 * 190 * @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}