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.
- checksums.yaml +4 -4
- data/.clang-format +15 -0
- data/.rspec +1 -0
- data/.yardopts +1 -3
- data/CHANGELOG.md +132 -12
- data/Gemfile +5 -7
- data/LICENSE.txt +1 -1
- data/README.md +147 -671
- data/Rakefile +9 -16
- data/examples/chatroom.nru +9 -0
- data/examples/client +74 -0
- data/examples/config-hello.nru +9 -0
- data/examples/config-hello.ru +11 -0
- data/examples/config-hijack.ru +13 -0
- data/examples/config-shootout.ru +43 -0
- data/examples/config.nru +120 -0
- data/examples/config.rb +37 -0
- data/examples/config.ru +42 -53
- data/examples/fake +24 -0
- data/examples/hello_neorack +137 -0
- data/examples/routes.nru +29 -0
- data/examples/routes.ru +42 -0
- data/exe/iodine +218 -258
- data/ext/iodine/extconf.rb +27 -100
- data/ext/iodine/fio-stl.h +128336 -0
- data/ext/iodine/fio-stl.md +26396 -0
- data/ext/iodine/iodine.c +213 -1393
- data/ext/iodine/iodine.h +250 -55
- data/ext/iodine/iodine_arg_helper.h +329 -0
- data/ext/iodine/iodine_caller.h +234 -25
- data/ext/iodine/iodine_cli.h +434 -0
- data/ext/iodine/iodine_compression.h +312 -0
- data/ext/iodine/iodine_connection.h +3484 -34
- data/ext/iodine/iodine_core.h +363 -0
- data/ext/iodine/iodine_crypto.h +1441 -0
- data/ext/iodine/iodine_defer.h +225 -4
- data/ext/iodine/iodine_json.h +738 -4
- data/ext/iodine/iodine_listener.h +263 -0
- data/ext/iodine/iodine_log.h +297 -0
- data/ext/iodine/iodine_minimap.h +543 -0
- data/ext/iodine/iodine_mustache.h +461 -4
- data/ext/iodine/iodine_pubsub_eng.h +729 -0
- data/ext/iodine/iodine_pubsub_history.h +547 -0
- data/ext/iodine/iodine_pubsub_msg.h +228 -0
- data/ext/iodine/iodine_redis.h +322 -0
- data/ext/iodine/iodine_store.h +418 -16
- data/ext/iodine/iodine_threads.h +279 -0
- data/ext/iodine/iodine_tls.h +515 -6
- data/ext/iodine/iodine_utils.h +923 -0
- data/iodine.gemspec +37 -42
- data/lib/iodine/benchmark.rb +1690 -0
- data/lib/iodine/documentation.rb +3085 -0
- data/lib/iodine/version.rb +17 -1
- data/lib/iodine.rb +54 -255
- data/logo.svg +36 -0
- data/mustache.md +55 -0
- data/sig/iodine.rbs +4 -0
- metadata +58 -184
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- data/.github/workflows/ruby.yml +0 -42
- data/.gitignore +0 -21
- data/LIMITS.md +0 -41
- data/SPEC-PubSub-Draft.md +0 -159
- data/SPEC-WebSocket-Draft.md +0 -239
- data/bin/console +0 -22
- data/bin/info.md +0 -353
- data/bin/mustache_bench.rb +0 -100
- data/bin/poc/Gemfile.lock +0 -23
- data/bin/poc/README.md +0 -37
- data/bin/poc/config.ru +0 -66
- data/bin/poc/gemfile +0 -1
- data/bin/poc/www/index.html +0 -57
- data/examples/async_task.ru +0 -92
- data/examples/bates/README.md +0 -3
- data/examples/bates/config.ru +0 -342
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +0 -600
- data/examples/echo.ru +0 -59
- data/examples/etag.ru +0 -16
- data/examples/hello.ru +0 -29
- data/examples/pubsub_engine.ru +0 -81
- data/examples/rack3.ru +0 -12
- data/examples/redis.ru +0 -70
- data/examples/shootout.ru +0 -73
- data/examples/sub-protocols.ru +0 -90
- data/examples/tcp_client.rb +0 -66
- data/examples/x-sendfile.ru +0 -14
- data/ext/iodine/fio.c +0 -11995
- data/ext/iodine/fio.h +0 -6371
- data/ext/iodine/fio_cli.c +0 -431
- data/ext/iodine/fio_cli.h +0 -189
- data/ext/iodine/fio_json_parser.h +0 -688
- data/ext/iodine/fio_siphash.c +0 -157
- data/ext/iodine/fio_siphash.h +0 -37
- data/ext/iodine/fio_tls.h +0 -129
- data/ext/iodine/fio_tls_missing.c +0 -649
- data/ext/iodine/fio_tls_openssl.c +0 -1106
- data/ext/iodine/fio_tmpfile.h +0 -50
- data/ext/iodine/fiobj.h +0 -44
- data/ext/iodine/fiobj4fio.h +0 -21
- data/ext/iodine/fiobj_ary.c +0 -333
- data/ext/iodine/fiobj_ary.h +0 -139
- data/ext/iodine/fiobj_data.c +0 -1187
- data/ext/iodine/fiobj_data.h +0 -167
- data/ext/iodine/fiobj_hash.c +0 -409
- data/ext/iodine/fiobj_hash.h +0 -176
- data/ext/iodine/fiobj_json.c +0 -622
- data/ext/iodine/fiobj_json.h +0 -68
- data/ext/iodine/fiobj_mem.h +0 -71
- data/ext/iodine/fiobj_mustache.c +0 -317
- data/ext/iodine/fiobj_mustache.h +0 -62
- data/ext/iodine/fiobj_numbers.c +0 -344
- data/ext/iodine/fiobj_numbers.h +0 -127
- data/ext/iodine/fiobj_str.c +0 -433
- data/ext/iodine/fiobj_str.h +0 -172
- data/ext/iodine/fiobject.c +0 -620
- data/ext/iodine/fiobject.h +0 -655
- data/ext/iodine/hpack.h +0 -1923
- data/ext/iodine/http.c +0 -2743
- data/ext/iodine/http.h +0 -1002
- data/ext/iodine/http1.c +0 -910
- data/ext/iodine/http1.h +0 -29
- data/ext/iodine/http1_parser.h +0 -878
- data/ext/iodine/http_internal.c +0 -1279
- data/ext/iodine/http_internal.h +0 -237
- data/ext/iodine/http_mime_parser.h +0 -350
- data/ext/iodine/iodine_caller.c +0 -230
- data/ext/iodine/iodine_connection.c +0 -941
- data/ext/iodine/iodine_defer.c +0 -420
- data/ext/iodine/iodine_fiobj2rb.h +0 -120
- data/ext/iodine/iodine_helpers.c +0 -282
- data/ext/iodine/iodine_helpers.h +0 -12
- data/ext/iodine/iodine_http.c +0 -1181
- data/ext/iodine/iodine_http.h +0 -23
- data/ext/iodine/iodine_json.c +0 -302
- data/ext/iodine/iodine_mustache.c +0 -567
- data/ext/iodine/iodine_pubsub.c +0 -580
- data/ext/iodine/iodine_pubsub.h +0 -26
- data/ext/iodine/iodine_rack_io.c +0 -273
- data/ext/iodine/iodine_rack_io.h +0 -20
- data/ext/iodine/iodine_store.c +0 -148
- data/ext/iodine/iodine_tcp.c +0 -346
- data/ext/iodine/iodine_tcp.h +0 -13
- data/ext/iodine/iodine_tls.c +0 -261
- data/ext/iodine/mustache_parser.h +0 -1546
- data/ext/iodine/redis_engine.c +0 -957
- data/ext/iodine/redis_engine.h +0 -79
- data/ext/iodine/resp_parser.h +0 -317
- data/ext/iodine/websocket_parser.h +0 -506
- data/ext/iodine/websockets.c +0 -743
- data/ext/iodine/websockets.h +0 -185
- data/lib/iodine/connection.rb +0 -61
- data/lib/iodine/json.rb +0 -42
- data/lib/iodine/mustache.rb +0 -113
- data/lib/iodine/pubsub.rb +0 -55
- data/lib/iodine/rack_utils.rb +0 -43
- data/lib/iodine/tls.rb +0 -16
- data/lib/rack/handler/iodine.rb +0 -33
- data/logo.png +0 -0
|
@@ -1,6 +1,463 @@
|
|
|
1
|
-
#ifndef
|
|
2
|
-
#define
|
|
1
|
+
#ifndef H___IODINE_MUSTA___H
|
|
2
|
+
#define H___IODINE_MUSTA___H
|
|
3
|
+
#include "iodine.h"
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
/* *****************************************************************************
|
|
6
|
+
Mustache Callbacks
|
|
7
|
+
***************************************************************************** */
|
|
5
8
|
|
|
6
|
-
|
|
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 */
|