180dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beustpackage org.testng.mustache; 280dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 380dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beustimport org.testng.collections.Lists; 480dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 580dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beustimport java.io.IOException; 680dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beustimport java.util.List; 780dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beustimport java.util.Map; 880dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 980dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beustpublic class Mustache { 1080dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 1180dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust public String run(String template, Map<String, Object> m) throws IOException { 1280dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust return run(template, new Model(m)); 1380dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 1480dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 1580dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust String run(String template, Model model) throws IOException { 1680dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust int lineNumber = 0; 1780dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 1880dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust List<BaseChunk> chunks = Lists.newArrayList(); 19c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust int ti = 0; 20c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust while (ti < template.length()) { 2180dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust int start; 2280dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust int end; 2380dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust if (template.charAt(ti) == '\n') lineNumber++; 2480dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 2580dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust if (template.charAt(ti) == '{' && ti + 1 < template.length() 2680dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust && template.charAt(ti + 1) == '{') { 2780dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust int index = ti + 2; 2880dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust start = index; 2980dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust boolean foundEnd = false; 3080dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust while (index < template.length() && ! foundEnd) { 3180dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust index++; 3280dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust foundEnd = template.charAt(index) == '}' && index + 1 < template.length() 3380dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust && template.charAt(index + 1) == '}'; 3480dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 3580dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 3680dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust if (foundEnd) { 3780dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust end = index; 3880dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust String variable = template.substring(start, end); 3980dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust p("Found variable:" + variable); 4080dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust if (variable.startsWith("#")) { 41c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust // Complex variable {{#foo}}. 4280dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust String conditionalVariable = variable.substring(1); 43c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust Value value = model.resolveValue(conditionalVariable); 44c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust int endIndex = findClosingIndex(template, ti, conditionalVariable); 45c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust Object v = value.get(); 46c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust if (v == null) { 47c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust // Null condition, do nothing 48c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust } else if (v instanceof Iterable) { 49c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust // Iterable, call a sub Mustache to process this chunk in a loop 50c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust // after pushing a new submodel 51c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust Iterable it = (Iterable) v; 52c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust String subTemplate = template.substring(ti + variable.length() + 4, endIndex); 53c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust for (Object o : it) { 54c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust model.push(conditionalVariable, o); 55c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust String r = new Mustache().run(subTemplate, model); 56c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust model.popSubModel(); 57c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust chunks.add(new StringChunk(model, r)); 5880dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 5980dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } else { 60c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust // Scope, call a sub Mustache to process this chunk 61c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust // after pushing a new submodel 62c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust String subTemplate = template.substring(ti + variable.length() + 4, endIndex); 63c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust model.push(conditionalVariable, v); 64c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust String r = new Mustache().run(subTemplate, model); 65c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust model.popSubModel(); 66c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust chunks.add(new StringChunk(model, r)); 6780dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 68c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust ti = endIndex + variable.length() + 4; 69c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust } else { 70c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust // Regular variable {{foo}} 7180dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust chunks.add(new VariableChunk(model, variable)); 72c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust ti += variable.length() + 4; 7380dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 7480dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } else { 7580dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust throw new RuntimeException("Unclosed variable at line " + lineNumber); 7680dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 7780dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } else { 78c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust chunks.add(new StringChunk(model, "" + template.charAt(ti))); 79c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust ti++; 8080dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 81c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust } // while 82c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust 83c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust p("******************** Final composition, chunks:"); 8480dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust StringBuilder result = new StringBuilder(); 85c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust p("*** Template:" + template); 86c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust for (BaseChunk bc : chunks) { 87c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust p("*** " + bc); 88c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust } 89c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust 9080dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust for (BaseChunk bc : chunks) { 9180dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust String c = bc.compose(); 9280dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust result.append(c); 9380dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 94c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust p("*** Final result:" + result); 9580dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust return result.toString(); 9680dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 9780dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 98c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust private int findClosingIndex(String template, int ti, 99c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust String conditionalVariable) { 100c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust int result = template.lastIndexOf("{{/" + conditionalVariable); 101c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust return result; 102c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust } 103c8514c756ed3d88c7d1c1a4cfe0ad12f8d5f0e36Cédric Beust 10480dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust private void p(String string) { 10580dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust if (false) { 10680dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust System.out.println(string); 10780dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 10880dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 10980dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust 11080dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust public static void main(String[] args) throws IOException { 11180dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust } 11280dba8c2788e707df77d3ac63b08f5f85a139e8cCédric Beust} 113