iodine 0.6.5 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +4 -4
- data/SPEC-Websocket-Draft.md +3 -6
- data/bin/mustache.rb +128 -0
- data/examples/test_template.mustache +16 -0
- data/ext/iodine/fio.c +9397 -0
- data/ext/iodine/fio.h +4723 -0
- data/ext/iodine/fio_ary.h +353 -54
- data/ext/iodine/fio_cli.c +351 -361
- data/ext/iodine/fio_cli.h +84 -105
- data/ext/iodine/fio_hashmap.h +70 -16
- data/ext/iodine/fio_json_parser.h +35 -24
- data/ext/iodine/fio_siphash.c +104 -4
- data/ext/iodine/fio_siphash.h +18 -2
- data/ext/iodine/fio_str.h +1218 -0
- data/ext/iodine/fio_tmpfile.h +1 -1
- data/ext/iodine/fiobj.h +13 -8
- data/ext/iodine/fiobj4sock.h +6 -8
- data/ext/iodine/fiobj_ary.c +107 -17
- data/ext/iodine/fiobj_ary.h +36 -4
- data/ext/iodine/fiobj_data.c +146 -127
- data/ext/iodine/fiobj_data.h +25 -23
- data/ext/iodine/fiobj_hash.c +7 -7
- data/ext/iodine/fiobj_hash.h +6 -5
- data/ext/iodine/fiobj_json.c +20 -17
- data/ext/iodine/fiobj_json.h +5 -5
- data/ext/iodine/fiobj_mem.h +71 -0
- data/ext/iodine/fiobj_mustache.c +310 -0
- data/ext/iodine/fiobj_mustache.h +40 -0
- data/ext/iodine/fiobj_numbers.c +199 -94
- data/ext/iodine/fiobj_numbers.h +7 -7
- data/ext/iodine/fiobj_str.c +142 -333
- data/ext/iodine/fiobj_str.h +65 -55
- data/ext/iodine/fiobject.c +49 -11
- data/ext/iodine/fiobject.h +40 -39
- data/ext/iodine/http.c +382 -190
- data/ext/iodine/http.h +124 -80
- data/ext/iodine/http1.c +99 -127
- data/ext/iodine/http1.h +5 -5
- data/ext/iodine/http1_parser.c +3 -2
- data/ext/iodine/http1_parser.h +2 -2
- data/ext/iodine/http_internal.c +14 -12
- data/ext/iodine/http_internal.h +25 -19
- data/ext/iodine/iodine.c +37 -18
- data/ext/iodine/iodine.h +4 -0
- data/ext/iodine/iodine_caller.c +9 -2
- data/ext/iodine/iodine_caller.h +2 -0
- data/ext/iodine/iodine_connection.c +82 -117
- data/ext/iodine/iodine_defer.c +57 -50
- data/ext/iodine/iodine_defer.h +0 -1
- data/ext/iodine/iodine_fiobj2rb.h +4 -2
- data/ext/iodine/iodine_helpers.c +4 -4
- data/ext/iodine/iodine_http.c +25 -32
- data/ext/iodine/iodine_json.c +2 -1
- data/ext/iodine/iodine_mustache.c +423 -0
- data/ext/iodine/iodine_mustache.h +6 -0
- data/ext/iodine/iodine_pubsub.c +48 -153
- data/ext/iodine/iodine_pubsub.h +5 -4
- data/ext/iodine/iodine_rack_io.c +7 -5
- data/ext/iodine/iodine_store.c +16 -13
- data/ext/iodine/iodine_tcp.c +26 -34
- data/ext/iodine/mustache_parser.h +1085 -0
- data/ext/iodine/redis_engine.c +740 -646
- data/ext/iodine/redis_engine.h +13 -15
- data/ext/iodine/resp_parser.h +11 -5
- data/ext/iodine/websocket_parser.h +13 -13
- data/ext/iodine/websockets.c +240 -393
- data/ext/iodine/websockets.h +52 -113
- data/lib/iodine.rb +1 -1
- data/lib/iodine/mustache.rb +140 -0
- data/lib/iodine/version.rb +1 -1
- metadata +15 -28
- data/ext/iodine/defer.c +0 -566
- data/ext/iodine/defer.h +0 -148
- data/ext/iodine/evio.c +0 -26
- data/ext/iodine/evio.h +0 -161
- data/ext/iodine/evio_callbacks.c +0 -26
- data/ext/iodine/evio_epoll.c +0 -251
- data/ext/iodine/evio_kqueue.c +0 -194
- data/ext/iodine/facil.c +0 -2325
- data/ext/iodine/facil.h +0 -616
- data/ext/iodine/fio_base64.c +0 -277
- data/ext/iodine/fio_base64.h +0 -71
- data/ext/iodine/fio_llist.h +0 -257
- data/ext/iodine/fio_mem.c +0 -675
- data/ext/iodine/fio_mem.h +0 -143
- data/ext/iodine/fio_random.c +0 -248
- data/ext/iodine/fio_random.h +0 -45
- data/ext/iodine/fio_sha1.c +0 -362
- data/ext/iodine/fio_sha1.h +0 -107
- data/ext/iodine/fio_sha2.c +0 -842
- data/ext/iodine/fio_sha2.h +0 -169
- data/ext/iodine/pubsub.c +0 -867
- data/ext/iodine/pubsub.h +0 -221
- data/ext/iodine/sock.c +0 -1366
- data/ext/iodine/sock.h +0 -566
- data/ext/iodine/spnlock.inc +0 -111
data/ext/iodine/iodine_tcp.c
CHANGED
@@ -2,8 +2,7 @@
|
|
2
2
|
#include <ruby/encoding.h>
|
3
3
|
#include <ruby/io.h>
|
4
4
|
|
5
|
-
#include "
|
6
|
-
#include "facil.h"
|
5
|
+
#include "fio.h"
|
7
6
|
|
8
7
|
/* *****************************************************************************
|
9
8
|
Static stuff
|
@@ -24,7 +23,7 @@ Raw TCP/IP Protocol
|
|
24
23
|
#define IODINE_MAX_READ 8192
|
25
24
|
|
26
25
|
typedef struct {
|
27
|
-
|
26
|
+
fio_protocol_s p;
|
28
27
|
VALUE io;
|
29
28
|
} iodine_protocol_s;
|
30
29
|
|
@@ -34,11 +33,6 @@ typedef struct {
|
|
34
33
|
char buffer[IODINE_MAX_READ];
|
35
34
|
} iodine_buffer_s;
|
36
35
|
|
37
|
-
/**
|
38
|
-
* A string to identify the protocol's service.
|
39
|
-
*/
|
40
|
-
static const char *iodine_tcp_service = "iodine TCP/IP raw connection";
|
41
|
-
|
42
36
|
/**
|
43
37
|
* Converts an iodine_buffer_s pointer to a Ruby string.
|
44
38
|
*/
|
@@ -57,21 +51,21 @@ static void *iodine_tcp_on_data_in_GIL(void *b_) {
|
|
57
51
|
}
|
58
52
|
|
59
53
|
/** Called when a data is available, but will not run concurrently */
|
60
|
-
static void iodine_tcp_on_data(intptr_t uuid,
|
54
|
+
static void iodine_tcp_on_data(intptr_t uuid, fio_protocol_s *protocol) {
|
61
55
|
iodine_buffer_s buffer;
|
62
|
-
buffer.len =
|
56
|
+
buffer.len = fio_read(uuid, buffer.buffer, IODINE_MAX_READ);
|
63
57
|
if (buffer.len <= 0) {
|
64
58
|
return;
|
65
59
|
}
|
66
60
|
buffer.io = ((iodine_protocol_s *)protocol)->io;
|
67
61
|
IodineCaller.enterGVL(iodine_tcp_on_data_in_GIL, &buffer);
|
68
62
|
if (buffer.len == IODINE_MAX_READ) {
|
69
|
-
|
63
|
+
fio_force_event(uuid, FIO_EVENT_ON_DATA);
|
70
64
|
}
|
71
65
|
}
|
72
66
|
|
73
67
|
/** called when the socket is ready to be written to. */
|
74
|
-
static void iodine_tcp_on_ready(intptr_t uuid,
|
68
|
+
static void iodine_tcp_on_ready(intptr_t uuid, fio_protocol_s *protocol) {
|
75
69
|
iodine_protocol_s *p = (iodine_protocol_s *)protocol;
|
76
70
|
iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_DRAINED, Qnil);
|
77
71
|
(void)uuid;
|
@@ -81,15 +75,16 @@ static void iodine_tcp_on_ready(intptr_t uuid, protocol_s *protocol) {
|
|
81
75
|
* Called when the server is shutting down, immediately before closing the
|
82
76
|
* connection.
|
83
77
|
*/
|
84
|
-
static
|
78
|
+
static uint8_t iodine_tcp_on_shutdown(intptr_t uuid, fio_protocol_s *protocol) {
|
85
79
|
iodine_protocol_s *p = (iodine_protocol_s *)protocol;
|
86
80
|
iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_SHUTDOWN, Qnil);
|
81
|
+
return 0;
|
87
82
|
(void)uuid;
|
88
83
|
}
|
89
84
|
|
90
85
|
/** Called when the connection was closed, but will not run concurrently */
|
91
86
|
|
92
|
-
static void iodine_tcp_on_close(intptr_t uuid,
|
87
|
+
static void iodine_tcp_on_close(intptr_t uuid, fio_protocol_s *protocol) {
|
93
88
|
iodine_protocol_s *p = (iodine_protocol_s *)protocol;
|
94
89
|
iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_CLOSE, Qnil);
|
95
90
|
free(p);
|
@@ -97,7 +92,7 @@ static void iodine_tcp_on_close(intptr_t uuid, protocol_s *protocol) {
|
|
97
92
|
}
|
98
93
|
|
99
94
|
/** called when a connection's timeout was reached */
|
100
|
-
static void iodine_tcp_ping(intptr_t uuid,
|
95
|
+
static void iodine_tcp_ping(intptr_t uuid, fio_protocol_s *protocol) {
|
101
96
|
iodine_protocol_s *p = (iodine_protocol_s *)protocol;
|
102
97
|
iodine_connection_fire_event(p->io, IODINE_CONNECTION_PING, Qnil);
|
103
98
|
(void)uuid;
|
@@ -121,7 +116,7 @@ static void iodine_tcp_on_finish(intptr_t uuid, void *udata) {
|
|
121
116
|
* The `on_connect` callback should return a pointer to a protocol object
|
122
117
|
* that will handle any connection related events.
|
123
118
|
*
|
124
|
-
* Should either call `
|
119
|
+
* Should either call `fio_attach` or close the connection.
|
125
120
|
*/
|
126
121
|
static void iodine_tcp_on_connect(intptr_t uuid, void *udata) {
|
127
122
|
VALUE handler = (VALUE)udata;
|
@@ -233,13 +228,12 @@ static VALUE iodine_tcp_listen(VALUE self, VALUE args) {
|
|
233
228
|
if (rb_port != Qnil) {
|
234
229
|
Check_Type(rb_port, T_STRING);
|
235
230
|
}
|
236
|
-
if (
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
.udata = (void *)rb_handler) == -1) {
|
231
|
+
if (fio_listen(.port = (rb_port == Qnil ? NULL : StringValueCStr(rb_port)),
|
232
|
+
.address =
|
233
|
+
(rb_address == Qnil ? NULL : StringValueCStr(rb_address)),
|
234
|
+
.on_open = iodine_tcp_on_open,
|
235
|
+
.on_finish = iodine_tcp_on_finish,
|
236
|
+
.udata = (void *)rb_handler) == -1) {
|
243
237
|
IodineStore.remove(rb_handler);
|
244
238
|
rb_raise(rb_eRuntimeError,
|
245
239
|
"failed to listen to requested address, unknown error.");
|
@@ -289,12 +283,12 @@ static VALUE iodine_tcp_connect(VALUE self, VALUE args) {
|
|
289
283
|
Check_Type(rb_timeout, T_FIXNUM);
|
290
284
|
timeout = NUM2USHORT(rb_timeout);
|
291
285
|
}
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
286
|
+
fio_connect(.port = (rb_port == Qnil ? NULL : StringValueCStr(rb_port)),
|
287
|
+
.address =
|
288
|
+
(rb_address == Qnil ? NULL : StringValueCStr(rb_address)),
|
289
|
+
.on_connect = iodine_tcp_on_connect,
|
290
|
+
.on_fail = iodine_tcp_on_fail, .timeout = timeout,
|
291
|
+
.udata = (void *)rb_handler);
|
298
292
|
return rb_handler;
|
299
293
|
(void)self;
|
300
294
|
}
|
@@ -322,7 +316,7 @@ static VALUE iodine_tcp_attach_fd(VALUE self, VALUE fd, VALUE handler) {
|
|
322
316
|
if (other == -1) {
|
323
317
|
rb_raise(rb_eIOError, "invalid fd.");
|
324
318
|
}
|
325
|
-
intptr_t uuid =
|
319
|
+
intptr_t uuid = fio_fd2uuid(other);
|
326
320
|
iodine_tcp_attch_uuid(uuid, handler);
|
327
321
|
IodineStore.remove(handler);
|
328
322
|
return handler;
|
@@ -355,7 +349,7 @@ Allow uuid attachment
|
|
355
349
|
/** assigns a protocol and IO object to a handler */
|
356
350
|
void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) {
|
357
351
|
if (handler == Qnil || handler == Qfalse || handler == Qtrue) {
|
358
|
-
|
352
|
+
fio_close(uuid);
|
359
353
|
return;
|
360
354
|
}
|
361
355
|
/* temporary, in case `iodine_connection_new` invokes the GC */
|
@@ -367,7 +361,6 @@ void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) {
|
|
367
361
|
*p = (iodine_protocol_s){
|
368
362
|
.p =
|
369
363
|
{
|
370
|
-
.service = iodine_tcp_service,
|
371
364
|
.on_data = iodine_tcp_on_data,
|
372
365
|
.on_ready = NULL /* set only after the on_open callback */,
|
373
366
|
.on_shutdown = iodine_tcp_on_shutdown,
|
@@ -378,8 +371,7 @@ void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) {
|
|
378
371
|
.arg = p, .handler = handler),
|
379
372
|
};
|
380
373
|
/* clear away (remember the connection object manages these concerns) */
|
381
|
-
|
374
|
+
fio_attach(uuid, &p->p);
|
382
375
|
iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_OPEN, Qnil);
|
383
376
|
p->p.on_ready = iodine_tcp_on_ready;
|
384
|
-
evio_add_write(sock_uuid2fd(uuid), (void *)uuid);
|
385
377
|
}
|
@@ -0,0 +1,1085 @@
|
|
1
|
+
/*
|
2
|
+
Copyright: Boaz Segev, 2018
|
3
|
+
License: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#ifndef H_MUSTACHE_LOADR_H
|
8
|
+
/**
|
9
|
+
* A mustache parser using a callback systems that allows this implementation to
|
10
|
+
* be framework agnostic (i.e., can be used with any JSON library).
|
11
|
+
*
|
12
|
+
* When including the mustache parser within an iumplementation file,
|
13
|
+
* `INCLUDE_MUSTACHE_IMPLEMENTATION` must be defined as 1. This allows the
|
14
|
+
* header's types to be exposed within a containing header.
|
15
|
+
*
|
16
|
+
* The API has three functions:
|
17
|
+
*
|
18
|
+
* 1. `mustache_load` loads a template file, converting it to instruction data.
|
19
|
+
* 2. `mustache_build` calls any callbacks according to the loaded instructions.
|
20
|
+
* 3. `mustache_free` frees the instruction and data memory (the template).
|
21
|
+
*
|
22
|
+
* The template is loaded and converted to an instruction array using
|
23
|
+
* `mustache_load`. This loads any nested templates / partials as well.
|
24
|
+
*
|
25
|
+
* The resulting instruction array (`mustache_s *`) is composed of three memory
|
26
|
+
* segments: header segment, instruction array segment and data segment.
|
27
|
+
*
|
28
|
+
* The instruction array (`mustache_s *`) can be used to build actual output
|
29
|
+
* data using the `mustache_build` function.
|
30
|
+
*
|
31
|
+
* The `mustache_build` function accepts two opaque pointers for user data
|
32
|
+
* (`udata1` and `udata2`) that can be used by the callbacks for data input and
|
33
|
+
* data output.
|
34
|
+
*
|
35
|
+
* The `mustache_build` function is thread safe and many threads can build
|
36
|
+
* content based on the same template.
|
37
|
+
*
|
38
|
+
* While the build function is performed, the following callback might be
|
39
|
+
* called:
|
40
|
+
*
|
41
|
+
* * `mustache_on_arg` - called to output an argument's value .
|
42
|
+
* * `mustache_on_text` - called to output raw text.
|
43
|
+
* * `mustache_on_section_test` - called when a section is tested for validity.
|
44
|
+
* * `mustache_on_section_start` - called when entering a named section.
|
45
|
+
* * `mustache_on_formatting_error` - called when a formatting error occurred.
|
46
|
+
*
|
47
|
+
* Once the template is no longer needed, it's easy to free the template using
|
48
|
+
* the `mustache_free` function (which, at the moment, simply calls `free`).
|
49
|
+
*
|
50
|
+
* For details about mustache templating scheme, see: https://mustache.github.io
|
51
|
+
*
|
52
|
+
*/
|
53
|
+
#define H_MUSTACHE_LOADR_H
|
54
|
+
|
55
|
+
#ifndef _GNU_SOURCE
|
56
|
+
#define _GNU_SOURCE
|
57
|
+
#endif
|
58
|
+
|
59
|
+
#include <ctype.h>
|
60
|
+
#include <stdint.h>
|
61
|
+
#include <stdio.h>
|
62
|
+
#include <stdlib.h>
|
63
|
+
#include <string.h>
|
64
|
+
#include <strings.h>
|
65
|
+
#include <unistd.h>
|
66
|
+
|
67
|
+
#include <errno.h>
|
68
|
+
#include <fcntl.h>
|
69
|
+
#include <sys/stat.h>
|
70
|
+
#include <sys/types.h>
|
71
|
+
|
72
|
+
#if !defined(MUSTACHE_NESTING_LIMIT) || !MUSTACHE_NESTING_LIMIT
|
73
|
+
#undef MUSTACHE_NESTING_LIMIT
|
74
|
+
#define MUSTACHE_NESTING_LIMIT 64
|
75
|
+
#endif
|
76
|
+
|
77
|
+
#if !defined(__GNUC__) && !defined(__clang__) && !defined(FIO_GNUC_BYPASS)
|
78
|
+
#define __attribute__(...)
|
79
|
+
#define __has_include(...) 0
|
80
|
+
#define __has_builtin(...) 0
|
81
|
+
#define FIO_GNUC_BYPASS 1
|
82
|
+
#elif !defined(__clang__) && __GNUC__ < 5
|
83
|
+
#define __has_builtin(...) 0
|
84
|
+
#define FIO_GNUC_BYPASS 1
|
85
|
+
#endif
|
86
|
+
|
87
|
+
#ifndef MUSTACHE_FUNC
|
88
|
+
#define MUSTACHE_FUNC static __attribute__((unused))
|
89
|
+
#endif
|
90
|
+
|
91
|
+
/* *****************************************************************************
|
92
|
+
Mustache API Functions and Arguments
|
93
|
+
***************************************************************************** */
|
94
|
+
|
95
|
+
/** an opaque type for mustache template data (when caching). */
|
96
|
+
typedef struct mustache_s mustache_s;
|
97
|
+
|
98
|
+
/** Error reporting (in case of errors). */
|
99
|
+
typedef enum mustache_error_en {
|
100
|
+
MUSTACHE_OK,
|
101
|
+
MUSTACHE_ERR_TOO_DEEP,
|
102
|
+
MUSTACHE_ERR_CLOSURE_MISMATCH,
|
103
|
+
MUSTACHE_ERR_FILE_NOT_FOUND,
|
104
|
+
MUSTACHE_ERR_FILE_TOO_BIG,
|
105
|
+
MUSTACHE_ERR_FILE_NAME_TOO_LONG,
|
106
|
+
MUSTACHE_ERR_EMPTY_TEMPLATE,
|
107
|
+
MUSTACHE_ERR_UNKNOWN,
|
108
|
+
MUSTACHE_ERR_USER_ERROR,
|
109
|
+
} mustache_error_en;
|
110
|
+
|
111
|
+
/** Arguments for the `mustache_load` function. */
|
112
|
+
typedef struct {
|
113
|
+
/** The root template's file name. */
|
114
|
+
char const *filename;
|
115
|
+
/** The file name's length. */
|
116
|
+
size_t filename_len;
|
117
|
+
/** Parsing error reporting (can be NULL). */
|
118
|
+
mustache_error_en *err;
|
119
|
+
} mustache_load_args_s;
|
120
|
+
|
121
|
+
/**
|
122
|
+
* Allows this header to be included within another header while limiting
|
123
|
+
* exposure.
|
124
|
+
*
|
125
|
+
* before including the header within an implementation faile, define
|
126
|
+
* INCLUDE_MUSTACHE_IMPLEMENTATION as 1.
|
127
|
+
*/
|
128
|
+
#if INCLUDE_MUSTACHE_IMPLEMENTATION
|
129
|
+
|
130
|
+
MUSTACHE_FUNC mustache_s *mustache_load(mustache_load_args_s args);
|
131
|
+
|
132
|
+
#define mustache_load(...) mustache_load((mustache_load_args_s){__VA_ARGS__})
|
133
|
+
|
134
|
+
/** free the mustache template */
|
135
|
+
inline MUSTACHE_FUNC void mustache_free(mustache_s *mustache) {
|
136
|
+
free(mustache);
|
137
|
+
}
|
138
|
+
|
139
|
+
/** Arguments for the `mustache_build` function. */
|
140
|
+
typedef struct {
|
141
|
+
/** The parsed template (an instruction collection). */
|
142
|
+
mustache_s *mustache;
|
143
|
+
/** Opaque user data (recommended for input review) - children will inherit
|
144
|
+
* the parent's udata value. Updated values will propegate to child sections
|
145
|
+
* but won't effect parent sections.
|
146
|
+
*/
|
147
|
+
void *udata1;
|
148
|
+
/** Opaque user data (recommended for output handling)- children will inherit
|
149
|
+
* the parent's udata value. Updated values will propegate to child sections
|
150
|
+
* but won't effect parent sections.
|
151
|
+
*/
|
152
|
+
void *udata2;
|
153
|
+
/** Formatting error reporting (can be NULL). */
|
154
|
+
mustache_error_en *err;
|
155
|
+
} mustache_build_args_s;
|
156
|
+
MUSTACHE_FUNC int mustache_build(mustache_build_args_s args);
|
157
|
+
|
158
|
+
#define mustache_build(mustache_s_ptr, ...) \
|
159
|
+
mustache_build( \
|
160
|
+
(mustache_build_args_s){.mustache = (mustache_s_ptr), __VA_ARGS__})
|
161
|
+
|
162
|
+
/* *****************************************************************************
|
163
|
+
Client Callbacks - MUST be implemented by the including file
|
164
|
+
***************************************************************************** */
|
165
|
+
|
166
|
+
/**
|
167
|
+
* A mustache section allows the callbacks to "walk" backwards towards the root
|
168
|
+
* in search of argument data.
|
169
|
+
*
|
170
|
+
* Note that every section is allowed a separate udata value.
|
171
|
+
*/
|
172
|
+
typedef struct mustache_section_s {
|
173
|
+
/**
|
174
|
+
* READ ONLY. The parent section (when nesting), if any.
|
175
|
+
*
|
176
|
+
* This is important for accessing the parent's `udata` values when searching
|
177
|
+
* for an argument's value.
|
178
|
+
*
|
179
|
+
* The root's parent is NULL.
|
180
|
+
*/
|
181
|
+
struct mustache_section_s *parent;
|
182
|
+
/** Opaque user data (recommended for input review) - children will inherit
|
183
|
+
* the parent's udata value. Updated values will propegate to child sections
|
184
|
+
* but won't effect parent sections.
|
185
|
+
*/
|
186
|
+
void *udata1;
|
187
|
+
/** Opaque user data (recommended for output handling)- children will inherit
|
188
|
+
* the parent's udata value. Updated values will propegate to child sections
|
189
|
+
* but won't effect parent sections.
|
190
|
+
*/
|
191
|
+
void *udata2;
|
192
|
+
} mustache_section_s;
|
193
|
+
|
194
|
+
/**
|
195
|
+
* Called when an argument name was detected in the current section.
|
196
|
+
*
|
197
|
+
* A conforming implementation will search for the named argument both in the
|
198
|
+
* existing section and all of it's parents (walking backwards towards the root)
|
199
|
+
* until a value is detected.
|
200
|
+
*
|
201
|
+
* A missing value should be treated the same as an empty string.
|
202
|
+
*
|
203
|
+
* A conforming implementation will output the named argument's value (either
|
204
|
+
* HTML escaped or not, depending on the `escape` flag) as a string.
|
205
|
+
*/
|
206
|
+
static int mustache_on_arg(mustache_section_s *section, const char *name,
|
207
|
+
uint32_t name_len, unsigned char escape);
|
208
|
+
|
209
|
+
/**
|
210
|
+
* Called when simple template text (string) is detected.
|
211
|
+
*
|
212
|
+
* A conforming implementation will output data as a string (no escaping).
|
213
|
+
*/
|
214
|
+
static int mustache_on_text(mustache_section_s *section, const char *data,
|
215
|
+
uint32_t data_len);
|
216
|
+
|
217
|
+
/**
|
218
|
+
* Called for nested sections, must return the number of objects in the new
|
219
|
+
* subsection (depending on the argument's name).
|
220
|
+
*
|
221
|
+
* Arrays should return the number of objects in the array.
|
222
|
+
*
|
223
|
+
* `true` values should return 1.
|
224
|
+
*
|
225
|
+
* `false` values should return 0.
|
226
|
+
*
|
227
|
+
* A return value of -1 will stop processing with an error.
|
228
|
+
*
|
229
|
+
* Please note, this will handle both normal and inverted sections.
|
230
|
+
*/
|
231
|
+
static int32_t mustache_on_section_test(mustache_section_s *section,
|
232
|
+
const char *name, uint32_t name_len);
|
233
|
+
|
234
|
+
/**
|
235
|
+
* Called when entering a nested section.
|
236
|
+
*
|
237
|
+
* `index` is a zero based index indicating the number of repetitions that
|
238
|
+
* occurred so far (same as the array index for arrays).
|
239
|
+
*
|
240
|
+
* A return value of -1 will stop processing with an error.
|
241
|
+
*
|
242
|
+
* Note: this is a good time to update the subsection's `udata` with the value
|
243
|
+
* of the array index. The `udata` will always contain the value or the parent's
|
244
|
+
* `udata`.
|
245
|
+
*/
|
246
|
+
static int mustache_on_section_start(mustache_section_s *section,
|
247
|
+
char const *name, uint32_t name_len,
|
248
|
+
uint32_t index);
|
249
|
+
|
250
|
+
/**
|
251
|
+
* Called for cleanup in case of error.
|
252
|
+
*/
|
253
|
+
static void mustache_on_formatting_error(void *udata1, void *udata2);
|
254
|
+
|
255
|
+
/* *****************************************************************************
|
256
|
+
|
257
|
+
IMPLEMENTATION (beware: monolithic functions ahead)
|
258
|
+
|
259
|
+
***************************************************************************** */
|
260
|
+
|
261
|
+
/* *****************************************************************************
|
262
|
+
Internal types
|
263
|
+
***************************************************************************** */
|
264
|
+
|
265
|
+
struct mustache_s {
|
266
|
+
/* The number of instructions in the engine */
|
267
|
+
union {
|
268
|
+
void *read_only_pt; /* ensure pointer wide padding */
|
269
|
+
struct {
|
270
|
+
uint32_t intruction_count;
|
271
|
+
uint32_t data_length;
|
272
|
+
} read_only;
|
273
|
+
} u;
|
274
|
+
};
|
275
|
+
|
276
|
+
typedef struct mustache__instruction_s {
|
277
|
+
enum {
|
278
|
+
MUSTACHE_WRITE_TEXT,
|
279
|
+
MUSTACHE_WRITE_ARG,
|
280
|
+
MUSTACHE_WRITE_ARG_UNESCAPED,
|
281
|
+
MUSTACHE_SECTION_START,
|
282
|
+
MUSTACHE_SECTION_START_INV,
|
283
|
+
MUSTACHE_SECTION_END,
|
284
|
+
MUSTACHE_SECTION_GOTO,
|
285
|
+
} instruction;
|
286
|
+
/** the data the instruction acts upon */
|
287
|
+
struct {
|
288
|
+
/** The offset from the beginning of the data segment. */
|
289
|
+
uint32_t start;
|
290
|
+
/** The length of the data. */
|
291
|
+
uint32_t len;
|
292
|
+
} data;
|
293
|
+
} mustache__instruction_s;
|
294
|
+
|
295
|
+
/* *****************************************************************************
|
296
|
+
Calling the instrustion list (using the template engine)
|
297
|
+
***************************************************************************** */
|
298
|
+
|
299
|
+
/*
|
300
|
+
* This function reviews the instructions listed at the end of the mustache_s
|
301
|
+
* and performs any callbacks necessary.
|
302
|
+
*
|
303
|
+
* The `mustache_s` data is looks like this:
|
304
|
+
*
|
305
|
+
* - header (the `mustache_s` struct): lists the length of the instruction
|
306
|
+
* array and data segments.
|
307
|
+
* - Instruction array: lists all the instructions extracted from the
|
308
|
+
* template(s) (an array of `mustache__instruction_s`).
|
309
|
+
* - Data segment: text and data related to the instructions.
|
310
|
+
*
|
311
|
+
* The instructions, much like machine code, might loop or jump. This is why the
|
312
|
+
* functions keep a stack of sorts. This allows the code to avoid recursion and
|
313
|
+
* minimize any risk of stack overflow caused by recursive templates.
|
314
|
+
*
|
315
|
+
* Note:
|
316
|
+
*
|
317
|
+
* For text and argument instructions, the mustache__instruction_s.data.start
|
318
|
+
* and mustache__instruction_s.data.len mark the beginning of the text/argument
|
319
|
+
* and it's name.
|
320
|
+
*
|
321
|
+
* However, for MUSTACHE_SECTION_START instructions, data.len marks position for
|
322
|
+
* the complementing MUSTACHE_SECTION_END instruction, allowing for easy jumps
|
323
|
+
* in cases where a section is skipped or in cases of a recursive template.
|
324
|
+
*/
|
325
|
+
MUSTACHE_FUNC int(mustache_build)(mustache_build_args_s args) {
|
326
|
+
/* extract the instruction array and data segment from the mustache_s */
|
327
|
+
mustache__instruction_s *pos =
|
328
|
+
(mustache__instruction_s *)(sizeof(*args.mustache) +
|
329
|
+
(uintptr_t)args.mustache);
|
330
|
+
mustache__instruction_s *const start = pos;
|
331
|
+
mustache__instruction_s *const end =
|
332
|
+
pos + args.mustache->u.read_only.intruction_count;
|
333
|
+
char *const data = (char *const)end;
|
334
|
+
|
335
|
+
/* prepare a pre-allocated stack space to flatten recursion needs */
|
336
|
+
struct {
|
337
|
+
mustache_section_s sec; /* client visible section data */
|
338
|
+
uint32_t start; /* section start instruction position */
|
339
|
+
uint32_t end; /* instruction to jump to after completion */
|
340
|
+
uint32_t index; /* zero based index forr section loops */
|
341
|
+
uint32_t count; /* the number of times the section should be performed */
|
342
|
+
} section_stack[MUSTACHE_NESTING_LIMIT];
|
343
|
+
|
344
|
+
/* first section (section 0) data */
|
345
|
+
section_stack[0].sec = (mustache_section_s){
|
346
|
+
.udata1 = args.udata1,
|
347
|
+
.udata2 = args.udata2,
|
348
|
+
};
|
349
|
+
section_stack[0].end = 0;
|
350
|
+
uint32_t nesting_pos = 0;
|
351
|
+
|
352
|
+
/* run through the instruction list and persorm each instruction */
|
353
|
+
while (pos < end) {
|
354
|
+
switch (pos->instruction) {
|
355
|
+
|
356
|
+
case MUSTACHE_WRITE_TEXT:
|
357
|
+
if (mustache_on_text(§ion_stack[nesting_pos].sec,
|
358
|
+
data + pos->data.start, pos->data.len) == -1) {
|
359
|
+
if (args.err) {
|
360
|
+
*args.err = MUSTACHE_ERR_USER_ERROR;
|
361
|
+
}
|
362
|
+
goto error;
|
363
|
+
}
|
364
|
+
break;
|
365
|
+
|
366
|
+
case MUSTACHE_WRITE_ARG:
|
367
|
+
if (mustache_on_arg(§ion_stack[nesting_pos].sec,
|
368
|
+
data + pos->data.start, pos->data.len, 1) == -1) {
|
369
|
+
if (args.err) {
|
370
|
+
*args.err = MUSTACHE_ERR_USER_ERROR;
|
371
|
+
}
|
372
|
+
goto error;
|
373
|
+
}
|
374
|
+
break;
|
375
|
+
|
376
|
+
case MUSTACHE_WRITE_ARG_UNESCAPED:
|
377
|
+
if (mustache_on_arg(§ion_stack[nesting_pos].sec,
|
378
|
+
data + pos->data.start, pos->data.len, 0) == -1) {
|
379
|
+
if (args.err) {
|
380
|
+
*args.err = MUSTACHE_ERR_USER_ERROR;
|
381
|
+
}
|
382
|
+
goto error;
|
383
|
+
}
|
384
|
+
break;
|
385
|
+
case MUSTACHE_SECTION_START_INV: /* overfloaw*/
|
386
|
+
case MUSTACHE_SECTION_START: {
|
387
|
+
/* starting a new section, increased nesting & review */
|
388
|
+
if (nesting_pos + 1 == MUSTACHE_NESTING_LIMIT) {
|
389
|
+
if (args.err) {
|
390
|
+
*args.err = MUSTACHE_ERR_TOO_DEEP;
|
391
|
+
}
|
392
|
+
goto error;
|
393
|
+
}
|
394
|
+
section_stack[nesting_pos + 1].sec = section_stack[nesting_pos].sec;
|
395
|
+
++nesting_pos;
|
396
|
+
|
397
|
+
/* find the end of the section */
|
398
|
+
mustache__instruction_s *section_end = start + pos->data.len;
|
399
|
+
|
400
|
+
/* test for template (partial) section (nameless) */
|
401
|
+
if (pos->data.start == 0) {
|
402
|
+
section_stack[nesting_pos].end = section_end - start;
|
403
|
+
section_stack[nesting_pos].start = pos - start;
|
404
|
+
section_stack[nesting_pos].index = 1;
|
405
|
+
section_stack[nesting_pos].count = 0;
|
406
|
+
break;
|
407
|
+
}
|
408
|
+
|
409
|
+
/* test for user abort signal and cycle value */
|
410
|
+
int32_t val = mustache_on_section_test(§ion_stack[nesting_pos].sec,
|
411
|
+
data + pos->data.start,
|
412
|
+
strlen(data + pos->data.start));
|
413
|
+
if (val == -1) {
|
414
|
+
if (args.err) {
|
415
|
+
*args.err = MUSTACHE_ERR_USER_ERROR;
|
416
|
+
}
|
417
|
+
goto error;
|
418
|
+
}
|
419
|
+
if (pos->instruction == MUSTACHE_SECTION_START_INV) {
|
420
|
+
if (val == 0) {
|
421
|
+
/* perform once for inverted sections */
|
422
|
+
val = 1;
|
423
|
+
} else {
|
424
|
+
/* or don't perform */
|
425
|
+
val = 0;
|
426
|
+
}
|
427
|
+
}
|
428
|
+
|
429
|
+
if (val == 0) {
|
430
|
+
--nesting_pos;
|
431
|
+
pos = section_end;
|
432
|
+
} else {
|
433
|
+
/* save start/end positions and index counter */
|
434
|
+
section_stack[nesting_pos].end = section_end - start;
|
435
|
+
section_stack[nesting_pos].start = pos - start;
|
436
|
+
section_stack[nesting_pos].index = val;
|
437
|
+
section_stack[nesting_pos].count = 0;
|
438
|
+
if (mustache_on_section_start(§ion_stack[nesting_pos].sec,
|
439
|
+
data + pos->data.start,
|
440
|
+
strlen(data + pos->data.start),
|
441
|
+
section_stack[nesting_pos].count) == -1) {
|
442
|
+
if (args.err) {
|
443
|
+
*args.err = MUSTACHE_ERR_USER_ERROR;
|
444
|
+
}
|
445
|
+
goto error;
|
446
|
+
}
|
447
|
+
}
|
448
|
+
break;
|
449
|
+
}
|
450
|
+
|
451
|
+
case MUSTACHE_SECTION_END:
|
452
|
+
++section_stack[nesting_pos].count;
|
453
|
+
if (section_stack[nesting_pos].index > section_stack[nesting_pos].count) {
|
454
|
+
pos = start + section_stack[nesting_pos].start;
|
455
|
+
if (nesting_pos) { /* revert to old udata values */
|
456
|
+
section_stack[nesting_pos].sec = section_stack[nesting_pos - 1].sec;
|
457
|
+
}
|
458
|
+
if (mustache_on_section_start(§ion_stack[nesting_pos].sec,
|
459
|
+
data + pos->data.start,
|
460
|
+
strlen(data + pos->data.start),
|
461
|
+
section_stack[nesting_pos].count)) {
|
462
|
+
if (args.err) {
|
463
|
+
*args.err = MUSTACHE_ERR_USER_ERROR;
|
464
|
+
}
|
465
|
+
goto error;
|
466
|
+
}
|
467
|
+
} else {
|
468
|
+
pos = start + section_stack[nesting_pos].end; /* in case of recursion */
|
469
|
+
--nesting_pos;
|
470
|
+
}
|
471
|
+
break;
|
472
|
+
|
473
|
+
case MUSTACHE_SECTION_GOTO: {
|
474
|
+
/* used to handle recursive sections and re-occuring partials. */
|
475
|
+
if (nesting_pos + 1 == MUSTACHE_NESTING_LIMIT) {
|
476
|
+
if (args.err) {
|
477
|
+
*args.err = MUSTACHE_ERR_TOO_DEEP;
|
478
|
+
}
|
479
|
+
goto error;
|
480
|
+
}
|
481
|
+
section_stack[nesting_pos + 1].sec = section_stack[nesting_pos].sec;
|
482
|
+
++nesting_pos;
|
483
|
+
if (start[pos->data.len].data.start == 0) {
|
484
|
+
section_stack[nesting_pos].end = pos - start;
|
485
|
+
section_stack[nesting_pos].index = 1;
|
486
|
+
section_stack[nesting_pos].count = 0;
|
487
|
+
section_stack[nesting_pos].start = pos->data.len;
|
488
|
+
pos = start + pos->data.len;
|
489
|
+
break;
|
490
|
+
}
|
491
|
+
int32_t val = mustache_on_section_test(
|
492
|
+
§ion_stack[nesting_pos].sec,
|
493
|
+
data + start[pos->data.len].data.start,
|
494
|
+
strlen(data + start[pos->data.len].data.start));
|
495
|
+
if (val == -1) {
|
496
|
+
if (args.err) {
|
497
|
+
*args.err = MUSTACHE_ERR_USER_ERROR;
|
498
|
+
}
|
499
|
+
goto error;
|
500
|
+
}
|
501
|
+
if (val == 0) {
|
502
|
+
--nesting_pos;
|
503
|
+
} else {
|
504
|
+
/* save start/end positions and index counter */
|
505
|
+
section_stack[nesting_pos].end = pos - start;
|
506
|
+
section_stack[nesting_pos].index = val;
|
507
|
+
section_stack[nesting_pos].count = 0;
|
508
|
+
section_stack[nesting_pos].start = pos->data.len;
|
509
|
+
pos = start + pos->data.len;
|
510
|
+
if (mustache_on_section_start(§ion_stack[nesting_pos].sec,
|
511
|
+
data + pos->data.start,
|
512
|
+
strlen(data + pos->data.start),
|
513
|
+
section_stack[nesting_pos].count) == -1) {
|
514
|
+
if (args.err) {
|
515
|
+
*args.err = MUSTACHE_ERR_USER_ERROR;
|
516
|
+
}
|
517
|
+
goto error;
|
518
|
+
}
|
519
|
+
}
|
520
|
+
} break;
|
521
|
+
default:
|
522
|
+
/* not a valid engine */
|
523
|
+
fprintf(stderr, "ERROR: invalid mustache instruction set detected (wrong "
|
524
|
+
"`mustache_s`?)\n");
|
525
|
+
if (args.err) {
|
526
|
+
*args.err = MUSTACHE_ERR_UNKNOWN;
|
527
|
+
}
|
528
|
+
goto error;
|
529
|
+
}
|
530
|
+
++pos;
|
531
|
+
}
|
532
|
+
|
533
|
+
return 0;
|
534
|
+
error:
|
535
|
+
mustache_on_formatting_error(args.udata1, args.udata2);
|
536
|
+
return -1;
|
537
|
+
}
|
538
|
+
|
539
|
+
/* *****************************************************************************
|
540
|
+
Building the instrustion list (parsing the template)
|
541
|
+
***************************************************************************** */
|
542
|
+
|
543
|
+
/* The parsing implementation, converts a template to an instruction array */
|
544
|
+
MUSTACHE_FUNC mustache_s *(mustache_load)(mustache_load_args_s args) {
|
545
|
+
/* Make sure the args string length is set and prepare the path name */
|
546
|
+
char *path = NULL;
|
547
|
+
uint32_t path_capa = 0;
|
548
|
+
uint32_t path_len = 0;
|
549
|
+
if (args.filename && !args.filename_len) {
|
550
|
+
args.filename_len = strlen(args.filename);
|
551
|
+
}
|
552
|
+
|
553
|
+
/* copy the path data (and resolve) into writable memory */
|
554
|
+
if (args.filename[0] == '~' && args.filename[1] == '/' && getenv("HOME")) {
|
555
|
+
const char *home = getenv("HOME");
|
556
|
+
path_len = strlen(home);
|
557
|
+
path_capa =
|
558
|
+
path_len + 1 + args.filename_len + 1 + 9 + 1; /* + file extension */
|
559
|
+
path = malloc(path_capa);
|
560
|
+
if (!path) {
|
561
|
+
perror("FATAL ERROR: couldn't allocate memory for path resolution");
|
562
|
+
exit(errno);
|
563
|
+
}
|
564
|
+
memcpy(path, home, path_len);
|
565
|
+
if (path[path_len - 1] != '/') {
|
566
|
+
path[path_len++] = '/';
|
567
|
+
}
|
568
|
+
memcpy(path + path_len, args.filename + 2, args.filename_len);
|
569
|
+
args.filename_len += path_len;
|
570
|
+
args.filename = path;
|
571
|
+
}
|
572
|
+
/* divide faile name from the root path to the file */
|
573
|
+
|
574
|
+
/*
|
575
|
+
* We need a dynamic array to hold the list of instructions...
|
576
|
+
* We might as well use the same memory structure as the final product, saving
|
577
|
+
* us an allocation and a copy at the end.
|
578
|
+
*
|
579
|
+
* Allocation starts with 32 instructions.
|
580
|
+
*/
|
581
|
+
struct {
|
582
|
+
mustache_s head; /* instruction array capacity and length */
|
583
|
+
mustache__instruction_s ary[]; /* the instruction array */
|
584
|
+
} *instructions =
|
585
|
+
malloc(sizeof(*instructions) + (32 * sizeof(mustache__instruction_s)));
|
586
|
+
if (!instructions) {
|
587
|
+
perror(
|
588
|
+
"FATAL ERROR: couldn't allocate memory for mustache template parsing");
|
589
|
+
exit(errno);
|
590
|
+
}
|
591
|
+
/* initialize dynamic array */
|
592
|
+
instructions->head.u.read_only.intruction_count = 0;
|
593
|
+
instructions->head.u.read_only.data_length = 32;
|
594
|
+
uint32_t data_len = 0;
|
595
|
+
uint8_t *data = NULL;
|
596
|
+
|
597
|
+
/* We define a dynamic array handling macro, using 32 instruction chunks */
|
598
|
+
#define PUSH_INSTRUCTION(...) \
|
599
|
+
do { \
|
600
|
+
if (instructions->head.u.read_only.intruction_count == \
|
601
|
+
instructions->head.u.read_only.data_length) { \
|
602
|
+
instructions->head.u.read_only.data_length += 32; \
|
603
|
+
instructions = realloc(instructions, \
|
604
|
+
sizeof(*instructions) + \
|
605
|
+
(instructions->head.u.read_only.data_length * \
|
606
|
+
sizeof(mustache__instruction_s))); \
|
607
|
+
if (!instructions) { \
|
608
|
+
perror("FATAL ERROR: couldn't reallocate memory for mustache " \
|
609
|
+
"template path"); \
|
610
|
+
exit(errno); \
|
611
|
+
} \
|
612
|
+
} \
|
613
|
+
instructions->ary[instructions->head.u.read_only.intruction_count++] = \
|
614
|
+
(mustache__instruction_s){__VA_ARGS__}; \
|
615
|
+
} while (0);
|
616
|
+
|
617
|
+
/* a limited local template stack to manage template data "jumps" */
|
618
|
+
/* Note: templates can be recursive. */
|
619
|
+
int32_t stack_pos = 0;
|
620
|
+
struct {
|
621
|
+
uint8_t *delimiter_start; /* currunt instruction start delimiter */
|
622
|
+
uint8_t *delimiter_end; /* currunt instruction end delimiter */
|
623
|
+
uint32_t data_start; /* template starting position (with header) */
|
624
|
+
uint32_t data_pos; /* data reading position (how much was consumed) */
|
625
|
+
uint32_t data_end; /* data ending position (for this template) */
|
626
|
+
uint16_t del_start_len; /* delimiter length (start) */
|
627
|
+
uint16_t del_end_len; /* delimiter length (end) */
|
628
|
+
} template_stack[MUSTACHE_NESTING_LIMIT];
|
629
|
+
template_stack[0].data_start = 0;
|
630
|
+
template_stack[0].data_pos = 0;
|
631
|
+
template_stack[0].data_end = 0;
|
632
|
+
template_stack[0].delimiter_start = (uint8_t *)"{{";
|
633
|
+
template_stack[0].delimiter_end = (uint8_t *)"}}";
|
634
|
+
template_stack[0].del_start_len = 2;
|
635
|
+
template_stack[0].del_end_len = 2;
|
636
|
+
|
637
|
+
/* a section data stack, allowing us to safely mark section closures */
|
638
|
+
int32_t section_depth = 0;
|
639
|
+
struct {
|
640
|
+
/* section name, for closure validation */
|
641
|
+
struct {
|
642
|
+
uint32_t start;
|
643
|
+
uint32_t len;
|
644
|
+
} name;
|
645
|
+
/* position for the section start instruction */
|
646
|
+
uint32_t instruction_pos;
|
647
|
+
} section_stack[MUSTACHE_NESTING_LIMIT];
|
648
|
+
|
649
|
+
#define SECTION2FILENAME() (data + template_stack[stack_pos].data_start + 10)
|
650
|
+
#define SECTION2FLEN() \
|
651
|
+
((((uint8_t *)data + template_stack[stack_pos].data_start + 4)[0] << 1) | \
|
652
|
+
(((uint8_t *)data + template_stack[stack_pos].data_start + 4)[1]))
|
653
|
+
|
654
|
+
/* append a filename to the path, managing the C string memory and length */
|
655
|
+
#define PATH2FULL(folder, folder_len, filename, filename_len) \
|
656
|
+
do { \
|
657
|
+
if (path_capa < (filename_len) + (folder_len) + 9 + 1) { \
|
658
|
+
path_capa = (filename_len) + (folder_len) + 9 + 1; \
|
659
|
+
path = realloc(path, path_capa); \
|
660
|
+
if (!path) { \
|
661
|
+
perror("FATAL ERROR: couldn't allocate memory for path resolution"); \
|
662
|
+
exit(errno); \
|
663
|
+
} \
|
664
|
+
} \
|
665
|
+
if ((folder_len) && (filename)[0] != '/') { \
|
666
|
+
memcpy(path, (folder), (folder_len)); \
|
667
|
+
path_len = (folder_len); \
|
668
|
+
} else { \
|
669
|
+
path_len = 0; \
|
670
|
+
} \
|
671
|
+
if (path != (char *)(filename)) \
|
672
|
+
memcpy(path + path_len, (filename), (filename_len)); \
|
673
|
+
path_len += (filename_len); \
|
674
|
+
path[path_len] = 0; \
|
675
|
+
} while (0);
|
676
|
+
|
677
|
+
/* append a filename to the path, managing the C string memory and length */
|
678
|
+
#define PATH_WITH_EXT() \
|
679
|
+
do { \
|
680
|
+
memcpy(path + path_len, ".mustache", 9); \
|
681
|
+
path[path_len + 9] = 0; /* keep path_len the same */ \
|
682
|
+
} while (0);
|
683
|
+
|
684
|
+
/* We define a dynamic template loading macro to manage memory details */
|
685
|
+
#define LOAD_TEMPLATE(root, root_len, filename, filname_len) \
|
686
|
+
do { \
|
687
|
+
/* find root filename's path start */ \
|
688
|
+
int32_t root_len_tmp = (root_len); \
|
689
|
+
while (root_len_tmp && (((char *)(root))[root_len_tmp - 1] != '/' || \
|
690
|
+
(root_len_tmp > 1 && \
|
691
|
+
((char *)(root))[root_len_tmp - 2] == '\\'))) { \
|
692
|
+
--root_len_tmp; \
|
693
|
+
} \
|
694
|
+
if ((filname_len) + root_len_tmp >= ((uint32_t)1 << 16)) { \
|
695
|
+
*args.err = MUSTACHE_ERR_FILE_NAME_TOO_LONG; \
|
696
|
+
goto error; \
|
697
|
+
} \
|
698
|
+
PATH2FULL((root), root_len_tmp, (filename), (filname_len)); \
|
699
|
+
struct stat f_data; \
|
700
|
+
{ \
|
701
|
+
/* test file name with and without the .mustache extension */ \
|
702
|
+
int stat_result = stat(path, &f_data); \
|
703
|
+
if (stat_result == -1) { \
|
704
|
+
PATH_WITH_EXT(); \
|
705
|
+
stat_result = stat(path, &f_data); \
|
706
|
+
} \
|
707
|
+
if (stat_result == -1) { \
|
708
|
+
if (args.err) { \
|
709
|
+
*args.err = MUSTACHE_ERR_FILE_NOT_FOUND; \
|
710
|
+
} \
|
711
|
+
goto error; \
|
712
|
+
} \
|
713
|
+
} \
|
714
|
+
if (f_data.st_size >= ((uint32_t)1 << 24)) { \
|
715
|
+
*args.err = MUSTACHE_ERR_FILE_TOO_BIG; \
|
716
|
+
goto error; \
|
717
|
+
} \
|
718
|
+
/* the data segment's new length after loading the the template */ \
|
719
|
+
/* The data segments includes a template header: */ \
|
720
|
+
/* | 4 bytes template start instruction position | */ \
|
721
|
+
/* | 2 bytes template name | 4 bytes next template position | */ \
|
722
|
+
/* | template name (filename) | ...[template data]... */ \
|
723
|
+
/* this allows template data to be reused when repeating a template */ \
|
724
|
+
const uint32_t new_len = \
|
725
|
+
data_len + 4 + 2 + 4 + path_len + f_data.st_size + 1; \
|
726
|
+
/* reallocate memory */ \
|
727
|
+
data = realloc(data, new_len); \
|
728
|
+
if (!data) { \
|
729
|
+
perror("FATAL ERROR: couldn't reallocate memory for mustache " \
|
730
|
+
"data segment"); \
|
731
|
+
exit(errno); \
|
732
|
+
} \
|
733
|
+
/* save instruction position length into template header */ \
|
734
|
+
data[data_len + 0] = \
|
735
|
+
(instructions->head.u.read_only.intruction_count >> 3) & 0xFF; \
|
736
|
+
data[data_len + 1] = \
|
737
|
+
(instructions->head.u.read_only.intruction_count >> 2) & 0xFF; \
|
738
|
+
data[data_len + 2] = \
|
739
|
+
(instructions->head.u.read_only.intruction_count >> 1) & 0xFF; \
|
740
|
+
data[data_len + 3] = \
|
741
|
+
(instructions->head.u.read_only.intruction_count) & 0xFF; \
|
742
|
+
/* Add section start marker (to support recursion or repeated partials) */ \
|
743
|
+
PUSH_INSTRUCTION(.instruction = MUSTACHE_SECTION_START); \
|
744
|
+
/* save filename length */ \
|
745
|
+
data[data_len + 4 + 0] = (path_len >> 1) & 0xFF; \
|
746
|
+
data[data_len + 4 + 1] = path_len & 0xFF; \
|
747
|
+
/* save data length ("next" pointer) */ \
|
748
|
+
data[data_len + 4 + 2 + 0] = ((uint32_t)new_len >> 3) & 0xFF; \
|
749
|
+
data[data_len + 4 + 2 + 1] = ((uint32_t)new_len >> 2) & 0xFF; \
|
750
|
+
data[data_len + 4 + 2 + 2] = ((uint32_t)new_len >> 1) & 0xFF; \
|
751
|
+
data[data_len + 4 + 2 + 3] = ((uint32_t)new_len) & 0xFF; \
|
752
|
+
/* copy filename */ \
|
753
|
+
memcpy(data + data_len + 4 + 2 + 4, path, path_len); \
|
754
|
+
/* open file and dump it into the data segment after the new header */ \
|
755
|
+
int fd = open(path, O_RDONLY); \
|
756
|
+
if (fd == -1) { \
|
757
|
+
if (args.err) { \
|
758
|
+
*args.err = MUSTACHE_ERR_FILE_NOT_FOUND; \
|
759
|
+
} \
|
760
|
+
goto error; \
|
761
|
+
} \
|
762
|
+
if (pread(fd, (data + data_len + 4 + 3 + 3 + path_len), f_data.st_size, \
|
763
|
+
0) != (ssize_t)f_data.st_size) { \
|
764
|
+
if (args.err) { \
|
765
|
+
*args.err = MUSTACHE_ERR_FILE_NOT_FOUND; \
|
766
|
+
} \
|
767
|
+
close(fd); \
|
768
|
+
goto error; \
|
769
|
+
} \
|
770
|
+
if (stack_pos + 1 == MUSTACHE_NESTING_LIMIT) { \
|
771
|
+
if (args.err) { \
|
772
|
+
*args.err = MUSTACHE_ERR_TOO_DEEP; \
|
773
|
+
} \
|
774
|
+
close(fd); \
|
775
|
+
goto error; \
|
776
|
+
} \
|
777
|
+
close(fd); \
|
778
|
+
/* increase the data stack pointer and setup new stack frame */ \
|
779
|
+
++stack_pos; \
|
780
|
+
template_stack[stack_pos].data_start = data_len; \
|
781
|
+
template_stack[stack_pos].data_pos = data_len + 4 + 3 + 3 + path_len; \
|
782
|
+
template_stack[stack_pos].data_end = new_len - 1; \
|
783
|
+
template_stack[stack_pos].delimiter_start = (uint8_t *)"{{"; \
|
784
|
+
template_stack[stack_pos].delimiter_end = (uint8_t *)"}}"; \
|
785
|
+
template_stack[stack_pos].del_start_len = 2; \
|
786
|
+
template_stack[stack_pos].del_end_len = 2; \
|
787
|
+
/* update new data segment length and add NUL marker */ \
|
788
|
+
data_len = new_len; \
|
789
|
+
data[new_len - 1] = 0; \
|
790
|
+
} while (0);
|
791
|
+
|
792
|
+
#define IGNORE_WHITESPACE(str, step) \
|
793
|
+
while (isspace(*(str))) { \
|
794
|
+
(str) += (step); \
|
795
|
+
}
|
796
|
+
|
797
|
+
/* Our first template to load is the root template */
|
798
|
+
LOAD_TEMPLATE(NULL, 0, args.filename, args.filename_len);
|
799
|
+
|
800
|
+
/*** As long as the stack has templated to parse - parse the template ***/
|
801
|
+
while (stack_pos) {
|
802
|
+
/* test reading position against template ending and parse */
|
803
|
+
while (template_stack[stack_pos].data_pos <
|
804
|
+
template_stack[stack_pos].data_end) {
|
805
|
+
/* start parsing at current position */
|
806
|
+
uint8_t *const start = data + template_stack[stack_pos].data_pos;
|
807
|
+
/* find the next instruction (beg == beginning) */
|
808
|
+
uint8_t *beg = (uint8_t *)strstr(
|
809
|
+
(char *)start, (char *)template_stack[stack_pos].delimiter_start);
|
810
|
+
if (!beg || beg >= data + template_stack[stack_pos].data_end) {
|
811
|
+
/* no instructions left, only text */
|
812
|
+
PUSH_INSTRUCTION(.instruction = MUSTACHE_WRITE_TEXT,
|
813
|
+
.data = {
|
814
|
+
.start = template_stack[stack_pos].data_pos,
|
815
|
+
.len = template_stack[stack_pos].data_end -
|
816
|
+
template_stack[stack_pos].data_pos,
|
817
|
+
});
|
818
|
+
template_stack[stack_pos].data_pos = template_stack[stack_pos].data_end;
|
819
|
+
continue;
|
820
|
+
}
|
821
|
+
if (beg + template_stack[stack_pos].del_start_len >=
|
822
|
+
data + template_stack[stack_pos].data_end) {
|
823
|
+
/* overshot... ending the template with a delimiter...*/
|
824
|
+
if (args.err) {
|
825
|
+
*args.err = MUSTACHE_ERR_CLOSURE_MISMATCH;
|
826
|
+
}
|
827
|
+
goto error;
|
828
|
+
}
|
829
|
+
beg[0] = 0; /* mark the end of any text segment or string, just in case */
|
830
|
+
/* find the ending of the instruction */
|
831
|
+
uint8_t *end = (uint8_t *)strstr(
|
832
|
+
(char *)beg + template_stack[stack_pos].del_start_len,
|
833
|
+
(char *)template_stack[stack_pos].delimiter_end);
|
834
|
+
if (!end || end >= data + template_stack[stack_pos].data_end) {
|
835
|
+
/* delimiter not closed */
|
836
|
+
if (args.err) {
|
837
|
+
*args.err = MUSTACHE_ERR_CLOSURE_MISMATCH;
|
838
|
+
}
|
839
|
+
goto error;
|
840
|
+
}
|
841
|
+
/* text before instruction? add text instruction */
|
842
|
+
if (beg != data + template_stack[stack_pos].data_pos) {
|
843
|
+
PUSH_INSTRUCTION(.instruction = MUSTACHE_WRITE_TEXT,
|
844
|
+
.data = {
|
845
|
+
.start = template_stack[stack_pos].data_pos,
|
846
|
+
.len = beg -
|
847
|
+
(data + template_stack[stack_pos].data_pos),
|
848
|
+
});
|
849
|
+
}
|
850
|
+
/* update reading position in the stack */
|
851
|
+
template_stack[stack_pos].data_pos =
|
852
|
+
(end - data) + template_stack[stack_pos].del_end_len;
|
853
|
+
|
854
|
+
/* move the beginning marker the the instruction's content */
|
855
|
+
beg += template_stack[stack_pos].del_start_len;
|
856
|
+
|
857
|
+
/* review template instruction (the {{tag}}) */
|
858
|
+
uint8_t escape_str = 1;
|
859
|
+
switch (beg[0]) {
|
860
|
+
case '!':
|
861
|
+
/* comment, do nothing */
|
862
|
+
break;
|
863
|
+
|
864
|
+
case '=':
|
865
|
+
/* define new seperators */
|
866
|
+
++beg;
|
867
|
+
--end;
|
868
|
+
if (end[0] != '=') {
|
869
|
+
if (args.err) {
|
870
|
+
*args.err = MUSTACHE_ERR_CLOSURE_MISMATCH;
|
871
|
+
}
|
872
|
+
goto error;
|
873
|
+
}
|
874
|
+
{
|
875
|
+
uint8_t *div = beg;
|
876
|
+
while (div < end && !isspace(*(char *)div)) {
|
877
|
+
++div;
|
878
|
+
}
|
879
|
+
if (div == end) {
|
880
|
+
if (args.err) {
|
881
|
+
*args.err = MUSTACHE_ERR_CLOSURE_MISMATCH;
|
882
|
+
}
|
883
|
+
goto error;
|
884
|
+
}
|
885
|
+
template_stack[stack_pos].delimiter_start = beg;
|
886
|
+
template_stack[stack_pos].del_start_len = div - beg;
|
887
|
+
div[0] = 0;
|
888
|
+
++div;
|
889
|
+
IGNORE_WHITESPACE(div, 1);
|
890
|
+
template_stack[stack_pos].delimiter_end = div;
|
891
|
+
template_stack[stack_pos].del_end_len = end - div;
|
892
|
+
end[0] = 0;
|
893
|
+
}
|
894
|
+
break;
|
895
|
+
|
896
|
+
case '^': /*overflow*/
|
897
|
+
escape_str = 0;
|
898
|
+
case '#':
|
899
|
+
/* start section (or inverted section) */
|
900
|
+
++beg;
|
901
|
+
--end;
|
902
|
+
IGNORE_WHITESPACE(beg, 1);
|
903
|
+
IGNORE_WHITESPACE(end, -1);
|
904
|
+
end[1] = 0;
|
905
|
+
if (section_depth >= MUSTACHE_NESTING_LIMIT) {
|
906
|
+
if (args.err) {
|
907
|
+
*args.err = MUSTACHE_ERR_CLOSURE_MISMATCH;
|
908
|
+
}
|
909
|
+
goto error;
|
910
|
+
}
|
911
|
+
section_stack[section_depth].instruction_pos =
|
912
|
+
instructions->head.u.read_only.intruction_count;
|
913
|
+
section_stack[section_depth].name.start = beg - data;
|
914
|
+
section_stack[section_depth].name.len = (end - beg) + 1;
|
915
|
+
++section_depth;
|
916
|
+
PUSH_INSTRUCTION(.instruction =
|
917
|
+
(escape_str ? MUSTACHE_SECTION_START
|
918
|
+
: MUSTACHE_SECTION_START_INV),
|
919
|
+
.data = {
|
920
|
+
.start = beg - data,
|
921
|
+
.len = (end - beg) + 1,
|
922
|
+
});
|
923
|
+
break;
|
924
|
+
|
925
|
+
case '>':
|
926
|
+
/* partial template - search data for loaded template or load new */
|
927
|
+
++beg;
|
928
|
+
--end;
|
929
|
+
IGNORE_WHITESPACE(beg, 1);
|
930
|
+
IGNORE_WHITESPACE(end, -1);
|
931
|
+
++end;
|
932
|
+
end[0] = 0;
|
933
|
+
{
|
934
|
+
uint8_t *loaded = data;
|
935
|
+
uint8_t *const data_end = data + data_len;
|
936
|
+
while (loaded < data_end) {
|
937
|
+
uint32_t const fn_len =
|
938
|
+
((loaded[4] & 0xFF) << 1) | (loaded[5] & 0xFF);
|
939
|
+
if (fn_len != end - beg || memcmp(beg, loaded + 10, end - beg)) {
|
940
|
+
uint32_t const next_offset =
|
941
|
+
((loaded[6] & 0xFF) << 3) | ((loaded[7] & 0xFF) << 2) |
|
942
|
+
((loaded[8] & 0xFF) << 1) | (loaded[9] & 0xFF);
|
943
|
+
loaded = data + next_offset;
|
944
|
+
continue;
|
945
|
+
}
|
946
|
+
uint32_t const section_start =
|
947
|
+
((loaded[0] & 0xFF) << 3) | ((loaded[1] & 0xFF) << 2) |
|
948
|
+
((loaded[2] & 0xFF) << 1) | (loaded[3] & 0xFF);
|
949
|
+
PUSH_INSTRUCTION(.instruction = MUSTACHE_SECTION_GOTO,
|
950
|
+
.data = {
|
951
|
+
.len = section_start,
|
952
|
+
});
|
953
|
+
break;
|
954
|
+
}
|
955
|
+
if (loaded >= data_end) {
|
956
|
+
LOAD_TEMPLATE(SECTION2FILENAME(), SECTION2FLEN(), beg, (end - beg));
|
957
|
+
}
|
958
|
+
}
|
959
|
+
break;
|
960
|
+
|
961
|
+
case '/':
|
962
|
+
/* section end */
|
963
|
+
++beg;
|
964
|
+
--end;
|
965
|
+
IGNORE_WHITESPACE(beg, 1);
|
966
|
+
IGNORE_WHITESPACE(end, -1);
|
967
|
+
end[1] = 0;
|
968
|
+
--section_depth;
|
969
|
+
if (!(section_depth + 1) ||
|
970
|
+
(end - beg) + 1 != section_stack[section_depth].name.len ||
|
971
|
+
memcmp(beg, data + section_stack[section_depth].name.start,
|
972
|
+
section_stack[section_depth].name.len)) {
|
973
|
+
if (args.err) {
|
974
|
+
*args.err = MUSTACHE_ERR_CLOSURE_MISMATCH;
|
975
|
+
}
|
976
|
+
goto error;
|
977
|
+
}
|
978
|
+
/* update the section_start instruction with the ending's location */
|
979
|
+
instructions->ary[section_stack[section_depth].instruction_pos]
|
980
|
+
.data.len = instructions->head.u.read_only.intruction_count;
|
981
|
+
/* push sction end instruction */
|
982
|
+
PUSH_INSTRUCTION(.instruction = MUSTACHE_SECTION_END,
|
983
|
+
.data = {
|
984
|
+
.len =
|
985
|
+
section_stack[section_depth].instruction_pos,
|
986
|
+
});
|
987
|
+
break;
|
988
|
+
|
989
|
+
case '{':
|
990
|
+
/* step the read position forward if the ending was '}}}' */
|
991
|
+
if ((data + template_stack[stack_pos].data_pos)[0] == '}') {
|
992
|
+
++template_stack[stack_pos].data_pos;
|
993
|
+
}
|
994
|
+
/*overflow*/
|
995
|
+
case '&': /*overflow*/
|
996
|
+
/* unescaped variable data */
|
997
|
+
escape_str = 0;
|
998
|
+
/* overflow to default */
|
999
|
+
case ':': /*overflow*/
|
1000
|
+
case '<': /*overflow*/
|
1001
|
+
++beg; /*overflow*/
|
1002
|
+
default:
|
1003
|
+
--end;
|
1004
|
+
IGNORE_WHITESPACE(beg, 1);
|
1005
|
+
IGNORE_WHITESPACE(end, -1);
|
1006
|
+
end[1] = 0;
|
1007
|
+
PUSH_INSTRUCTION(.instruction =
|
1008
|
+
(escape_str ? MUSTACHE_WRITE_ARG
|
1009
|
+
: MUSTACHE_WRITE_ARG_UNESCAPED),
|
1010
|
+
.data = {
|
1011
|
+
.start = beg - data,
|
1012
|
+
.len = (end - beg) + 1,
|
1013
|
+
});
|
1014
|
+
break;
|
1015
|
+
}
|
1016
|
+
}
|
1017
|
+
/* templates are treated as sections, allowing for recursion using "goto" */
|
1018
|
+
/* update the template's section_start instruction with the end position */
|
1019
|
+
uint32_t const section_start =
|
1020
|
+
((data[template_stack[stack_pos].data_start + 0] & 0xFF) << 3) |
|
1021
|
+
((data[template_stack[stack_pos].data_start + 1] & 0xFF) << 2) |
|
1022
|
+
((data[template_stack[stack_pos].data_start + 2] & 0xFF) << 1) |
|
1023
|
+
(data[template_stack[stack_pos].data_start + 3] & 0xFF);
|
1024
|
+
instructions->ary[section_start].data.len =
|
1025
|
+
instructions->head.u.read_only.intruction_count;
|
1026
|
+
/* add section end instructiomn for the template section */
|
1027
|
+
PUSH_INSTRUCTION(.instruction = MUSTACHE_SECTION_END,
|
1028
|
+
.data.len = section_start);
|
1029
|
+
/* pop the stack frame */
|
1030
|
+
--stack_pos;
|
1031
|
+
}
|
1032
|
+
/*** done parsing ***/
|
1033
|
+
|
1034
|
+
/* is the template empty?*/
|
1035
|
+
if (!instructions->head.u.read_only.intruction_count) {
|
1036
|
+
if (args.err) {
|
1037
|
+
*args.err = MUSTACHE_ERR_EMPTY_TEMPLATE;
|
1038
|
+
}
|
1039
|
+
goto error;
|
1040
|
+
}
|
1041
|
+
|
1042
|
+
/* We're done making up the instruction list, time to finalize the product */
|
1043
|
+
/* Make room for the String data at the end of the instruction array */
|
1044
|
+
instructions = realloc(instructions,
|
1045
|
+
sizeof(*instructions) +
|
1046
|
+
(instructions->head.u.read_only.intruction_count *
|
1047
|
+
sizeof(mustache__instruction_s)) +
|
1048
|
+
data_len);
|
1049
|
+
if (!instructions) {
|
1050
|
+
perror("FATAL ERROR: couldn't reallocate memory for mustache "
|
1051
|
+
"template finalization");
|
1052
|
+
exit(errno);
|
1053
|
+
}
|
1054
|
+
/* Copy the data segment to the end of the instruction array */
|
1055
|
+
instructions->head.u.read_only.data_length = data_len;
|
1056
|
+
memcpy((void *)((uintptr_t)(instructions + 1) +
|
1057
|
+
(instructions->head.u.read_only.intruction_count *
|
1058
|
+
sizeof(mustache__instruction_s))),
|
1059
|
+
data, data_len);
|
1060
|
+
/* Cleanup, set error code and return. */
|
1061
|
+
free(data);
|
1062
|
+
free(path);
|
1063
|
+
if (args.err) {
|
1064
|
+
*args.err = MUSTACHE_OK;
|
1065
|
+
}
|
1066
|
+
return &instructions->head;
|
1067
|
+
error:
|
1068
|
+
free(instructions);
|
1069
|
+
free(data);
|
1070
|
+
free(path);
|
1071
|
+
return NULL;
|
1072
|
+
|
1073
|
+
#undef PATH2FULL
|
1074
|
+
#undef PATH_WITH_EXT
|
1075
|
+
#undef SECTION2FILENAME
|
1076
|
+
#undef SECTION2FLEN
|
1077
|
+
#undef LOAD_TEMPLATE
|
1078
|
+
#undef PUSH_INSTRUCTION
|
1079
|
+
#undef IGNORE_WHITESPACE
|
1080
|
+
}
|
1081
|
+
|
1082
|
+
#endif /* INCLUDE_MUSTACHE_IMPLEMENTATION */
|
1083
|
+
|
1084
|
+
#undef MUSTACHE_FUNC
|
1085
|
+
#endif /* H_MUSTACHE_LOADR_H */
|