learn-xcpretty 0.1.11

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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.kick +17 -0
  4. data/.travis.yml +18 -0
  5. data/CHANGELOG.md +152 -0
  6. data/CONTRIBUTING.md +60 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE.txt +61 -0
  9. data/README.md +143 -0
  10. data/Rakefile +24 -0
  11. data/assets/report.html.erb +155 -0
  12. data/bin/learn-xcpretty +80 -0
  13. data/features/custom_formatter.feature +15 -0
  14. data/features/fixtures/xcodebuild.log +5963 -0
  15. data/features/html_report.feature +40 -0
  16. data/features/json_compilation_database_report.feature +21 -0
  17. data/features/junit_report.feature +44 -0
  18. data/features/knock_format.feature +11 -0
  19. data/features/simple_format.feature +172 -0
  20. data/features/steps/formatting_steps.rb +268 -0
  21. data/features/steps/html_steps.rb +23 -0
  22. data/features/steps/json_steps.rb +37 -0
  23. data/features/steps/junit_steps.rb +38 -0
  24. data/features/steps/report_steps.rb +21 -0
  25. data/features/steps/xcpretty_steps.rb +31 -0
  26. data/features/support/env.rb +108 -0
  27. data/features/tap_format.feature +31 -0
  28. data/features/test_format.feature +39 -0
  29. data/features/xcpretty.feature +14 -0
  30. data/learn-xcpretty.gemspec +37 -0
  31. data/lib/xcpretty/ansi.rb +71 -0
  32. data/lib/xcpretty/formatters/formatter.rb +134 -0
  33. data/lib/xcpretty/formatters/knock.rb +34 -0
  34. data/lib/xcpretty/formatters/rspec.rb +27 -0
  35. data/lib/xcpretty/formatters/simple.rb +155 -0
  36. data/lib/xcpretty/formatters/tap.rb +39 -0
  37. data/lib/xcpretty/parser.rb +421 -0
  38. data/lib/xcpretty/printer.rb +20 -0
  39. data/lib/xcpretty/reporters/html.rb +73 -0
  40. data/lib/xcpretty/reporters/json_compilation_database.rb +58 -0
  41. data/lib/xcpretty/reporters/junit.rb +99 -0
  42. data/lib/xcpretty/reporters/learn.rb +154 -0
  43. data/lib/xcpretty/snippet.rb +34 -0
  44. data/lib/xcpretty/syntax.rb +20 -0
  45. data/lib/xcpretty/version.rb +3 -0
  46. data/lib/xcpretty.rb +39 -0
  47. data/spec/fixtures/NSStringTests.m +64 -0
  48. data/spec/fixtures/constants.rb +546 -0
  49. data/spec/fixtures/custom_formatter.rb +17 -0
  50. data/spec/fixtures/oneliner.m +1 -0
  51. data/spec/fixtures/raw_kiwi_compilation_fail.txt +24 -0
  52. data/spec/fixtures/raw_kiwi_fail.txt +1896 -0
  53. data/spec/fixtures/raw_specta_fail.txt +3110 -0
  54. data/spec/spec_helper.rb +6 -0
  55. data/spec/support/matchers/colors.rb +20 -0
  56. data/spec/xcpretty/ansi_spec.rb +46 -0
  57. data/spec/xcpretty/formatters/formatter_spec.rb +113 -0
  58. data/spec/xcpretty/formatters/rspec_spec.rb +55 -0
  59. data/spec/xcpretty/formatters/simple_spec.rb +129 -0
  60. data/spec/xcpretty/parser_spec.rb +421 -0
  61. data/spec/xcpretty/printer_spec.rb +53 -0
  62. data/spec/xcpretty/snippet_spec.rb +39 -0
  63. data/spec/xcpretty/syntax_spec.rb +35 -0
  64. data/vendor/json_pure/COPYING +57 -0
  65. data/vendor/json_pure/LICENSE +340 -0
  66. data/vendor/json_pure/generator.rb +443 -0
  67. data/vendor/json_pure/parser.rb +364 -0
  68. metadata +261 -0
