Electroneum
mustache.h
Go to the documentation of this file.
1 #pragma once
2 #include <string>
3 #include <vector>
4 #include <fstream>
5 #include <iterator>
6 #include <functional>
7 #include "json.h"
8 namespace crow
9 {
10  namespace mustache
11  {
13 
14  template_t load(const std::string& filename);
15 
16  class invalid_template_exception : public std::exception
17  {
18  public:
19  invalid_template_exception(const std::string& msg)
20  : msg("crow::mustache error: " + msg)
21  {
22  }
23  virtual const char* what() const throw()
24  {
25  return msg.c_str();
26  }
27  std::string msg;
28  };
29 
30  enum class ActionType
31  {
32  Ignore,
33  Tag,
35  OpenBlock,
36  CloseBlock,
37  ElseBlock,
38  Partial,
39  };
40 
41  struct Action
42  {
43  int start;
44  int end;
45  int pos;
47  Action(ActionType t, int start, int end, int pos = 0)
48  : start(start), end(end), pos(pos), t(t)
49  {}
50  };
51 
52  class template_t
53  {
54  public:
55  template_t(std::string body)
56  : body_(std::move(body))
57  {
58  // {{ {{# {{/ {{^ {{! {{> {{=
59  parse();
60  }
61 
62  private:
63  std::string tag_name(const Action& action)
64  {
65  return body_.substr(action.start, action.end - action.start);
66  }
67  auto find_context(const std::string& name, const std::vector<context*>& stack)->std::pair<bool, context&>
68  {
69  if (name == ".")
70  {
71  return {true, *stack.back()};
72  }
73  int dotPosition = name.find(".");
74  if (dotPosition == (int)name.npos)
75  {
76  for(auto it = stack.rbegin(); it != stack.rend(); ++it)
77  {
78  if ((*it)->t() == json::type::Object)
79  {
80  if ((*it)->count(name))
81  return {true, (**it)[name]};
82  }
83  }
84  }
85  else
86  {
87  std::vector<int> dotPositions;
88  dotPositions.push_back(-1);
89  while(dotPosition != (int)name.npos)
90  {
91  dotPositions.push_back(dotPosition);
92  dotPosition = name.find(".", dotPosition+1);
93  }
94  dotPositions.push_back(name.size());
95  std::vector<std::string> names;
96  names.reserve(dotPositions.size()-1);
97  for(int i = 1; i < (int)dotPositions.size(); i ++)
98  names.emplace_back(name.substr(dotPositions[i-1]+1, dotPositions[i]-dotPositions[i-1]-1));
99 
100  for(auto it = stack.rbegin(); it != stack.rend(); ++it)
101  {
102  context* view = *it;
103  bool found = true;
104  for(auto jt = names.begin(); jt != names.end(); ++jt)
105  {
106  if (view->t() == json::type::Object &&
107  view->count(*jt))
108  {
109  view = &(*view)[*jt];
110  }
111  else
112  {
113  found = false;
114  break;
115  }
116  }
117  if (found)
118  return {true, *view};
119  }
120 
121  }
122 
123  static json::wvalue empty_str;
124  empty_str = "";
125  return {false, empty_str};
126  }
127 
128  void escape(const std::string& in, std::string& out)
129  {
130  out.reserve(out.size() + in.size());
131  for(auto it = in.begin(); it != in.end(); ++it)
132  {
133  switch(*it)
134  {
135  case '&': out += "&amp;"; break;
136  case '<': out += "&lt;"; break;
137  case '>': out += "&gt;"; break;
138  case '"': out += "&quot;"; break;
139  case '\'': out += "&#39;"; break;
140  case '/': out += "&#x2F;"; break;
141  default: out += *it; break;
142  }
143  }
144  }
145 
146  void render_internal(int actionBegin, int actionEnd, std::vector<context*>& stack, std::string& out, int indent)
147  {
148  int current = actionBegin;
149 
150  if (indent)
151  out.insert(out.size(), indent, ' ');
152 
153  while(current < actionEnd)
154  {
155  auto& fragment = fragments_[current];
156  auto& action = actions_[current];
157  render_fragment(fragment, indent, out);
158  switch(action.t)
159  {
160  case ActionType::Ignore:
161  // do nothing
162  break;
163  case ActionType::Partial:
164  {
165  std::string partial_name = tag_name(action);
166  auto partial_templ = load(partial_name);
167  int partial_indent = action.pos;
168  partial_templ.render_internal(0, partial_templ.fragments_.size()-1, stack, out, partial_indent?indent+partial_indent:0);
169  }
170  break;
172  case ActionType::Tag:
173  {
174  auto optional_ctx = find_context(tag_name(action), stack);
175  auto& ctx = optional_ctx.second;
176  switch(ctx.t())
177  {
178  case json::type::Number:
179  out += json::dump(ctx);
180  break;
181  case json::type::String:
182  if (action.t == ActionType::Tag)
183  escape(ctx.s, out);
184  else
185  out += ctx.s;
186  break;
187  default:
188  throw std::runtime_error("not implemented tag type" + boost::lexical_cast<std::string>((int)ctx.t()));
189  }
190  }
191  break;
193  {
194  static context nullContext;
195  auto optional_ctx = find_context(tag_name(action), stack);
196  if (!optional_ctx.first)
197  {
198  stack.emplace_back(&nullContext);
199  break;
200  }
201 
202  auto& ctx = optional_ctx.second;
203  switch(ctx.t())
204  {
205  case json::type::List:
206  if (ctx.l && !ctx.l->empty())
207  current = action.pos;
208  else
209  stack.emplace_back(&nullContext);
210  break;
211  case json::type::False:
212  case json::type::Null:
213  stack.emplace_back(&nullContext);
214  break;
215  default:
216  current = action.pos;
217  break;
218  }
219  break;
220  }
222  {
223  auto optional_ctx = find_context(tag_name(action), stack);
224  if (!optional_ctx.first)
225  {
226  current = action.pos;
227  break;
228  }
229 
230  auto& ctx = optional_ctx.second;
231  switch(ctx.t())
232  {
233  case json::type::List:
234  if (ctx.l)
235  for(auto it = ctx.l->begin(); it != ctx.l->end(); ++it)
236  {
237  stack.push_back(&*it);
238  render_internal(current+1, action.pos, stack, out, indent);
239  stack.pop_back();
240  }
241  current = action.pos;
242  break;
243  case json::type::Number:
244  case json::type::String:
245  case json::type::Object:
246  case json::type::True:
247  stack.push_back(&ctx);
248  break;
249  case json::type::False:
250  case json::type::Null:
251  current = action.pos;
252  break;
253  default:
254  throw std::runtime_error("{{#: not implemented context type: " + boost::lexical_cast<std::string>((int)ctx.t()));
255  break;
256  }
257  break;
258  }
260  stack.pop_back();
261  break;
262  default:
263  throw std::runtime_error("not implemented " + boost::lexical_cast<std::string>((int)action.t));
264  }
265  current++;
266  }
267  auto& fragment = fragments_[actionEnd];
268  render_fragment(fragment, indent, out);
269  }
270  void render_fragment(const std::pair<int, int> fragment, int indent, std::string& out)
271  {
272  if (indent)
273  {
274  for(int i = fragment.first; i < fragment.second; i ++)
275  {
276  out += body_[i];
277  if (body_[i] == '\n' && i+1 != (int)body_.size())
278  out.insert(out.size(), indent, ' ');
279  }
280  }
281  else
282  out.insert(out.size(), body_, fragment.first, fragment.second-fragment.first);
283  }
284  public:
285  std::string render()
286  {
287  context empty_ctx;
288  std::vector<context*> stack;
289  stack.emplace_back(&empty_ctx);
290 
291  std::string ret;
292  render_internal(0, fragments_.size()-1, stack, ret, 0);
293  return ret;
294  }
295  std::string render(context& ctx)
296  {
297  std::vector<context*> stack;
298  stack.emplace_back(&ctx);
299 
300  std::string ret;
301  render_internal(0, fragments_.size()-1, stack, ret, 0);
302  return ret;
303  }
304 
305  private:
306 
307  void parse()
308  {
309  std::string tag_open = "{{";
310  std::string tag_close = "}}";
311 
312  std::vector<int> blockPositions;
313 
314  size_t current = 0;
315  while(1)
316  {
317  size_t idx = body_.find(tag_open, current);
318  if (idx == body_.npos)
319  {
320  fragments_.emplace_back(current, body_.size());
321  actions_.emplace_back(ActionType::Ignore, 0, 0);
322  break;
323  }
324  fragments_.emplace_back(current, idx);
325 
326  idx += tag_open.size();
327  size_t endIdx = body_.find(tag_close, idx);
328  if (endIdx == idx)
329  {
330  throw invalid_template_exception("empty tag is not allowed");
331  }
332  if (endIdx == body_.npos)
333  {
334  // error, no matching tag
335  throw invalid_template_exception("not matched opening tag");
336  }
337  current = endIdx + tag_close.size();
338  switch(body_[idx])
339  {
340  case '#':
341  idx++;
342  while(body_[idx] == ' ') idx++;
343  while(body_[endIdx-1] == ' ') endIdx--;
344  blockPositions.emplace_back(actions_.size());
345  actions_.emplace_back(ActionType::OpenBlock, idx, endIdx);
346  break;
347  case '/':
348  idx++;
349  while(body_[idx] == ' ') idx++;
350  while(body_[endIdx-1] == ' ') endIdx--;
351  {
352  auto& matched = actions_[blockPositions.back()];
353  if (body_.compare(idx, endIdx-idx,
354  body_, matched.start, matched.end - matched.start) != 0)
355  {
356  throw invalid_template_exception("not matched {{# {{/ pair: " +
357  body_.substr(matched.start, matched.end - matched.start) + ", " +
358  body_.substr(idx, endIdx-idx));
359  }
360  matched.pos = actions_.size();
361  }
362  actions_.emplace_back(ActionType::CloseBlock, idx, endIdx, blockPositions.back());
363  blockPositions.pop_back();
364  break;
365  case '^':
366  idx++;
367  while(body_[idx] == ' ') idx++;
368  while(body_[endIdx-1] == ' ') endIdx--;
369  blockPositions.emplace_back(actions_.size());
370  actions_.emplace_back(ActionType::ElseBlock, idx, endIdx);
371  break;
372  case '!':
373  // do nothing action
374  actions_.emplace_back(ActionType::Ignore, idx+1, endIdx);
375  break;
376  case '>': // partial
377  idx++;
378  while(body_[idx] == ' ') idx++;
379  while(body_[endIdx-1] == ' ') endIdx--;
380  actions_.emplace_back(ActionType::Partial, idx, endIdx);
381  break;
382  case '{':
383  if (tag_open != "{{" || tag_close != "}}")
384  throw invalid_template_exception("cannot use triple mustache when delimiter changed");
385 
386  idx ++;
387  if (body_[endIdx+2] != '}')
388  {
389  throw invalid_template_exception("{{{: }}} not matched");
390  }
391  while(body_[idx] == ' ') idx++;
392  while(body_[endIdx-1] == ' ') endIdx--;
393  actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
394  current++;
395  break;
396  case '&':
397  idx ++;
398  while(body_[idx] == ' ') idx++;
399  while(body_[endIdx-1] == ' ') endIdx--;
400  actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
401  break;
402  case '=':
403  // tag itself is no-op
404  idx ++;
405  actions_.emplace_back(ActionType::Ignore, idx, endIdx);
406  endIdx --;
407  if (body_[endIdx] != '=')
408  throw invalid_template_exception("{{=: not matching = tag: "+body_.substr(idx, endIdx-idx));
409  endIdx --;
410  while(body_[idx] == ' ') idx++;
411  while(body_[endIdx] == ' ') endIdx--;
412  endIdx++;
413  {
414  bool succeeded = false;
415  for(size_t i = idx; i < endIdx; i++)
416  {
417  if (body_[i] == ' ')
418  {
419  tag_open = body_.substr(idx, i-idx);
420  while(body_[i] == ' ') i++;
421  tag_close = body_.substr(i, endIdx-i);
422  if (tag_open.empty())
423  throw invalid_template_exception("{{=: empty open tag");
424  if (tag_close.empty())
425  throw invalid_template_exception("{{=: empty close tag");
426 
427  if (tag_close.find(" ") != tag_close.npos)
428  throw invalid_template_exception("{{=: invalid open/close tag: "+tag_open+" " + tag_close);
429  succeeded = true;
430  break;
431  }
432  }
433  if (!succeeded)
434  throw invalid_template_exception("{{=: cannot find space between new open/close tags");
435  }
436  break;
437  default:
438  // normal tag case;
439  while(body_[idx] == ' ') idx++;
440  while(body_[endIdx-1] == ' ') endIdx--;
441  actions_.emplace_back(ActionType::Tag, idx, endIdx);
442  break;
443  }
444  }
445 
446  // removing standalones
447  for(int i = actions_.size()-2; i >= 0; i --)
448  {
450  continue;
451  auto& fragment_before = fragments_[i];
452  auto& fragment_after = fragments_[i+1];
453  bool is_last_action = i == (int)actions_.size()-2;
454  bool all_space_before = true;
455  int j, k;
456  for(j = fragment_before.second-1;j >= fragment_before.first;j--)
457  {
458  if (body_[j] != ' ')
459  {
460  all_space_before = false;
461  break;
462  }
463  }
464  if (all_space_before && i > 0)
465  continue;
466  if (!all_space_before && body_[j] != '\n')
467  continue;
468  bool all_space_after = true;
469  for(k = fragment_after.first; k < (int)body_.size() && k < fragment_after.second; k ++)
470  {
471  if (body_[k] != ' ')
472  {
473  all_space_after = false;
474  break;
475  }
476  }
477  if (all_space_after && !is_last_action)
478  continue;
479  if (!all_space_after &&
480  !(
481  body_[k] == '\n'
482  ||
483  (body_[k] == '\r' &&
484  k + 1 < (int)body_.size() &&
485  body_[k+1] == '\n')))
486  continue;
487  if (actions_[i].t == ActionType::Partial)
488  {
489  actions_[i].pos = fragment_before.second - j - 1;
490  }
491  fragment_before.second = j+1;
492  if (!all_space_after)
493  {
494  if (body_[k] == '\n')
495  k++;
496  else
497  k += 2;
498  fragment_after.first = k;
499  }
500  }
501  }
502 
503  std::vector<std::pair<int,int>> fragments_;
504  std::vector<Action> actions_;
505  std::string body_;
506  };
507 
508  inline template_t compile(const std::string& body)
509  {
510  return template_t(body);
511  }
512  namespace detail
513  {
514  inline std::string& get_template_base_directory_ref()
515  {
516  static std::string template_base_directory = "templates";
517  return template_base_directory;
518  }
519  }
520 
521  inline std::string default_loader(const std::string& filename)
522  {
523  std::string path = detail::get_template_base_directory_ref();
524  if (!(path.back() == '/' || path.back() == '\\'))
525  path += '/';
526  path += filename;
527  std::ifstream inf(path);
528  if (!inf)
529  return {};
530  return {std::istreambuf_iterator<char>(inf), std::istreambuf_iterator<char>()};
531  }
532 
533  namespace detail
534  {
535  inline std::function<std::string (std::string)>& get_loader_ref()
536  {
537  static std::function<std::string (std::string)> loader = default_loader;
538  return loader;
539  }
540  }
541 
542  inline void set_base(const std::string& path)
543  {
545  base = path;
546  if (base.back() != '\\' &&
547  base.back() != '/')
548  {
549  base += '/';
550  }
551  }
552 
553  inline void set_loader(std::function<std::string(std::string)> loader)
554  {
555  detail::get_loader_ref() = std::move(loader);
556  }
557 
558  inline template_t load(const std::string& filename)
559  {
560  return compile(detail::get_loader_ref()(filename));
561  }
562  }
563 }
Action(ActionType t, int start, int end, int pos=0)
Definition: mustache.h:47
Definition: mustache.h:41
std::vector< std::pair< int, int > > fragments_
Definition: mustache.h:503
type t() const
Definition: json.h:1090
int end
Definition: mustache.h:44
void set_loader(std::function< std::string(std::string)> loader)
Definition: mustache.h:553
std::string default_loader(const std::string &filename)
Definition: mustache.h:521
invalid_template_exception(const std::string &msg)
Definition: mustache.h:19
std::string dump(const wvalue &v)
Definition: json.h:1426
Definition: block_queue.cpp:41
Definition: mustache.h:52
std::string msg
Definition: mustache.h:27
void set_base(const std::string &path)
Definition: mustache.h:542
template_t load(const std::string &filename)
Definition: mustache.h:558
std::function< std::string(std::string)> & get_loader_ref()
Definition: mustache.h:535
declaration and default definition for the functions used the API
std::vector< Action > actions_
Definition: mustache.h:504
Definition: json.h:1086
int pos
Definition: mustache.h:45
std::string & get_template_base_directory_ref()
Definition: mustache.h:514
std::string render(context &ctx)
Definition: mustache.h:295
std::string body_
Definition: mustache.h:505
void render_fragment(const std::pair< int, int > fragment, int indent, std::string &out)
Definition: mustache.h:270
void parse()
Definition: mustache.h:307
std::string tag_name(const Action &action)
Definition: mustache.h:63
void render_internal(int actionBegin, int actionEnd, std::vector< context *> &stack, std::string &out, int indent)
Definition: mustache.h:146
template_t(std::string body)
Definition: mustache.h:55
Definition: base.py:1
std::string render()
Definition: mustache.h:285
int start
Definition: mustache.h:43
virtual const char * what() const
Definition: mustache.h:23
ActionType
Definition: mustache.h:30
ActionType t
Definition: mustache.h:46
template_t compile(const std::string &body)
Definition: mustache.h:508
int bool
Definition: stdbool.h:36
Definition: ci_map.h:7
const char * name
Definition: simplewallet.cpp:180
void escape(const std::string &in, std::string &out)
Definition: mustache.h:128
auto find_context(const std::string &name, const std::vector< context *> &stack) -> std::pair< bool, context &>
Definition: mustache.h:67
int count(const std::string &str)
Definition: json.h:1295