iodine 0.7.60 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +15 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -3
  5. data/CHANGELOG.md +132 -12
  6. data/Gemfile +5 -7
  7. data/LICENSE.txt +1 -1
  8. data/README.md +147 -671
  9. data/Rakefile +9 -16
  10. data/examples/chatroom.nru +9 -0
  11. data/examples/client +74 -0
  12. data/examples/config-hello.nru +9 -0
  13. data/examples/config-hello.ru +11 -0
  14. data/examples/config-hijack.ru +13 -0
  15. data/examples/config-shootout.ru +43 -0
  16. data/examples/config.nru +120 -0
  17. data/examples/config.rb +37 -0
  18. data/examples/config.ru +42 -53
  19. data/examples/fake +24 -0
  20. data/examples/hello_neorack +137 -0
  21. data/examples/routes.nru +29 -0
  22. data/examples/routes.ru +42 -0
  23. data/exe/iodine +218 -258
  24. data/ext/iodine/extconf.rb +27 -100
  25. data/ext/iodine/fio-stl.h +128336 -0
  26. data/ext/iodine/fio-stl.md +26396 -0
  27. data/ext/iodine/iodine.c +213 -1393
  28. data/ext/iodine/iodine.h +250 -55
  29. data/ext/iodine/iodine_arg_helper.h +329 -0
  30. data/ext/iodine/iodine_caller.h +234 -25
  31. data/ext/iodine/iodine_cli.h +434 -0
  32. data/ext/iodine/iodine_compression.h +312 -0
  33. data/ext/iodine/iodine_connection.h +3484 -34
  34. data/ext/iodine/iodine_core.h +363 -0
  35. data/ext/iodine/iodine_crypto.h +1441 -0
  36. data/ext/iodine/iodine_defer.h +225 -4
  37. data/ext/iodine/iodine_json.h +738 -4
  38. data/ext/iodine/iodine_listener.h +263 -0
  39. data/ext/iodine/iodine_log.h +297 -0
  40. data/ext/iodine/iodine_minimap.h +543 -0
  41. data/ext/iodine/iodine_mustache.h +461 -4
  42. data/ext/iodine/iodine_pubsub_eng.h +729 -0
  43. data/ext/iodine/iodine_pubsub_history.h +547 -0
  44. data/ext/iodine/iodine_pubsub_msg.h +228 -0
  45. data/ext/iodine/iodine_redis.h +322 -0
  46. data/ext/iodine/iodine_store.h +418 -16
  47. data/ext/iodine/iodine_threads.h +279 -0
  48. data/ext/iodine/iodine_tls.h +515 -6
  49. data/ext/iodine/iodine_utils.h +923 -0
  50. data/iodine.gemspec +37 -42
  51. data/lib/iodine/benchmark.rb +1690 -0
  52. data/lib/iodine/documentation.rb +3085 -0
  53. data/lib/iodine/version.rb +17 -1
  54. data/lib/iodine.rb +54 -255
  55. data/logo.svg +36 -0
  56. data/mustache.md +55 -0
  57. data/sig/iodine.rbs +4 -0
  58. metadata +58 -184
  59. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  60. data/.github/workflows/ruby.yml +0 -42
  61. data/.gitignore +0 -21
  62. data/LIMITS.md +0 -41
  63. data/SPEC-PubSub-Draft.md +0 -159
  64. data/SPEC-WebSocket-Draft.md +0 -239
  65. data/bin/console +0 -22
  66. data/bin/info.md +0 -353
  67. data/bin/mustache_bench.rb +0 -100
  68. data/bin/poc/Gemfile.lock +0 -23
  69. data/bin/poc/README.md +0 -37
  70. data/bin/poc/config.ru +0 -66
  71. data/bin/poc/gemfile +0 -1
  72. data/bin/poc/www/index.html +0 -57
  73. data/examples/async_task.ru +0 -92
  74. data/examples/bates/README.md +0 -3
  75. data/examples/bates/config.ru +0 -342
  76. data/examples/bates/david+bold.pdf +0 -0
  77. data/examples/bates/public/drop-pdf.png +0 -0
  78. data/examples/bates/public/index.html +0 -600
  79. data/examples/echo.ru +0 -59
  80. data/examples/etag.ru +0 -16
  81. data/examples/hello.ru +0 -29
  82. data/examples/pubsub_engine.ru +0 -81
  83. data/examples/rack3.ru +0 -12
  84. data/examples/redis.ru +0 -70
  85. data/examples/shootout.ru +0 -73
  86. data/examples/sub-protocols.ru +0 -90
  87. data/examples/tcp_client.rb +0 -66
  88. data/examples/x-sendfile.ru +0 -14
  89. data/ext/iodine/fio.c +0 -11995
  90. data/ext/iodine/fio.h +0 -6371
  91. data/ext/iodine/fio_cli.c +0 -431
  92. data/ext/iodine/fio_cli.h +0 -189
  93. data/ext/iodine/fio_json_parser.h +0 -688
  94. data/ext/iodine/fio_siphash.c +0 -157
  95. data/ext/iodine/fio_siphash.h +0 -37
  96. data/ext/iodine/fio_tls.h +0 -129
  97. data/ext/iodine/fio_tls_missing.c +0 -649
  98. data/ext/iodine/fio_tls_openssl.c +0 -1106
  99. data/ext/iodine/fio_tmpfile.h +0 -50
  100. data/ext/iodine/fiobj.h +0 -44
  101. data/ext/iodine/fiobj4fio.h +0 -21
  102. data/ext/iodine/fiobj_ary.c +0 -333
  103. data/ext/iodine/fiobj_ary.h +0 -139
  104. data/ext/iodine/fiobj_data.c +0 -1187
  105. data/ext/iodine/fiobj_data.h +0 -167
  106. data/ext/iodine/fiobj_hash.c +0 -409
  107. data/ext/iodine/fiobj_hash.h +0 -176
  108. data/ext/iodine/fiobj_json.c +0 -622
  109. data/ext/iodine/fiobj_json.h +0 -68
  110. data/ext/iodine/fiobj_mem.h +0 -71
  111. data/ext/iodine/fiobj_mustache.c +0 -317
  112. data/ext/iodine/fiobj_mustache.h +0 -62
  113. data/ext/iodine/fiobj_numbers.c +0 -344
  114. data/ext/iodine/fiobj_numbers.h +0 -127
  115. data/ext/iodine/fiobj_str.c +0 -433
  116. data/ext/iodine/fiobj_str.h +0 -172
  117. data/ext/iodine/fiobject.c +0 -620
  118. data/ext/iodine/fiobject.h +0 -655
  119. data/ext/iodine/hpack.h +0 -1923
  120. data/ext/iodine/http.c +0 -2743
  121. data/ext/iodine/http.h +0 -1002
  122. data/ext/iodine/http1.c +0 -910
  123. data/ext/iodine/http1.h +0 -29
  124. data/ext/iodine/http1_parser.h +0 -878
  125. data/ext/iodine/http_internal.c +0 -1279
  126. data/ext/iodine/http_internal.h +0 -237
  127. data/ext/iodine/http_mime_parser.h +0 -350
  128. data/ext/iodine/iodine_caller.c +0 -230
  129. data/ext/iodine/iodine_connection.c +0 -941
  130. data/ext/iodine/iodine_defer.c +0 -420
  131. data/ext/iodine/iodine_fiobj2rb.h +0 -120
  132. data/ext/iodine/iodine_helpers.c +0 -282
  133. data/ext/iodine/iodine_helpers.h +0 -12
  134. data/ext/iodine/iodine_http.c +0 -1181
  135. data/ext/iodine/iodine_http.h +0 -23
  136. data/ext/iodine/iodine_json.c +0 -302
  137. data/ext/iodine/iodine_mustache.c +0 -567
  138. data/ext/iodine/iodine_pubsub.c +0 -580
  139. data/ext/iodine/iodine_pubsub.h +0 -26
  140. data/ext/iodine/iodine_rack_io.c +0 -273
  141. data/ext/iodine/iodine_rack_io.h +0 -20
  142. data/ext/iodine/iodine_store.c +0 -148
  143. data/ext/iodine/iodine_tcp.c +0 -346
  144. data/ext/iodine/iodine_tcp.h +0 -13
  145. data/ext/iodine/iodine_tls.c +0 -261
  146. data/ext/iodine/mustache_parser.h +0 -1546
  147. data/ext/iodine/redis_engine.c +0 -957
  148. data/ext/iodine/redis_engine.h +0 -79
  149. data/ext/iodine/resp_parser.h +0 -317
  150. data/ext/iodine/websocket_parser.h +0 -506
  151. data/ext/iodine/websockets.c +0 -743
  152. data/ext/iodine/websockets.h +0 -185
  153. data/lib/iodine/connection.rb +0 -61
  154. data/lib/iodine/json.rb +0 -42
  155. data/lib/iodine/mustache.rb +0 -113
  156. data/lib/iodine/pubsub.rb +0 -55
  157. data/lib/iodine/rack_utils.rb +0 -43
  158. data/lib/iodine/tls.rb +0 -16
  159. data/lib/rack/handler/iodine.rb +0 -33
  160. data/logo.png +0 -0
