stash 1.0.0 → 2.0.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.
@@ -0,0 +1,208 @@
1
+ #include "intern.h"
2
+
3
+ /* forward declare helper functions */
4
+ static bool contains_node_type(mustache_node_t* root, enum mustache_node_type type);
5
+ static void remove_node(mustache_node_t* node);
6
+ static bool is_posfix_standalone(mustache_node_t* node);
7
+ bool is_whitespace(mustache_node_t* text_node);
8
+
9
+ bool optimize_block(mustache_node_t* node)
10
+ {
11
+ mustache_node_t* child = node->first_child;
12
+ while (child) {
13
+ if (!child->optimize(child)) {
14
+ return false;
15
+ }
16
+ child = child->next_sibling;
17
+ }
18
+ return true;
19
+ }
20
+
21
+ bool optimize_text(mustache_node_t* node)
22
+ {
23
+ // Remove indentation from starting standalone tags
24
+ if ((!node->prv_sibling || node->prv_sibling->type == NODE_NEWLINE) &&
25
+ node->next_sibling &&
26
+ (node->next_sibling->type == NODE_SECTION || node->next_sibling->type == NODE_INVERTED_SECTION) &&
27
+ contains_node_type(node->next_sibling->first_child, NODE_NEWLINE) &&
28
+ is_whitespace(node)) {
29
+ remove_node(node);
30
+ }
31
+
32
+ // Remove indentation from ending standalone tags if beginning tag was intented
33
+ if (!node->next_sibling &&
34
+ node->prv_sibling &&
35
+ node->prv_sibling->type == NODE_NEWLINE &&
36
+ node->parent &&
37
+ node->parent->parent &&
38
+ (node->parent->parent->type == NODE_SECTION || node->parent->parent->type == NODE_INVERTED_SECTION) &&
39
+ (!node->parent->parent->next_sibling || (node->parent->parent->prv_sibling && is_whitespace(node->parent->parent->prv_sibling))) &&
40
+ is_whitespace(node)) {
41
+ remove_node(node);
42
+ }
43
+
44
+ return true;
45
+ }
46
+
47
+ bool optimize_newline(mustache_node_t* node)
48
+ {
49
+ // Remove newlines after starting standalone section tags
50
+ if (!node->prv_sibling &&
51
+ node->parent &&
52
+ node->parent->parent &&
53
+ (node->parent->parent->type == NODE_SECTION || node->parent->parent->type == NODE_INVERTED_SECTION)&&
54
+ (!node->parent->parent->prv_sibling || is_whitespace(node->parent->parent->prv_sibling))) {
55
+ remove_node(node);
56
+ }
57
+
58
+ // Remove newlines after ending standalone section tags
59
+ if (node->prv_sibling &&
60
+ (node->prv_sibling->type == NODE_SECTION || node->prv_sibling->type == NODE_INVERTED_SECTION) &&
61
+ (!node->prv_sibling->first_child->first_child || contains_node_type(node->prv_sibling->first_child, NODE_NEWLINE))) {
62
+ remove_node(node);
63
+ }
64
+ return true;
65
+ }
66
+
67
+ bool optimize_variable(mustache_node_t* node)
68
+ {
69
+ return true;
70
+ }
71
+
72
+ bool optimize_section(mustache_node_t* node)
73
+ {
74
+ return node->first_child->optimize(node->first_child);
75
+ }
76
+
77
+ bool optimize_comment(mustache_node_t* node)
78
+ {
79
+ // Remove whitespace from standalone comments.
80
+ // Comments that satisfy any of the following conditions are considered standalone:
81
+ // 1. "{{!comment}}"
82
+ // 2. "{{!comment}} "
83
+ // 3. "{{!comment}}\n"
84
+ // 4. "{{!comment}} \n"
85
+ // 5. " {{!comment}}"
86
+ // 6. " {{!comment}} "
87
+ // 7. " {{!comment}}\n"
88
+ // 8. " {{!comment}} \n"
89
+ // 9. "\n{{!comment}}"
90
+ // 10. "\n{{!comment}} "
91
+ // 11. "\n{{!comment}}\n"
92
+ // 12. "\n{{!comment}} \n"
93
+ // 13. "\n {{!comment}}"
94
+ // 14. "\n {{!comment}} "
95
+ // 15. "\n {{!comment}}\n"
96
+ // 16. "\n {{!comment}} \n"
97
+
98
+ bool is_standalone = false;
99
+ mustache_node_t* tmp = NULL;
100
+ mustache_node_t* prv = node->prv_sibling;
101
+ mustache_node_t* next = node->next_sibling;
102
+ if (!prv) {
103
+ is_standalone = is_posfix_standalone(node);
104
+ } else if (is_whitespace(prv)) {
105
+ if (!prv->prv_sibling &&
106
+ (!node->parent || !node->parent->parent || !(node->parent->parent->type == NODE_SECTION || node->parent->parent->type == NODE_INVERTED_SECTION))) {
107
+ is_standalone = is_posfix_standalone(node);
108
+ } else if (prv->type == NODE_NEWLINE) {
109
+ is_standalone = is_posfix_standalone(node);
110
+ } else if (prv->prv_sibling && prv->prv_sibling->type == NODE_NEWLINE) {
111
+ is_standalone = is_posfix_standalone(node);
112
+ }
113
+ }
114
+
115
+ // if the comment is standalone, remove the unnessesary nodes.
116
+ if (is_standalone) {
117
+ if (prv && prv->type == NODE_TEXT && node->type == NODE_COMMENT) {
118
+ remove_node(prv);
119
+ }
120
+ if (next && next->type == NODE_TEXT) {
121
+ tmp = next->next_sibling;
122
+ remove_node(next);
123
+ next = tmp;
124
+ }
125
+ while (next && next->type == NODE_NEWLINE) {
126
+ tmp = next->next_sibling;
127
+ remove_node(next);
128
+ next = tmp;
129
+ }
130
+ }
131
+ return true;
132
+ }
133
+
134
+ bool optimize_partial(mustache_node_t* node)
135
+ {
136
+ return optimize_comment(node);
137
+ }
138
+
139
+
140
+ /* helper functions */
141
+
142
+ static bool contains_node_type(mustache_node_t* root, enum mustache_node_type type)
143
+ {
144
+ if (root->type == type) {
145
+ return true;
146
+ }
147
+
148
+ if ((root->first_child && contains_node_type(root->first_child, type)) ||
149
+ (root->next_sibling && contains_node_type(root->next_sibling, type))) {
150
+ return true;
151
+ }
152
+
153
+ return false;
154
+ }
155
+
156
+ bool is_whitespace(mustache_node_t* text_node)
157
+ {
158
+ if (text_node->type == NODE_NEWLINE) {
159
+ return true;
160
+ }
161
+
162
+ if (text_node->type != NODE_TEXT) {
163
+ return false;
164
+ }
165
+
166
+ char* text = text_node->token->text;
167
+ while (text[0]) {
168
+ if (!isspace(text[0])) {
169
+ return false;
170
+ }
171
+ text++;
172
+ }
173
+ return true;
174
+ }
175
+
176
+ static void remove_node(mustache_node_t* node)
177
+ {
178
+ if (!node) {
179
+ return;
180
+ }
181
+
182
+ mustache_node_t* prv = node->prv_sibling;
183
+ mustache_node_t* next = node->next_sibling;
184
+
185
+ if (prv) {
186
+ prv->next_sibling = next;
187
+ } else {
188
+ node->parent->first_child = next;
189
+ }
190
+
191
+ if (next) {
192
+ next->prv_sibling = prv;
193
+ }
194
+
195
+ // TODO: free node
196
+ // TODO: free node's children
197
+ }
198
+
199
+ static bool is_posfix_standalone(mustache_node_t* node)
200
+ {
201
+ mustache_node_t* next = node->next_sibling;
202
+ if (!next || next->type == NODE_NEWLINE) {
203
+ return true;
204
+ } else if (next->type == NODE_TEXT && is_whitespace(next)) {
205
+ return (!next->next_sibling || next->next_sibling->type == NODE_NEWLINE);
206
+ }
207
+ return false;
208
+ }
@@ -0,0 +1,177 @@
1
+ #include <ruby.h>
2
+ #include <sys/time.h>
3
+ #include <sys/resource.h>
4
+ #include "mustache.h"
5
+ #include "stash.h"
6
+
7
+
8
+
9
+ extern void log_template_execution_time(mustache_context_t* m_ctx, double time);
10
+ extern double get_time();
11
+
12
+ VALUE cTemplate = Qnil;
13
+
14
+ static VALUE cTemplate_init(VALUE self, VALUE rb_template_string)
15
+ {
16
+ mustache_node_t* c_template;
17
+ Data_Get_Struct(self, mustache_node_t, c_template);
18
+
19
+ char* c_template_string;
20
+ long c_template_string_length;
21
+ mustache_error_t* error = NULL;
22
+
23
+ c_template_string = strdup(rb_str2cstr(rb_template_string, &c_template_string_length));
24
+ mustache_build_template(c_template_string, c_template, &error);
25
+
26
+ if (error) {
27
+ rb_raise(rb_eSyntaxError, error->error_text);
28
+ }
29
+
30
+ return self;
31
+ }
32
+
33
+ static VALUE cTemplate_render(int argc, VALUE* argv, VALUE self)
34
+ {
35
+ VALUE data = Qnil;
36
+ VALUE partials = Qnil;
37
+ VALUE debug = Qnil;
38
+
39
+ if (argc > 0) {
40
+ VALUE type = TYPE(argv[0]);
41
+ if (type != T_OBJECT && type != T_HASH) {
42
+ rb_raise(rb_eArgError, "Expected an object or hash as argument 1");
43
+ return Qnil;
44
+ }
45
+ data = argv[0];
46
+ }
47
+
48
+ if (argc > 1) {
49
+ VALUE type = TYPE(argv[1]);
50
+ if (type != T_HASH) {
51
+ rb_raise(rb_eArgError, "Expected a hash as argument 2");
52
+ return Qnil;
53
+ }
54
+ partials = argv[1];
55
+ }
56
+
57
+ if (argc > 2) {
58
+ if (strcmp(rb_obj_classname(argv[2]), "Template::Debug") != 0) {
59
+ rb_raise(rb_eArgError, "Expected an instance of Template::Debug as argument 3");
60
+ return Qnil;
61
+ }
62
+ debug = argv[2];
63
+ }
64
+
65
+ // Get the template
66
+ mustache_node_t* c_template;
67
+ Data_Get_Struct(self, mustache_node_t, c_template);
68
+
69
+ // Create the context
70
+ mustache_context_t* m_ctx = create_mustache_context(NULL);
71
+ mustache_ruby_context_t* r_ctx = (mustache_ruby_context_t*) m_ctx->custom;
72
+ m_ctx->data = (void*) data;
73
+ m_ctx->partials = (void*) partials;
74
+ m_ctx->debug_mode = debug != Qnil;
75
+
76
+ double start = 0;
77
+ if (m_ctx->debug_mode) {
78
+ start = get_time();
79
+ r_ctx->debugObject = debug;
80
+ }
81
+
82
+ // Execute the template
83
+ mustache_execute_template(c_template, m_ctx);
84
+
85
+ if (m_ctx->debug_mode) {
86
+ log_template_execution_time(m_ctx, get_time() - start);
87
+ }
88
+
89
+ // Return the buffer
90
+ VALUE result = rb_str_resize(r_ctx->buffer, r_ctx->buffer_length);
91
+ m_ctx->destroy(m_ctx);
92
+ return result;
93
+ }
94
+
95
+ static void template_mark(mustache_node_t* node)
96
+ {
97
+ }
98
+
99
+ static void template_free(mustache_node_t* node)
100
+ {
101
+ }
102
+
103
+ static VALUE template_allocate(VALUE klass)
104
+ {
105
+ mustache_node_t* c_template = mustache_create_node();
106
+ return Data_Wrap_Struct(cTemplate, template_mark, template_free, c_template);
107
+ }
108
+
109
+ void Init_stash()
110
+ {
111
+ // Define the Template class
112
+ cTemplate = rb_define_class("Template", rb_cObject);
113
+ rb_define_alloc_func(cTemplate, template_allocate);
114
+ rb_define_method(cTemplate, "initialize", cTemplate_init, 1);
115
+ rb_define_method(cTemplate, "render", cTemplate_render, -1);
116
+ }
117
+
118
+ bool mustache_write_to_buffer(mustache_context_t* m_ctx, char* data, size_t data_length)
119
+ {
120
+ if (data_length == 0) {
121
+ return true;
122
+ }
123
+
124
+ mustache_ruby_context_t* r_ctx = (mustache_ruby_context_t*) m_ctx->custom;
125
+ rb_str_buf_cat(r_ctx->buffer, data, data_length);
126
+ r_ctx->buffer_length += data_length;
127
+ return true;
128
+ }
129
+
130
+ void* mustache_malloc(long size)
131
+ {
132
+ return ruby_xmalloc(size);
133
+ }
134
+
135
+ void mustache_free(void* ptr)
136
+ {
137
+ ruby_xfree(ptr);
138
+ }
139
+
140
+ mustache_node_t* get_partial(char* key, void* partials)
141
+ {
142
+ VALUE rb_partials = (VALUE) partials;
143
+ Check_Type(rb_partials, T_HASH);
144
+
145
+ VALUE rb_value;
146
+ if (NIL_P(rb_value = rb_hash_aref(rb_partials, ID2SYM(rb_intern(key))))) {
147
+ return NULL;
148
+ }
149
+
150
+ if (rb_class_of(rb_value) != cTemplate) {
151
+ rb_raise(rb_eTypeError, "Expected Template");
152
+ }
153
+
154
+ mustache_node_t* template = NULL;
155
+ Data_Get_Struct(rb_value, mustache_node_t, template);
156
+ return template;
157
+ }
158
+
159
+ double get_time()
160
+ {
161
+ struct timeval t;
162
+ struct timezone tzp;
163
+ gettimeofday(&t, &tzp);
164
+ return t.tv_sec + t.tv_usec*1e-6;
165
+ }
166
+
167
+ void log_template_execution_time(mustache_context_t* m_ctx, double time)
168
+ {
169
+ mustache_ruby_context_t* r_ctx = (mustache_ruby_context_t*) m_ctx->custom;
170
+ rb_ivar_set(r_ctx->debugObject, rb_intern("@total_time"), rb_float_new(time));
171
+ }
172
+
173
+ void log_ruby_callback_execution_time(mustache_context_t* m_ctx, ID callback_name, double time)
174
+ {
175
+ mustache_ruby_context_t* r_ctx = (mustache_ruby_context_t*) m_ctx->custom;
176
+ rb_funcall(r_ctx->debugObject, rb_intern("log_callback"), 2, ID2SYM(callback_name), rb_float_new(time));
177
+ }
@@ -0,0 +1,17 @@
1
+ typedef struct mustache_ruby_context {
2
+ VALUE buffer;
3
+ long buffer_length;
4
+ VALUE debugObject;
5
+ } mustache_ruby_context_t;
6
+
7
+ mustache_context_t* create_mustache_context(mustache_context_t* parent);
8
+
9
+ mustache_value_t* convert_type(
10
+ char* key,
11
+ void* context,
12
+ mustache_context_t* m_lookup_context,
13
+ mustache_context_t* m_exection_context);
14
+
15
+ mustache_node_t* get_partial(char* key, void* partials);
16
+ void log_ruby_callback_execution_time(mustache_context_t* m_ctx, ID callback_name, double time);
17
+ double get_time();
@@ -0,0 +1,151 @@
1
+ #include "intern.h"
2
+
3
+ void set_start_delimiter(char* delimiter, scanner_data_t* data)
4
+ {
5
+ data->statement_start = strdup(delimiter);
6
+ }
7
+
8
+ void use_next_end_delimiter(scanner_data_t* data)
9
+ {
10
+ data->statement_end = data->next_statement_end;
11
+ data->next_statement_end = "";
12
+ }
13
+
14
+ void set_next_end_delimiter(char* delimiter, scanner_data_t* data)
15
+ {
16
+ data->statement_end = strdup(delimiter);
17
+ }
18
+
19
+ mustache_token_t* pop_token(mustache_token_t** head)
20
+ {
21
+ mustache_token_t* token = *head;
22
+ *head = token->next;
23
+ return token;
24
+ }
25
+
26
+ void reverse(mustache_token_t** head)
27
+ {
28
+ mustache_token_t* prv = NULL;
29
+ mustache_token_t* current = *head;
30
+
31
+ while (current) {
32
+ mustache_token_t* next = current->next;
33
+ current->next = prv;
34
+ prv = current;
35
+ current = next;
36
+ }
37
+
38
+ *head = prv;
39
+ }
40
+
41
+ bool is_token(mustache_token_t* head, enum mustache_token_type type)
42
+ {
43
+ if (!head) {
44
+ return false;
45
+ }
46
+
47
+ return head->type == type;
48
+ }
49
+
50
+ void free_token(mustache_token_t* token)
51
+ {
52
+ if (!token) {
53
+ return;
54
+ }
55
+
56
+ if (*token->text) {
57
+ free(token->text);
58
+ }
59
+
60
+ free(token);
61
+ }
62
+
63
+ bool is_string_in_buffer(char* string, int string_len, char* buffer, int buffer_len)
64
+ {
65
+ if (buffer_len < string_len) {
66
+ return false;
67
+ }
68
+
69
+ return strncmp(&buffer[buffer_len - string_len], string, string_len) == 0;
70
+ }
71
+
72
+ mustache_token_t* create_token(enum mustache_token_type type, char* text)
73
+ {
74
+ mustache_token_t* token = malloc(sizeof(mustache_token_t));
75
+ char* copied_text = strdup(text);
76
+
77
+ if (token) {
78
+ token->type = type;
79
+ token->text = copied_text;
80
+ token->next = NULL;
81
+ }
82
+
83
+ return token;
84
+ }
85
+
86
+ void push_token(enum mustache_token_type type, char* text, yyscan_t yyscanner)
87
+ {
88
+ scanner_data_t* data = (scanner_data_t*) yyget_extra(yyscanner);
89
+ mustache_token_t* token = create_token(type, text);
90
+
91
+ if (data->head_token) {
92
+ token->next = data->head_token;
93
+ }
94
+
95
+ data->head_token = token;
96
+ }
97
+
98
+ void push_text(yyscan_t yyscanner)
99
+ {
100
+ // Get the accumulated text
101
+ scanner_data_t* data = (scanner_data_t*) yyget_extra(yyscanner);
102
+
103
+ // If the buffer contains text, push the token and reset buffer
104
+ if (data->buffer[0]) {
105
+ push_token(TOKEN_TEXT, data->buffer, yyscanner);
106
+ memset(data->buffer, '\0', data->buffer_size);
107
+ data->buffer_index = 0;
108
+ }
109
+ }
110
+
111
+ void push_comment(yyscan_t yyscanner)
112
+ {
113
+ // Get the accumulated text
114
+ scanner_data_t* data = (scanner_data_t*) yyget_extra(yyscanner);
115
+
116
+ // If the buffer contains text, push the token and reset buffer
117
+ if (data->buffer[0]) {
118
+ push_token(TOKEN_COMMENT, data->buffer, yyscanner);
119
+ memset(data->buffer, '\0', data->buffer_size);
120
+ data->buffer_index = 0;
121
+ }
122
+ }
123
+
124
+ void push_error(yyscan_t yyscanner)
125
+ {
126
+ return;
127
+ }
128
+
129
+ int get_tokens(char* input, mustache_token_t** tokens)
130
+ {
131
+ yyscan_t scanner;
132
+ scanner_data_t data;
133
+ data.head_token = NULL;
134
+ data.buffer_size = sizeof(char) * strlen(input) + 1;
135
+ data.buffer = malloc(data.buffer_size);
136
+ memset(data.buffer, '\0', data.buffer_size);
137
+ data.buffer_index = 0;
138
+ data.statement_start = "{{";
139
+ data.statement_end = "}}";
140
+ data.next_statement_end = "";
141
+
142
+ yylex_init_extra(&data, &scanner);
143
+ yy_scan_string(input, scanner);
144
+ yylex(scanner);
145
+ yylex_destroy(scanner);
146
+ *tokens = data.head_token;
147
+ free(data.buffer);
148
+ reverse(tokens);
149
+
150
+ return true;
151
+ }