usamin 7.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 38c48b49c05c5a95289abb777b32db4b47252fd31c5553acf09b11f3c076b500
4
+ data.tar.gz: 023641ec4fb9fc7277044ae4c6a1dcca134ace8ed4aaddf2b64c80a4fb7ef89e
5
+ SHA512:
6
+ metadata.gz: 493e984049b444963a1be108cc624bc6a426edbf3c951c860f4662f4f3121e309e1768be11a919a16353c36fc96fb90124daf68bb68ec8f5756a82850c4a7618
7
+ data.tar.gz: 74cfe2c5148808a6be7f43a6bddf5ec1306ee3692a7833ea775392b61948fee537fc83befe55d0961d5ff3771e43a91d208294d2a8b4c41c8df20c738976cb5a
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.bundle
10
+ *.so
11
+ *.o
12
+ *.a
13
+ mkmf.log
14
+
15
+ .DS_Store
16
+ /testdata/
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in usamin.gemspec
6
+ gemspec
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ usamin (7.7.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.2)
10
+ method_source (0.9.0)
11
+ pry (0.11.3)
12
+ coderay (~> 1.1.0)
13
+ method_source (~> 0.9.0)
14
+ rake (12.3.0)
15
+ rake-compiler (1.0.4)
16
+ rake
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ bundler (~> 1.16)
23
+ pry (~> 0.11)
24
+ rake (~> 12.3)
25
+ rake-compiler
26
+ usamin!
27
+
28
+ BUNDLED WITH
29
+ 1.16.1
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Ishotihadus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,255 @@
1
+ # Usamin
2
+
3
+ A fast JSON serializer / deserializer for Ruby with [RapidJSON](http://rapidjson.org/).
4
+
5
+ The name of "Usamin" is derived from [Nana Abe](https://www.project-imas.com/wiki/Nana_Abe).
6
+
7
+ ## Installation
8
+
9
+ Install RapidJSON beforehand.
10
+
11
+ Next, add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'usamin'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install usamin
24
+
25
+ The directory of RapidJSON can be explicitly specified with `--with-rapidjson-dir` option.
26
+
27
+ $ gem install usamin -- --with-rapidjson-dir=/usr/local/opt/rapidjson
28
+
29
+ ## Usage
30
+
31
+ ### Loading library
32
+
33
+ ```ruby
34
+ require 'usamin'
35
+ ```
36
+
37
+ ### Parsing
38
+
39
+ ```ruby
40
+ json = '{
41
+ "miku maekawa": {
42
+ "age": 15,
43
+ "height": 152,
44
+ "weight": 45,
45
+ "body_size": [85, 55, 81],
46
+ "birthday": [2, 22],
47
+ "cv": "natsumi takamori"
48
+ },
49
+ "nana abe": {
50
+ "age": 17,
51
+ "height": 146,
52
+ "weight": 40,
53
+ "body_size": [84, 57, 84],
54
+ "birthday": [5, 15],
55
+ "cv": "marie miyake"
56
+ }
57
+ }'
58
+
59
+ Usamin.parse(json)
60
+ ```
61
+
62
+ ### Lazy loading
63
+
64
+ ```ruby
65
+ data = Usamin.load(json)
66
+ ```
67
+
68
+ Here, `data` is not a Hash, but this can be handled like a Hash.
69
+
70
+ ```ruby
71
+ data.keys
72
+ #=> ["miku maekawa", "nana abe"]
73
+
74
+ data.map{|k, v| [k, v['age']]}.to_h
75
+ #=> {"miku maekawa"=>15, "nana abe"=>17}
76
+ ```
77
+
78
+ Array data also can be handled like an Array object.
79
+
80
+ ```ruby
81
+ data['nana abe']['body_size'].size
82
+ #=> 3
83
+
84
+ data['nana abe']['body_size'].inject(:+)
85
+ #=> 225
86
+ ```
87
+
88
+ The `eval` and `eval_r` converts these data structures into the Ruby data structures. `_r` means recursiveness.
89
+
90
+ ```ruby
91
+ data.eval
92
+ #=> {"miku maekawa"=>#<Usamin::Hash>, "nana abe"=>#<Usamin::Hash>}
93
+
94
+ data['miku maekawa'].eval_r
95
+ #=> {"age"=>15, "height"=>152, "weight"=>45, "body_size"=>[85, 55, 81], "birthday"=>[2, 22], "cv"=>"natsumi takamori"}
96
+
97
+ data.eval_r
98
+ #=> same as Usamin.parse(json)
99
+ ```
100
+
101
+ #### Notes about lazy loading data
102
+
103
+ - Frozen. Modification is not allowed.
104
+ - A key list of Hash is based on not hash tables but arrays. An index access to hash costs O(n).
105
+
106
+ ### Generating
107
+
108
+ ```ruby
109
+ data = {
110
+ "miku maekawa" => {
111
+ "age"=>15,
112
+ "height"=>152,
113
+ "weight"=>45,
114
+ "body_size"=>[85, 55, 81],
115
+ "birthday"=>[2, 22],
116
+ "cv"=>"natsumi takamori"},
117
+ "nana abe"=> {
118
+ "age"=>17,
119
+ "height"=>146,
120
+ "weight"=>40,
121
+ "body_size"=>[84, 57, 84],
122
+ "birthday"=>[5, 15],
123
+ "cv"=>"marie miyake"}}
124
+
125
+ Usamin.generate(data)
126
+
127
+ # pretty generation is also supported
128
+ Usamin.pretty_generate(data)
129
+ ```
130
+
131
+ Of course, UsaminValue also can be serialized.
132
+
133
+ ```ruby
134
+ data = Usamin.load(json)
135
+
136
+ Usamin.generate(data['nana abe'])
137
+ ```
138
+
139
+ ### Fast parsing
140
+
141
+ Usamin uses `kParseFullPrecisionFlag` by default, but this option makes parsing a little slow.
142
+
143
+ You can use `:fast` option to avoid this.
144
+
145
+ ```ruby
146
+ # default
147
+ Usamin.parse('77.777700000000795')
148
+ #=> 77.77770000000079
149
+
150
+ # fast but not precise
151
+ Usamin.parse('77.777700000000795', fast: true)
152
+ #=> 77.7777000000008
153
+ ```
154
+
155
+ ### Error handling
156
+
157
+ ```ruby
158
+ str = '{"this is bad example"'
159
+ Usamin.parse(str)
160
+ # Usamin::ParserError: Missing a colon after a name of object member. Offset: 22
161
+ ```
162
+
163
+ ### Documentation
164
+
165
+ See: http://www.rubydoc.info/gems/usamin/
166
+
167
+ ## Benchmarks
168
+
169
+ Based on [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark).
170
+
171
+ ### Roundtrips
172
+
173
+ Usamin passes all roundtrips, and the results are same as official JSON package.
174
+
175
+ ### Reliability of parsed data
176
+
177
+ Usamin and JSON load the same data from 3 big json data in nativejson-benchmark.
178
+
179
+ ### Performance
180
+
181
+ The values show the elapsed time for operating 20 times.
182
+
183
+ #### Parsing
184
+
185
+ ```
186
+ nativejson-benchmark/data/canada.json
187
+ json 0.785524 0.007927 0.793451 ( 0.805269)
188
+ oj 1.833580 0.043648 1.877228 ( 1.895704)
189
+ usamin 0.595523 0.012299 0.607822 ( 0.612215)
190
+ usamin (fast) 0.286458 0.008710 0.295168 ( 0.297998)
191
+ usamin (load) 0.484056 0.011770 0.495826 ( 0.498110)
192
+ usamin (load / fast) 0.187712 0.020202 0.207914 ( 0.210711)
193
+
194
+ nativejson-benchmark/data/citm_catalog.json
195
+ json 0.469471 0.005208 0.474679 ( 0.478920)
196
+ oj 0.327143 0.002947 0.330090 ( 0.331882)
197
+ usamin 0.362764 0.005633 0.368397 ( 0.370940)
198
+ usamin (fast) 0.359859 0.005884 0.365743 ( 0.371016)
199
+ usamin (load) 0.117140 0.007003 0.124143 ( 0.126548)
200
+ usamin (load / fast) 0.115237 0.010061 0.125298 ( 0.128470)
201
+
202
+ nativejson-benchmark/data/twitter.json
203
+ json 0.238582 0.002561 0.241143 ( 0.243726)
204
+ oj 0.162212 0.001031 0.163243 ( 0.165047)
205
+ usamin 0.194476 0.001523 0.195999 ( 0.197920)
206
+ usamin (fast) 0.192985 0.001339 0.194324 ( 0.197404)
207
+ usamin (load) 0.070360 0.005012 0.075372 ( 0.078090)
208
+ usamin (load / fast) 0.067618 0.006416 0.074034 ( 0.076244)
209
+ ```
210
+
211
+ #### Generating
212
+
213
+ ```
214
+ nativejson-benchmark/data/canada.json
215
+ json 1.988155 0.029728 2.017883 ( 2.023475)
216
+ oj 2.092999 0.033136 2.126135 ( 2.135765)
217
+ usamin 0.296385 0.031412 0.327797 ( 0.330314)
218
+
219
+ nativejson-benchmark/data/citm_catalog.json
220
+ json 0.243795 0.007463 0.251258 ( 0.256621)
221
+ oj 0.076474 0.009617 0.086091 ( 0.087966)
222
+ usamin 0.059434 0.009616 0.069050 ( 0.071158)
223
+
224
+ nativejson-benchmark/data/twitter.json
225
+ json 0.159030 0.006730 0.165760 ( 0.170796)
226
+ oj 0.052856 0.009164 0.062020 ( 0.064344)
227
+ usamin 0.047707 0.010265 0.057972 ( 0.061729)
228
+ ```
229
+
230
+ #### Pretty Generating
231
+
232
+ ```
233
+ nativejson-benchmark/data/canada.json
234
+ json 2.188493 0.066003 2.254496 ( 2.260332)
235
+ oj 1.583613 0.029798 1.613411 ( 1.619652)
236
+ usamin 0.399066 0.081314 0.480380 ( 0.482841)
237
+
238
+ nativejson-benchmark/data/citm_catalog.json
239
+ json 0.300601 0.029596 0.330197 ( 0.335351)
240
+ oj 0.065151 0.009553 0.074704 ( 0.077010)
241
+ usamin 0.089872 0.022203 0.112075 ( 0.116063)
242
+
243
+ nativejson-benchmark/data/twitter.json
244
+ json 0.189439 0.010086 0.199525 ( 0.204491)
245
+ oj 0.040982 0.008203 0.049185 ( 0.049352)
246
+ usamin 0.044296 0.010095 0.054391 ( 0.056245)
247
+ ```
248
+
249
+ ## Contributing
250
+
251
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Ishotihadus/usamin.
252
+
253
+ ## License
254
+
255
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT) at the request of RapidJSON.
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ require "rake/extensiontask"
11
+
12
+ task :build => :compile
13
+
14
+ Rake::ExtensionTask.new("usamin") do |ext|
15
+ ext.lib_dir = "lib/usamin"
16
+ end
17
+
18
+ task :default => [:clobber, :compile, :test]
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "usamin"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "pry"
14
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ have_library('stdc++')
4
+ dir_config('rapidjson')
5
+ $CPPFLAGS << ' -O3 -Wall -Wextra -Wvla -std=c++14'
6
+ create_makefile('usamin/usamin')
@@ -0,0 +1,987 @@
1
+ #include <string.h>
2
+ #include <stdint.h>
3
+ #include <ruby.h>
4
+ #include <ruby/encoding.h>
5
+ #define RAPIDJSON_PARSE_DEFAULT_FLAGS \
6
+ rapidjson::kParseFullPrecisionFlag | rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag | rapidjson::kParseNanAndInfFlag
7
+ #define kParseFastFlags (rapidjson::kParseDefaultFlags & ~rapidjson::kParseFullPrecisionFlag)
8
+ #define RAPIDJSON_WRITE_DEFAULT_FLAGS rapidjson::kWriteNanAndInfFlag
9
+ #include <rapidjson/document.h>
10
+ #include <rapidjson/writer.h>
11
+ #include <rapidjson/prettywriter.h>
12
+ #include <rapidjson/error/en.h>
13
+
14
+ #if SIZEOF_VALUE < SIZEOF_VOIDP
15
+ #error SIZEOF_VOIDP must not be greater than SIZEOF_VALUE.
16
+ #endif
17
+
18
+
19
+ rb_encoding *utf8;
20
+ int utf8index;
21
+ ID id_to_s;
22
+ VALUE rb_cUsaminValue, rb_cUsaminHash, rb_cUsaminArray, rb_eUsaminError, rb_eParserError;
23
+ VALUE utf8value, sym_fast, sym_indent, sym_single_line_array;
24
+
25
+ class UsaminValue {
26
+ public:
27
+ rapidjson::Value *value;
28
+ bool free_flag;
29
+
30
+ UsaminValue(rapidjson::Value *value = nullptr, bool free_flag = false);
31
+ ~UsaminValue();
32
+ };
33
+
34
+ static inline VALUE get_utf8_str(VALUE str) {
35
+ Check_Type(str, T_STRING);
36
+ int encoding = rb_enc_get_index(str);
37
+ if (encoding == utf8index || rb_enc_compatible(str, utf8value) == utf8)
38
+ return str;
39
+ else
40
+ return rb_str_conv_enc(str, rb_enc_from_index(encoding), utf8);
41
+ }
42
+
43
+ static inline VALUE new_utf8_str(const char* cstr, const long len) {
44
+ VALUE ret = rb_str_new(cstr, len);
45
+ rb_enc_set_index(ret, utf8index);
46
+ return ret;
47
+ }
48
+
49
+ static inline bool str_compare(const char* str1, const long len1, const char* str2, const long len2) {
50
+ if (len1 != len2 || len1 < 0)
51
+ return false;
52
+ return memcmp(str1, str2, len1) == 0;
53
+ }
54
+
55
+ static inline void check_value(UsaminValue *ptr) {
56
+ if (!ptr || !ptr->value)
57
+ rb_raise(rb_eUsaminError, "Null Reference.");
58
+ }
59
+
60
+ static inline void check_object(UsaminValue *ptr) {
61
+ check_value(ptr);
62
+ if (!ptr->value->IsObject())
63
+ rb_raise(rb_eUsaminError, "Value is not an object.");
64
+ }
65
+
66
+ static inline void check_array(UsaminValue *ptr) {
67
+ check_value(ptr);
68
+ if (!ptr->value->IsArray())
69
+ rb_raise(rb_eUsaminError, "Value is not an array.");
70
+ }
71
+
72
+
73
+ static VALUE usamin_free(UsaminValue **ptr) {
74
+ if (*ptr)
75
+ delete *ptr;
76
+ ruby_xfree(ptr);
77
+ return Qnil;
78
+ }
79
+
80
+ static VALUE usamin_alloc(const VALUE klass) {
81
+ UsaminValue** ptr = (UsaminValue**)ruby_xmalloc(sizeof(UsaminValue*));
82
+ *ptr = nullptr;
83
+ return Data_Wrap_Struct(klass, NULL, usamin_free, ptr);
84
+ }
85
+
86
+
87
+ static inline UsaminValue* get_value(VALUE value) {
88
+ UsaminValue** ptr;
89
+ Data_Get_Struct(value, UsaminValue*, ptr);
90
+ return *ptr;
91
+ }
92
+
93
+ static inline void set_value(VALUE value, UsaminValue *usamin) {
94
+ UsaminValue **ptr;
95
+ Data_Get_Struct(value, UsaminValue*, ptr);
96
+ *ptr = usamin;
97
+ }
98
+
99
+ static inline VALUE make_hash(UsaminValue *value) {
100
+ VALUE ret = rb_obj_alloc(rb_cUsaminHash);
101
+ set_value(ret, value);
102
+ return ret;
103
+ }
104
+
105
+ static inline VALUE make_array(UsaminValue *value) {
106
+ VALUE ret = rb_obj_alloc(rb_cUsaminArray);
107
+ set_value(ret, value);
108
+ return ret;
109
+ }
110
+
111
+ static inline rapidjson::ParseResult parse(rapidjson::Document &doc, const VALUE str, bool fast = false) {
112
+ VALUE v = get_utf8_str(str);
113
+ return fast ? doc.Parse<kParseFastFlags>(RSTRING_PTR(v), RSTRING_LEN(v)) : doc.Parse(RSTRING_PTR(v), RSTRING_LEN(v));
114
+ }
115
+
116
+
117
+ static inline VALUE eval_num(rapidjson::Value &value);
118
+ static inline VALUE eval_str(rapidjson::Value &value);
119
+ static inline VALUE eval_object(rapidjson::Value &value);
120
+ static inline VALUE eval_object_r(rapidjson::Value &value);
121
+ static inline VALUE eval_array(rapidjson::Value &value);
122
+ static inline VALUE eval_array_r(rapidjson::Value &value);
123
+
124
+ static VALUE eval(rapidjson::Value &value) {
125
+ switch (value.GetType()) {
126
+ case rapidjson::kObjectType:
127
+ return make_hash(new UsaminValue(&value, false));
128
+ case rapidjson::kArrayType:
129
+ return make_array(new UsaminValue(&value, false));
130
+ case rapidjson::kNullType:
131
+ return Qnil;
132
+ case rapidjson::kFalseType:
133
+ return Qfalse;
134
+ case rapidjson::kTrueType:
135
+ return Qtrue;
136
+ case rapidjson::kNumberType:
137
+ return eval_num(value);
138
+ case rapidjson::kStringType:
139
+ return eval_str(value);
140
+ default:
141
+ rb_raise(rb_eUsaminError, "Unknown Value Type: %d", value.GetType());
142
+ return Qnil;
143
+ }
144
+ }
145
+
146
+ static VALUE eval_r(rapidjson::Value &value) {
147
+ switch (value.GetType()) {
148
+ case rapidjson::kObjectType:
149
+ return eval_object_r(value);
150
+ case rapidjson::kArrayType:
151
+ return eval_array_r(value);
152
+ case rapidjson::kNullType:
153
+ return Qnil;
154
+ case rapidjson::kFalseType:
155
+ return Qfalse;
156
+ case rapidjson::kTrueType:
157
+ return Qtrue;
158
+ case rapidjson::kNumberType:
159
+ return eval_num(value);
160
+ case rapidjson::kStringType:
161
+ return eval_str(value);
162
+ default:
163
+ rb_raise(rb_eUsaminError, "Unknown Value Type: %d", value.GetType());
164
+ return Qnil;
165
+ }
166
+ }
167
+
168
+ static inline VALUE eval_num(rapidjson::Value &value) {
169
+ if (value.IsInt())
170
+ return INT2FIX(value.GetInt());
171
+ else if (value.IsUint())
172
+ return UINT2NUM(value.GetUint());
173
+ else if (value.IsInt64())
174
+ return LL2NUM(value.GetInt64());
175
+ else if (value.IsUint64())
176
+ return ULL2NUM(value.GetUint64());
177
+ else
178
+ return DBL2NUM(value.GetDouble());
179
+ }
180
+
181
+ static inline VALUE eval_str(rapidjson::Value &value) {
182
+ return new_utf8_str(value.GetString(), value.GetStringLength());
183
+ }
184
+
185
+ static inline VALUE eval_object(rapidjson::Value &value) {
186
+ VALUE ret = rb_hash_new();
187
+ for (auto &m : value.GetObject())
188
+ rb_hash_aset(ret, eval_str(m.name), eval(m.value));
189
+ return ret;
190
+ }
191
+
192
+ static inline VALUE eval_object_r(rapidjson::Value &value) {
193
+ VALUE ret = rb_hash_new();
194
+ for (auto &m : value.GetObject())
195
+ rb_hash_aset(ret, eval_str(m.name), eval_r(m.value));
196
+ return ret;
197
+ }
198
+
199
+ static inline VALUE eval_array(rapidjson::Value &value) {
200
+ VALUE ret = rb_ary_new2(value.Size());
201
+ for (auto &v : value.GetArray())
202
+ rb_ary_push(ret, eval(v));
203
+ return ret;
204
+ }
205
+
206
+ static inline VALUE eval_array_r(rapidjson::Value &value) {
207
+ VALUE ret = rb_ary_new2(value.Size());
208
+ for (auto &v : value.GetArray())
209
+ rb_ary_push(ret, eval_r(v));
210
+ return ret;
211
+ }
212
+
213
+
214
+ UsaminValue::UsaminValue(rapidjson::Value *value, bool free_flag) {
215
+ this->value = value;
216
+ this->free_flag = free_flag;
217
+ }
218
+
219
+ UsaminValue::~UsaminValue() {
220
+ if (value && free_flag)
221
+ delete value;
222
+ }
223
+
224
+ /*
225
+ * Parse the JSON string into UsaminValue.
226
+ * If the document root is not an object or an array, the same value as {#parse} will be returned.
227
+ *
228
+ * @overload load(source, opts = {})
229
+ * @param [String] source JSON string to parse
230
+ * @param [::Hash] opts options
231
+ * @option opts :fast fast mode (but not precise)
232
+ * @return [Object]
233
+ */
234
+ static VALUE w_load(const int argc, VALUE *argv, const VALUE self) {
235
+ VALUE source, options;
236
+ rb_scan_args(argc, argv, "1:", &source, &options);
237
+ rapidjson::Document *doc = new rapidjson::Document;
238
+ auto result = parse(*doc, source, argc > 1 && RTEST(rb_hash_lookup(options, sym_fast)));
239
+ if (!result) {
240
+ delete doc;
241
+ rb_raise(rb_eParserError, "%s Offset: %lu", GetParseError_En(result.Code()), result.Offset());
242
+ }
243
+
244
+ VALUE ret;
245
+ switch (doc->GetType()) {
246
+ case rapidjson::kObjectType:
247
+ return make_hash(new UsaminValue(doc, true));
248
+ case rapidjson::kArrayType:
249
+ return make_array(new UsaminValue(doc, true));
250
+ case rapidjson::kNullType:
251
+ delete doc;
252
+ return Qnil;
253
+ case rapidjson::kFalseType:
254
+ delete doc;
255
+ return Qfalse;
256
+ case rapidjson::kTrueType:
257
+ delete doc;
258
+ return Qtrue;
259
+ case rapidjson::kNumberType:
260
+ ret = eval_num(*doc);
261
+ delete doc;
262
+ return ret;
263
+ case rapidjson::kStringType:
264
+ ret = eval_str(*doc);
265
+ delete doc;
266
+ return ret;
267
+ default:
268
+ rb_raise(rb_eUsaminError, "Unknown Value Type: %d", doc->GetType());
269
+ return Qnil;
270
+ }
271
+ }
272
+
273
+ /*
274
+ * Parse the JSON string into Ruby data structures.
275
+ *
276
+ * @overload parse(source, opts = {})
277
+ * @param [String] source a JSON string to parse
278
+ * @param [::Hash] opts options
279
+ * @option opts :fast fast mode (but not precise)
280
+ * @return [Object]
281
+ */
282
+ static VALUE w_parse(const int argc, VALUE *argv, const VALUE self) {
283
+ VALUE source, options;
284
+ rb_scan_args(argc, argv, "1:", &source, &options);
285
+ rapidjson::Document doc;
286
+ auto result = parse(doc, source, argc > 1 && RTEST(rb_hash_lookup(options, sym_fast)));
287
+ if (!result)
288
+ rb_raise(rb_eParserError, "%s Offset: %lu", GetParseError_En(result.Code()), result.Offset());
289
+ return eval_r(doc);
290
+ }
291
+
292
+ /*
293
+ * Returns whether the value is array.
294
+ */
295
+ static VALUE w_value_isarray(const VALUE self) {
296
+ UsaminValue *value = get_value(self);
297
+ check_value(value);
298
+ return value->value->IsArray() ? Qtrue : Qfalse;
299
+ }
300
+
301
+ /*
302
+ * Returns whether the value is object.
303
+ */
304
+ static VALUE w_value_isobject(const VALUE self) {
305
+ UsaminValue *value = get_value(self);
306
+ check_value(value);
307
+ return value->value->IsObject() ? Qtrue : Qfalse;
308
+ }
309
+
310
+ /*
311
+ * Convert to Ruby data structures.
312
+ *
313
+ * @return [Object]
314
+ */
315
+ static VALUE w_value_eval(const VALUE self) {
316
+ UsaminValue *value = get_value(self);
317
+ check_value(value);
318
+ if (value->value->IsObject())
319
+ return eval_object(*(value->value));
320
+ else if (value->value->IsArray())
321
+ return eval_array(*(value->value));
322
+ return Qnil;
323
+ }
324
+
325
+ /*
326
+ * Convert to Ruby data structures recursively.
327
+ *
328
+ * @return [Object]
329
+ */
330
+ static VALUE w_value_eval_r(const VALUE self) {
331
+ UsaminValue *value = get_value(self);
332
+ check_value(value);
333
+ return eval_r(*(value->value));
334
+ }
335
+
336
+ /*
337
+ * Always true.
338
+ */
339
+ static VALUE w_value_isfrozen(const VALUE self) {
340
+ return Qtrue;
341
+ }
342
+
343
+ template <class Writer> static void write_value(Writer&, rapidjson::Value&);
344
+
345
+ /*
346
+ * Dumps data in JSON.
347
+ *
348
+ * @return [String]
349
+ */
350
+ static VALUE w_value_marshal_dump(const VALUE self) {
351
+ UsaminValue *value = get_value(self);
352
+ check_value(value);
353
+ rapidjson::StringBuffer buf;
354
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
355
+ write_value(writer, *(value->value));
356
+ return rb_str_new(buf.GetString(), buf.GetSize());
357
+ }
358
+
359
+ /*
360
+ * Loads marshal data.
361
+ *
362
+ * @return [self]
363
+ */
364
+ static VALUE w_value_marshal_load(const VALUE self, VALUE source) {
365
+ Check_Type(source, T_STRING);
366
+ rapidjson::Document *doc = new rapidjson::Document();
367
+ rapidjson::ParseResult result = doc->Parse<rapidjson::kParseFullPrecisionFlag | rapidjson::kParseNanAndInfFlag>(RSTRING_PTR(source), RSTRING_LEN(source));
368
+ if (!result) {
369
+ delete doc;
370
+ rb_raise(rb_eParserError, "%s Offset: %lu", GetParseError_En(result.Code()), result.Offset());
371
+ }
372
+ if (doc->IsObject() || doc->IsArray()) {
373
+ set_value(self, new UsaminValue(doc, true));
374
+ return self;
375
+ }
376
+ auto type = doc->GetType();
377
+ delete doc;
378
+ rb_raise(rb_eUsaminError, "Invalid Value Type for marshal_load: %d", type);
379
+ return Qnil;
380
+ }
381
+
382
+
383
+ /*
384
+ * @overload [](name)
385
+ * @return [Object | nil]
386
+ *
387
+ * @note This method has linear time complexity.
388
+ */
389
+ static VALUE w_hash_operator_indexer(const VALUE self, VALUE key) {
390
+ UsaminValue *value = get_value(self);
391
+ check_object(value);
392
+ VALUE kvalue = get_utf8_str(key);
393
+ for (auto &m : value->value->GetObject())
394
+ if (str_compare(RSTRING_PTR(kvalue), RSTRING_LEN(kvalue), m.name.GetString(), m.name.GetStringLength()))
395
+ return eval(m.value);
396
+ return Qnil;
397
+ }
398
+
399
+ static VALUE hash_enum_size(const VALUE self, VALUE args, VALUE eobj) {
400
+ UsaminValue *value = get_value(self);
401
+ check_object(value);
402
+ return UINT2NUM(value->value->MemberCount());
403
+ }
404
+
405
+ /*
406
+ * @yield [key, value]
407
+ * @yieldparam key [String]
408
+ * @return [Enumerator | self]
409
+ */
410
+ static VALUE w_hash_each(const VALUE self) {
411
+ UsaminValue *value = get_value(self);
412
+ check_object(value);
413
+ RETURN_SIZED_ENUMERATOR(self, 0, nullptr, hash_enum_size);
414
+ if (rb_proc_arity(rb_block_proc()) > 1) {
415
+ for (auto &m : value->value->GetObject()) {
416
+ VALUE args[] = { eval_str(m.name), eval(m.value) };
417
+ rb_yield_values2(2, args);
418
+ }
419
+ } else {
420
+ for (auto &m : value->value->GetObject())
421
+ rb_yield(rb_assoc_new(eval_str(m.name), eval(m.value)));
422
+ }
423
+ return self;
424
+ }
425
+
426
+ /*
427
+ * @yield [key]
428
+ * @yieldparam key [String]
429
+ * @return [Enumerator | self]
430
+ */
431
+ static VALUE w_hash_each_key(const VALUE self) {
432
+ UsaminValue *value = get_value(self);
433
+ check_object(value);
434
+ RETURN_SIZED_ENUMERATOR(self, 0, nullptr, hash_enum_size);
435
+ for (auto &m : value->value->GetObject())
436
+ rb_yield(eval_str(m.name));
437
+ return self;
438
+ }
439
+
440
+ /*
441
+ * @yield [value]
442
+ * @return [Enumerator | self]
443
+ */
444
+ static VALUE w_hash_each_value(const VALUE self) {
445
+ UsaminValue *value = get_value(self);
446
+ check_object(value);
447
+ RETURN_SIZED_ENUMERATOR(self, 0, nullptr, hash_enum_size);
448
+ for (auto &m : value->value->GetObject())
449
+ rb_yield(eval(m.value));
450
+ return self;
451
+ }
452
+
453
+ static VALUE w_hash_isempty(const VALUE self) {
454
+ UsaminValue *value = get_value(self);
455
+ check_object(value);
456
+ return value->value->MemberCount() == 0 ? Qtrue : Qfalse;
457
+ }
458
+
459
+ /*
460
+ * @note This method has linear time complexity.
461
+ */
462
+ static VALUE w_hash_haskey(const VALUE self, VALUE name) {
463
+ UsaminValue *value = get_value(self);
464
+ check_object(value);
465
+ VALUE key = get_utf8_str(name);
466
+ for (auto &m : value->value->GetObject())
467
+ if (str_compare(RSTRING_PTR(key), RSTRING_LEN(key), m.name.GetString(), m.name.GetStringLength()))
468
+ return Qtrue;
469
+ return Qfalse;
470
+ }
471
+
472
+ /*
473
+ * @return [::Array<String>]
474
+ */
475
+ static VALUE w_hash_keys(const VALUE self) {
476
+ UsaminValue *value = get_value(self);
477
+ check_object(value);
478
+ VALUE ret = rb_ary_new2(value->value->MemberCount());
479
+ for (auto &m : value->value->GetObject())
480
+ rb_ary_push(ret, eval_str(m.name));
481
+ return ret;
482
+ }
483
+
484
+ /*
485
+ * @return [Integer]
486
+ */
487
+ static VALUE w_hash_length(const VALUE self) {
488
+ UsaminValue *value = get_value(self);
489
+ check_object(value);
490
+ return UINT2NUM(value->value->MemberCount());
491
+ }
492
+
493
+ /*
494
+ * Convert to Ruby Hash. Same as {Value#eval}.
495
+ *
496
+ * @return [::Hash]
497
+ */
498
+ static VALUE w_hash_eval(const VALUE self) {
499
+ UsaminValue *value = get_value(self);
500
+ check_object(value);
501
+ return eval_object(*(value->value));
502
+ }
503
+
504
+ /*
505
+ * @return [::Array<Object>]
506
+ */
507
+ static VALUE w_hash_values(const VALUE self) {
508
+ UsaminValue *value = get_value(self);
509
+ check_object(value);
510
+ VALUE ret = rb_ary_new2(value->value->MemberCount());
511
+ for (auto &m : value->value->GetObject())
512
+ rb_ary_push(ret, eval(m.value));
513
+ return ret;
514
+ }
515
+
516
+
517
+ /*
518
+ * @overload [](nth)
519
+ * @param [Integer] nth
520
+ * @return [Object | nil]
521
+ *
522
+ * @overload [](start, length)
523
+ * @param [Integer] start
524
+ * @param [Integer] length
525
+ * @return [::Array<Object> | nil]
526
+ *
527
+ * @overload [](range)
528
+ * @param [Range] range
529
+ * @return [::Array<Object> | nil]
530
+ */
531
+ static VALUE w_array_operator_indexer(const int argc, VALUE* argv, const VALUE self) {
532
+ rb_check_arity(argc, 1, 2);
533
+ UsaminValue *value = get_value(self);
534
+ check_array(value);
535
+ if (argc == 2) {
536
+ long beg = NUM2LONG(argv[0]);
537
+ long len = NUM2LONG(argv[1]);
538
+ if (beg < 0)
539
+ beg = beg + value->value->Size();
540
+ if (beg >= 0 && len >= 0) {
541
+ long end = beg + len;
542
+ if (end > value->value->Size())
543
+ end = value->value->Size();
544
+ VALUE ret = rb_ary_new2(end - beg);
545
+ for (long i = beg; i < end; i++)
546
+ rb_ary_push(ret, eval((*(value->value))[static_cast<unsigned int>(i)]));
547
+ return ret;
548
+ }
549
+ } else if (rb_obj_is_kind_of(argv[0], rb_cRange)) {
550
+ long beg, len;
551
+ if (rb_range_beg_len(argv[0], &beg, &len, value->value->Size(), 0) == Qtrue) {
552
+ VALUE ret = rb_ary_new2(len);
553
+ for (long i = beg; i < beg + len; i++)
554
+ rb_ary_push(ret, eval((*(value->value))[static_cast<unsigned int>(i)]));
555
+ return ret;
556
+ }
557
+ } else {
558
+ long l = NUM2LONG(argv[0]);
559
+ if (l < 0)
560
+ l = l + value->value->Size();
561
+ if (l >= 0) {
562
+ unsigned int i = static_cast<unsigned int>(l);
563
+ if (i < value->value->Size())
564
+ return eval((*(value->value))[i]);
565
+ }
566
+ }
567
+ return Qnil;
568
+ }
569
+
570
+ static VALUE array_enum_size(const VALUE self, VALUE args, VALUE eobj) {
571
+ UsaminValue *value = get_value(self);
572
+ check_array(value);
573
+ return UINT2NUM(value->value->Size());
574
+ }
575
+
576
+ /*
577
+ * @yield [value]
578
+ * @return [Enumerator | self]
579
+ */
580
+ static VALUE w_array_each(const VALUE self) {
581
+ UsaminValue *value = get_value(self);
582
+ check_array(value);
583
+ RETURN_SIZED_ENUMERATOR(self, 0, nullptr, array_enum_size);
584
+ for (auto &v : value->value->GetArray())
585
+ rb_yield(eval(v));
586
+ return self;
587
+ }
588
+
589
+ /*
590
+ * @yield [index]
591
+ * @yieldparam index [Integer]
592
+ * @return [Enumerator | self]
593
+ */
594
+ static VALUE w_array_each_index(const VALUE self) {
595
+ UsaminValue *value = get_value(self);
596
+ check_array(value);
597
+ RETURN_SIZED_ENUMERATOR(self, 0, nullptr, array_enum_size);
598
+ for (rapidjson::SizeType i = 0; i < value->value->Size(); i++)
599
+ rb_yield(UINT2NUM(i));
600
+ return self;
601
+ }
602
+
603
+ static VALUE w_array_isempty(const VALUE self) {
604
+ UsaminValue *value = get_value(self);
605
+ check_array(value);
606
+ return value->value->Size() == 0 ? Qtrue : Qfalse;
607
+ }
608
+
609
+ /*
610
+ * @yield [value]
611
+ * @return [Integer]
612
+ */
613
+ static VALUE w_array_index(int argc, VALUE* argv, const VALUE self) {
614
+ rb_check_arity(argc, 0, 1);
615
+ UsaminValue *value = get_value(self);
616
+ check_array(value);
617
+
618
+ if (argc == 1) {
619
+ for (rapidjson::SizeType i = 0; i < value->value->Size(); i++) {
620
+ if (rb_equal(argv[0], eval((*(value->value))[i])) == Qtrue)
621
+ return UINT2NUM(i);
622
+ }
623
+ return Qnil;
624
+ }
625
+
626
+ RETURN_SIZED_ENUMERATOR(self, 0, nullptr, array_enum_size);
627
+ for (rapidjson::SizeType i = 0; i < value->value->Size(); i++) {
628
+ if (RTEST(rb_yield(eval((*(value->value))[i]))))
629
+ return UINT2NUM(i);
630
+ }
631
+ return Qnil;
632
+ }
633
+
634
+ /*
635
+ * @overload first
636
+ * @return [Object | nil]
637
+ *
638
+ * @overload first(n)
639
+ * @return [::Array<Object>]
640
+ */
641
+ static VALUE w_array_first(const int argc, VALUE* argv, const VALUE self) {
642
+ rb_check_arity(argc, 0, 1);
643
+ UsaminValue *value = get_value(self);
644
+ check_array(value);
645
+
646
+ if (argc == 0) {
647
+ if (value->value->Size() == 0)
648
+ return Qnil;
649
+ return eval((*(value->value))[0]);
650
+ }
651
+
652
+ long l = NUM2LONG(argv[0]);
653
+ if (l < 0)
654
+ l = 0;
655
+ else if (l > value->value->Size())
656
+ l = value->value->Size();
657
+ unsigned int li = static_cast<unsigned int>(l);
658
+ VALUE ret = rb_ary_new2(li);
659
+ for (unsigned int i = 0; i < li; i++)
660
+ rb_ary_push(ret, eval((*(value->value))[i]));
661
+
662
+ return ret;
663
+ }
664
+
665
+ /*
666
+ * @return [Integer]
667
+ */
668
+ static VALUE w_array_length(const VALUE self) {
669
+ UsaminValue *value = get_value(self);
670
+ check_array(value);
671
+ return UINT2NUM(value->value->Size());
672
+ }
673
+
674
+ /*
675
+ * Convert to Ruby data structures. Same as {Value#eval}.
676
+ *
677
+ * @return [::Array<Object>]
678
+ */
679
+ static VALUE w_array_eval(const VALUE self) {
680
+ UsaminValue *value = get_value(self);
681
+ check_array(value);
682
+ return eval_array(*(value->value));
683
+ }
684
+
685
+
686
+ template <class Writer> static void write(Writer&, const VALUE);
687
+
688
+ template <class Writer> static inline void write_str(Writer &writer, const VALUE value) {
689
+ VALUE v = get_utf8_str(value);
690
+ writer.String(RSTRING_PTR(v), static_cast<unsigned int>(RSTRING_LEN(v)));
691
+ }
692
+
693
+ template <class Writer> static inline void write_to_s(Writer &writer, const VALUE value) {
694
+ write_str(writer, rb_funcall(value, id_to_s, 0));
695
+ }
696
+
697
+ template <class Writer> static inline void write_key_str(Writer &writer, const VALUE value) {
698
+ VALUE v = get_utf8_str(value);
699
+ writer.Key(RSTRING_PTR(v), static_cast<unsigned int>(RSTRING_LEN(v)));
700
+ }
701
+
702
+ template <class Writer> static inline void write_key_to_s(Writer &writer, const VALUE value) {
703
+ write_key_str(writer, rb_funcall(value, id_to_s, 0));
704
+ }
705
+
706
+ template <class Writer> static inline void write_bignum(Writer &writer, const VALUE value) {
707
+ VALUE v = rb_big2str(value, 10);
708
+ writer.RawValue(RSTRING_PTR(v), static_cast<unsigned int>(RSTRING_LEN(v)), rapidjson::kNumberType);
709
+ }
710
+
711
+ template <class Writer> static inline int write_hash_each(VALUE key, VALUE value, Writer *writer) {
712
+ if (RB_TYPE_P(key, T_STRING))
713
+ write_key_str(*writer, key);
714
+ else if (RB_TYPE_P(key, T_SYMBOL))
715
+ write_key_str(*writer, rb_sym_to_s(key));
716
+ else
717
+ write_key_to_s(*writer, key);
718
+ write(*writer, value);
719
+ return ST_CONTINUE;
720
+ }
721
+
722
+ template <class Writer> static inline void write_hash(Writer &writer, const VALUE hash) {
723
+ writer.StartObject();
724
+ rb_hash_foreach(hash, (int (*)(ANYARGS))write_hash_each<Writer>, reinterpret_cast<VALUE>((&writer)));
725
+ writer.EndObject();
726
+ }
727
+
728
+ template <class Writer> static inline void write_array(Writer &writer, const VALUE value) {
729
+ writer.StartArray();
730
+ const VALUE *ptr = rb_array_const_ptr(value);
731
+ for (long i = 0; i < rb_array_len(value); i++, ptr++)
732
+ write(writer, *ptr);
733
+ writer.EndArray();
734
+ }
735
+
736
+ template <class Writer> static inline void write_struct(Writer &writer, const VALUE value) {
737
+ writer.StartObject();
738
+ VALUE members = rb_struct_members(value);
739
+ const VALUE *ptr = rb_array_const_ptr(members);
740
+ for (long i = 0; i < rb_array_len(members); i++, ptr++) {
741
+ if (RB_TYPE_P(*ptr, T_SYMBOL))
742
+ write_key_str(writer, rb_sym_to_s(*ptr));
743
+ else if (RB_TYPE_P(*ptr, T_STRING))
744
+ write_key_str(writer, *ptr);
745
+ else
746
+ write_key_to_s(writer, *ptr);
747
+ write(writer, rb_struct_aref(value, *ptr));
748
+ }
749
+ writer.EndObject();
750
+ }
751
+
752
+ template <class Writer> static void write_value(Writer &writer, rapidjson::Value &value) {
753
+ switch (value.GetType()) {
754
+ case rapidjson::kObjectType:
755
+ writer.StartObject();
756
+ for (auto &m : value.GetObject()) {
757
+ writer.Key(m.name.GetString(), m.name.GetStringLength());
758
+ write_value(writer, m.value);
759
+ }
760
+ writer.EndObject();
761
+ break;
762
+ case rapidjson::kArrayType:
763
+ writer.StartArray();
764
+ for (auto &v : value.GetArray())
765
+ write_value(writer, v);
766
+ writer.EndArray();
767
+ break;
768
+ case rapidjson::kNullType:
769
+ writer.Null();
770
+ break;
771
+ case rapidjson::kFalseType:
772
+ writer.Bool(false);
773
+ break;
774
+ case rapidjson::kTrueType:
775
+ writer.Bool(true);
776
+ break;
777
+ case rapidjson::kNumberType:
778
+ if (value.IsInt())
779
+ writer.Int(value.GetInt());
780
+ else if (value.IsUint())
781
+ writer.Uint(value.GetUint());
782
+ else if (value.IsInt64())
783
+ writer.Int64(value.GetInt64());
784
+ else if (value.IsUint64())
785
+ writer.Uint64(value.GetUint64());
786
+ else
787
+ writer.Double(value.GetDouble());
788
+ break;
789
+ case rapidjson::kStringType:
790
+ writer.String(value.GetString(), value.GetStringLength());
791
+ break;
792
+ default:
793
+ rb_raise(rb_eUsaminError, "Unknown Value Type: %d", value.GetType());
794
+ }
795
+ }
796
+
797
+ template <class Writer> static inline void write_usamin(Writer &writer, const VALUE self) {
798
+ UsaminValue *value = get_value(self);
799
+ check_value(value);
800
+ write_value(writer, *(value->value));
801
+ }
802
+
803
+ template <class Writer> static void write(Writer &writer, const VALUE value) {
804
+ if (value == Qnil) {
805
+ writer.Null();
806
+ } else if (value == Qfalse) {
807
+ writer.Bool(false);
808
+ } else if (value == Qtrue) {
809
+ writer.Bool(true);
810
+ } else if (RB_FIXNUM_P(value)) {
811
+ writer.Int64(FIX2LONG(value));
812
+ } else if (RB_FLOAT_TYPE_P(value)) {
813
+ writer.Double(NUM2DBL(value));
814
+ } else if (RB_STATIC_SYM_P(value)) {
815
+ write_str(writer, rb_sym_to_s(value));
816
+ } else {
817
+ switch (RB_BUILTIN_TYPE(value)) {
818
+ case T_STRING:
819
+ write_str(writer, value);
820
+ break;
821
+ case T_HASH:
822
+ write_hash(writer, value);
823
+ break;
824
+ case T_ARRAY:
825
+ write_array(writer, value);
826
+ break;
827
+ case T_BIGNUM:
828
+ write_bignum(writer, value);
829
+ break;
830
+ case T_STRUCT:
831
+ write_struct(writer, value);
832
+ break;
833
+ default:
834
+ if (rb_obj_is_kind_of(value, rb_cUsaminValue))
835
+ write_usamin(writer, value);
836
+ else
837
+ write_to_s(writer, value);
838
+ break;
839
+ }
840
+ }
841
+ }
842
+
843
+ /*
844
+ * Generate the JSON string from Ruby data structures.
845
+ *
846
+ * @overload generate(obj)
847
+ * @param [Object] obj an object to serialize
848
+ * @return [String]
849
+ */
850
+ static VALUE w_generate(const VALUE self, VALUE value) {
851
+ rapidjson::StringBuffer buf;
852
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
853
+ write(writer, value);
854
+ return new_utf8_str(buf.GetString(), buf.GetSize());
855
+ }
856
+
857
+ /*
858
+ * Generate the prettified JSON string from Ruby data structures.
859
+ *
860
+ * @overload generate(obj, opts = {})
861
+ * @param [Object] obj an object to serialize
862
+ * @param [::Hash] opts options
863
+ * @option opts [String] :indent (' ') a string used to indent
864
+ * @return [String]
865
+ */
866
+ static VALUE w_pretty_generate(const int argc, VALUE *argv, const VALUE self) {
867
+ VALUE value, options;
868
+ rb_scan_args(argc, argv, "1:", &value, &options);
869
+ rapidjson::StringBuffer buf;
870
+ rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buf);
871
+
872
+ char indent_char = ' ';
873
+ unsigned int indent_count = 2;
874
+ if (argc > 1) {
875
+ VALUE v_indent = rb_hash_lookup(options, sym_indent);
876
+ VALUE v_single_line_array = rb_hash_lookup(options, sym_single_line_array);
877
+
878
+ if (RTEST(v_indent)) {
879
+ if (RB_FIXNUM_P(v_indent)) {
880
+ long l = FIX2LONG(v_indent);
881
+ indent_count = l > 0 ? static_cast<unsigned int>(l) : 0;
882
+ } else {
883
+ VALUE v = get_utf8_str(v_indent);
884
+ long vlen = RSTRING_LEN(v);
885
+ if (vlen == 0) {
886
+ indent_count = 0;
887
+ } else {
888
+ const char *indent_str = RSTRING_PTR(v);
889
+ switch (indent_str[0]) {
890
+ case '\0':
891
+ indent_count = 0;
892
+ break;
893
+ case ' ':
894
+ case '\t':
895
+ case '\r':
896
+ case '\n':
897
+ indent_char = indent_str[0];
898
+ break;
899
+ default:
900
+ rb_raise(rb_eUsaminError, ":indent must be a repetation of \" \", \"\\t\", \"\\r\" or \"\\n\".");
901
+ }
902
+
903
+ for (long i = 1; i < vlen; i++)
904
+ if (indent_str[0] != indent_str[i])
905
+ rb_raise(rb_eUsaminError, ":indent must be a repetation of \" \", \"\\t\", \"\\r\" or \"\\n\".");
906
+ indent_count = static_cast<unsigned int>(vlen);
907
+ }
908
+ }
909
+ }
910
+
911
+ if (RTEST(v_single_line_array))
912
+ writer.SetFormatOptions(rapidjson::kFormatSingleLineArray);
913
+ }
914
+ writer.SetIndent(indent_char, indent_count);
915
+
916
+ write(writer, value);
917
+ return new_utf8_str(buf.GetString(), buf.GetSize());
918
+ }
919
+
920
+
921
+ extern "C" void Init_usamin(void) {
922
+ utf8 = rb_utf8_encoding();
923
+ utf8index = rb_enc_to_index(utf8);
924
+ utf8value = rb_enc_from_encoding(utf8);
925
+ sym_fast = rb_id2sym(rb_intern("fast"));
926
+ sym_indent = rb_id2sym(rb_intern("indent"));
927
+ sym_single_line_array = rb_id2sym(rb_intern("single_line_array"));
928
+ id_to_s = rb_intern("to_s");
929
+
930
+ VALUE rb_mUsamin = rb_define_module("Usamin");
931
+ rb_define_module_function(rb_mUsamin, "load", RUBY_METHOD_FUNC(w_load), -1);
932
+ rb_define_module_function(rb_mUsamin, "parse", RUBY_METHOD_FUNC(w_parse), -1);
933
+ rb_define_module_function(rb_mUsamin, "generate", RUBY_METHOD_FUNC(w_generate), 1);
934
+ rb_define_module_function(rb_mUsamin, "pretty_generate", RUBY_METHOD_FUNC(w_pretty_generate), -1);
935
+
936
+ rb_cUsaminValue = rb_define_class_under(rb_mUsamin, "Value", rb_cObject);
937
+ rb_define_alloc_func(rb_cUsaminValue, usamin_alloc);
938
+ rb_undef_method(rb_cUsaminValue, "initialize");
939
+ rb_define_method(rb_cUsaminValue, "array?", RUBY_METHOD_FUNC(w_value_isarray), 0);
940
+ rb_define_method(rb_cUsaminValue, "hash?", RUBY_METHOD_FUNC(w_value_isobject), 0);
941
+ rb_define_method(rb_cUsaminValue, "object?", RUBY_METHOD_FUNC(w_value_isobject), 0);
942
+ rb_define_method(rb_cUsaminValue, "eval", RUBY_METHOD_FUNC(w_value_eval), 0);
943
+ rb_define_method(rb_cUsaminValue, "eval_r", RUBY_METHOD_FUNC(w_value_eval_r), 0);
944
+ rb_define_method(rb_cUsaminValue, "frozen?", RUBY_METHOD_FUNC(w_value_isfrozen), 0);
945
+ rb_define_method(rb_cUsaminValue, "marshal_dump", RUBY_METHOD_FUNC(w_value_marshal_dump), 0);
946
+ rb_define_method(rb_cUsaminValue, "marshal_load", RUBY_METHOD_FUNC(w_value_marshal_load), 1);
947
+
948
+ rb_cUsaminHash = rb_define_class_under(rb_mUsamin, "Hash", rb_cUsaminValue);
949
+ rb_include_module(rb_cUsaminHash, rb_mEnumerable);
950
+ rb_define_alloc_func(rb_cUsaminHash, usamin_alloc);
951
+ rb_undef_method(rb_cUsaminHash, "initialize");
952
+ rb_define_method(rb_cUsaminHash, "[]", RUBY_METHOD_FUNC(w_hash_operator_indexer), 1);
953
+ rb_define_method(rb_cUsaminHash, "each", RUBY_METHOD_FUNC(w_hash_each), 0);
954
+ rb_define_method(rb_cUsaminHash, "each_pair", RUBY_METHOD_FUNC(w_hash_each), 0);
955
+ rb_define_method(rb_cUsaminHash, "each_key", RUBY_METHOD_FUNC(w_hash_each_key), 0);
956
+ rb_define_method(rb_cUsaminHash, "each_value", RUBY_METHOD_FUNC(w_hash_each_value), 0);
957
+ rb_define_method(rb_cUsaminHash, "empty?", RUBY_METHOD_FUNC(w_hash_isempty), 0);
958
+ rb_define_method(rb_cUsaminHash, "has_key?", RUBY_METHOD_FUNC(w_hash_haskey), 1);
959
+ rb_define_method(rb_cUsaminHash, "include?", RUBY_METHOD_FUNC(w_hash_haskey), 1);
960
+ rb_define_method(rb_cUsaminHash, "key?", RUBY_METHOD_FUNC(w_hash_haskey), 1);
961
+ rb_define_method(rb_cUsaminHash, "member?", RUBY_METHOD_FUNC(w_hash_haskey), 1);
962
+ rb_define_method(rb_cUsaminHash, "keys", RUBY_METHOD_FUNC(w_hash_keys), 0);
963
+ rb_define_method(rb_cUsaminHash, "length", RUBY_METHOD_FUNC(w_hash_length), 0);
964
+ rb_define_method(rb_cUsaminHash, "size", RUBY_METHOD_FUNC(w_hash_length), 0);
965
+ rb_define_method(rb_cUsaminHash, "to_h", RUBY_METHOD_FUNC(w_hash_eval), 0);
966
+ rb_define_method(rb_cUsaminHash, "to_hash", RUBY_METHOD_FUNC(w_hash_eval), 0);
967
+ rb_define_method(rb_cUsaminHash, "values", RUBY_METHOD_FUNC(w_hash_values), 0);
968
+
969
+ rb_cUsaminArray = rb_define_class_under(rb_mUsamin, "Array", rb_cUsaminValue);
970
+ rb_include_module(rb_cUsaminArray, rb_mEnumerable);
971
+ rb_define_alloc_func(rb_cUsaminArray, usamin_alloc);
972
+ rb_undef_method(rb_cUsaminArray, "initialize");
973
+ rb_define_method(rb_cUsaminArray, "[]", RUBY_METHOD_FUNC(w_array_operator_indexer), -1);
974
+ rb_define_method(rb_cUsaminArray, "each", RUBY_METHOD_FUNC(w_array_each), 0);
975
+ rb_define_method(rb_cUsaminArray, "each_index", RUBY_METHOD_FUNC(w_array_each_index), 0);
976
+ rb_define_method(rb_cUsaminArray, "empty?", RUBY_METHOD_FUNC(w_array_isempty), 0);
977
+ rb_define_method(rb_cUsaminArray, "index", RUBY_METHOD_FUNC(w_array_index), -1);
978
+ rb_define_method(rb_cUsaminArray, "find_index", RUBY_METHOD_FUNC(w_array_index), -1);
979
+ rb_define_method(rb_cUsaminArray, "first", RUBY_METHOD_FUNC(w_array_first), -1);
980
+ rb_define_method(rb_cUsaminArray, "length", RUBY_METHOD_FUNC(w_array_length), 0);
981
+ rb_define_method(rb_cUsaminArray, "size", RUBY_METHOD_FUNC(w_array_length), 0);
982
+ rb_define_method(rb_cUsaminArray, "to_a", RUBY_METHOD_FUNC(w_array_eval), 0);
983
+ rb_define_method(rb_cUsaminArray, "to_ary", RUBY_METHOD_FUNC(w_array_eval), 0);
984
+
985
+ rb_eUsaminError = rb_define_class_under(rb_mUsamin, "UsaminError", rb_eStandardError);
986
+ rb_eParserError = rb_define_class_under(rb_mUsamin, "ParserError", rb_eUsaminError);
987
+ }