nosj 0.1.0 → 0.3.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/CHANGELOG.md +110 -1
- data/Cargo.lock +13 -3
- data/README.md +272 -24
- data/ext/nosj/Cargo.toml +5 -2
- data/ext/nosj/src/errors.rs +171 -0
- data/ext/nosj/src/files.rs +240 -0
- data/ext/nosj/src/gen/errors.rs +2 -9
- data/ext/nosj/src/gen/mod.rs +198 -20
- data/ext/nosj/src/gen/opts.rs +87 -9
- data/ext/nosj/src/gen/ruby.rs +51 -0
- data/ext/nosj/src/gen/walker.rs +97 -59
- data/ext/nosj/src/lazy.rs +422 -0
- data/ext/nosj/src/lib.rs +60 -0
- data/ext/nosj/src/lines.rs +90 -0
- data/ext/nosj/src/parse.rs +50 -14
- data/ext/nosj/src/patch.rs +547 -0
- data/ext/nosj/src/pointer.rs +26 -17
- data/ext/nosj/src/reformat.rs +301 -0
- data/ext/nosj/src/sink.rs +6 -0
- data/ext/nosj/src/stats.rs +301 -0
- data/lib/nosj/json.rb +30 -6
- data/lib/nosj/lazy.rb +165 -0
- data/lib/nosj/multi_json.rb +1 -1
- data/lib/nosj/rails.rb +76 -0
- data/lib/nosj/version.rb +1 -1
- data/lib/nosj.rb +434 -5
- data/sig/nosj.rbs +120 -0
- metadata +16 -3
data/ext/nosj/src/gen/mod.rs
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
mod errors;
|
|
24
24
|
mod hash_iter;
|
|
25
25
|
mod keys;
|
|
26
|
-
mod opts;
|
|
26
|
+
pub(crate) mod opts;
|
|
27
27
|
mod ruby;
|
|
28
28
|
mod walker;
|
|
29
29
|
|
|
@@ -46,12 +46,17 @@ use walker::Gen;
|
|
|
46
46
|
struct GenScratch {
|
|
47
47
|
buf: Vec<u8>,
|
|
48
48
|
keys: GenKeyCache,
|
|
49
|
+
/// Keys pre-escaped under HtmlSafe (the Rails encoder): cached
|
|
50
|
+
/// bytes bake in the escape mode, so each cacheable mode owns a
|
|
51
|
+
/// cache (see walker::mode_cacheable).
|
|
52
|
+
html_keys: GenKeyCache,
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
thread_local! {
|
|
52
56
|
static GEN_SCRATCH: RefCell<GenScratch> = RefCell::new(GenScratch {
|
|
53
57
|
buf: Vec::new(),
|
|
54
58
|
keys: GenKeyCache::with_capacity(256),
|
|
59
|
+
html_keys: GenKeyCache::with_capacity(64),
|
|
55
60
|
});
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -86,6 +91,29 @@ pub fn generate_entry(ruby: &Ruby, _rb_self: Value, args: &[Value]) -> Result<RS
|
|
|
86
91
|
generate_scratched(ruby, obj, cfg, cap_hint)
|
|
87
92
|
}
|
|
88
93
|
|
|
94
|
+
/// `NOSJ.generate_rails_native(obj, escape_html, escape_js)`: compact
|
|
95
|
+
/// generation under ActiveSupport walk semantics (non-native values
|
|
96
|
+
/// recurse through as_json, non-finite floats emit null). Every escape
|
|
97
|
+
/// flag combination maps to a crate escape mode, so HTML/JS-safety
|
|
98
|
+
/// escaping is always fused into the emission kernels: one pass, no
|
|
99
|
+
/// post-processing. The Rails encoder installed by
|
|
100
|
+
/// `require "nosj/rails"` is the only caller.
|
|
101
|
+
pub fn generate_rails_native(
|
|
102
|
+
ruby: &Ruby,
|
|
103
|
+
_rb_self: Value,
|
|
104
|
+
obj: Value,
|
|
105
|
+
escape_html: bool,
|
|
106
|
+
escape_js: bool,
|
|
107
|
+
) -> Result<RString, Error> {
|
|
108
|
+
let cfg = match (escape_html, escape_js) {
|
|
109
|
+
(true, true) => &opts::RAILS_HTML_SAFE_CONFIG,
|
|
110
|
+
(true, false) => &opts::RAILS_HTML_ENTITIES_CONFIG,
|
|
111
|
+
(false, true) => &opts::RAILS_JS_SEPARATORS_CONFIG,
|
|
112
|
+
(false, false) => &opts::RAILS_CONFIG,
|
|
113
|
+
};
|
|
114
|
+
generate_scratched(ruby, obj, cfg, 0)
|
|
115
|
+
}
|
|
116
|
+
|
|
89
117
|
/// `NOSJ.generate_native(obj, opts)`: the fixed-arity entry kept for
|
|
90
118
|
/// the Ruby-level wrappers (pretty_generate merges options first).
|
|
91
119
|
pub fn generate_native(
|
|
@@ -102,45 +130,102 @@ pub fn generate_native(
|
|
|
102
130
|
generate_scratched(ruby, obj, &cfg, cap_hint)
|
|
103
131
|
}
|
|
104
132
|
|
|
133
|
+
/// Generate `obj` under a Ruby options hash (or nil) and hand the
|
|
134
|
+
/// finished bytes to `finish` instead of building a Ruby String: the
|
|
135
|
+
/// entry `NOSJ.write_file` uses to stream straight to disk.
|
|
136
|
+
pub(crate) fn generate_bytes_into<R>(
|
|
137
|
+
ruby: &Ruby,
|
|
138
|
+
obj: Value,
|
|
139
|
+
opts: Value,
|
|
140
|
+
finish: impl FnOnce(&Ruby, &[u8]) -> Result<R, Error>,
|
|
141
|
+
) -> Result<R, Error> {
|
|
142
|
+
use magnus::value::ReprValue;
|
|
143
|
+
if opts.is_nil() {
|
|
144
|
+
return generate_scratched_into(ruby, obj, &opts::DEFAULT_CONFIG, 0, finish);
|
|
145
|
+
}
|
|
146
|
+
let (cfg, cap_hint) = opts::parse_gen_opts(ruby, opts)?;
|
|
147
|
+
generate_scratched_into(ruby, obj, &cfg, cap_hint, finish)
|
|
148
|
+
}
|
|
149
|
+
|
|
105
150
|
fn generate_scratched(
|
|
106
151
|
ruby: &Ruby,
|
|
107
152
|
obj: Value,
|
|
108
153
|
cfg: &opts::GenConfig,
|
|
109
154
|
cap_hint: usize,
|
|
110
155
|
) -> Result<RString, Error> {
|
|
156
|
+
generate_scratched_into(ruby, obj, cfg, cap_hint, finish_rstring)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// The default finisher: the generated bytes as a Ruby String.
|
|
160
|
+
fn finish_rstring(_ruby: &Ruby, out: &[u8]) -> Result<RString, Error> {
|
|
161
|
+
Ok(unsafe {
|
|
162
|
+
RString::from_value(Value::from_raw(rb_sys::rb_utf8_str_new(
|
|
163
|
+
out.as_ptr().cast(),
|
|
164
|
+
out.len() as std::os::raw::c_long,
|
|
165
|
+
)))
|
|
166
|
+
.expect("rb_utf8_str_new returns a String")
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn generate_scratched_into<R>(
|
|
171
|
+
ruby: &Ruby,
|
|
172
|
+
obj: Value,
|
|
173
|
+
cfg: &opts::GenConfig,
|
|
174
|
+
cap_hint: usize,
|
|
175
|
+
finish: impl FnOnce(&Ruby, &[u8]) -> Result<R, Error>,
|
|
176
|
+
) -> Result<R, Error> {
|
|
111
177
|
GEN_SCRATCH.with(|cell| match cell.try_borrow_mut() {
|
|
112
178
|
Ok(mut scratch) => {
|
|
113
179
|
let scratch = &mut *scratch;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
180
|
+
let GenScratch {
|
|
181
|
+
buf,
|
|
182
|
+
keys,
|
|
183
|
+
html_keys,
|
|
184
|
+
..
|
|
185
|
+
} = scratch;
|
|
186
|
+
let keys = if cfg.mode == nosj::emit::EscapeMode::HtmlSafe {
|
|
187
|
+
html_keys
|
|
188
|
+
} else {
|
|
189
|
+
keys
|
|
190
|
+
};
|
|
191
|
+
generate_with(ruby, obj, cfg, cap_hint, buf, keys, finish)
|
|
122
192
|
}
|
|
123
193
|
Err(_) => {
|
|
124
194
|
let mut buf = Vec::new();
|
|
125
195
|
let mut keys = GenKeyCache::default();
|
|
126
|
-
generate_with(ruby, obj, cfg, cap_hint, &mut buf, &mut keys)
|
|
196
|
+
generate_with(ruby, obj, cfg, cap_hint, &mut buf, &mut keys, finish)
|
|
127
197
|
}
|
|
128
198
|
})
|
|
129
199
|
}
|
|
130
200
|
|
|
131
|
-
fn generate_with(
|
|
201
|
+
fn generate_with<R>(
|
|
132
202
|
ruby: &Ruby,
|
|
133
203
|
obj: Value,
|
|
134
204
|
cfg: &opts::GenConfig,
|
|
135
205
|
cap_hint: usize,
|
|
136
206
|
out: &mut Vec<u8>,
|
|
137
207
|
keys: &mut GenKeyCache,
|
|
138
|
-
) -> Result<
|
|
208
|
+
finish: impl FnOnce(&Ruby, &[u8]) -> Result<R, Error>,
|
|
209
|
+
) -> Result<R, Error> {
|
|
139
210
|
out.clear();
|
|
140
211
|
if out.capacity() < cap_hint {
|
|
141
212
|
out.reserve(cap_hint);
|
|
142
213
|
}
|
|
214
|
+
emit_one(ruby, obj, cfg, out, keys)?;
|
|
215
|
+
finish(ruby, out)
|
|
216
|
+
}
|
|
143
217
|
|
|
218
|
+
/// Emit one value into `out` (appending, not clearing), mapping walker
|
|
219
|
+
/// failures onto the gem's exceptions. `inline(always)`: this is the
|
|
220
|
+
/// single-document hot path's only call layer.
|
|
221
|
+
#[inline(always)]
|
|
222
|
+
fn emit_one(
|
|
223
|
+
ruby: &Ruby,
|
|
224
|
+
obj: Value,
|
|
225
|
+
cfg: &opts::GenConfig,
|
|
226
|
+
out: &mut Vec<u8>,
|
|
227
|
+
keys: &mut GenKeyCache,
|
|
228
|
+
) -> Result<(), Error> {
|
|
144
229
|
let mut g = Gen {
|
|
145
230
|
out,
|
|
146
231
|
cfg,
|
|
@@ -153,15 +238,9 @@ fn generate_with(
|
|
|
153
238
|
g.emit_value::<false>(obj.as_raw(), cfg.start_depth)
|
|
154
239
|
};
|
|
155
240
|
|
|
156
|
-
let Gen {
|
|
241
|
+
let Gen { fail, .. } = g;
|
|
157
242
|
match (result, fail) {
|
|
158
|
-
(Ok(()), None) => Ok(
|
|
159
|
-
RString::from_value(Value::from_raw(rb_sys::rb_utf8_str_new(
|
|
160
|
-
out.as_ptr().cast(),
|
|
161
|
-
out.len() as std::os::raw::c_long,
|
|
162
|
-
)))
|
|
163
|
-
.expect("rb_utf8_str_new returns a String")
|
|
164
|
-
}),
|
|
243
|
+
(Ok(()), None) => Ok(()),
|
|
165
244
|
(_, Some(fail)) => Err(raise_fail(ruby, fail)),
|
|
166
245
|
(Err(()), None) => Err(Error::new(
|
|
167
246
|
ruby.exception_runtime_error(),
|
|
@@ -169,3 +248,102 @@ fn generate_with(
|
|
|
169
248
|
)),
|
|
170
249
|
}
|
|
171
250
|
}
|
|
251
|
+
|
|
252
|
+
/// Emit one value into `out` under `cfg`, borrowing the pooled key
|
|
253
|
+
/// cache. The splice/patch entries use this to generate replacement
|
|
254
|
+
/// values straight into their output buffer.
|
|
255
|
+
pub(crate) fn emit_into(
|
|
256
|
+
ruby: &Ruby,
|
|
257
|
+
obj: Value,
|
|
258
|
+
cfg: &opts::GenConfig,
|
|
259
|
+
out: &mut Vec<u8>,
|
|
260
|
+
) -> Result<(), Error> {
|
|
261
|
+
GEN_SCRATCH.with(|cell| match cell.try_borrow_mut() {
|
|
262
|
+
Ok(mut scratch) => emit_one(ruby, obj, cfg, out, &mut scratch.keys),
|
|
263
|
+
Err(_) => emit_one(ruby, obj, cfg, out, &mut GenKeyCache::default()),
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// Formatting strings holding a newline (or carriage return) would
|
|
268
|
+
/// split one value across NDJSON lines; the lines entries refuse them.
|
|
269
|
+
fn breaks_line_framing(cfg: &opts::GenConfig) -> bool {
|
|
270
|
+
[
|
|
271
|
+
&cfg.indent,
|
|
272
|
+
&cfg.space,
|
|
273
|
+
&cfg.space_before,
|
|
274
|
+
&cfg.object_nl,
|
|
275
|
+
&cfg.array_nl,
|
|
276
|
+
]
|
|
277
|
+
.into_iter()
|
|
278
|
+
.any(|v| v.iter().any(|&b| b == b'\n' || b == b'\r'))
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// `NOSJ.generate_lines_native(values, opts)`: NDJSON generation, one
|
|
282
|
+
/// compact document per element into ONE pooled buffer, newline after
|
|
283
|
+
/// each (every line terminated, per the format).
|
|
284
|
+
pub fn generate_lines_native(
|
|
285
|
+
ruby: &Ruby,
|
|
286
|
+
_rb_self: Value,
|
|
287
|
+
values: magnus::RArray,
|
|
288
|
+
opts: Value,
|
|
289
|
+
) -> Result<RString, Error> {
|
|
290
|
+
generate_lines_bytes_into(ruby, values, opts, finish_rstring)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/// The write-to-disk form of [`generate_lines_native`], used by
|
|
294
|
+
/// `NOSJ.write_lines`.
|
|
295
|
+
pub(crate) fn generate_lines_bytes_into<R>(
|
|
296
|
+
ruby: &Ruby,
|
|
297
|
+
values: magnus::RArray,
|
|
298
|
+
opts: Value,
|
|
299
|
+
finish: impl FnOnce(&Ruby, &[u8]) -> Result<R, Error>,
|
|
300
|
+
) -> Result<R, Error> {
|
|
301
|
+
use magnus::value::ReprValue;
|
|
302
|
+
let built;
|
|
303
|
+
let (cfg, cap_hint): (&opts::GenConfig, usize) = if opts.is_nil() {
|
|
304
|
+
(&opts::DEFAULT_CONFIG, 0)
|
|
305
|
+
} else {
|
|
306
|
+
let (c, hint) = opts::parse_gen_opts(ruby, opts)?;
|
|
307
|
+
built = c;
|
|
308
|
+
(&built, hint)
|
|
309
|
+
};
|
|
310
|
+
if breaks_line_framing(cfg) {
|
|
311
|
+
return Err(Error::new(
|
|
312
|
+
ruby.exception_arg_error(),
|
|
313
|
+
"formatting options containing newlines would break JSON Lines framing",
|
|
314
|
+
));
|
|
315
|
+
}
|
|
316
|
+
GEN_SCRATCH.with(|cell| match cell.try_borrow_mut() {
|
|
317
|
+
Ok(mut scratch) => {
|
|
318
|
+
let scratch = &mut *scratch;
|
|
319
|
+
let GenScratch { buf, keys, .. } = scratch;
|
|
320
|
+
generate_lines_with(ruby, values, cfg, cap_hint, buf, keys, finish)
|
|
321
|
+
}
|
|
322
|
+
Err(_) => {
|
|
323
|
+
let mut buf = Vec::new();
|
|
324
|
+
let mut keys = GenKeyCache::default();
|
|
325
|
+
generate_lines_with(ruby, values, cfg, cap_hint, &mut buf, &mut keys, finish)
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
fn generate_lines_with<R>(
|
|
331
|
+
ruby: &Ruby,
|
|
332
|
+
values: magnus::RArray,
|
|
333
|
+
cfg: &opts::GenConfig,
|
|
334
|
+
cap_hint: usize,
|
|
335
|
+
out: &mut Vec<u8>,
|
|
336
|
+
keys: &mut GenKeyCache,
|
|
337
|
+
finish: impl FnOnce(&Ruby, &[u8]) -> Result<R, Error>,
|
|
338
|
+
) -> Result<R, Error> {
|
|
339
|
+
out.clear();
|
|
340
|
+
if out.capacity() < cap_hint {
|
|
341
|
+
out.reserve(cap_hint);
|
|
342
|
+
}
|
|
343
|
+
for i in 0..values.len() {
|
|
344
|
+
let obj: Value = values.entry(i as isize)?;
|
|
345
|
+
emit_one(ruby, obj, cfg, out, keys)?;
|
|
346
|
+
out.push(b'\n');
|
|
347
|
+
}
|
|
348
|
+
finish(ruby, out)
|
|
349
|
+
}
|
data/ext/nosj/src/gen/opts.rs
CHANGED
|
@@ -5,18 +5,23 @@ use magnus::value::ReprValue;
|
|
|
5
5
|
use magnus::{Error, RHash, RString, Ruby, Value};
|
|
6
6
|
use nosj::emit::EscapeMode;
|
|
7
7
|
|
|
8
|
-
pub(
|
|
9
|
-
pub(
|
|
10
|
-
pub(
|
|
11
|
-
pub(
|
|
12
|
-
pub(
|
|
13
|
-
pub(
|
|
8
|
+
pub(crate) struct GenConfig {
|
|
9
|
+
pub(crate) indent: Vec<u8>,
|
|
10
|
+
pub(crate) space: Vec<u8>,
|
|
11
|
+
pub(crate) space_before: Vec<u8>,
|
|
12
|
+
pub(crate) object_nl: Vec<u8>,
|
|
13
|
+
pub(crate) array_nl: Vec<u8>,
|
|
14
14
|
/// 0 = unlimited.
|
|
15
15
|
pub(super) max_nesting: usize,
|
|
16
16
|
pub(super) start_depth: usize,
|
|
17
17
|
pub(super) allow_nan: bool,
|
|
18
18
|
pub(super) strict: bool,
|
|
19
|
-
|
|
19
|
+
/// ActiveSupport walk semantics: non-native values recurse through
|
|
20
|
+
/// as_json instead of splicing to_json, and non-finite floats emit
|
|
21
|
+
/// null (Float#as_json parity). Set only by the Rails entry, never
|
|
22
|
+
/// from user option hashes.
|
|
23
|
+
pub(super) rails: bool,
|
|
24
|
+
pub(crate) mode: EscapeMode,
|
|
20
25
|
/// Precomputed "any formatting string set": scanning the five
|
|
21
26
|
/// vectors per call was measurable on tiny documents.
|
|
22
27
|
pub(super) pretty: bool,
|
|
@@ -27,7 +32,7 @@ pub(super) struct GenConfig {
|
|
|
27
32
|
/// on tiny documents (the json gem likewise reuses a cached State for
|
|
28
33
|
/// the default options). Safe as a static: `Vec::new` is const and
|
|
29
34
|
/// allocation-free, and generation only ever borrows the config.
|
|
30
|
-
pub(
|
|
35
|
+
pub(crate) static DEFAULT_CONFIG: GenConfig = GenConfig {
|
|
31
36
|
indent: Vec::new(),
|
|
32
37
|
space: Vec::new(),
|
|
33
38
|
space_before: Vec::new(),
|
|
@@ -37,6 +42,78 @@ pub(super) static DEFAULT_CONFIG: GenConfig = GenConfig {
|
|
|
37
42
|
start_depth: 0,
|
|
38
43
|
allow_nan: false,
|
|
39
44
|
strict: false,
|
|
45
|
+
rails: false,
|
|
46
|
+
mode: EscapeMode::Standard,
|
|
47
|
+
pretty: false,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/// The Rails-encoder configuration for ActiveSupport's default escape
|
|
51
|
+
/// flags (HTML entities and JS separators both on, the overwhelmingly
|
|
52
|
+
/// common case): escaping is fused into the crate's HtmlSafe kernels,
|
|
53
|
+
/// one pass, no post-scan.
|
|
54
|
+
pub(super) static RAILS_HTML_SAFE_CONFIG: GenConfig = GenConfig {
|
|
55
|
+
indent: Vec::new(),
|
|
56
|
+
space: Vec::new(),
|
|
57
|
+
space_before: Vec::new(),
|
|
58
|
+
object_nl: Vec::new(),
|
|
59
|
+
array_nl: Vec::new(),
|
|
60
|
+
max_nesting: 100,
|
|
61
|
+
start_depth: 0,
|
|
62
|
+
allow_nan: false,
|
|
63
|
+
strict: false,
|
|
64
|
+
rails: true,
|
|
65
|
+
mode: EscapeMode::HtmlSafe,
|
|
66
|
+
pretty: false,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/// Rails-encoder configuration with HTML entities on and JS separators
|
|
70
|
+
/// off.
|
|
71
|
+
pub(super) static RAILS_HTML_ENTITIES_CONFIG: GenConfig = GenConfig {
|
|
72
|
+
indent: Vec::new(),
|
|
73
|
+
space: Vec::new(),
|
|
74
|
+
space_before: Vec::new(),
|
|
75
|
+
object_nl: Vec::new(),
|
|
76
|
+
array_nl: Vec::new(),
|
|
77
|
+
max_nesting: 100,
|
|
78
|
+
start_depth: 0,
|
|
79
|
+
allow_nan: false,
|
|
80
|
+
strict: false,
|
|
81
|
+
rails: true,
|
|
82
|
+
mode: EscapeMode::HtmlEntities,
|
|
83
|
+
pretty: false,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/// Rails-encoder configuration with JS separators on and HTML entities
|
|
87
|
+
/// off.
|
|
88
|
+
pub(super) static RAILS_JS_SEPARATORS_CONFIG: GenConfig = GenConfig {
|
|
89
|
+
indent: Vec::new(),
|
|
90
|
+
space: Vec::new(),
|
|
91
|
+
space_before: Vec::new(),
|
|
92
|
+
object_nl: Vec::new(),
|
|
93
|
+
array_nl: Vec::new(),
|
|
94
|
+
max_nesting: 100,
|
|
95
|
+
start_depth: 0,
|
|
96
|
+
allow_nan: false,
|
|
97
|
+
strict: false,
|
|
98
|
+
rails: true,
|
|
99
|
+
mode: EscapeMode::JsSeparators,
|
|
100
|
+
pretty: false,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/// The Rails-encoder configuration with every escape flag off
|
|
104
|
+
/// (encode(escape: false)). Mirrors JSONGemEncoder#stringify, which
|
|
105
|
+
/// generates with the json gem's defaults.
|
|
106
|
+
pub(super) static RAILS_CONFIG: GenConfig = GenConfig {
|
|
107
|
+
indent: Vec::new(),
|
|
108
|
+
space: Vec::new(),
|
|
109
|
+
space_before: Vec::new(),
|
|
110
|
+
object_nl: Vec::new(),
|
|
111
|
+
array_nl: Vec::new(),
|
|
112
|
+
max_nesting: 100,
|
|
113
|
+
start_depth: 0,
|
|
114
|
+
allow_nan: false,
|
|
115
|
+
strict: false,
|
|
116
|
+
rails: true,
|
|
40
117
|
mode: EscapeMode::Standard,
|
|
41
118
|
pretty: false,
|
|
42
119
|
};
|
|
@@ -53,6 +130,7 @@ impl Default for GenConfig {
|
|
|
53
130
|
start_depth: 0,
|
|
54
131
|
allow_nan: false,
|
|
55
132
|
strict: false,
|
|
133
|
+
rails: false,
|
|
56
134
|
mode: EscapeMode::Standard,
|
|
57
135
|
pretty: false,
|
|
58
136
|
}
|
|
@@ -86,7 +164,7 @@ fn opt_bool(ruby: &Ruby, opts: RHash, name: &str) -> Option<bool> {
|
|
|
86
164
|
|
|
87
165
|
/// Decode a non-nil options hash (nil takes [`DEFAULT_CONFIG`] at the
|
|
88
166
|
/// call site without constructing anything).
|
|
89
|
-
pub(
|
|
167
|
+
pub(crate) fn parse_gen_opts(ruby: &Ruby, opts: Value) -> Result<(GenConfig, usize), Error> {
|
|
90
168
|
let mut cfg = GenConfig::default();
|
|
91
169
|
let mut cap_hint = 0usize;
|
|
92
170
|
if opts.is_nil() {
|
data/ext/nosj/src/gen/ruby.rs
CHANGED
|
@@ -23,6 +23,57 @@ pub(super) fn protected_to_json(v: VALUE) -> Result<VALUE, Error> {
|
|
|
23
23
|
magnus::rb_sys::protect(|| unsafe { rb_sys::rb_funcall(v, to_json_id(), 0) })
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/// `v.as_json`, protected. Argument-less on purpose: ActiveSupport's
|
|
27
|
+
/// JSONGemEncoder#jsonify recursion also calls as_json without
|
|
28
|
+
/// options (only the top-level value receives them).
|
|
29
|
+
pub(super) fn protected_as_json(v: VALUE) -> Result<VALUE, Error> {
|
|
30
|
+
magnus::rb_sys::protect(|| unsafe { rb_sys::rb_funcall(v, as_json_id(), 0) })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Interned `as_json` method ID, resolved once per process.
|
|
34
|
+
fn as_json_id() -> rb_sys::ID {
|
|
35
|
+
static AS_JSON: OnceLock<usize> = OnceLock::new();
|
|
36
|
+
*AS_JSON.get_or_init(|| unsafe { rb_sys::rb_intern(c"as_json".as_ptr()) } as usize)
|
|
37
|
+
as rb_sys::ID
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Whether `v` is a `JSON::Fragment` (pre-rendered JSON to splice
|
|
41
|
+
/// verbatim: the gem accepts fragments even under `strict`, and
|
|
42
|
+
/// ActiveSupport's encoder passes them through). The class is resolved
|
|
43
|
+
/// lazily and cached only on success, so a json gem loaded after the
|
|
44
|
+
/// first generate is still found; a fragment instance existing implies
|
|
45
|
+
/// its class does. The cached VALUE is a constant of the JSON module,
|
|
46
|
+
/// so it can never be collected.
|
|
47
|
+
pub(super) fn is_json_fragment(v: VALUE) -> bool {
|
|
48
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
49
|
+
static FRAGMENT: AtomicUsize = AtomicUsize::new(0);
|
|
50
|
+
let mut cls = FRAGMENT.load(Ordering::Relaxed);
|
|
51
|
+
if cls == 0 {
|
|
52
|
+
cls = resolve_json_fragment();
|
|
53
|
+
if cls == 0 {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
FRAGMENT.store(cls, Ordering::Relaxed);
|
|
57
|
+
}
|
|
58
|
+
unsafe { rb_sys::rb_obj_is_kind_of(v, cls as VALUE) != QFALSE }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn resolve_json_fragment() -> usize {
|
|
62
|
+
unsafe {
|
|
63
|
+
let object = rb_sys::rb_cObject;
|
|
64
|
+
let json_id = rb_sys::rb_intern(c"JSON".as_ptr());
|
|
65
|
+
if rb_sys::rb_const_defined(object, json_id) == 0 {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
let json = rb_sys::rb_const_get(object, json_id);
|
|
69
|
+
let fragment_id = rb_sys::rb_intern(c"Fragment".as_ptr());
|
|
70
|
+
if rb_sys::rb_const_defined(json, fragment_id) == 0 {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
rb_sys::rb_const_get(json, fragment_id) as usize
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
26
77
|
/// Encode `v` to UTF-8, protected. `rb_str_encode` raises on
|
|
27
78
|
/// undefined/invalid conversions, matching the gem, which wraps that
|
|
28
79
|
/// exception as GeneratorError (`rb_str_export_to_enc` is lenient and
|