faml 0.6.5 → 0.7.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 +3 -0
- data/CHANGELOG.md +4 -0
- data/ext/attribute_builder/attribute_builder.cc +345 -0
- data/ext/attribute_builder/extconf.rb +2 -1
- data/incompatibilities/README.md +1 -1
- data/lib/faml/version.rb +1 -1
- metadata +4 -3
- data/ext/attribute_builder/attribute_builder.c +0 -366
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbb7777d09caaf6c398326ad22eb8f86a6fb4f2b
|
4
|
+
data.tar.gz: f5ce8d0e1e6f7a0d43e1e153bdf47f2905e147a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 963b5897db86f1fdb168725f38194b5a29ffc5d40b5d4ebae219b19bd4bc7c7b82fd06b1ca23c7636917c71a8a9407c415fcf695505e0b0bb4e3b5ebf1dc9a1b
|
7
|
+
data.tar.gz: 41b0e8980767ae9fad3dcec64dbda59dfbb8a2d40bad2cf3c7545072ac2168bad850511f89c7ce904c6fcfb98765f4a452147d0c4346b88c7354dd2b744dd0d1
|
data/.clang-format
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## 0.7.0 (2015-11-30)
|
2
|
+
- Rewrite AttributeBuilder in C++
|
3
|
+
- It improves performance, memory usage, and readability
|
4
|
+
|
1
5
|
## 0.6.5 (2015-11-28)
|
2
6
|
- Delete falsey values after merging all attributes
|
3
7
|
- `%span(foo=true){foo: false}` now renders `<span></span>`, because old attributes have priority even if the value is falsey.
|
@@ -0,0 +1,345 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <ruby/version.h>
|
3
|
+
#include "houdini.h"
|
4
|
+
#include <algorithm>
|
5
|
+
#include <map>
|
6
|
+
#include <sstream>
|
7
|
+
#include <string>
|
8
|
+
#include <vector>
|
9
|
+
|
10
|
+
/* faml requires Ruby >= 2.0.0 */
|
11
|
+
/* RARRAY_AREF() is available since Ruby 2.1 */
|
12
|
+
#if RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR < 1
|
13
|
+
#define RARRAY_AREF(a, i) RARRAY_PTR(a)[i]
|
14
|
+
#endif
|
15
|
+
/* rb_utf8_str_new() is available since Ruby 2.2 */
|
16
|
+
#if RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR < 2
|
17
|
+
#include <ruby/encoding.h>
|
18
|
+
#define rb_utf8_str_new(ptr, len) rb_enc_str_new(ptr, len, rb_utf8_encoding())
|
19
|
+
#endif
|
20
|
+
|
21
|
+
#define FOREACH_FUNC(func) reinterpret_cast<int (*)(ANYARGS)>(func)
|
22
|
+
|
23
|
+
VALUE rb_mAttributeBuilder;
|
24
|
+
static ID id_flatten;
|
25
|
+
|
26
|
+
static inline std::string string_from_value(VALUE v) {
|
27
|
+
return std::string(RSTRING_PTR(v), RSTRING_LEN(v));
|
28
|
+
}
|
29
|
+
|
30
|
+
enum attribute_value_type {
|
31
|
+
ATTRIBUTE_TYPE_TRUE,
|
32
|
+
ATTRIBUTE_TYPE_FALSE,
|
33
|
+
ATTRIBUTE_TYPE_VALUE,
|
34
|
+
};
|
35
|
+
|
36
|
+
struct attribute_value {
|
37
|
+
attribute_value_type type_;
|
38
|
+
std::string str_;
|
39
|
+
|
40
|
+
attribute_value(attribute_value_type type) : type_(type) {}
|
41
|
+
attribute_value(const char* cstr, long len)
|
42
|
+
: type_(ATTRIBUTE_TYPE_VALUE), str_(cstr, len) {}
|
43
|
+
attribute_value(const std::string& str)
|
44
|
+
: type_(ATTRIBUTE_TYPE_VALUE), str_(str) {}
|
45
|
+
|
46
|
+
static attribute_value from_value(VALUE value) {
|
47
|
+
if (RB_TYPE_P(value, T_TRUE)) {
|
48
|
+
return attribute_value(ATTRIBUTE_TYPE_TRUE);
|
49
|
+
} else if (!RTEST(value)) {
|
50
|
+
return attribute_value(ATTRIBUTE_TYPE_FALSE);
|
51
|
+
} else {
|
52
|
+
value = rb_convert_type(value, T_STRING, "String", "to_s");
|
53
|
+
return attribute_value(string_from_value(value));
|
54
|
+
}
|
55
|
+
}
|
56
|
+
};
|
57
|
+
|
58
|
+
typedef std::map<std::string, attribute_value> attributes_type;
|
59
|
+
|
60
|
+
void upsert_attribute(attributes_type& m, const std::string& key,
|
61
|
+
const attribute_value& value) {
|
62
|
+
const std::pair<attributes_type::iterator, bool> r =
|
63
|
+
m.insert(std::make_pair(key, value));
|
64
|
+
if (!r.second) {
|
65
|
+
r.first->second = value;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
struct attribute_holder {
|
70
|
+
std::vector<attribute_value> ids_, classes_;
|
71
|
+
attributes_type m_;
|
72
|
+
|
73
|
+
inline void upsert(const std::string& key, const attribute_value& value) {
|
74
|
+
return upsert_attribute(m_, key, value);
|
75
|
+
}
|
76
|
+
};
|
77
|
+
|
78
|
+
static VALUE to_value(const attributes_type& m) {
|
79
|
+
VALUE h = rb_hash_new();
|
80
|
+
for (attributes_type::const_iterator it = m.begin(); it != m.end(); ++it) {
|
81
|
+
VALUE k = rb_utf8_str_new(it->first.data(), it->first.size());
|
82
|
+
VALUE v = Qnil;
|
83
|
+
switch (it->second.type_) {
|
84
|
+
case ATTRIBUTE_TYPE_TRUE:
|
85
|
+
v = Qtrue;
|
86
|
+
break;
|
87
|
+
case ATTRIBUTE_TYPE_FALSE:
|
88
|
+
v = Qnil;
|
89
|
+
break;
|
90
|
+
case ATTRIBUTE_TYPE_VALUE:
|
91
|
+
v = rb_utf8_str_new(it->second.str_.data(), it->second.str_.size());
|
92
|
+
break;
|
93
|
+
}
|
94
|
+
rb_hash_aset(h, k, v);
|
95
|
+
}
|
96
|
+
return h;
|
97
|
+
}
|
98
|
+
|
99
|
+
static void concat_array_attribute(std::vector<attribute_value>& ary,
|
100
|
+
VALUE value) {
|
101
|
+
if (RB_TYPE_P(value, T_ARRAY)) {
|
102
|
+
value = rb_funcall(value, id_flatten, 0);
|
103
|
+
} else {
|
104
|
+
value = rb_Array(value);
|
105
|
+
}
|
106
|
+
const long len = RARRAY_LEN(value);
|
107
|
+
for (long i = 0; i < len; i++) {
|
108
|
+
ary.push_back(attribute_value::from_value(RARRAY_AREF(value, i)));
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
static attributes_type normalize_data(VALUE data);
|
113
|
+
|
114
|
+
static int normalize_data_i(VALUE key, VALUE value, VALUE arg) {
|
115
|
+
attributes_type* normalized = reinterpret_cast<attributes_type*>(arg);
|
116
|
+
key = rb_convert_type(key, T_STRING, "String", "to_s");
|
117
|
+
std::string key_ = string_from_value(key);
|
118
|
+
std::replace(key_.begin(), key_.end(), '_', '-');
|
119
|
+
|
120
|
+
if (RB_TYPE_P(value, T_HASH)) {
|
121
|
+
const attributes_type sub = normalize_data(value);
|
122
|
+
for (attributes_type::const_iterator it = sub.begin(); it != sub.end();
|
123
|
+
++it) {
|
124
|
+
upsert_attribute(*normalized, key_ + "-" + it->first, it->second);
|
125
|
+
}
|
126
|
+
} else {
|
127
|
+
upsert_attribute(*normalized, key_, attribute_value::from_value(value));
|
128
|
+
}
|
129
|
+
return ST_CONTINUE;
|
130
|
+
}
|
131
|
+
|
132
|
+
static attributes_type normalize_data(VALUE data) {
|
133
|
+
Check_Type(data, T_HASH);
|
134
|
+
attributes_type m;
|
135
|
+
rb_hash_foreach(data, FOREACH_FUNC(normalize_data_i),
|
136
|
+
reinterpret_cast<VALUE>(&m));
|
137
|
+
return m;
|
138
|
+
}
|
139
|
+
|
140
|
+
static int merge_one_i(VALUE key, VALUE value, VALUE arg) {
|
141
|
+
attribute_holder* attributes = reinterpret_cast<attribute_holder*>(arg);
|
142
|
+
|
143
|
+
key = rb_convert_type(key, T_STRING, "String", "to_s");
|
144
|
+
const std::string key_ = string_from_value(key);
|
145
|
+
if (key_ == "class") {
|
146
|
+
concat_array_attribute(attributes->classes_, value);
|
147
|
+
} else if (key_ == "id") {
|
148
|
+
concat_array_attribute(attributes->ids_, value);
|
149
|
+
} else if (key_ == "data" && RB_TYPE_P(value, T_HASH)) {
|
150
|
+
const attributes_type data = normalize_data(value);
|
151
|
+
for (attributes_type::const_iterator it = data.begin(); it != data.end();
|
152
|
+
++it) {
|
153
|
+
attributes->upsert("data-" + it->first, it->second);
|
154
|
+
}
|
155
|
+
} else {
|
156
|
+
attributes->upsert(key_, attribute_value::from_value(value));
|
157
|
+
}
|
158
|
+
return ST_CONTINUE;
|
159
|
+
}
|
160
|
+
|
161
|
+
static void merge_one(attribute_holder& attributes, VALUE h) {
|
162
|
+
Check_Type(h, T_HASH);
|
163
|
+
rb_hash_foreach(h, FOREACH_FUNC(merge_one_i),
|
164
|
+
reinterpret_cast<VALUE>(&attributes));
|
165
|
+
}
|
166
|
+
|
167
|
+
static void join_class_attribute(attribute_holder& attributes) {
|
168
|
+
const std::vector<attribute_value>& classes = attributes.classes_;
|
169
|
+
std::vector<std::string> ary;
|
170
|
+
|
171
|
+
for (std::vector<attribute_value>::const_iterator it = classes.begin();
|
172
|
+
it != classes.end(); ++it) {
|
173
|
+
switch (it->type_) {
|
174
|
+
case ATTRIBUTE_TYPE_FALSE:
|
175
|
+
break;
|
176
|
+
case ATTRIBUTE_TYPE_TRUE:
|
177
|
+
ary.push_back("true");
|
178
|
+
break;
|
179
|
+
case ATTRIBUTE_TYPE_VALUE:
|
180
|
+
size_t prev = 0, pos;
|
181
|
+
while ((pos = it->str_.find_first_of(' ', prev)) != std::string::npos) {
|
182
|
+
if (pos != prev) {
|
183
|
+
ary.push_back(std::string(it->str_, prev, pos - prev));
|
184
|
+
}
|
185
|
+
prev = pos + 1;
|
186
|
+
}
|
187
|
+
ary.push_back(std::string(it->str_, prev, it->str_.size() - prev));
|
188
|
+
break;
|
189
|
+
}
|
190
|
+
}
|
191
|
+
if (ary.empty()) {
|
192
|
+
return;
|
193
|
+
}
|
194
|
+
|
195
|
+
std::sort(ary.begin(), ary.end());
|
196
|
+
ary.erase(std::unique(ary.begin(), ary.end()), ary.end());
|
197
|
+
std::ostringstream oss;
|
198
|
+
for (std::vector<std::string>::const_iterator it = ary.begin();
|
199
|
+
it != ary.end(); ++it) {
|
200
|
+
if (it != ary.begin()) {
|
201
|
+
oss << ' ';
|
202
|
+
}
|
203
|
+
oss << *it;
|
204
|
+
}
|
205
|
+
attributes.upsert("class", attribute_value(oss.str()));
|
206
|
+
}
|
207
|
+
|
208
|
+
static void join_id_attribute(attribute_holder& attributes) {
|
209
|
+
const std::vector<attribute_value>& ids = attributes.ids_;
|
210
|
+
std::ostringstream oss;
|
211
|
+
bool first = true;
|
212
|
+
|
213
|
+
for (std::vector<attribute_value>::const_iterator it = ids.begin();
|
214
|
+
it != ids.end(); ++it) {
|
215
|
+
switch (it->type_) {
|
216
|
+
case ATTRIBUTE_TYPE_FALSE:
|
217
|
+
break;
|
218
|
+
case ATTRIBUTE_TYPE_TRUE:
|
219
|
+
if (!first) {
|
220
|
+
oss << '_';
|
221
|
+
}
|
222
|
+
oss << "true";
|
223
|
+
first = false;
|
224
|
+
break;
|
225
|
+
case ATTRIBUTE_TYPE_VALUE:
|
226
|
+
if (!first) {
|
227
|
+
oss << '_';
|
228
|
+
}
|
229
|
+
oss << it->str_;
|
230
|
+
first = false;
|
231
|
+
break;
|
232
|
+
}
|
233
|
+
}
|
234
|
+
if (first) {
|
235
|
+
return;
|
236
|
+
}
|
237
|
+
|
238
|
+
attributes.upsert("id", attribute_value(oss.str()));
|
239
|
+
}
|
240
|
+
|
241
|
+
static void delete_falsey_values(attributes_type& m) {
|
242
|
+
for (attributes_type::iterator it = m.begin(); it != m.end();) {
|
243
|
+
if (it->second.type_ == ATTRIBUTE_TYPE_FALSE) {
|
244
|
+
attributes_type::iterator jt = it;
|
245
|
+
++it;
|
246
|
+
m.erase(jt);
|
247
|
+
} else {
|
248
|
+
++it;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
static attributes_type merge(VALUE object_ref, int argc, VALUE* argv) {
|
254
|
+
int i;
|
255
|
+
attribute_holder attributes;
|
256
|
+
|
257
|
+
for (i = 0; i < argc; i++) {
|
258
|
+
merge_one(attributes, argv[i]);
|
259
|
+
}
|
260
|
+
if (!NIL_P(object_ref)) {
|
261
|
+
merge_one(attributes, object_ref);
|
262
|
+
}
|
263
|
+
|
264
|
+
join_class_attribute(attributes);
|
265
|
+
join_id_attribute(attributes);
|
266
|
+
delete_falsey_values(attributes.m_);
|
267
|
+
|
268
|
+
return attributes.m_;
|
269
|
+
}
|
270
|
+
|
271
|
+
static void put_attribute(std::ostringstream& oss,
|
272
|
+
const std::string& attr_quote, const std::string& key,
|
273
|
+
const std::string& value) {
|
274
|
+
oss << " " << key << "=" << attr_quote;
|
275
|
+
|
276
|
+
gh_buf ob = GH_BUF_INIT;
|
277
|
+
if (houdini_escape_html0(&ob, (const uint8_t*)value.data(), value.size(),
|
278
|
+
0)) {
|
279
|
+
oss << std::string(ob.ptr, ob.size);
|
280
|
+
gh_buf_free(&ob);
|
281
|
+
} else {
|
282
|
+
oss << value;
|
283
|
+
}
|
284
|
+
oss << attr_quote;
|
285
|
+
}
|
286
|
+
|
287
|
+
static void build_attribute(std::ostringstream& oss,
|
288
|
+
const std::string& attr_quote, int is_html,
|
289
|
+
const std::string& key,
|
290
|
+
const attribute_value& value) {
|
291
|
+
if (value.type_ == ATTRIBUTE_TYPE_TRUE) {
|
292
|
+
if (is_html) {
|
293
|
+
oss << ' ' << key;
|
294
|
+
} else {
|
295
|
+
put_attribute(oss, attr_quote, key, key);
|
296
|
+
}
|
297
|
+
} else {
|
298
|
+
put_attribute(oss, attr_quote, key, value.str_);
|
299
|
+
}
|
300
|
+
}
|
301
|
+
|
302
|
+
static VALUE m_build(int argc, VALUE* argv, RB_UNUSED_VAR(VALUE self)) {
|
303
|
+
VALUE object_ref;
|
304
|
+
int is_html;
|
305
|
+
|
306
|
+
rb_check_arity(argc, 3, UNLIMITED_ARGUMENTS);
|
307
|
+
Check_Type(argv[0], T_STRING);
|
308
|
+
const std::string attr_quote = string_from_value(argv[0]);
|
309
|
+
is_html = RTEST(argv[1]);
|
310
|
+
object_ref = argv[2];
|
311
|
+
const attributes_type attributes = merge(object_ref, argc - 3, argv + 3);
|
312
|
+
|
313
|
+
std::ostringstream oss;
|
314
|
+
for (attributes_type::const_iterator it = attributes.begin();
|
315
|
+
it != attributes.end(); ++it) {
|
316
|
+
build_attribute(oss, attr_quote, is_html, it->first, it->second);
|
317
|
+
}
|
318
|
+
|
319
|
+
const std::string str = oss.str();
|
320
|
+
return rb_utf8_str_new(str.data(), str.size());
|
321
|
+
}
|
322
|
+
|
323
|
+
static VALUE m_normalize_data(RB_UNUSED_VAR(VALUE self), VALUE data) {
|
324
|
+
return to_value(normalize_data(data));
|
325
|
+
}
|
326
|
+
|
327
|
+
static VALUE m_merge(int argc, VALUE* argv, RB_UNUSED_VAR(VALUE self)) {
|
328
|
+
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
329
|
+
return to_value(merge(argv[0], argc - 1, argv + 1));
|
330
|
+
}
|
331
|
+
|
332
|
+
extern "C" {
|
333
|
+
void Init_attribute_builder(void) {
|
334
|
+
VALUE mFaml = rb_define_module("Faml");
|
335
|
+
rb_mAttributeBuilder = rb_define_module_under(mFaml, "AttributeBuilder");
|
336
|
+
rb_define_singleton_method(rb_mAttributeBuilder, "build",
|
337
|
+
RUBY_METHOD_FUNC(m_build), -1);
|
338
|
+
rb_define_singleton_method(rb_mAttributeBuilder, "normalize_data",
|
339
|
+
RUBY_METHOD_FUNC(m_normalize_data), 1);
|
340
|
+
rb_define_singleton_method(rb_mAttributeBuilder, "merge",
|
341
|
+
RUBY_METHOD_FUNC(m_merge), -1);
|
342
|
+
|
343
|
+
id_flatten = rb_intern("flatten");
|
344
|
+
}
|
345
|
+
};
|
@@ -2,10 +2,11 @@
|
|
2
2
|
require 'mkmf'
|
3
3
|
|
4
4
|
$CFLAGS << ' -Wall -W'
|
5
|
+
$CXXFLAGS << ' -Wall -W -std=c++03'
|
5
6
|
houdini_dir = File.expand_path('../../vendor/houdini', __dir__)
|
6
7
|
$INCFLAGS << " -I#{houdini_dir}"
|
7
8
|
|
8
|
-
$srcs = %w[attribute_builder.
|
9
|
+
$srcs = %w[attribute_builder.cc]
|
9
10
|
%w[
|
10
11
|
buffer.c
|
11
12
|
houdini_html_e.c
|
data/incompatibilities/README.md
CHANGED
data/lib/faml/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: faml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kohei Suzuki
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: escape_utils
|
@@ -299,6 +299,7 @@ extensions:
|
|
299
299
|
- ext/attribute_builder/extconf.rb
|
300
300
|
extra_rdoc_files: []
|
301
301
|
files:
|
302
|
+
- ".clang-format"
|
302
303
|
- ".gitignore"
|
303
304
|
- ".gitmodules"
|
304
305
|
- ".rspec"
|
@@ -320,7 +321,7 @@ files:
|
|
320
321
|
- benchmark/view.haml
|
321
322
|
- benchmark/view.slim
|
322
323
|
- bin/faml
|
323
|
-
- ext/attribute_builder/attribute_builder.
|
324
|
+
- ext/attribute_builder/attribute_builder.cc
|
324
325
|
- ext/attribute_builder/extconf.rb
|
325
326
|
- faml.gemspec
|
326
327
|
- gemfiles/rails_4.0.gemfile
|
@@ -1,366 +0,0 @@
|
|
1
|
-
#include <ruby.h>
|
2
|
-
#include <ruby/encoding.h>
|
3
|
-
#include <ruby/version.h>
|
4
|
-
#include "houdini.h"
|
5
|
-
|
6
|
-
#if (RUBY_API_VERSION_MAJOR > 2) || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 1)
|
7
|
-
/* define nothing */
|
8
|
-
#else
|
9
|
-
# define RARRAY_AREF(a, i) RARRAY_PTR(a)[i]
|
10
|
-
# define rb_ary_new_capa rb_ary_new2
|
11
|
-
#endif
|
12
|
-
|
13
|
-
#define FOREACH_FUNC(func) ((int (*)(ANYARGS))(func))
|
14
|
-
|
15
|
-
VALUE rb_mAttributeBuilder;
|
16
|
-
static ID id_keys, id_sort_bang, id_uniq_bang, id_merge_bang, id_flatten;
|
17
|
-
static ID id_id, id_class, id_underscore, id_hyphen, id_space, id_equal;
|
18
|
-
|
19
|
-
static void
|
20
|
-
concat_array_attribute(VALUE attributes, VALUE hash, VALUE key)
|
21
|
-
{
|
22
|
-
VALUE v;
|
23
|
-
|
24
|
-
Check_Type(hash, T_HASH);
|
25
|
-
v = rb_hash_delete(hash, key);
|
26
|
-
if (!NIL_P(v)) {
|
27
|
-
VALUE ary;
|
28
|
-
|
29
|
-
if (RB_TYPE_P(v, T_ARRAY)) {
|
30
|
-
v = rb_funcall(v, id_flatten, 0);
|
31
|
-
} else {
|
32
|
-
v = rb_Array(v);
|
33
|
-
}
|
34
|
-
ary = rb_hash_lookup(attributes, key);
|
35
|
-
Check_Type(ary, T_ARRAY);
|
36
|
-
rb_ary_concat(ary, v);
|
37
|
-
}
|
38
|
-
}
|
39
|
-
|
40
|
-
static int
|
41
|
-
stringify_keys_i(VALUE key, VALUE value, VALUE arg)
|
42
|
-
{
|
43
|
-
key = rb_convert_type(key, T_STRING, "String", "to_s");
|
44
|
-
rb_hash_aset(arg, key, value);
|
45
|
-
return ST_CONTINUE;
|
46
|
-
}
|
47
|
-
|
48
|
-
static VALUE
|
49
|
-
stringify_keys(VALUE hash)
|
50
|
-
{
|
51
|
-
VALUE h = rb_hash_new();
|
52
|
-
rb_hash_foreach(hash, FOREACH_FUNC(stringify_keys_i), h);
|
53
|
-
return h;
|
54
|
-
}
|
55
|
-
|
56
|
-
struct normalize_data_i2_arg {
|
57
|
-
VALUE key, normalized;
|
58
|
-
};
|
59
|
-
|
60
|
-
static VALUE
|
61
|
-
substitute_underscores(VALUE str)
|
62
|
-
{
|
63
|
-
int frozen;
|
64
|
-
long i, len;
|
65
|
-
|
66
|
-
/* gsub('_', '-') */
|
67
|
-
Check_Type(str, T_STRING);
|
68
|
-
len = RSTRING_LEN(str);
|
69
|
-
frozen = OBJ_FROZEN(str);
|
70
|
-
for (i = 0; i < len; i++) {
|
71
|
-
if (RSTRING_PTR(str)[i] == '_') {
|
72
|
-
if (frozen) {
|
73
|
-
str = rb_str_dup(str);
|
74
|
-
frozen = 0;
|
75
|
-
}
|
76
|
-
rb_str_update(str, i, 1, rb_const_get(rb_mAttributeBuilder, id_hyphen));
|
77
|
-
}
|
78
|
-
}
|
79
|
-
|
80
|
-
return str;
|
81
|
-
}
|
82
|
-
|
83
|
-
static int
|
84
|
-
normalize_data_i2(VALUE key, VALUE value, VALUE ptr)
|
85
|
-
{
|
86
|
-
struct normalize_data_i2_arg *arg = (struct normalize_data_i2_arg *)ptr;
|
87
|
-
VALUE k = rb_str_dup(arg->key);
|
88
|
-
|
89
|
-
rb_str_cat(k, "-", 1);
|
90
|
-
rb_str_append(k, key);
|
91
|
-
rb_hash_aset(arg->normalized, k, value);
|
92
|
-
return ST_CONTINUE;
|
93
|
-
}
|
94
|
-
|
95
|
-
static VALUE normalize_data(VALUE data);
|
96
|
-
|
97
|
-
static int
|
98
|
-
normalize_data_i(VALUE key, VALUE value, VALUE normalized)
|
99
|
-
{
|
100
|
-
key = rb_convert_type(key, T_STRING, "String", "to_s");
|
101
|
-
key = substitute_underscores(key);
|
102
|
-
|
103
|
-
if (RB_TYPE_P(value, T_HASH)) {
|
104
|
-
struct normalize_data_i2_arg arg;
|
105
|
-
arg.key = key;
|
106
|
-
arg.normalized = normalized;
|
107
|
-
rb_hash_foreach(normalize_data(value), FOREACH_FUNC(normalize_data_i2), (VALUE)(&arg));
|
108
|
-
} else if (RB_TYPE_P(value, T_TRUE) || !RTEST(value)) {
|
109
|
-
/* Keep Qtrue and falsey value */
|
110
|
-
rb_hash_aset(normalized, key, value);
|
111
|
-
} else {
|
112
|
-
rb_hash_aset(normalized, key, rb_convert_type(value, T_STRING, "String", "to_s"));
|
113
|
-
}
|
114
|
-
return ST_CONTINUE;
|
115
|
-
}
|
116
|
-
|
117
|
-
static VALUE
|
118
|
-
normalize_data(VALUE data)
|
119
|
-
{
|
120
|
-
VALUE normalized;
|
121
|
-
|
122
|
-
Check_Type(data, T_HASH);
|
123
|
-
normalized = rb_hash_new();
|
124
|
-
rb_hash_foreach(data, FOREACH_FUNC(normalize_data_i), normalized);
|
125
|
-
return normalized;
|
126
|
-
}
|
127
|
-
|
128
|
-
static int
|
129
|
-
put_data_attribute(VALUE key, VALUE val, VALUE hash)
|
130
|
-
{
|
131
|
-
VALUE k = rb_str_buf_new(5 + RSTRING_LEN(key));
|
132
|
-
rb_str_buf_cat(k, "data-", 5);
|
133
|
-
rb_str_buf_append(k, key);
|
134
|
-
rb_hash_aset(hash, k, val);
|
135
|
-
return ST_CONTINUE;
|
136
|
-
}
|
137
|
-
|
138
|
-
static void
|
139
|
-
normalize(VALUE hash)
|
140
|
-
{
|
141
|
-
VALUE keys = rb_funcall(hash, id_keys, 0);
|
142
|
-
const long len = RARRAY_LEN(keys);
|
143
|
-
long i;
|
144
|
-
for (i = 0; i < len; i++) {
|
145
|
-
VALUE key = RARRAY_AREF(keys, i);
|
146
|
-
VALUE value = rb_hash_lookup(hash, key);
|
147
|
-
|
148
|
-
/* key must be String because it is already stringified by stringify_keys */
|
149
|
-
Check_Type(key, T_STRING);
|
150
|
-
if (RB_TYPE_P(value, T_HASH) && RSTRING_LEN(key) == 4 && memcmp(RSTRING_PTR(key), "data", 4) == 0) {
|
151
|
-
VALUE data;
|
152
|
-
|
153
|
-
rb_hash_delete(hash, key);
|
154
|
-
data = normalize_data(value);
|
155
|
-
rb_hash_foreach(data, FOREACH_FUNC(put_data_attribute), hash);
|
156
|
-
} else if (RB_TYPE_P(value, T_TRUE) || !RTEST(value)) {
|
157
|
-
/* Keep Qtrue and falsey value */
|
158
|
-
} else {
|
159
|
-
rb_hash_aset(hash, key, rb_convert_type(value, T_STRING, "String", "to_s"));
|
160
|
-
}
|
161
|
-
}
|
162
|
-
}
|
163
|
-
|
164
|
-
static void
|
165
|
-
merge_one(VALUE attributes, VALUE arg)
|
166
|
-
{
|
167
|
-
VALUE h;
|
168
|
-
|
169
|
-
Check_Type(arg, T_HASH);
|
170
|
-
h = stringify_keys(arg);
|
171
|
-
concat_array_attribute(attributes, h, rb_const_get(rb_mAttributeBuilder, id_class));
|
172
|
-
concat_array_attribute(attributes, h, rb_const_get(rb_mAttributeBuilder, id_id));
|
173
|
-
normalize(h);
|
174
|
-
rb_funcall(attributes, id_merge_bang, 1, h);
|
175
|
-
}
|
176
|
-
|
177
|
-
static void
|
178
|
-
join_class_attribute(VALUE attributes, VALUE key)
|
179
|
-
{
|
180
|
-
long len;
|
181
|
-
VALUE val;
|
182
|
-
|
183
|
-
val = rb_hash_delete(attributes, key);
|
184
|
-
Check_Type(val, T_ARRAY);
|
185
|
-
len = RARRAY_LEN(val);
|
186
|
-
if (len != 0) {
|
187
|
-
long i;
|
188
|
-
VALUE ary = rb_ary_new_capa(len);
|
189
|
-
for (i = 0; i < len; i++) {
|
190
|
-
VALUE v = RARRAY_AREF(val, i);
|
191
|
-
if (RTEST(v)) {
|
192
|
-
rb_ary_concat(ary, rb_str_split(rb_convert_type(v, T_STRING, "String", "to_s"), " "));
|
193
|
-
}
|
194
|
-
}
|
195
|
-
rb_funcall(ary, id_sort_bang, 0);
|
196
|
-
rb_funcall(ary, id_uniq_bang, 0);
|
197
|
-
rb_hash_aset(attributes, key, rb_ary_join(ary, rb_const_get(rb_mAttributeBuilder, id_space)));
|
198
|
-
}
|
199
|
-
}
|
200
|
-
|
201
|
-
static void
|
202
|
-
join_id_attribute(VALUE attributes, VALUE key)
|
203
|
-
{
|
204
|
-
long len;
|
205
|
-
VALUE val;
|
206
|
-
|
207
|
-
val = rb_hash_delete(attributes, key);
|
208
|
-
Check_Type(val, T_ARRAY);
|
209
|
-
len = RARRAY_LEN(val);
|
210
|
-
if (len != 0) {
|
211
|
-
long i;
|
212
|
-
VALUE ary = rb_ary_new_capa(len);
|
213
|
-
for (i = 0; i < len; i++) {
|
214
|
-
VALUE v = RARRAY_AREF(val, i);
|
215
|
-
if (RTEST(v)) {
|
216
|
-
rb_ary_push(ary, rb_convert_type(v, T_STRING, "String", "to_s"));
|
217
|
-
}
|
218
|
-
}
|
219
|
-
rb_hash_aset(attributes, key, rb_ary_join(ary, rb_const_get(rb_mAttributeBuilder, id_underscore)));
|
220
|
-
}
|
221
|
-
}
|
222
|
-
|
223
|
-
static int
|
224
|
-
delete_falsey_values_i(RB_UNUSED_VAR(VALUE key), VALUE value, RB_UNUSED_VAR(VALUE arg))
|
225
|
-
{
|
226
|
-
if (RTEST(value)) {
|
227
|
-
return ST_CONTINUE;
|
228
|
-
} else {
|
229
|
-
return ST_DELETE;
|
230
|
-
}
|
231
|
-
}
|
232
|
-
|
233
|
-
static void
|
234
|
-
delete_falsey_values(VALUE attributes)
|
235
|
-
{
|
236
|
-
rb_hash_foreach(attributes, FOREACH_FUNC(delete_falsey_values_i), Qnil);
|
237
|
-
}
|
238
|
-
|
239
|
-
static VALUE
|
240
|
-
merge(VALUE object_ref, int argc, VALUE *argv)
|
241
|
-
{
|
242
|
-
VALUE attributes, id_str, class_str;
|
243
|
-
int i;
|
244
|
-
|
245
|
-
attributes = rb_hash_new();
|
246
|
-
id_str = rb_const_get(rb_mAttributeBuilder, id_id);
|
247
|
-
class_str = rb_const_get(rb_mAttributeBuilder, id_class);
|
248
|
-
rb_hash_aset(attributes, id_str, rb_ary_new());
|
249
|
-
rb_hash_aset(attributes, class_str, rb_ary_new());
|
250
|
-
|
251
|
-
for (i = 0; i < argc; i++) {
|
252
|
-
merge_one(attributes, argv[i]);
|
253
|
-
}
|
254
|
-
if (!NIL_P(object_ref)) {
|
255
|
-
merge_one(attributes, object_ref);
|
256
|
-
}
|
257
|
-
|
258
|
-
join_class_attribute(attributes, class_str);
|
259
|
-
join_id_attribute(attributes, id_str);
|
260
|
-
delete_falsey_values(attributes);
|
261
|
-
|
262
|
-
return attributes;
|
263
|
-
}
|
264
|
-
|
265
|
-
static void
|
266
|
-
put_attribute(VALUE buf, VALUE attr_quote, VALUE key, VALUE value)
|
267
|
-
{
|
268
|
-
gh_buf ob = GH_BUF_INIT;
|
269
|
-
|
270
|
-
Check_Type(value, T_STRING);
|
271
|
-
if (houdini_escape_html0(&ob, (const uint8_t *)RSTRING_PTR(value), RSTRING_LEN(value), 0)) {
|
272
|
-
value = rb_enc_str_new(ob.ptr, ob.size, rb_utf8_encoding());
|
273
|
-
gh_buf_free(&ob);
|
274
|
-
}
|
275
|
-
|
276
|
-
rb_ary_push(buf, rb_const_get(rb_mAttributeBuilder, id_space));
|
277
|
-
rb_ary_push(buf, key);
|
278
|
-
rb_ary_push(buf, rb_const_get(rb_mAttributeBuilder, id_equal));
|
279
|
-
rb_ary_push(buf, attr_quote);
|
280
|
-
rb_ary_push(buf, value);
|
281
|
-
rb_ary_push(buf, attr_quote);
|
282
|
-
}
|
283
|
-
|
284
|
-
static void
|
285
|
-
build_attribute(VALUE buf, VALUE attr_quote, int is_html, VALUE key, VALUE value)
|
286
|
-
{
|
287
|
-
Check_Type(key, T_STRING);
|
288
|
-
if (RB_TYPE_P(value, T_TRUE)) {
|
289
|
-
if (is_html) {
|
290
|
-
rb_ary_push(buf, rb_const_get(rb_mAttributeBuilder, id_space));
|
291
|
-
rb_ary_push(buf, key);
|
292
|
-
} else {
|
293
|
-
put_attribute(buf, attr_quote, key, key);
|
294
|
-
}
|
295
|
-
} else {
|
296
|
-
put_attribute(buf, attr_quote, key, value);
|
297
|
-
}
|
298
|
-
}
|
299
|
-
|
300
|
-
static VALUE
|
301
|
-
m_build(int argc, VALUE *argv, RB_UNUSED_VAR(VALUE self))
|
302
|
-
{
|
303
|
-
VALUE attr_quote, object_ref, attributes, keys, buf;
|
304
|
-
int is_html;
|
305
|
-
long len, i;
|
306
|
-
|
307
|
-
rb_check_arity(argc, 3, UNLIMITED_ARGUMENTS);
|
308
|
-
attr_quote = argv[0];
|
309
|
-
is_html = RTEST(argv[1]);
|
310
|
-
object_ref = argv[2];
|
311
|
-
attributes = merge(object_ref, argc-3, argv+3);
|
312
|
-
|
313
|
-
keys = rb_funcall(attributes, id_keys, 0);
|
314
|
-
rb_funcall(keys, id_sort_bang, 0);
|
315
|
-
len = RARRAY_LEN(keys);
|
316
|
-
buf = rb_ary_new();
|
317
|
-
for (i = 0; i < len; i++) {
|
318
|
-
VALUE k = RARRAY_AREF(keys, i);
|
319
|
-
build_attribute(buf, attr_quote, is_html, k, rb_hash_lookup(attributes, k));
|
320
|
-
}
|
321
|
-
|
322
|
-
return rb_ary_join(buf, Qnil);
|
323
|
-
}
|
324
|
-
|
325
|
-
static VALUE
|
326
|
-
m_normalize_data(RB_UNUSED_VAR(VALUE self), VALUE data)
|
327
|
-
{
|
328
|
-
return normalize_data(data);
|
329
|
-
}
|
330
|
-
|
331
|
-
static VALUE
|
332
|
-
m_merge(int argc, VALUE *argv, RB_UNUSED_VAR(VALUE self))
|
333
|
-
{
|
334
|
-
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
335
|
-
return merge(argv[0], argc-1, argv+1);
|
336
|
-
}
|
337
|
-
|
338
|
-
void
|
339
|
-
Init_attribute_builder(void)
|
340
|
-
{
|
341
|
-
VALUE mFaml = rb_define_module("Faml");
|
342
|
-
rb_mAttributeBuilder = rb_define_module_under(mFaml, "AttributeBuilder");
|
343
|
-
rb_define_singleton_method(rb_mAttributeBuilder, "build", RUBY_METHOD_FUNC(m_build), -1);
|
344
|
-
rb_define_singleton_method(rb_mAttributeBuilder, "normalize_data", RUBY_METHOD_FUNC(m_normalize_data), 1);
|
345
|
-
rb_define_singleton_method(rb_mAttributeBuilder, "merge", RUBY_METHOD_FUNC(m_merge), -1);
|
346
|
-
|
347
|
-
id_keys = rb_intern("keys");
|
348
|
-
id_sort_bang = rb_intern("sort!");
|
349
|
-
id_uniq_bang = rb_intern("uniq!");
|
350
|
-
id_merge_bang = rb_intern("merge!");
|
351
|
-
id_flatten = rb_intern("flatten");
|
352
|
-
|
353
|
-
id_id = rb_intern("ID");
|
354
|
-
id_class = rb_intern("CLASS");
|
355
|
-
id_underscore = rb_intern("UNDERSCORE");
|
356
|
-
id_hyphen = rb_intern("HYPHEN");
|
357
|
-
id_space = rb_intern("SPACE");
|
358
|
-
id_equal = rb_intern("EQUAL");
|
359
|
-
|
360
|
-
rb_const_set(rb_mAttributeBuilder, id_id, rb_obj_freeze(rb_str_new_cstr("id")));
|
361
|
-
rb_const_set(rb_mAttributeBuilder, id_class, rb_obj_freeze(rb_str_new_cstr("class")));
|
362
|
-
rb_const_set(rb_mAttributeBuilder, id_underscore, rb_obj_freeze(rb_str_new_cstr("_")));
|
363
|
-
rb_const_set(rb_mAttributeBuilder, id_hyphen, rb_obj_freeze(rb_str_new_cstr("-")));
|
364
|
-
rb_const_set(rb_mAttributeBuilder, id_space, rb_obj_freeze(rb_str_new_cstr(" ")));
|
365
|
-
rb_const_set(rb_mAttributeBuilder, id_equal, rb_obj_freeze(rb_str_new_cstr("=")));
|
366
|
-
}
|