1818import org .junit .Assert ;
1919import org .junit .Test ;
2020
21- import java .io .*;
22- import java .util .*;
21+ import java .io .File ;
22+ import java .io .IOException ;
23+ import java .io .OutputStreamWriter ;
24+ import java .io .Reader ;
25+ import java .io .StringReader ;
26+ import java .io .StringWriter ;
27+ import java .io .Writer ;
28+ import java .util .ArrayList ;
29+ import java .util .Arrays ;
30+ import java .util .Collections ;
31+ import java .util .HashMap ;
32+ import java .util .HashSet ;
33+ import java .util .Iterator ;
34+ import java .util .List ;
35+ import java .util .Locale ;
36+ import java .util .Map ;
37+ import java .util .Optional ;
38+ import java .util .Set ;
2339import java .util .concurrent .Callable ;
40+ import java .util .concurrent .ConcurrentHashMap ;
41+ import java .util .concurrent .ConcurrentMap ;
2442import java .util .concurrent .CountDownLatch ;
2543import java .util .concurrent .ExecutionException ;
2644import java .util .concurrent .Executors ;
@@ -80,7 +98,7 @@ public Reader getReader(String resourceName) {
8098 // First look with locale
8199 Reader reader = super .getReader (newResourceName );
82100
83- if (reader == null ) {
101+ if (reader == null ) {
84102 // Fallback to non-localized resourceName
85103 reader = super .getReader (resourceName );
86104 }
@@ -427,7 +445,7 @@ public void testOptional() throws IOException {
427445 StringReader template = new StringReader ("{{person}}{{#person}} is present{{/person}}{{^person}}Is not present{{/person}}" );
428446 Mustache m = c .compile (template , "test" );
429447 StringWriter sw = new StringWriter ();
430- m .execute (sw , new Object () {
448+ m .execute (sw , new Object () {
431449 Optional <String > person = Optional .of ("Test" );
432450 });
433451 assertEquals ("Test is present" , sw .toString ());
@@ -626,6 +644,60 @@ public void testSerialCallable() throws MustacheException, IOException {
626644 assertEquals (getContents (root , "complex.txt" ), sw .toString ());
627645 }
628646
647+ public void testDynamicPartial () throws MustacheException , IOException {
648+ // Implement >+ syntax that dynamically includes template files at runtime
649+ MustacheFactory c = new DefaultMustacheFactory (root ) {
650+ @ Override
651+ public MustacheVisitor createMustacheVisitor () {
652+ return new DefaultMustacheVisitor (this ) {
653+ @ Override
654+ public void partial (TemplateContext tc , String variable ) {
655+ if (variable .startsWith ("+" )) {
656+ // This is a dynamic partial rather than a static one
657+ TemplateContext partialTC = new TemplateContext ("{{" , "}}" , tc .file (), tc .line (), tc .startOfLine ());
658+ list .add (new PartialCode (partialTC , df , variable .substring (1 ).trim ()) {
659+ @ Override
660+ public synchronized void init () {
661+ filterText ();
662+ // Treat the actual text as a Mustache with single [ ] as the delimiter
663+ // so we can do interpoliation of things like [foo]/[bar].txt
664+ partial = df .compile (new StringReader (name ), "__dynpartial__" , "[" , "]" );
665+ if (partial == null ) {
666+ throw new MustacheException ("Failed to parse partial name template: " + name );
667+ }
668+ }
669+
670+ ConcurrentMap <String , Mustache > dynamicaPartialCache = new ConcurrentHashMap <>();
671+
672+ @ Override
673+ public Writer execute (Writer writer , List <Object > scopes ) {
674+ // Calculate the name of the dynamic partial
675+ StringWriter sw = new StringWriter ();
676+ partial .execute (sw , scopes );
677+ Mustache mustache = dynamicaPartialCache .computeIfAbsent (sw .toString (), df ::compilePartial );
678+ Writer execute = mustache .execute (writer , scopes );
679+ return appendText (execute );
680+ }
681+ });
682+ } else {
683+ super .partial (tc , variable );
684+ }
685+ }
686+ };
687+ }
688+ };
689+ Mustache m = c .compile (new StringReader ("{{>+ [foo].html}}" ), "test.html" );
690+ StringWriter sw = new StringWriter ();
691+ m .execute (sw , new HashMap <String , Object >() {{
692+ put ("name" , "Chris" );
693+ put ("value" , 10000 );
694+ put ("taxed_value" , 6000 );
695+ put ("in_ca" , true );
696+ put ("foo" , "simple" );
697+ }});
698+ assertEquals (getContents (root , "simple.txt" ), sw .toString ());
699+ }
700+
629701 /*
630702 @SuppressWarnings("serial")
631703 public void testCurrentElementInArray() throws IOException, MustacheException {
@@ -914,20 +986,20 @@ public MustacheVisitor createMustacheVisitor() {
914986 public void testPragmaWhiteSpaceHandling () throws IOException {
915987 DefaultMustacheFactory mf = new DefaultMustacheFactory () {
916988 @ Override
917- public MustacheVisitor createMustacheVisitor () {
989+ public MustacheVisitor createMustacheVisitor () {
918990 DefaultMustacheVisitor visitor = new DefaultMustacheVisitor (this );
919- // Add a pragma handler that simply renders a dash to visualize the output
920- visitor .addPragmaHandler ("pragma" , (tc , pragma , args ) -> new WriteCode (tc , this , "-" ));
991+ // Add a pragma handler that simply renders a dash to visualize the output
992+ visitor .addPragmaHandler ("pragma" , (tc , pragma , args ) -> new WriteCode (tc , this , "-" ));
921993 return visitor ;
922994 }
923- };
995+ };
924996
925- Mustache m = mf .compile (new StringReader (" {{% pragma}} {{% pragma}} " ), "testPragma" );
926- StringWriter sw = new StringWriter ();
927- m .execute (sw , "" ).close ();
997+ Mustache m = mf .compile (new StringReader (" {{% pragma}} {{% pragma}} " ), "testPragma" );
998+ StringWriter sw = new StringWriter ();
999+ m .execute (sw , "" ).close ();
9281000
929- // Pragma rendering should preserve template whitespace
930- assertEquals (" - - " , sw .toString ());
1001+ // Pragma rendering should preserve template whitespace
1002+ assertEquals (" - - " , sw .toString ());
9311003 }
9321004
9331005 public void testNotIterableCallable () throws IOException {
@@ -1185,6 +1257,7 @@ public void testTemplateFunctionWithData() {
11851257 public TemplateFunction parse () {
11861258 return s -> "blablabla {{anotherVar}}, blablabla {{yetAnotherVar}}" ;
11871259 }
1260+
11881261 String anotherVar = "banana" ;
11891262 String yetAnotherVar = "apple" ;
11901263 });
@@ -1201,6 +1274,7 @@ public MustacheVisitor createMustacheVisitor() {
12011274 public void iterable (final TemplateContext templateContext , String variable , Mustache mustache ) {
12021275 list .add (new IterableCode (templateContext , df , mustache , variable ) {
12031276 Binding binding = oh .createBinding ("params" , templateContext , this );
1277+
12041278 protected Writer handleFunction (Writer writer , Function function , List <Object > scopes ) {
12051279 boolean added = addScope (scopes , binding .get (scopes ));
12061280 try {
@@ -1220,7 +1294,9 @@ protected Writer handleFunction(Writer writer, Function function, List<Object> s
12201294 public TemplateFunction parse () {
12211295 return s -> "blablabla {{anotherVar}}, blablabla {{yetAnotherVar}}" ;
12221296 }
1297+
12231298 Map <String , Object > params = new HashMap <>();
1299+
12241300 {
12251301 params .put ("anotherVar" , "banana" );
12261302 params .put ("yetAnotherVar" , "apple" );
@@ -1239,7 +1315,7 @@ public void testPropertyWithDot() throws IOException {
12391315 mustache .execute (writer , new Object []{properties }).close ();
12401316 Assert .assertEquals ("value=some.value" , writer .toString ());
12411317 }
1242-
1318+
12431319 public void testLeavingAloneMissingVariables () throws IOException {
12441320 DefaultMustacheFactory dmf = new DefaultMustacheFactory (root ) {
12451321 @ Override
0 commit comments