@@ -0,0 +1,443 @@
1
+ module JSON
2
+ MAP = {
3
+ "\x0" => '\u0000',
4
+ "\x1" => '\u0001',
5
+ "\x2" => '\u0002',
6
+ "\x3" => '\u0003',
7
+ "\x4" => '\u0004',
8
+ "\x5" => '\u0005',
9
+ "\x6" => '\u0006',
10
+ "\x7" => '\u0007',
11
+ "\b" => '\b',
12
+ "\t" => '\t',
13
+ "\n" => '\n',
14
+ "\xb" => '\u000b',
15
+ "\f" => '\f',
16
+ "\r" => '\r',
17
+ "\xe" => '\u000e',
18
+ "\xf" => '\u000f',
19
+ "\x10" => '\u0010',
20
+ "\x11" => '\u0011',
21
+ "\x12" => '\u0012',
22
+ "\x13" => '\u0013',
23
+ "\x14" => '\u0014',
24
+ "\x15" => '\u0015',
25
+ "\x16" => '\u0016',
26
+ "\x17" => '\u0017',
27
+ "\x18" => '\u0018',
28
+ "\x19" => '\u0019',
29
+ "\x1a" => '\u001a',
30
+ "\x1b" => '\u001b',
31
+ "\x1c" => '\u001c',
32
+ "\x1d" => '\u001d',
33
+ "\x1e" => '\u001e',
34
+ "\x1f" => '\u001f',
35
+ '"' => '\"',
36
+ '\\' => '\\\\',
37
+ } # :nodoc:
38
+
39
+ if defined?(::Encoding)
40
+ def utf8_to_json(string) # :nodoc:
41
+ string = string.dup
42
+ string.force_encoding(::Encoding::ASCII_8BIT)
43
+ string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
44
+ string.force_encoding(::Encoding::UTF_8)
45
+ string
46
+ end
47
+
48
+ def utf8_to_json_ascii(string) # :nodoc:
49
+ string = string.dup
50
+ string.force_encoding(::Encoding::ASCII_8BIT)
51
+ string.gsub!(/["\\\x0-\x1f]/n) { MAP[$&] }
52
+ string.gsub!(/(
53
+ (?:
54
+ [\xc2-\xdf][\x80-\xbf] |
55
+ [\xe0-\xef][\x80-\xbf]{2} |
56
+ [\xf0-\xf4][\x80-\xbf]{3}
57
+ )+ |
58
+ [\x80-\xc1\xf5-\xff] # invalid
59
+ )/nx) { |c|
60
+ c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
61
+ s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
62
+ s.force_encoding(::Encoding::ASCII_8BIT)
63
+ s.gsub!(/.{4}/n, '\\\\u\&')
64
+ s.force_encoding(::Encoding::UTF_8)
65
+ }
66
+ string.force_encoding(::Encoding::UTF_8)
67
+ string
68
+ rescue => e
69
+ raise GeneratorError.wrap(e)
70
+ end
71
+
72
+ def valid_utf8?(string)
73
+ encoding = string.encoding
74
+ (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII) &&
75
+ string.valid_encoding?
76
+ end
77
+ module_function :valid_utf8?
78
+ else
79
+ def utf8_to_json(string) # :nodoc:
80
+ string.gsub(/["\\\x0-\x1f]/n) { MAP[$&] }
81
+ end
82
+
83
+ def utf8_to_json_ascii(string) # :nodoc:
84
+ string = string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
85
+ string.gsub!(/(
86
+ (?:
87
+ [\xc2-\xdf][\x80-\xbf] |
88
+ [\xe0-\xef][\x80-\xbf]{2} |
89
+ [\xf0-\xf4][\x80-\xbf]{3}
90
+ )+ |
91
+ [\x80-\xc1\xf5-\xff] # invalid
92
+ )/nx) { |c|
93
+ c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
94
+ s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
95
+ s.gsub!(/.{4}/n, '\\\\u\&')
96
+ }
97
+ string
98
+ rescue => e
99
+ raise GeneratorError.wrap(e)
100
+ end
101
+
102
+ def valid_utf8?(string)
103
+ string =~
104
+ /\A( [\x09\x0a\x0d\x20-\x7e] # ASCII
105
+ | [\xc2-\xdf][\x80-\xbf] # non-overlong 2-byte
106
+ | \xe0[\xa0-\xbf][\x80-\xbf] # excluding overlongs
107
+ | [\xe1-\xec\xee\xef][\x80-\xbf]{2} # straight 3-byte
108
+ | \xed[\x80-\x9f][\x80-\xbf] # excluding surrogates
109
+ | \xf0[\x90-\xbf][\x80-\xbf]{2} # planes 1-3
110
+ | [\xf1-\xf3][\x80-\xbf]{3} # planes 4-15
111
+ | \xf4[\x80-\x8f][\x80-\xbf]{2} # plane 16
112
+ )*\z/nx
113
+ end
114
+ end
115
+ module_function :utf8_to_json, :utf8_to_json_ascii, :valid_utf8?
116
+
117
+
118
+ module Pure
119
+ module Generator
120
+ class State
121
+
122
+ def self.from_state(opts)
123
+ case
124
+ when self === opts
125
+ opts
126
+ when opts.respond_to?(:to_hash)
127
+ new(opts.to_hash)
128
+ when opts.respond_to?(:to_h)
129
+ new(opts.to_h)
130
+ else
131
+ new
132
+ end
133
+ end
134
+
135
+ def initialize(opts = {})
136
+ @indent = ''
137
+ @space = ''
138
+ @space_before = ''
139
+ @object_nl = ''
140
+ @array_nl = ''
141
+ @allow_nan = false
142
+ @ascii_only = false
143
+ @quirks_mode = false
144
+ @buffer_initial_length = 1024
145
+
146
+ configure opts
147
+ end
148
+
149
+ attr_accessor :indent, :space, :space_before, :object_nl, :array_nl,
150
+ :max_nesting, :quirks_mode, :buffer_initial_length, :depth
151
+
152
+ def buffer_initial_length=(length)
153
+ if length > 0
154
+ @buffer_initial_length = length
155
+ end
156
+ end
157
+
158
+ def check_max_nesting # :nodoc:
159
+ return if @max_nesting.zero?
160
+ current_nesting = depth + 1
161
+ current_nesting > @max_nesting and
162
+ raise NestingError, "nesting of #{current_nesting} is too deep"
163
+ end
164
+
165
+ def check_circular?
166
+ !@max_nesting.zero?
167
+ end
168
+
169
+ def allow_nan?
170
+ @allow_nan
171
+ end
172
+
173
+ def ascii_only?
174
+ @ascii_only
175
+ end
176
+
177
+ def quirks_mode?
178
+ @quirks_mode
179
+ end
180
+
181
+ def configure(opts)
182
+ if opts.respond_to?(:to_hash)
183
+ opts = opts.to_hash
184
+ elsif opts.respond_to?(:to_h)
185
+ opts = opts.to_h
186
+ else
187
+ raise TypeError, "can't convert #{opts.class} into Hash"
188
+ end
189
+ for key, value in opts
190
+ instance_variable_set "@#{key}", value
191
+ end
192
+ @indent = opts[:indent] if opts.key?(:indent)
193
+ @space = opts[:space] if opts.key?(:space)
194
+ @space_before = opts[:space_before] if opts.key?(:space_before)
195
+ @object_nl = opts[:object_nl] if opts.key?(:object_nl)
196
+ @array_nl = opts[:array_nl] if opts.key?(:array_nl)
197
+ @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
198
+ @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
199
+ @depth = opts[:depth] || 0
200
+ @quirks_mode = opts[:quirks_mode] if opts.key?(:quirks_mode)
201
+ @buffer_initial_length ||= opts[:buffer_initial_length]
202
+
203
+ if !opts.key?(:max_nesting) # defaults to 100
204
+ @max_nesting = 100
205
+ elsif opts[:max_nesting]
206
+ @max_nesting = opts[:max_nesting]
207
+ else
208
+ @max_nesting = 0
209
+ end
210
+ self
211
+ end
212
+ alias merge configure
213
+
214
+ def to_h
215
+ result = {}
216
+ for iv in instance_variables
217
+ iv = iv.to_s[1..-1]
218
+ result[iv.to_sym] = self[iv]
219
+ end
220
+ result
221
+ end
222
+
223
+ alias to_hash to_h
224
+
225
+ def generate(obj)
226
+ result = obj.to_json(self)
227
+ JSON.valid_utf8?(result) or raise GeneratorError,
228
+ "source sequence #{result.inspect} is illegal/malformed utf-8"
229
+ unless @quirks_mode
230
+ unless result =~ /\A\s*\[/ && result =~ /\]\s*\Z/ ||
231
+ result =~ /\A\s*\{/ && result =~ /\}\s*\Z/
232
+ then
233
+ raise GeneratorError, "only generation of JSON objects or arrays allowed"
234
+ end
235
+ end
236
+ result
237
+ end
238
+
239
+ def [](name)
240
+ if respond_to?(name)
241
+ __send__(name)
242
+ else
243
+ instance_variable_get("@#{name}")
244
+ end
245
+ end
246
+
247
+ def []=(name, value)
248
+ if respond_to?(name_writer = "#{name}=")
249
+ __send__ name_writer, value
250
+ else
251
+ instance_variable_set "@#{name}", value
252
+ end
253
+ end
254
+ end
255
+
256
+ module GeneratorMethods
257
+ module Object
258
+ def to_json(*) to_s.to_json end
259
+ end
260
+
261
+ module Hash
262
+ def to_json(state = nil, *)
263
+ state = State.from_state(state)
264
+ state.check_max_nesting
265
+ json_transform(state)
266
+ end
267
+
268
+ private
269
+
270
+ def json_shift(state)
271
+ state.object_nl.empty? or return ''
272
+ state.indent * state.depth
273
+ end
274
+
275
+ def json_transform(state)
276
+ delim = ','
277
+ delim << state.object_nl
278
+ result = '{'
279
+ result << state.object_nl
280
+ depth = state.depth += 1
281
+ first = true
282
+ indent = !state.object_nl.empty?
283
+ each { |key,value|
284
+ result << delim unless first
285
+ result << state.indent * depth if indent
286
+ result << key.to_s.to_json(state)
287
+ result << state.space_before
288
+ result << ':'
289
+ result << state.space
290
+ result << value.to_json(state)
291
+ first = false
292
+ }
293
+ depth = state.depth -= 1
294
+ result << state.object_nl
295
+ result << state.indent * depth if indent
296
+ result << '}'
297
+ result
298
+ end
299
+ end
300
+
301
+ module Array
302
+ def to_json(state = nil, *)
303
+ state = State.from_state(state)
304
+ state.check_max_nesting
305
+ json_transform(state)
306
+ end
307
+
308
+ private
309
+
310
+ def json_transform(state)
311
+ delim = ','
312
+ delim << state.array_nl
313
+ result = '['
314
+ result << state.array_nl
315
+ depth = state.depth += 1
316
+ first = true
317
+ indent = !state.array_nl.empty?
318
+ each { |value|
319
+ result << delim unless first
320
+ result << state.indent * depth if indent
321
+ result << value.to_json(state)
322
+ first = false
323
+ }
324
+ depth = state.depth -= 1
325
+ result << state.array_nl
326
+ result << state.indent * depth if indent
327
+ result << ']'
328
+ end
329
+ end
330
+
331
+ module Integer
332
+ def to_json(*) to_s end
333
+ end
334
+
335
+ module Float
336
+ def to_json(state = nil, *)
337
+ state = State.from_state(state)
338
+ case
339
+ when infinite?
340
+ if state.allow_nan?
341
+ to_s
342
+ else
343
+ raise GeneratorError, "#{self} not allowed in JSON"
344
+ end
345
+ when nan?
346
+ if state.allow_nan?
347
+ to_s
348
+ else
349
+ raise GeneratorError, "#{self} not allowed in JSON"
350
+ end
351
+ else
352
+ to_s
353
+ end
354
+ end
355
+ end
356
+
357
+ module String
358
+ if defined?(::Encoding)
359
+ def to_json(state = nil, *args)
360
+ state = State.from_state(state)
361
+ if encoding == ::Encoding::UTF_8
362
+ string = self
363
+ else
364
+ string = encode(::Encoding::UTF_8)
365
+ end
366
+ if state.ascii_only?
367
+ '"' << JSON.utf8_to_json_ascii(string) << '"'
368
+ else
369
+ '"' << JSON.utf8_to_json(string) << '"'
370
+ end
371
+ end
372
+ else
373
+ def to_json(state = nil, *args)
374
+ state = State.from_state(state)
375
+ if state.ascii_only?
376
+ '"' << JSON.utf8_to_json_ascii(self) << '"'
377
+ else
378
+ '"' << JSON.utf8_to_json(self) << '"'
379
+ end
380
+ end
381
+ end
382
+
383
+ module Extend
384
+ def json_create(o)
385
+ o['raw'].pack('C*')
386
+ end
387
+ end
388
+
389
+ def self.included(modul)
390
+ modul.extend Extend
391
+ end
392
+
393
+ def to_json_raw_object
394
+ {
395
+ JSON.create_id => self.class.name,
396
+ 'raw' => self.unpack('C*'),
397
+ }
398
+ end
399
+
400
+ def to_json_raw(*args)
401
+ to_json_raw_object.to_json(*args)
402
+ end
403
+ end
404
+
405
+ module TrueClass
406
+ def to_json(*) 'true' end
407
+ end
408
+
409
+ module FalseClass
410
+ def to_json(*) 'false' end
411
+ end
412
+
413
+ module NilClass
414
+ def to_json(*) 'null' end
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end
420
+
421
+ class NilClass
422
+ include JSON::Pure::Generator::GeneratorMethods::NilClass
423
+ end
424
+
425
+ class Hash
426
+ include JSON::Pure::Generator::GeneratorMethods::Hash
427
+ end
428
+
429
+ class Array
430
+ include JSON::Pure::Generator::GeneratorMethods::Array
431
+ end
432
+
433
+ class String
434
+ include JSON::Pure::Generator::GeneratorMethods::String
435
+ end
436
+
437
+ class Integer
438
+ include JSON::Pure::Generator::GeneratorMethods::Integer
439
+ end
440
+
441
+ class Float
442
+ include JSON::Pure::Generator::GeneratorMethods::Float
443
+ end
@@ -0,0 +1,364 @@
1
+ require 'strscan'
2
+
3
+ module JSON
4
+
5
+ def self.parse source
6
+ Pure::Parser.new(source).parse
7
+ end
8
+
9
+ module Pure
10
+ # This class implements the JSON parser that is used to parse a JSON string
11
+ # into a Ruby data structure.
12
+ class Parser < StringScanner
13
+ STRING = /" ((?:[^\x0-\x1f"\\] |
14
+ # escaped special characters:
15
+ \\["\\\/bfnrt] |
16
+ \\u[0-9a-fA-F]{4} |
17
+ # match all but escaped special characters:
18
+ \\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\xff])*)
19
+ "/nx
20
+ INTEGER = /(-?0|-?[1-9]\d*)/
21
+ FLOAT = /(-?
22
+ (?:0|[1-9]\d*)
23
+ (?:
24
+ \.\d+(?i:e[+-]?\d+) |
25
+ \.\d+ |
26
+ (?i:e[+-]?\d+)
27
+ )
28
+ )/x
29
+ NAN = /NaN/
30
+ INFINITY = /Infinity/
31
+ MINUS_INFINITY = /-Infinity/
32
+ OBJECT_OPEN = /\{/
33
+ OBJECT_CLOSE = /\}/
34
+ ARRAY_OPEN = /\[/
35
+ ARRAY_CLOSE = /\]/
36
+ PAIR_DELIMITER = /:/
37
+ COLLECTION_DELIMITER = /,/
38
+ TRUE = /true/
39
+ FALSE = /false/
40
+ NULL = /null/
41
+ IGNORE = %r(
42
+ (?:
43
+ //[^\n\r]*[\n\r]| # line comments
44
+ /\* # c-style comments
45
+ (?:
46
+ [^*/]| # normal chars
47
+ /[^*]| # slashes that do not start a nested comment
48
+ \*[^/]| # asterisks that do not end this comment
49
+ /(?=\*/) # single slash before this comment's end
50
+ )*
51
+ \*/ # the End of this comment
52
+ |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr
53
+ )+
54
+ )mx
55
+
56
+ UNPARSED = Object.new
57
+
58
+ # Creates a new JSON::Pure::Parser instance for the string _source_.
59
+ #
60
+ # It will be configured by the _opts_ hash. _opts_ can have the following
61
+ # keys:
62
+ # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
63
+ # structures. Disable depth checking with :max_nesting => false|nil|0,
64
+ # it defaults to 100.
65
+ # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
66
+ # defiance of RFC 4627 to be parsed by the Parser. This option defaults
67
+ # to false.
68
+ # * *symbolize_names*: If set to true, returns symbols for the names
69
+ # (keys) in a JSON object. Otherwise strings are returned, which is also
70
+ # the default.
71
+ # * *create_additions*: If set to true, the Parser creates
72
+ # additions when if a matching class and create_id was found. This
73
+ # option defaults to false.
74
+ # * *object_class*: Defaults to Hash
75
+ # * *array_class*: Defaults to Array
76
+ # * *quirks_mode*: Enables quirks_mode for parser, that is for example
77
+ # parsing single JSON values instead of documents is possible.
78
+ def initialize(source, opts = {})
79
+ opts ||= {}
80
+ unless @quirks_mode = opts[:quirks_mode]
81
+ source = convert_encoding source
82
+ end
83
+ super source
84
+ if !opts.key?(:max_nesting) # defaults to 100
85
+ @max_nesting = 100
86
+ elsif opts[:max_nesting]
87
+ @max_nesting = opts[:max_nesting]
88
+ else
89
+ @max_nesting = 0
90
+ end
91
+ @allow_nan = !!opts[:allow_nan]
92
+ @symbolize_names = !!opts[:symbolize_names]
93
+ if opts.key?(:create_additions)
94
+ @create_additions = !!opts[:create_additions]
95
+ else
96
+ @create_additions = false
97
+ end
98
+ @create_id = @create_additions ? JSON.create_id : nil
99
+ @object_class = opts[:object_class] || Hash
100
+ @array_class = opts[:array_class] || Array
101
+ @match_string = opts[:match_string]
102
+ end
103
+
104
+ alias source string
105
+
106
+ def quirks_mode?
107
+ !!@quirks_mode
108
+ end
109
+
110
+ def reset
111
+ super
112
+ @current_nesting = 0
113
+ end
114
+
115
+ # Parses the current JSON string _source_ and returns the complete data
116
+ # structure as a result.
117
+ def parse
118
+ reset
119
+ obj = nil
120
+ if @quirks_mode
121
+ while !eos? && skip(IGNORE)
122
+ end
123
+ if eos?
124
+ raise ParserError, "source did not contain any JSON!"
125
+ else
126
+ obj = parse_value
127
+ obj == UNPARSED and raise ParserError, "source did not contain any JSON!"
128
+ end
129
+ else
130
+ until eos?
131
+ case
132
+ when scan(OBJECT_OPEN)
133
+ obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
134
+ @current_nesting = 1
135
+ obj = parse_object
136
+ when scan(ARRAY_OPEN)
137
+ obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
138
+ @current_nesting = 1
139
+ obj = parse_array
140
+ when skip(IGNORE)
141
+ ;
142
+ else
143
+ raise ParserError, "source '#{peek(20)}' not in JSON!"
144
+ end
145
+ end
146
+ obj or raise ParserError, "source did not contain any JSON!"
147
+ end
148
+ obj
149
+ end
150
+
151
+ private
152
+
153
+ def convert_encoding(source)
154
+ if source.respond_to?(:to_str)
155
+ source = source.to_str
156
+ else
157
+ raise TypeError, "#{source.inspect} is not like a string"
158
+ end
159
+ if defined?(::Encoding)
160
+ if source.encoding == ::Encoding::ASCII_8BIT
161
+ b = source[0, 4].bytes.to_a
162
+ source =
163
+ case
164
+ when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
165
+ source.dup.force_encoding(::Encoding::UTF_32BE).encode!(::Encoding::UTF_8)
166
+ when b.size >= 4 && b[0] == 0 && b[2] == 0
167
+ source.dup.force_encoding(::Encoding::UTF_16BE).encode!(::Encoding::UTF_8)
168
+ when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
169
+ source.dup.force_encoding(::Encoding::UTF_32LE).encode!(::Encoding::UTF_8)
170
+ when b.size >= 4 && b[1] == 0 && b[3] == 0
171
+ source.dup.force_encoding(::Encoding::UTF_16LE).encode!(::Encoding::UTF_8)
172
+ else
173
+ source.dup
174
+ end
175
+ else
176
+ source = source.encode(::Encoding::UTF_8)
177
+ end
178
+ source.force_encoding(::Encoding::ASCII_8BIT)
179
+ else
180
+ b = source
181
+ source =
182
+ case
183
+ when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
184
+ JSON.iconv('utf-8', 'utf-32be', b)
185
+ when b.size >= 4 && b[0] == 0 && b[2] == 0
186
+ JSON.iconv('utf-8', 'utf-16be', b)
187
+ when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
188
+ JSON.iconv('utf-8', 'utf-32le', b)
189
+ when b.size >= 4 && b[1] == 0 && b[3] == 0
190
+ JSON.iconv('utf-8', 'utf-16le', b)
191
+ else
192
+ b
193
+ end
194
+ end
195
+ source
196
+ end
197
+
198
+ # Unescape characters in strings.
199
+ UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
200
+ UNESCAPE_MAP.update({
201
+ ?" => '"',
202
+ ?\\ => '\\',
203
+ ?/ => '/',
204
+ ?b => "\b",
205
+ ?f => "\f",
206
+ ?n => "\n",
207
+ ?r => "\r",
208
+ ?t => "\t",
209
+ ?u => nil,
210
+ })
211
+
212
+ EMPTY_8BIT_STRING = ''
213
+ if ::String.method_defined?(:encode)
214
+ EMPTY_8BIT_STRING.force_encoding Encoding::ASCII_8BIT
215
+ end
216
+
217
+ def parse_string
218
+ if scan(STRING)
219
+ return '' if self[1].empty?
220
+ string = self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
221
+ if u = UNESCAPE_MAP[$&[1]]
222
+ u
223
+ else # \uXXXX
224
+ bytes = EMPTY_8BIT_STRING.dup
225
+ i = 0
226
+ while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
227
+ bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
228
+ i += 1
229
+ end
230
+ JSON.iconv('utf-8', 'utf-16be', bytes)
231
+ end
232
+ end
233
+ if string.respond_to?(:force_encoding)
234
+ string.force_encoding(::Encoding::UTF_8)
235
+ end
236
+ if @create_additions and @match_string
237
+ for (regexp, klass) in @match_string
238
+ klass.json_creatable? or next
239
+ string =~ regexp and return klass.json_create(string)
240
+ end
241
+ end
242
+ string
243
+ else
244
+ UNPARSED
245
+ end
246
+ rescue => e
247
+ raise ParserError, "Caught #{e.class} at '#{peek(20)}': #{e}"
248
+ end
249
+
250
+ def parse_value
251
+ case
252
+ when scan(FLOAT)
253
+ Float(self[1])
254
+ when scan(INTEGER)
255
+ Integer(self[1])
256
+ when scan(TRUE)
257
+ true
258
+ when scan(FALSE)
259
+ false
260
+ when scan(NULL)
261
+ nil
262
+ when (string = parse_string) != UNPARSED
263
+ string
264
+ when scan(ARRAY_OPEN)
265
+ @current_nesting += 1
266
+ ary = parse_array
267
+ @current_nesting -= 1
268
+ ary
269
+ when scan(OBJECT_OPEN)
270
+ @current_nesting += 1
271
+ obj = parse_object
272
+ @current_nesting -= 1
273
+ obj
274
+ when @allow_nan && scan(NAN)
275
+ NaN
276
+ when @allow_nan && scan(INFINITY)
277
+ Infinity
278
+ when @allow_nan && scan(MINUS_INFINITY)
279
+ MinusInfinity
280
+ else
281
+ UNPARSED
282
+ end
283
+ end
284
+
285
+ def parse_array
286
+ raise NestingError, "nesting of #@current_nesting is too deep" if
287
+ @max_nesting.nonzero? && @current_nesting > @max_nesting
288
+ result = @array_class.new
289
+ delim = false
290
+ until eos?
291
+ case
292
+ when (value = parse_value) != UNPARSED
293
+ delim = false
294
+ result << value
295
+ skip(IGNORE)
296
+ if scan(COLLECTION_DELIMITER)
297
+ delim = true
298
+ elsif match?(ARRAY_CLOSE)
299
+ ;
300
+ else
301
+ raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
302
+ end
303
+ when scan(ARRAY_CLOSE)
304
+ if delim
305
+ raise ParserError, "expected next element in array at '#{peek(20)}'!"
306
+ end
307
+ break
308
+ when skip(IGNORE)
309
+ ;
310
+ else
311
+ raise ParserError, "unexpected token in array at '#{peek(20)}'!"
312
+ end
313
+ end
314
+ result
315
+ end
316
+
317
+ def parse_object
318
+ raise NestingError, "nesting of #@current_nesting is too deep" if
319
+ @max_nesting.nonzero? && @current_nesting > @max_nesting
320
+ result = @object_class.new
321
+ delim = false
322
+ until eos?
323
+ case
324
+ when (string = parse_string) != UNPARSED
325
+ skip(IGNORE)
326
+ unless scan(PAIR_DELIMITER)
327
+ raise ParserError, "expected ':' in object at '#{peek(20)}'!"
328
+ end
329
+ skip(IGNORE)
330
+ unless (value = parse_value).equal? UNPARSED
331
+ result[@symbolize_names ? string.to_sym : string] = value
332
+ delim = false
333
+ skip(IGNORE)
334
+ if scan(COLLECTION_DELIMITER)
335
+ delim = true
336
+ elsif match?(OBJECT_CLOSE)
337
+ ;
338
+ else
339
+ raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
340
+ end
341
+ else
342
+ raise ParserError, "expected value in object at '#{peek(20)}'!"
343
+ end
344
+ when scan(OBJECT_CLOSE)
345
+ if delim
346
+ raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
347
+ end
348
+ if @create_additions and klassname = result[@create_id]
349
+ klass = JSON.deep_const_get klassname
350
+ break unless klass and klass.json_creatable?
351
+ result = klass.json_create(result)
352
+ end
353
+ break
354
+ when skip(IGNORE)
355
+ ;
356
+ else
357
+ raise ParserError, "unexpected token in object at '#{peek(20)}'!"
358
+ end
359
+ end
360
+ result
361
+ end
362
+ end
363
+ end
364
+ end