7c9b2adbd0a09118aeebfdc4897cede8308a1893
[packages/precise/mcollective.git] / lib / mcollective / vendor / json / lib / json / pure / generator.rb
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   # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
40   # UTF16 big endian characters as \u????, and return it.
41   if defined?(::Encoding)
42     def utf8_to_json(string) # :nodoc:
43       string = string.dup
44       string << '' # XXX workaround: avoid buffer sharing
45       string.force_encoding(::Encoding::ASCII_8BIT)
46       string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
47       string.force_encoding(::Encoding::UTF_8)
48       string
49     end
50
51     def utf8_to_json_ascii(string) # :nodoc:
52       string = string.dup
53       string << '' # XXX workaround: avoid buffer sharing
54       string.force_encoding(::Encoding::ASCII_8BIT)
55       string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
56       string.gsub!(/(
57                       (?:
58                         [\xc2-\xdf][\x80-\xbf]    |
59                         [\xe0-\xef][\x80-\xbf]{2} |
60                         [\xf0-\xf4][\x80-\xbf]{3}
61                       )+ |
62                       [\x80-\xc1\xf5-\xff]       # invalid
63                     )/nx) { |c|
64                       c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
65                       s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
66                       s.gsub!(/.{4}/n, '\\\\u\&')
67                     }
68       string.force_encoding(::Encoding::UTF_8)
69       string
70     rescue => e
71       raise GeneratorError, "Caught #{e.class}: #{e}"
72     end
73   else
74     def utf8_to_json(string) # :nodoc:
75       string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
76     end
77
78     def utf8_to_json_ascii(string) # :nodoc:
79       string = string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
80       string.gsub!(/(
81                       (?:
82                         [\xc2-\xdf][\x80-\xbf]    |
83                         [\xe0-\xef][\x80-\xbf]{2} |
84                         [\xf0-\xf4][\x80-\xbf]{3}
85                       )+ |
86                       [\x80-\xc1\xf5-\xff]       # invalid
87                     )/nx) { |c|
88         c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
89         s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
90         s.gsub!(/.{4}/n, '\\\\u\&')
91       }
92       string
93     rescue => e
94       raise GeneratorError, "Caught #{e.class}: #{e}"
95     end
96   end
97   module_function :utf8_to_json, :utf8_to_json_ascii
98
99   module Pure
100     module Generator
101       # This class is used to create State instances, that are use to hold data
102       # while generating a JSON text from a Ruby data structure.
103       class State
104         # Creates a State object from _opts_, which ought to be Hash to create
105         # a new State instance configured by _opts_, something else to create
106         # an unconfigured instance. If _opts_ is a State object, it is just
107         # returned.
108         def self.from_state(opts)
109           case
110           when self === opts
111             opts
112           when opts.respond_to?(:to_hash)
113             new(opts.to_hash)
114           when opts.respond_to?(:to_h)
115             new(opts.to_h)
116           else
117             SAFE_STATE_PROTOTYPE.dup
118           end
119         end
120
121         # Instantiates a new State object, configured by _opts_.
122         #
123         # _opts_ can have the following keys:
124         #
125         # * *indent*: a string used to indent levels (default: ''),
126         # * *space*: a string that is put after, a : or , delimiter (default: ''),
127         # * *space_before*: a string that is put before a : pair delimiter (default: ''),
128         # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
129         # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
130         # * *check_circular*: is deprecated now, use the :max_nesting option instead,
131         # * *max_nesting*: sets the maximum level of data structure nesting in
132         #   the generated JSON, max_nesting = 0 if no maximum should be checked.
133         # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
134         #   generated, otherwise an exception is thrown, if these values are
135         #   encountered. This options defaults to false.
136         # * *quirks_mode*: Enables quirks_mode for parser, that is for example
137         #   generating single JSON values instead of documents is possible.
138         def initialize(opts = {})
139           @indent         = ''
140           @space          = ''
141           @space_before   = ''
142           @object_nl      = ''
143           @array_nl       = ''
144           @allow_nan      = false
145           @ascii_only     = false
146           @quirks_mode    = false
147           configure opts
148         end
149
150         # This string is used to indent levels in the JSON text.
151         attr_accessor :indent
152
153         # This string is used to insert a space between the tokens in a JSON
154         # string.
155         attr_accessor :space
156
157         # This string is used to insert a space before the ':' in JSON objects.
158         attr_accessor :space_before
159
160         # This string is put at the end of a line that holds a JSON object (or
161         # Hash).
162         attr_accessor :object_nl
163
164         # This string is put at the end of a line that holds a JSON array.
165         attr_accessor :array_nl
166
167         # This integer returns the maximum level of data structure nesting in
168         # the generated JSON, max_nesting = 0 if no maximum is checked.
169         attr_accessor :max_nesting
170
171         # If this attribute is set to true, quirks mode is enabled, otherwise
172         # it's disabled.
173         attr_accessor :quirks_mode
174
175         # This integer returns the current depth data structure nesting in the
176         # generated JSON.
177         attr_accessor :depth
178
179         def check_max_nesting # :nodoc:
180           return if @max_nesting.zero?
181           current_nesting = depth + 1
182           current_nesting > @max_nesting and
183             raise NestingError, "nesting of #{current_nesting} is too deep"
184         end
185
186         # Returns true, if circular data structures are checked,
187         # otherwise returns false.
188         def check_circular?
189           !@max_nesting.zero?
190         end
191
192         # Returns true if NaN, Infinity, and -Infinity should be considered as
193         # valid JSON and output.
194         def allow_nan?
195           @allow_nan
196         end
197
198         # Returns true, if only ASCII characters should be generated. Otherwise
199         # returns false.
200         def ascii_only?
201           @ascii_only
202         end
203
204         # Returns true, if quirks mode is enabled. Otherwise returns false.
205         def quirks_mode?
206           @quirks_mode
207         end
208
209         # Configure this State instance with the Hash _opts_, and return
210         # itself.
211         def configure(opts)
212           @indent         = opts[:indent] if opts.key?(:indent)
213           @space          = opts[:space] if opts.key?(:space)
214           @space_before   = opts[:space_before] if opts.key?(:space_before)
215           @object_nl      = opts[:object_nl] if opts.key?(:object_nl)
216           @array_nl       = opts[:array_nl] if opts.key?(:array_nl)
217           @allow_nan      = !!opts[:allow_nan] if opts.key?(:allow_nan)
218           @ascii_only     = opts[:ascii_only] if opts.key?(:ascii_only)
219           @depth          = opts[:depth] || 0
220           @quirks_mode    = opts[:quirks_mode] if opts.key?(:quirks_mode)
221           if !opts.key?(:max_nesting) # defaults to 19
222             @max_nesting = 19
223           elsif opts[:max_nesting]
224             @max_nesting = opts[:max_nesting]
225           else
226             @max_nesting = 0
227           end
228           self
229         end
230         alias merge configure
231
232         # Returns the configuration instance variables as a hash, that can be
233         # passed to the configure method.
234         def to_h
235           result = {}
236           for iv in %w[indent space space_before object_nl array_nl allow_nan max_nesting ascii_only quirks_mode depth]
237             result[iv.intern] = instance_variable_get("@#{iv}")
238           end
239           result
240         end
241
242         # Generates a valid JSON document from object +obj+ and returns the
243         # result. If no valid JSON document can be created this method raises a
244         # GeneratorError exception.
245         def generate(obj)
246           result = obj.to_json(self)
247           if !@quirks_mode && result !~ /\A\s*(?:\[.*\]|\{.*\})\s*\Z/m
248             raise GeneratorError, "only generation of JSON objects or arrays allowed"
249           end
250           result
251         end
252
253         # Return the value returned by method +name+.
254         def [](name)
255           __send__ name
256         end
257       end
258
259       module GeneratorMethods
260         module Object
261           # Converts this object to a string (calling #to_s), converts
262           # it to a JSON string, and returns the result. This is a fallback, if no
263           # special method #to_json was defined for some object.
264           def to_json(*) to_s.to_json end
265         end
266
267         module Hash
268           # Returns a JSON string containing a JSON object, that is unparsed from
269           # this Hash instance.
270           # _state_ is a JSON::State object, that can also be used to configure the
271           # produced JSON string output further.
272           # _depth_ is used to find out nesting depth, to indent accordingly.
273           def to_json(state = nil, *)
274             state = State.from_state(state)
275             state.check_max_nesting
276             json_transform(state)
277           end
278
279           private
280
281           def json_shift(state)
282             state.object_nl.empty? or return ''
283             state.indent * state.depth
284           end
285
286           def json_transform(state)
287             delim = ','
288             delim << state.object_nl
289             result = '{'
290             result << state.object_nl
291             depth = state.depth += 1
292             first = true
293             indent = !state.object_nl.empty?
294             each { |key,value|
295               result << delim unless first
296               result << state.indent * depth if indent
297               result << key.to_s.to_json(state)
298               result << state.space_before
299               result << ':'
300               result << state.space
301               result << value.to_json(state)
302               first = false
303             }
304             depth = state.depth -= 1
305             result << state.object_nl
306             result << state.indent * depth if indent if indent
307             result << '}'
308             result
309           end
310         end
311
312         module Array
313           # Returns a JSON string containing a JSON array, that is unparsed from
314           # this Array instance.
315           # _state_ is a JSON::State object, that can also be used to configure the
316           # produced JSON string output further.
317           def to_json(state = nil, *)
318             state = State.from_state(state)
319             state.check_max_nesting
320             json_transform(state)
321           end
322
323           private
324
325           def json_transform(state)
326             delim = ','
327             delim << state.array_nl
328             result = '['
329             result << state.array_nl
330             depth = state.depth += 1
331             first = true
332             indent = !state.array_nl.empty?
333             each { |value|
334               result << delim unless first
335               result << state.indent * depth if indent
336               result << value.to_json(state)
337               first = false
338             }
339             depth = state.depth -= 1
340             result << state.array_nl
341             result << state.indent * depth if indent
342             result << ']'
343           end
344         end
345
346         module Integer
347           # Returns a JSON string representation for this Integer number.
348           def to_json(*) to_s end
349         end
350
351         module Float
352           # Returns a JSON string representation for this Float number.
353           def to_json(state = nil, *)
354             state = State.from_state(state)
355             case
356             when infinite?
357               if state.allow_nan?
358                 to_s
359               else
360                 raise GeneratorError, "#{self} not allowed in JSON"
361               end
362             when nan?
363               if state.allow_nan?
364                 to_s
365               else
366                 raise GeneratorError, "#{self} not allowed in JSON"
367               end
368             else
369               to_s
370             end
371           end
372         end
373
374         module String
375           if defined?(::Encoding)
376             # This string should be encoded with UTF-8 A call to this method
377             # returns a JSON string encoded with UTF16 big endian characters as
378             # \u????.
379             def to_json(state = nil, *args)
380               state = State.from_state(state)
381               if encoding == ::Encoding::UTF_8
382                 string = self
383               else
384                 string = encode(::Encoding::UTF_8)
385               end
386               if state.ascii_only?
387                 '"' << JSON.utf8_to_json_ascii(string) << '"'
388               else
389                 '"' << JSON.utf8_to_json(string) << '"'
390               end
391             end
392           else
393             # This string should be encoded with UTF-8 A call to this method
394             # returns a JSON string encoded with UTF16 big endian characters as
395             # \u????.
396             def to_json(state = nil, *args)
397               state = State.from_state(state)
398               if state.ascii_only?
399                 '"' << JSON.utf8_to_json_ascii(self) << '"'
400               else
401                 '"' << JSON.utf8_to_json(self) << '"'
402               end
403             end
404           end
405
406           # Module that holds the extinding methods if, the String module is
407           # included.
408           module Extend
409             # Raw Strings are JSON Objects (the raw bytes are stored in an
410             # array for the key "raw"). The Ruby String can be created by this
411             # module method.
412             def json_create(o)
413               o['raw'].pack('C*')
414             end
415           end
416
417           # Extends _modul_ with the String::Extend module.
418           def self.included(modul)
419             modul.extend Extend
420           end
421
422           # This method creates a raw object hash, that can be nested into
423           # other data structures and will be unparsed as a raw string. This
424           # method should be used, if you want to convert raw strings to JSON
425           # instead of UTF-8 strings, e. g. binary data.
426           def to_json_raw_object
427             {
428               JSON.create_id  => self.class.name,
429               'raw'           => self.unpack('C*'),
430             }
431           end
432
433           # This method creates a JSON text from the result of
434           # a call to to_json_raw_object of this String.
435           def to_json_raw(*args)
436             to_json_raw_object.to_json(*args)
437           end
438         end
439
440         module TrueClass
441           # Returns a JSON string for true: 'true'.
442           def to_json(*) 'true' end
443         end
444
445         module FalseClass
446           # Returns a JSON string for false: 'false'.
447           def to_json(*) 'false' end
448         end
449
450         module NilClass
451           # Returns a JSON string for nil: 'null'.
452           def to_json(*) 'null' end
453         end
454       end
455     end
456   end
457 end