json-projection 0.1.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 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: []