json-projection 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ed7412ad6cf14e0b9a5d5c7ae2667289d7e407b3
4
+ data.tar.gz: 0912f5a454752fe11c589a65d08ab1f0cb291668
5
+ SHA512:
6
+ metadata.gz: 9d5517cd1c7e2f69a08d0cada96df5cbd20f3a20d4152bea642e3b7920a81454b7cd8889399b1d538ffd5b74209249278336d34cf8253be810a86c52ef5ff992
7
+ data.tar.gz: 57e8cb50913047a8801a30a5fbf9259098a367e5ae38c500e1cc6be41096be6c4b730bab7c5330db81eebb220420db888be3ed33987b2643040eb61049bd3d50
@@ -0,0 +1,126 @@
1
+ # encoding: UTF-8
2
+
3
+ # Based on JSON::Stream::Buffer from https://github.com/dgraham/json-stream
4
+ # license preserved below.
5
+ #
6
+ # Copyright (c) 2010-2014 David Graham
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+
26
+ module JsonProjection
27
+
28
+ # A character buffer that expects a UTF-8 encoded stream of bytes.
29
+ # This handles truncated multi-byte characters properly so we can just
30
+ # feed it binary data and receive a properly formatted UTF-8 String as
31
+ # output.
32
+ #
33
+ # More UTF-8 parsing details are available at:
34
+ #
35
+ # http://en.wikipedia.org/wiki/UTF-8
36
+ # http://tools.ietf.org/html/rfc3629#section-3
37
+ class Buffer
38
+ def initialize
39
+ @state = :start
40
+ @buffer = []
41
+ @need = 0
42
+ end
43
+
44
+ # Fill the buffer with a String of binary UTF-8 encoded bytes. Returns
45
+ # as much of the data in a UTF-8 String as we have. Truncated multi-byte
46
+ # characters are saved in the buffer until the next call to this method
47
+ # where we expect to receive the rest of the multi-byte character.
48
+ #
49
+ # data - The partial binary encoded String data.
50
+ #
51
+ # Raises JSON::Stream::ParserError if the UTF-8 byte sequence is malformed.
52
+ #
53
+ # Returns a UTF-8 encoded String.
54
+ def <<(data)
55
+ # Avoid state machine for complete UTF-8.
56
+ if @buffer.empty?
57
+ data.force_encoding(Encoding::UTF_8)
58
+ return data if data.valid_encoding?
59
+ end
60
+
61
+ bytes = []
62
+ data.each_byte do |byte|
63
+ case @state
64
+ when :start
65
+ if byte < 128
66
+ bytes << byte
67
+ elsif byte >= 192
68
+ @state = :multi_byte
69
+ @buffer << byte
70
+ @need =
71
+ case
72
+ when byte >= 240 then 4
73
+ when byte >= 224 then 3
74
+ when byte >= 192 then 2
75
+ end
76
+ else
77
+ error('Expected start of multi-byte or single byte char')
78
+ end
79
+ when :multi_byte
80
+ if byte > 127 && byte < 192
81
+ @buffer << byte
82
+ if @buffer.size == @need
83
+ bytes += @buffer.slice!(0, @buffer.size)
84
+ @state = :start
85
+ end
86
+ else
87
+ error('Expected continuation byte')
88
+ end
89
+ end
90
+ end
91
+
92
+ # Build UTF-8 encoded string from completed codepoints.
93
+ bytes.pack('C*').force_encoding(Encoding::UTF_8).tap do |text|
94
+ error('Invalid UTF-8 byte sequence') unless text.valid_encoding?
95
+ end
96
+ end
97
+
98
+ # Determine if the buffer contains partial UTF-8 continuation bytes that
99
+ # are waiting on subsequent completion bytes before a full codepoint is
100
+ # formed.
101
+ #
102
+ # Examples
103
+ #
104
+ # bytes = "é".bytes
105
+ #
106
+ # buffer << bytes[0]
107
+ # buffer.empty?
108
+ # # => false
109
+ #
110
+ # buffer << bytes[1]
111
+ # buffer.empty?
112
+ # # => true
113
+ #
114
+ # Returns true if the buffer is empty.
115
+ def empty?
116
+ @buffer.empty?
117
+ end
118
+
119
+ private
120
+
121
+ def error(message)
122
+ raise ParseError, message
123
+ end
124
+ end
125
+
126
+ end
@@ -0,0 +1,7 @@
1
+ module JsonProjection
2
+ class Error < StandardError
3
+ end
4
+
5
+ class ParseError < Error
6
+ end
7
+ end
@@ -0,0 +1,81 @@
1
+ module JsonProjection
2
+
3
+ # Sum type
4
+ class StreamEvent
5
+
6
+ def self.empty
7
+ @empty ||= new
8
+ end
9
+
10
+ def ==(other)
11
+ other.is_a?(self.class)
12
+ end
13
+
14
+ def hash
15
+ self.class.hash
16
+ end
17
+
18
+ end
19
+
20
+ class StartDocument < StreamEvent
21
+ end
22
+
23
+ class EndDocument < StreamEvent
24
+ end
25
+
26
+ class StartObject < StreamEvent
27
+ end
28
+
29
+ class EndObject < StreamEvent
30
+ end
31
+
32
+ class StartArray < StreamEvent
33
+ end
34
+
35
+ class EndArray < StreamEvent
36
+ end
37
+
38
+ class Key < StreamEvent
39
+ attr_reader :key
40
+ def initialize(key)
41
+ @key = key
42
+ end
43
+
44
+ def ==(other)
45
+ return false unless super(other)
46
+ return key == other.key
47
+ end
48
+
49
+ def hash
50
+ key.hash
51
+ end
52
+ end
53
+
54
+ class Value < StreamEvent
55
+ attr_reader :value
56
+ def initialize(value)
57
+ @value = value
58
+ end
59
+
60
+ def ==(other)
61
+ return false unless super(other)
62
+ return value == other.value
63
+ end
64
+ end
65
+
66
+ class String < Value
67
+ end
68
+
69
+ class Number < Value
70
+ end
71
+
72
+ class Boolean < Value
73
+ end
74
+
75
+ class Null < Value
76
+ def self.empty
77
+ new(nil)
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,53 @@
1
+ module JsonProjection
2
+ class Fifo
3
+
4
+ def self.empty
5
+ @empty ||= self.new
6
+ end
7
+
8
+ def self.pure(val)
9
+ Fifo.new([val])
10
+ end
11
+
12
+ def initialize(stack = [])
13
+ @stack = stack
14
+ end
15
+
16
+ def push(val)
17
+ Fifo.new(@stack.dup.insert(0, val))
18
+ end
19
+
20
+ def pop()
21
+ return self, nil if empty?
22
+
23
+ init = @stack.slice(0, @stack.size - 1)
24
+ last = @stack.last
25
+
26
+ return Fifo.new(init), last
27
+ end
28
+
29
+ def empty?
30
+ @stack.empty?
31
+ end
32
+
33
+ def ==(other)
34
+ return false unless other.is_a?(Fifo)
35
+ return stack == other.stack
36
+ end
37
+
38
+ def hash
39
+ stack.hash
40
+ end
41
+
42
+ def append(fifo)
43
+ return self if fifo.empty?
44
+ return fifo if self.empty?
45
+ Fifo.new(@stack.concat(fifo.stack))
46
+ end
47
+
48
+ protected
49
+
50
+ attr_reader :stack
51
+
52
+ end
53
+ end
@@ -0,0 +1,594 @@
1
+ # encoding: UTF-8
2
+
3
+ # Based on JSON::Stream::Parser from https://github.com/dgraham/json-stream
4
+ # license preserved below.
5
+ #
6
+ # Copyright (c) 2010-2014 David Graham
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+
26
+ require_relative 'parser/buffer'
27
+ require_relative 'parser/events'
28
+ require_relative 'parser/errors'
29
+ require_relative 'parser/fifo'
30
+
31
+ module JsonProjection
32
+
33
+ # A streaming JSON parser that generates SAX-like events for state changes.
34
+ # Use the json gem for small documents. Use this for huge documents that
35
+ # won't fit in memory.
36
+ class Parser
37
+ BUF_SIZE = 4096
38
+ CONTROL = /[\x00-\x1F]/
39
+ WS = /[ \n\t\r]/
40
+ HEX = /[0-9a-fA-F]/
41
+ DIGIT = /[0-9]/
42
+ DIGIT_1_9 = /[1-9]/
43
+ DIGIT_END = /\d$/
44
+ TRUE_RE = /[rue]/
45
+ FALSE_RE = /[alse]/
46
+ NULL_RE = /[ul]/
47
+ TRUE_KEYWORD = 'true'
48
+ FALSE_KEYWORD = 'false'
49
+ NULL_KEYWORD = 'null'
50
+ LEFT_BRACE = '{'
51
+ RIGHT_BRACE = '}'
52
+ LEFT_BRACKET = '['
53
+ RIGHT_BRACKET = ']'
54
+ BACKSLASH = '\\'
55
+ SLASH = '/'
56
+ QUOTE = '"'
57
+ COMMA = ','
58
+ COLON = ':'
59
+ ZERO = '0'
60
+ MINUS = '-'
61
+ PLUS = '+'
62
+ POINT = '.'
63
+ EXPONENT = /[eE]/
64
+ B,F,N,R,T,U = %w[b f n r t u]
65
+
66
+ # Initialize a new parser with a stream. The stream cursor is advanced as
67
+ # events are drawn from the parser. The parser maintains a small data cache
68
+ # of bytes read from the stream.
69
+ #
70
+ # stream :: IO
71
+ # IO stream to read data from.
72
+ #
73
+ # Returns nothing.
74
+ def initialize(stream)
75
+ @stream = stream
76
+
77
+ @event_buffer = Fifo.new
78
+
79
+ @bytes_buffer = Buffer.new
80
+ @bytes = nil
81
+
82
+ @pos = -1
83
+ @state = :start_document
84
+ @stack = []
85
+
86
+ @value_buffer = ""
87
+ @unicode = ""
88
+ end
89
+
90
+ # Draw bytes from the stream until an event can be constructed. May raise
91
+ # IO errors.
92
+ #
93
+ # Returns a JsonProject::StreamEvent subclass or raises StandardError.
94
+ def next_event()
95
+ # Are there any already read events, return the oldest
96
+ @event_buffer, event = @event_buffer.pop
97
+ return event unless event.nil?
98
+
99
+ if @state == :end_document
100
+ error("already EOF, no more events")
101
+ end
102
+
103
+ while true do
104
+ if @bytes.nil? || @bytes.empty?
105
+ data = stream.read(BUF_SIZE)
106
+ if data == nil # hit EOF
107
+ error("unexpected EOF")
108
+ end
109
+
110
+ @bytes = @bytes_buffer.<<(data).each_char.to_a
111
+ end
112
+
113
+ head = @bytes.first
114
+ tail = @bytes.slice!(1, @bytes.size - 1)
115
+
116
+ @bytes = tail
117
+ @pos += 1
118
+
119
+ new_state, events = handle_character(@state, head)
120
+
121
+ @state = new_state
122
+ @event_buffer = events.append(@event_buffer)
123
+
124
+ unless @event_buffer.empty?
125
+ @event_buffer, event = @event_buffer.pop
126
+ return event
127
+ end
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def stream
134
+ @stream
135
+ end
136
+
137
+ # Given a state and new character, return a new state and fifo of events to
138
+ # yield to pull callers.
139
+ #
140
+ # state :: Symbol
141
+ #
142
+ # ch :: String
143
+ #
144
+ # Returns a tuple of (Symbol, Fifo<Event>) or raises StandardError.
145
+ def handle_character(state, ch)
146
+ case state
147
+ when :start_document
148
+ case ch
149
+ when WS
150
+ return :start_document, Fifo.empty
151
+ when LEFT_BRACE
152
+ @stack.push(:object)
153
+
154
+ events = Fifo.pure(StartDocument.empty).push(StartObject.empty)
155
+
156
+ return :start_object, events
157
+ when LEFT_BRACKET
158
+ @stack.push(:array)
159
+
160
+ events = Fifo.pure(StartDocument.empty).push(StartArray.empty)
161
+
162
+ return :start_array, events
163
+ end
164
+
165
+ error('Expected whitespace, object `{` or array `[` start token')
166
+
167
+ when :start_object
168
+ case ch
169
+ when WS
170
+ return :start_object, Fifo.empty
171
+ when QUOTE
172
+ @stack.push(:key)
173
+ return :start_string, Fifo.empty
174
+ when RIGHT_BRACE
175
+ return end_container(:object)
176
+ end
177
+
178
+ error('Expected object key `"` start')
179
+
180
+ when :start_string
181
+ case ch
182
+ when QUOTE
183
+ if @stack.pop == :string
184
+ events = Fifo.pure(end_value(@value_buffer.dup))
185
+ @value_buffer.clear
186
+
187
+ return :end_value, events
188
+ else # :key
189
+ events = Fifo.pure(Key.new(@value_buffer.dup))
190
+ @value_buffer.clear
191
+
192
+ return :end_key, events
193
+ end
194
+ when BACKSLASH
195
+ return :start_escape, Fifo.empty
196
+ when CONTROL
197
+ error('Control characters must be escaped')
198
+ else
199
+ @value_buffer << ch
200
+ return :start_string, Fifo.empty
201
+ end
202
+
203
+ when :start_escape
204
+ case ch
205
+ when QUOTE, BACKSLASH, SLASH
206
+ @value_buffer << ch
207
+ return :start_string, Fifo.empty
208
+ when B
209
+ @value_buffer << "\b"
210
+ return :start_string, Fifo.empty
211
+ when F
212
+ @value_buffer << "\f"
213
+ return :start_string, Fifo.empty
214
+ when N
215
+ @value_buffer << "\n"
216
+ return :start_string, Fifo.empty
217
+ when R
218
+ @value_buffer << "\r"
219
+ return :start_string, Fifo.empty
220
+ when T
221
+ @value_buffer << "\t"
222
+ return :start_string, Fifo.empty
223
+ when U
224
+ return :unicode_escape, Fifo.empty
225
+ else
226
+ error('Expected escaped character')
227
+ end
228
+
229
+ when :unicode_escape
230
+ case ch
231
+ when HEX
232
+ @unicode << ch
233
+ if @unicode.size == 4
234
+ codepoint = @unicode.slice!(0, 4).hex
235
+ if codepoint >= 0xD800 && codepoint <= 0xDBFF
236
+ error('Expected low surrogate pair half') if @stack[-1].is_a?(Fixnum)
237
+ @stack.push(codepoint)
238
+ return :start_surrogate_pair, Fifo.empty
239
+ elsif codepoint >= 0xDC00 && codepoint <= 0xDFFF
240
+ high = @stack.pop
241
+ error('Expected high surrogate pair half') unless high.is_a?(Fixnum)
242
+ pair = ((high - 0xD800) * 0x400) + (codepoint - 0xDC00) + 0x10000
243
+ @value_buffer << pair
244
+ return :start_string, Fifo.empty
245
+ else
246
+ @value_buffer << codepoint
247
+ return :start_string, Fifo.empty
248
+ end
249
+ end
250
+
251
+ return :unicode_escape, Fifo.empty
252
+ else
253
+ error('Expected unicode escape hex digit')
254
+ end
255
+
256
+ when :start_surrogate_pair
257
+ case ch
258
+ when BACKSLASH
259
+ return :start_surrogate_pair_u, Fifo.empty
260
+ else
261
+ error('Expected low surrogate pair half')
262
+ end
263
+
264
+ when :start_surrogate_pair_u
265
+ case ch
266
+ when U
267
+ return :unicode_escape, Fifo.empty
268
+ else
269
+ error('Expected low surrogate pair half')
270
+ end
271
+
272
+ when :start_negative_number
273
+ case ch
274
+ when ZERO
275
+ @value_buffer << ch
276
+ return :start_zero, Fifo.empty
277
+ when DIGIT_1_9
278
+ @value_buffer << ch
279
+ return :start_int, Fifo.empty
280
+ else
281
+ error('Expected 0-9 digit')
282
+ end
283
+
284
+ when :start_zero
285
+ case ch
286
+ when POINT
287
+ @value_buffer << ch
288
+ return :start_float, Fifo.empty
289
+ when EXPONENT
290
+ @value_buffer << ch
291
+ return :start_exponent, Fifo.empty
292
+ else
293
+ events = Fifo.pure(end_value(@value_buffer.to_i))
294
+ @value_buffer.clear
295
+
296
+ state = :end_value
297
+
298
+ state, new_events = handle_character(state, ch)
299
+
300
+ return state, new_events.append(events)
301
+ end
302
+
303
+ when :start_float
304
+ case ch
305
+ when DIGIT
306
+ @value_buffer << ch
307
+ return :in_float, Fifo.empty
308
+ end
309
+
310
+ error('Expected 0-9 digit')
311
+
312
+ when :in_float
313
+ case ch
314
+ when DIGIT
315
+ @value_buffer << ch
316
+ return :in_float, Fifo.empty
317
+ when EXPONENT
318
+ @value_buffer << ch
319
+ return :start_exponent, Fifo.empty
320
+ else
321
+ events = Fifo.pure(end_value(@value_buffer.to_f))
322
+ @value_buffer.clear
323
+
324
+ state = :end_value
325
+
326
+ state, new_events = handle_character(state, ch)
327
+
328
+ return state, new_events.append(events)
329
+ end
330
+
331
+ when :start_exponent
332
+ case ch
333
+ when MINUS, PLUS, DIGIT
334
+ @value_buffer << ch
335
+ return :in_exponent, Fifo.empty
336
+ end
337
+
338
+ error('Expected +, -, or 0-9 digit')
339
+
340
+ when :in_exponent
341
+ case ch
342
+ when DIGIT
343
+ @value_buffer << ch
344
+ return :in_exponent, Fifo.empty
345
+ else
346
+ error('Expected 0-9 digit') unless @value_buffer =~ DIGIT_END
347
+
348
+ events = Fifo.pure(end_value(@value_buffer.to_f))
349
+ @value_buffer.clear
350
+
351
+ state = :end_value
352
+
353
+ state, new_events = handle_character(state, ch)
354
+
355
+ return state, new_events.append(events)
356
+ end
357
+
358
+ when :start_int
359
+ case ch
360
+ when DIGIT
361
+ @value_buffer << ch
362
+ return :start_int, Fifo.empty
363
+ when POINT
364
+ @value_buffer << ch
365
+ return :start_float, Fifo.empty
366
+ when EXPONENT
367
+ @value_buffer << ch
368
+ return :start_exponent, Fifo.empty
369
+ else
370
+ events = Fifo.pure(end_value(@value_buffer.to_i))
371
+ @value_buffer.clear
372
+
373
+ state = :end_value
374
+
375
+ state, new_events = handle_character(state, ch)
376
+
377
+ return state, new_events.append(events)
378
+ end
379
+
380
+ when :start_true
381
+ state, event = keyword(TRUE_KEYWORD, true, TRUE_RE, ch)
382
+ if state.nil?
383
+ return :start_true, Fifo.empty
384
+ end
385
+
386
+ return state, Fifo.pure(event)
387
+ when :start_false
388
+ state, event = keyword(FALSE_KEYWORD, false, FALSE_RE, ch)
389
+ if state.nil?
390
+ return :start_false, Fifo.empty
391
+ end
392
+
393
+ return state, Fifo.pure(event)
394
+ when :start_null
395
+ state, event = keyword(NULL_KEYWORD, nil, NULL_RE, ch)
396
+ if state.nil?
397
+ return :start_null, Fifo.empty
398
+ end
399
+
400
+ return state, Fifo.pure(event)
401
+
402
+ when :end_key
403
+ case ch
404
+ when WS
405
+ return :end_key, Fifo.empty
406
+ when COLON
407
+ return :key_sep, Fifo.empty
408
+ end
409
+
410
+ error('Expected colon key separator')
411
+
412
+ when :key_sep
413
+ case ch
414
+ when WS
415
+ return :key_sep, Fifo.empty
416
+ else
417
+ return start_value(ch)
418
+ end
419
+
420
+ when :start_array
421
+ case ch
422
+ when WS
423
+ return :start_array, Fifo.empty
424
+ when RIGHT_BRACKET
425
+ return end_container(:array)
426
+ else
427
+ return start_value(ch)
428
+ end
429
+
430
+ when :end_value
431
+ case ch
432
+ when WS
433
+ return :end_value, Fifo.empty
434
+ when COMMA
435
+ return :value_sep, Fifo.empty
436
+ when RIGHT_BRACE
437
+ return end_container(:object)
438
+ when RIGHT_BRACKET
439
+ return end_container(:array)
440
+ end
441
+
442
+ error('Expected comma `,` object `}` or array `]` close')
443
+
444
+ when :value_sep
445
+ if @stack[-1] == :object
446
+ case ch
447
+ when WS
448
+ return :value_sep, Fifo.empty
449
+ when QUOTE
450
+ @stack.push(:key)
451
+ return :start_string, Fifo.empty
452
+ end
453
+
454
+ error('Expected whitespace or object key start `"`')
455
+ end
456
+
457
+ case ch
458
+ when WS
459
+ return :value_sep, Fifo.empty
460
+ else
461
+ return start_value(ch)
462
+ end
463
+
464
+ when :end_document
465
+ error('Unexpected data') unless ch =~ WS
466
+ end
467
+ end
468
+
469
+ # Complete an object or array container value type.
470
+ #
471
+ # type - The Symbol, :object or :array, of the expected type.
472
+ #
473
+ # Raises a JSON::Stream::ParserError if the expected container type
474
+ # was not completed.
475
+ #
476
+ # Returns a tuple of (Symbol, Fifo<Event>) instance or raises a
477
+ # JsonProjection::ParseError if the character does not signal the start of
478
+ # a value.
479
+ def end_container(type)
480
+ state = :end_value
481
+ events = Fifo.empty
482
+
483
+ if @stack.pop == type
484
+ case type
485
+ when :object then
486
+ events = events.push(EndObject.empty)
487
+ when :array then
488
+ events = events.push(EndArray.empty)
489
+ end
490
+ else
491
+ error("Expected end of #{type}")
492
+ end
493
+
494
+ if @stack.empty?
495
+ state = :end_document
496
+ events = events.push(EndDocument.empty)
497
+ end
498
+
499
+ return state, events
500
+ end
501
+
502
+ # Parse one of the three allowed keywords: true, false, null.
503
+ #
504
+ # word - The String keyword ('true', 'false', 'null').
505
+ # value - The Ruby value (true, false, nil).
506
+ # re - The Regexp of allowed keyword characters.
507
+ # ch - The current String character being parsed.
508
+ #
509
+ # Raises a JSON::Stream::ParserError if the character does not belong
510
+ # in the expected keyword.
511
+ #
512
+ # Returns a JsonProjection::StreamEvent? instance or raises.
513
+ def keyword(word, value, re, ch)
514
+ if ch =~ re
515
+ @value_buffer << ch
516
+ else
517
+ error("Expected #{word} keyword")
518
+ end
519
+
520
+ if @value_buffer.size != word.size
521
+ return nil
522
+ elsif @value_buffer == word
523
+ event = end_value(value)
524
+ @value_buffer.clear
525
+
526
+ return :end_value, event
527
+ else
528
+ error("Expected #{word} keyword")
529
+ end
530
+ end
531
+
532
+ # Process the first character of one of the seven possible JSON
533
+ # values: object, array, string, true, false, null, number.
534
+ #
535
+ # ch :: String
536
+ # The current character String.
537
+ #
538
+ # Returns a JsonProjection::StreamEvent? subclass.
539
+ def start_value(ch)
540
+ case ch
541
+ when LEFT_BRACE
542
+ @stack.push(:object)
543
+ return :start_object, Fifo.pure(StartObject.empty)
544
+ when LEFT_BRACKET
545
+ @stack.push(:array)
546
+ return :start_array, Fifo.pure(StartArray.empty)
547
+ when QUOTE
548
+ @stack.push(:string)
549
+ return :start_string, Fifo.empty
550
+ when T
551
+ @value_buffer << ch
552
+ return :start_true, Fifo.empty
553
+ when F
554
+ @value_buffer << ch
555
+ return :start_false, Fifo.empty
556
+ when N
557
+ @value_buffer << ch
558
+ return :start_null, Fifo.empty
559
+ when MINUS
560
+ @value_buffer << ch
561
+ return :start_negative_number, Fifo.empty
562
+ when ZERO
563
+ @value_buffer << ch
564
+ return :start_zero, Fifo.empty
565
+ when DIGIT_1_9
566
+ @value_buffer << ch
567
+ return :start_int, Fifo.empty
568
+ end
569
+
570
+ error('Expected value')
571
+ end
572
+
573
+ # Advance the state machine and construct the event for the value just read.
574
+ #
575
+ # Returns a JsonProjection::StreamEvent subclass.
576
+ def end_value(value)
577
+ case value
578
+ when TrueClass, FalseClass
579
+ Boolean.new(value)
580
+ when Numeric
581
+ Number.new(value)
582
+ when ::String
583
+ JsonProjection::String.new(value)
584
+ when NilClass
585
+ Null.empty
586
+ end
587
+ end
588
+
589
+ def error(message)
590
+ raise ParseError, "#{message}: char #{@pos}"
591
+ end
592
+ end
593
+
594
+ end
@@ -0,0 +1,6 @@
1
+ require_relative 'json-projection/parser'
2
+ require_relative 'json-projection/projector'
3
+
4
+ module JsonProjection
5
+
6
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json-projection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Keith Duncan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 11.2.2
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 11.2.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 5.0.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 5.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest-focus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 1.1.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.1.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 5.9.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 5.9.0
69
+ description: Iteratively parse a stream of JSON data and project it into a smaller
70
+ version which can be held in memory
71
+ email: keith.duncan@github.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/json-projection.rb
77
+ - lib/json-projection/parser.rb
78
+ - lib/json-projection/parser/buffer.rb
79
+ - lib/json-projection/parser/errors.rb
80
+ - lib/json-projection/parser/events.rb
81
+ - lib/json-projection/parser/fifo.rb
82
+ homepage: https://github.com/keithduncan/json-projection
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.0.14.1
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: JSON structure preserving transform
106
+ test_files: []