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