@@ -1,6 +1,463 @@
1
- #ifndef H_IODINE_MUSTACHE_H
2
- #define H_IODINE_MUSTACHE_H
1
+ #ifndef H___IODINE_MUSTA___H
2
+ #define H___IODINE_MUSTA___H
3
+ #include "iodine.h"
3
4
 
4
- void iodine_init_mustache(void);
5
+ /* *****************************************************************************
6
+ Mustache Callbacks
7
+ ***************************************************************************** */
5
8
 
6
- #endif
9
+ static void *mus_get_var(void *ctx, fio_buf_info_s name) {
10
+ VALUE r = Qnil;
11
+ VALUE c = (VALUE)ctx;
12
+ ID to_hash;
13
+ fio_buf_info_s buf;
14
+ int64_t index;
15
+ if (TYPE(c) == RUBY_T_ARRAY)
16
+ goto is_an_array;
17
+ if (TYPE(c) != RUBY_T_HASH)
18
+ goto not_a_hash;
19
+ r = rb_hash_aref(c, rb_id2sym(rb_intern2(name.buf, name.len)));
20
+ if (FIO_LIKELY(r != Qnil))
21
+ goto found;
22
+ r = rb_hash_aref(c, rb_str_new_static(name.buf, name.len));
23
+ if (FIO_LIKELY(r != Qnil))
24
+ goto found;
25
+ buf = name;
26
+ index = fio_atol(&buf.buf);
27
+ if (buf.buf == name.buf + name.len) {
28
+ r = rb_hash_aref(c, LL2NUM(index));
29
+ }
30
+ if (r == Qnil)
31
+ r = (VALUE)NULL;
32
+ found:
33
+ STORE.hold(r);
34
+ return (void *)r;
35
+
36
+ not_a_hash:
37
+ if (iodine_is_minimap(c))
38
+ goto is_minimap;
39
+ to_hash = rb_intern2("to_hash", 7);
40
+ if (c && TYPE(c) == RUBY_T_OBJECT && rb_respond_to(c, to_hash))
41
+ return mus_get_var((void *)rb_funcallv(c, to_hash, 0, &c), name);
42
+ return NULL;
43
+
44
+ is_minimap:
45
+ r = iodine_minimap_get(c, rb_id2sym(rb_intern2(name.buf, name.len)));
46
+ if (FIO_LIKELY(r != Qnil))
47
+ goto found;
48
+ r = iodine_minimap_get(c, rb_str_new_static(name.buf, name.len));
49
+ if (FIO_LIKELY(r != Qnil))
50
+ goto found;
51
+ buf = name;
52
+ index = fio_atol(&buf.buf);
53
+ if (buf.buf == name.buf + name.len) {
54
+ r = iodine_minimap_get(c, LL2NUM(index));
55
+ }
56
+ if (r == Qnil)
57
+ r = (VALUE)NULL;
58
+ goto found;
59
+
60
+ is_an_array:
61
+ buf = name;
62
+ if (name.len == 6 && fio_buf2u32u(name.buf) == fio_buf2u32u("leng") &&
63
+ fio_buf2u16u(name.buf + 4) == fio_buf2u16u("th"))
64
+ return (void *)LL2NUM(rb_array_len(c));
65
+ index = fio_atol(&buf.buf);
66
+ if (buf.buf == name.buf + name.len) {
67
+ r = rb_ary_entry(c, (long)index);
68
+ if (FIO_LIKELY(r != Qnil))
69
+ goto found;
70
+ }
71
+ return NULL;
72
+ }
73
+ static size_t mus_get_array_len(void *ctx) {
74
+ VALUE c = (VALUE)ctx;
75
+ if (TYPE(c) != RUBY_T_ARRAY)
76
+ return 0;
77
+ return RARRAY_LEN(c);
78
+ }
79
+
80
+ static void *mus_get_var_index(void *ctx, size_t index) {
81
+ VALUE c = (VALUE)ctx;
82
+ if (TYPE(c) != RUBY_T_ARRAY)
83
+ return NULL;
84
+ c = rb_ary_entry(c, index);
85
+ STORE.hold(c);
86
+ return (void *)c;
87
+ }
88
+ static fio_buf_info_s mus_var2str(void *var) {
89
+ if ((VALUE)var == Qnil || !var)
90
+ return FIO_BUF_INFO2(NULL, 0);
91
+ VALUE v = (VALUE)var;
92
+ switch (TYPE(v)) {
93
+ case RUBY_T_TRUE: return FIO_BUF_INFO2((char *)"true", 4);
94
+ case RUBY_T_FALSE: return FIO_BUF_INFO2((char *)"false", 5);
95
+ case RUBY_T_SYMBOL: v = rb_sym2str(v);
96
+ case RUBY_T_STRING:
97
+ return FIO_BUF_INFO2(RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
98
+ case RUBY_T_FIXNUM: /* fall through */
99
+ case RUBY_T_BIGNUM: /* fall through */
100
+ case RUBY_T_FLOAT: /* fall through */
101
+ case RUBY_T_ARRAY: /* fall through */
102
+ case RUBY_T_HASH: /* fall through */
103
+ v = rb_funcallv(v, IODINE_TO_S_ID, 0, &v);
104
+ return FIO_BUF_INFO2(RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
105
+ default:
106
+ if (rb_respond_to(v, rb_intern("call"))) {
107
+ return mus_var2str((void *)rb_proc_call(v, rb_ary_new()));
108
+ }
109
+ if ((v = rb_check_funcall(v, IODINE_TO_S_ID, 0, NULL)) != RUBY_Qundef) {
110
+ if (RB_TYPE_P(v, RUBY_T_STRING))
111
+ return FIO_BUF_INFO2(RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
112
+ }
113
+ return FIO_BUF_INFO2(NULL, 0);
114
+ }
115
+ }
116
+ static int mus_var_is_truthful(void *ctx) {
117
+ return ctx && ((VALUE)ctx) != Qnil && ((VALUE)ctx) != Qfalse &&
118
+ (TYPE(((VALUE)ctx)) != RUBY_T_ARRAY || rb_array_len(((VALUE)ctx)));
119
+ }
120
+
121
+ static void mus_release_var(void *ctx) { STORE.release((VALUE)ctx); }
122
+
123
+ static int mus_is_lambda(void **udata, void *ctx, fio_buf_info_s raw) {
124
+ VALUE c = (VALUE)ctx;
125
+ if (!rb_respond_to(c, rb_intern("call")))
126
+ return 0;
127
+ VALUE tmp = rb_ary_new();
128
+ if (raw.len)
129
+ rb_ary_push(tmp, rb_str_new(raw.buf, raw.len));
130
+ tmp = rb_proc_call(c, tmp);
131
+ fio_buf_info_s txt = mus_var2str((void *)tmp);
132
+ if (txt.len)
133
+ *udata = (void *)fio_bstr_write((char *)(*udata), txt.buf, txt.len);
134
+ return 1;
135
+ }
136
+
137
+ static void mus_on_yaml_front_matter(fio_buf_info_s yaml_front_matter,
138
+ void *udata) {
139
+ VALUE block = (VALUE)udata;
140
+ VALUE yaml_data = rb_ary_new();
141
+ rb_ary_push(yaml_data,
142
+ rb_str_new(yaml_front_matter.buf, yaml_front_matter.len));
143
+ rb_proc_call(block, yaml_data);
144
+ }
145
+
146
+ /* *****************************************************************************
147
+ Ruby Object.
148
+ ***************************************************************************** */
149
+
150
+ static size_t fio_mustache_wrapper_size(const void *ptr_) {
151
+ return fio_bstr_len(*(const char **)ptr_) + 8;
152
+ }
153
+
154
+ static void fio_mustache_wrapper_free(void *p) {
155
+ fio_mustache_s **pm = (fio_mustache_s **)p;
156
+ fio_mustache_free(*pm);
157
+ FIO_LEAK_COUNTER_ON_FREE(iodine_mustache);
158
+ ruby_xfree(pm);
159
+ }
160
+
161
+ static const rb_data_type_t IODINE_MUSTACHE_DATA_TYPE = {
162
+ .wrap_struct_name = "IodineMustache",
163
+ .function =
164
+ {
165
+ .dfree = fio_mustache_wrapper_free,
166
+ .dsize = fio_mustache_wrapper_size,
167
+ },
168
+ .data = NULL,
169
+ // .flags = RUBY_TYPED_FREE_IMMEDIATELY,
170
+ };
171
+
172
+ static VALUE fio_mustache_wrapper_alloc(VALUE klass) {
173
+ fio_mustache_s **mp;
174
+ VALUE o = TypedData_Make_Struct(klass,
175
+ fio_mustache_s *,
176
+ &IODINE_MUSTACHE_DATA_TYPE,
177
+ mp);
178
+ *mp = NULL;
179
+ FIO_LEAK_COUNTER_ON_ALLOC(iodine_mustache);
180
+ return o;
181
+ }
182
+
183
+ static fio_mustache_s **fio_mustache_wrapper_get(VALUE self) {
184
+ fio_mustache_s **mp;
185
+ return TypedData_Get_Struct(self,
186
+ fio_mustache_s *,
187
+ &IODINE_MUSTACHE_DATA_TYPE,
188
+ mp);
189
+ }
190
+
191
+ /* *****************************************************************************
192
+ API
193
+ ***************************************************************************** */
194
+ // clang-format off
195
+ /**
196
+ * Loads a template file and compiles it into a flattened instruction tree.
197
+ *
198
+ * Iodine::Mustache.new(file = nil, data = nil, on_yaml = nil, &block = nil)
199
+ *
200
+ * @param file [String=nil] a file name for the mustache template.
201
+ *
202
+ * @param template [String=nil] the content of the mustache template.
203
+ *
204
+ * @param on_yaml [Proc=nil] (optional) accepts a YAML front-matter String.
205
+ *
206
+ * @param &block to be used as an implicit \p on_yaml (if missing).
207
+ *
208
+ * @return [Iodine::Mustache] returns an Iodine::Mustache object with the
209
+ * provided template ready for rendering.
210
+ *
211
+ * **Note**: Either the file or template argument (or both) must be provided.
212
+ */
213
+ static VALUE mus_load_template(int argc, VALUE *argv, VALUE self) { // clang-format on
214
+ VALUE on_yaml_block = Qnil;
215
+ fio_buf_info_s fname = FIO_BUF_INFO0;
216
+ fio_buf_info_s data = FIO_BUF_INFO0;
217
+
218
+ iodine_rb2c_arg(argc,
219
+ argv,
220
+ IODINE_ARG_BUF(fname, 0, "file", 0),
221
+ IODINE_ARG_BUF(data, 0, "template", 0),
222
+ IODINE_ARG_PROC(on_yaml_block, 0, "on_yaml", 0));
223
+
224
+ if (!fname.buf && !data.buf)
225
+ rb_raise(rb_eArgError,
226
+ "either template `file` or `template` should be provided.");
227
+ if ((data.buf && !data.len) || (fname.buf && !fname.len))
228
+ rb_raise(rb_eArgError,
229
+ "neither template `file` nor `template` can be empty.");
230
+
231
+ fio_mustache_s *m =
232
+ fio_mustache_load(.data = data,
233
+ .filename = fname,
234
+ .on_yaml_front_matter =
235
+ (on_yaml_block == Qnil ? NULL
236
+ : mus_on_yaml_front_matter),
237
+ .udata = (void *)on_yaml_block);
238
+ if (!m)
239
+ rb_raise(rb_eStandardError,
240
+ "template couldn't be found or empty, nothing to build.");
241
+ fio_mustache_s **mp = fio_mustache_wrapper_get(self);
242
+ *mp = m;
243
+ return self;
244
+ }
245
+
246
+ /**
247
+ * Renders the template given at initialization with the provided context.
248
+ *
249
+ * m.render(ctx)
250
+ *
251
+ * @param ctx the top level context for the template data.
252
+ *
253
+ * @return [String] returns a String containing the rendered template.
254
+ */
255
+ static VALUE mus_render(VALUE self, VALUE ctx) {
256
+ fio_mustache_s **mp = fio_mustache_wrapper_get(self);
257
+ if (!*mp)
258
+ rb_raise(rb_eStandardError, "mustache template is empty, couldn't render.");
259
+ char *result =
260
+ (char *)fio_mustache_build(*mp,
261
+ .get_var = mus_get_var,
262
+ .array_length = mus_get_array_len,
263
+ .get_var_index = mus_get_var_index,
264
+ .var2str = mus_var2str,
265
+ .var_is_truthful = mus_var_is_truthful,
266
+ .release_var = mus_release_var,
267
+ .is_lambda = mus_is_lambda,
268
+ .ctx = (void *)ctx);
269
+ if (!result)
270
+ return Qnil;
271
+ VALUE str = rb_utf8_str_new(result, fio_bstr_len(result));
272
+ fio_bstr_free(result);
273
+ return str;
274
+ }
275
+ // clang-format off
276
+ /**
277
+ * Loads a template file and renders it into a String.
278
+ *
279
+ * Iodine::Mustache.render(file = nil, data = nil, ctx = nil, on_yaml = nil)
280
+ *
281
+ * @param file [String=nil] a file name for the mustache template.
282
+ *
283
+ * @param template [String=nil] the content of the mustache template.
284
+ *
285
+ * @param ctx [String=nil] the top level context for the template data.
286
+ *
287
+ * @param on_yaml [Proc=nil] (optional) accepts a YAML front-matter String.
288
+ *
289
+ * @param &block to be used as an implicit \p on_yaml (if missing).
290
+ *
291
+ * @return [String] returns a String containing the rendered template.
292
+ *
293
+ * **Note**: Either the file or template argument (or both) must be provided.
294
+ */
295
+ static VALUE mus_build_and_render(int argc, VALUE *argv, VALUE klass) { // clang-format on
296
+ VALUE ctx = Qnil;
297
+ VALUE on_yaml_block = Qnil;
298
+ fio_buf_info_s fname = FIO_BUF_INFO2(NULL, 0);
299
+ fio_buf_info_s data = FIO_BUF_INFO2(NULL, 0);
300
+
301
+ iodine_rb2c_arg(argc,
302
+ argv,
303
+ IODINE_ARG_BUF(fname, 0, "file", 0),
304
+ IODINE_ARG_BUF(data, 0, "template", 0),
305
+ IODINE_ARG_RB(ctx, 0, "ctx", 0),
306
+ IODINE_ARG_PROC(on_yaml_block, 0, "on_yaml", 0));
307
+
308
+ if (!fname.buf && !data.buf)
309
+ rb_raise(rb_eArgError,
310
+ "either template `file` or `template` should be provided.");
311
+ if ((data.buf && !data.len) || (fname.buf && !fname.len))
312
+ rb_raise(rb_eArgError,
313
+ "neither template `file` nor `template` can be empty.");
314
+
315
+ fio_mustache_s *m =
316
+ fio_mustache_load(.data = data,
317
+ .filename = fname,
318
+ .on_yaml_front_matter =
319
+ (on_yaml_block == Qnil ? NULL
320
+ : mus_on_yaml_front_matter),
321
+ .udata = (void *)on_yaml_block);
322
+ if (!m)
323
+ rb_raise(rb_eStandardError,
324
+ "template couldn't be found or empty, nothing to build.");
325
+
326
+ char *result = (char *)fio_mustache_build(m,
327
+ .get_var = mus_get_var,
328
+ .array_length = mus_get_array_len,
329
+ .get_var_index = mus_get_var_index,
330
+ .var2str = mus_var2str,
331
+ .release_var = mus_release_var,
332
+ .is_lambda = mus_is_lambda,
333
+ .ctx = (void *)ctx);
334
+ fio_mustache_free(m);
335
+ if (!result)
336
+ return Qnil;
337
+ VALUE str = rb_utf8_str_new(result, fio_bstr_len(result));
338
+ fio_bstr_free(result);
339
+ return str;
340
+ }
341
+
342
+ /** Initialize Iodine::Mustache */ // clang-format off
343
+ /**
344
+ Iodine::Mustache is a lighter implementation of the mustache template rendering gem, with a focus on a few minor security details:
345
+
346
+ 1. HTML escaping is more aggressive, increasing XSS protection. Read why at: [wonko.com/post/html-escaping](https://wonko.com/post/html-escaping).
347
+
348
+ 2. Dot notation is tested in whole as well as in part (i.e. `user.name.first` will be tested as is, than the couplet `user`, `name.first` and than as each `user`, `name` , `first`), allowing for the Hash data to contain keys with dots while still supporting dot notation shortcuts.
349
+
350
+ 3. Less logic: i.e., lambdas / procs do not automatically invoke a re-rendering... I'd remove them completely as unsafe, but for now there's that.
351
+
352
+ 4. Improved Protection against Endless Recursion: i.e., Partial templates reference themselves when recursively nested (instead of being recursively re-loaded); and Partial's context is limited to their starting point's context (cannot access parent context).
353
+
354
+ It wasn't designed specifically for speed or performance... but it ended up being significantly faster.
355
+
356
+ ## Usage
357
+
358
+ This approach to Mustache templates may require more forethought when designing either the template or the context's data format, however it should force implementations to be more secure and performance aware.
359
+
360
+ Approach:
361
+
362
+ require 'iodine'
363
+ # One-off rendering of (possibly dynamic) template:
364
+ result = Iodine::Mustache.render(template: "{{foo}}", ctx: {foo: "bar"}) # => "bar"
365
+ # caching of parsed template data for multiple render operations:
366
+ view = Iodine::Mustache.new(file: "./views/foo.mustache", template: "{{foo}}")
367
+ results = Array.new(100) {|i| view.render(foo: "bar#{i}") } # => ["bar0", "bar1", ...]
368
+
369
+ ## Performance
370
+
371
+ Performance may differ according to architecture and compiler used. Please measure:
372
+
373
+ require 'benchmark/ips'
374
+ require 'mustache'
375
+ require 'iodine'
376
+
377
+ # Benchmark code was copied, in part, from:
378
+ # https://github.com/mustache/mustache/blob/master/benchmarks/render_collection_benchmark.rb
379
+ # The test is, sadly, biased and doesn't test for missing elements, proc/method resolution or template partials.
380
+ def benchmark_mustache
381
+ template = """
382
+ {{#products}}
383
+ <div class='product_brick'>
384
+ <div class='container'>
385
+ <div class='element'>
386
+ <img src='images/{{image}}' class='product_miniature' />
387
+ </div>
388
+ <div class='element description'>
389
+ <a href={{url}} class='product_name block bold'>
390
+ {{external_index}}
391
+ </a>
392
+ </div>
393
+ </div>
394
+ </div>
395
+ {{/products}}
396
+ """
397
+
398
+ # fill Hash objects with values for template rendering
399
+ data_1000 = {
400
+ products: []
401
+ }
402
+ data_1000_escaped = {
403
+ products: []
404
+ }
405
+
406
+ 1000.times do
407
+ data_1000[:products] << {
408
+ :external_index=>"product",
409
+ :url=>"/products/7",
410
+ :image=>"products/product.jpg"
411
+ }
412
+ data_1000_escaped[:products] << {
413
+ :external_index=>"This <product> should've been \"properly\" escaped.",
414
+ :url=>"/products/7",
415
+ :image=>"products/product.jpg"
416
+ }
417
+ end
418
+
419
+ # prepare Iodine::Mustache reduced Mustache template engine
420
+ mus_view = Iodine::Mustache.new(template: template)
421
+
422
+ # prepare official Mustache template engine
423
+ view = Mustache.new
424
+ view.template = template
425
+ view.render # Call render once so the template will be compiled
426
+
427
+ # benchmark different use cases
428
+ Benchmark.ips do |x|
429
+ x.report("Ruby Mustache render list of 1000") do |times|
430
+ view.render(data_1000)
431
+ end
432
+ x.report("Iodine::Mustache render list of 1000") do |times|
433
+ mus_view.render(data_1000)
434
+ end
435
+
436
+ x.report("Ruby Mustache render list of 1000 with escaped data") do |times|
437
+ view.render(data_1000_escaped)
438
+ end
439
+ x.report("Iodine::Mustache render list of 1000 with escaped data") do |times|
440
+ mus_view.render(data_1000_escaped)
441
+ end
442
+
443
+ x.report("Ruby Mustache - no caching - render list of 1000") do |times|
444
+ Mustache.render(template, data_1000)
445
+ end
446
+ x.report("Iodine::Mustache - no caching - render list of 1000") do |times|
447
+ Iodine::Mustache.render(nil, template, data_1000)
448
+ end
449
+ x.compare!
450
+ end ; nil
451
+ end
452
+
453
+ benchmark_mustache
454
+
455
+ */
456
+ static void Init_Iodine_Mustache(void) {
457
+ VALUE m = rb_define_class_under(iodine_rb_IODINE, "Mustache", rb_cObject); // clang-format on
458
+ rb_define_alloc_func(m, fio_mustache_wrapper_alloc);
459
+ rb_define_method(m, "initialize", mus_load_template, -1);
460
+ rb_define_method(m, "render", mus_render, 1);
461
+ rb_define_singleton_method(m, "render", mus_build_and_render, -1);
462
+ }
463
+ #endif /* H___IODINE_MUSTA___H */