gist 4.0.0 → 4.0.3

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 (7) hide show
  1. data/Rakefile +29 -4
  2. data/bin/gist +1 -1
  3. data/build/gist +1821 -0
  4. data/build/gist.1 +215 -0
  5. data/lib/gist.rb +1 -1
  6. data/vendor/json.rb +1304 -0
  7. metadata +5 -2
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
- #
3
1
  task :default => :test
4
2
 
5
3
  desc 'run the tests' # that's non-DRY
@@ -12,8 +10,35 @@ task :clipfailtest do
12
10
  end
13
11
 
14
12
  task :man do
15
- File.write "README.md.ron", File.read("README.md").gsub(?‌, "* ")
13
+ mkdir_p "build"
14
+ File.write "README.md.ron", File.read("README.md").gsub("\u200c", "* ")
16
15
  sh 'ronn --roff --manual="Gist manual" README.md.ron'
17
16
  rm 'README.md.ron'
18
- sh 'man ./README.1'
17
+ mv 'README.1', 'build/gist.1'
18
+ end
19
+
20
+ task :standalone do
21
+ mkdir_p "build"
22
+ File.open("build/gist", "w") do |f|
23
+ f.puts "#!/usr/bin/env ruby"
24
+ f.puts "# This is generated from https://github.com/defunkt/gist using 'rake standalone'"
25
+ f.puts "# any changes will be overwritten."
26
+ f.puts File.read("lib/gist.rb").split("require 'json'\n").join(File.read("vendor/json.rb"))
27
+
28
+ f.puts File.read("bin/gist").gsub(/^require.*gist.*\n/, '');
29
+ end
30
+ sh 'chmod +x build/gist'
31
+ end
32
+
33
+ task :build => [:man, :standalone]
34
+
35
+ desc "Install standalone script and man pages"
36
+ task :install => :standalone do
37
+ prefix = ENV['PREFIX'] || ENV['prefix'] || '/usr/local'
38
+
39
+ FileUtils.mkdir_p "#{prefix}/bin"
40
+ FileUtils.cp "build/gist", "#{prefix}/bin"
41
+
42
+ FileUtils.mkdir_p "#{prefix}/share/man/man1"
43
+ FileUtils.cp "build/gist.1", "#{prefix}/share/man/man1"
19
44
  end
data/bin/gist CHANGED
@@ -39,7 +39,7 @@ Instead of creating a new gist, you can update an existing one by passing its ID
39
39
  or URL with "-u". For this to work, you must be logged in, and have created the
40
40
  original gist with the same GitHub account.
41
41
 
42
- Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-d DESC] -a] [-u URL] [-P] [-f NAME|-t EXT]* FILE*
42
+ Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-d DESC] [-a] [-u URL] [-P] [-f NAME|-t EXT]* FILE*
43
43
  #{executable_name} --login
44
44
 
45
45
  EOS
data/build/gist ADDED
@@ -0,0 +1,1821 @@
1
+ #!/usr/bin/env ruby
2
+ # This is generated from https://github.com/defunkt/gist using 'rake standalone'
3
+ # any changes will be overwritten.
4
+ require 'net/https'
5
+ require 'cgi'
6
+ require 'strscan'
7
+
8
+ module JSON
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 19.
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 false, the Parser doesn't create
72
+ # additions even if a matchin class and create_id was found. This option
73
+ # defaults to true.
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 19
85
+ @max_nesting = 19
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 = true
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
365
+
366
+ module JSON
367
+ MAP = {
368
+ "\x0" => '\u0000',
369
+ "\x1" => '\u0001',
370
+ "\x2" => '\u0002',
371
+ "\x3" => '\u0003',
372
+ "\x4" => '\u0004',
373
+ "\x5" => '\u0005',
374
+ "\x6" => '\u0006',
375
+ "\x7" => '\u0007',
376
+ "\b" => '\b',
377
+ "\t" => '\t',
378
+ "\n" => '\n',
379
+ "\xb" => '\u000b',
380
+ "\f" => '\f',
381
+ "\r" => '\r',
382
+ "\xe" => '\u000e',
383
+ "\xf" => '\u000f',
384
+ "\x10" => '\u0010',
385
+ "\x11" => '\u0011',
386
+ "\x12" => '\u0012',
387
+ "\x13" => '\u0013',
388
+ "\x14" => '\u0014',
389
+ "\x15" => '\u0015',
390
+ "\x16" => '\u0016',
391
+ "\x17" => '\u0017',
392
+ "\x18" => '\u0018',
393
+ "\x19" => '\u0019',
394
+ "\x1a" => '\u001a',
395
+ "\x1b" => '\u001b',
396
+ "\x1c" => '\u001c',
397
+ "\x1d" => '\u001d',
398
+ "\x1e" => '\u001e',
399
+ "\x1f" => '\u001f',
400
+ '"' => '\"',
401
+ '\\' => '\\\\',
402
+ } # :nodoc:
403
+
404
+ # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
405
+ # UTF16 big endian characters as \u????, and return it.
406
+ if defined?(::Encoding)
407
+ def utf8_to_json(string) # :nodoc:
408
+ string = string.dup
409
+ string << '' # XXX workaround: avoid buffer sharing
410
+ string.force_encoding(::Encoding::ASCII_8BIT)
411
+ string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
412
+ string.force_encoding(::Encoding::UTF_8)
413
+ string
414
+ end
415
+
416
+ def utf8_to_json_ascii(string) # :nodoc:
417
+ string = string.dup
418
+ string << '' # XXX workaround: avoid buffer sharing
419
+ string.force_encoding(::Encoding::ASCII_8BIT)
420
+ string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
421
+ string.gsub!(/(
422
+ (?:
423
+ [\xc2-\xdf][\x80-\xbf] |
424
+ [\xe0-\xef][\x80-\xbf]{2} |
425
+ [\xf0-\xf4][\x80-\xbf]{3}
426
+ )+ |
427
+ [\x80-\xc1\xf5-\xff] # invalid
428
+ )/nx) { |c|
429
+ c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
430
+ s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
431
+ s.gsub!(/.{4}/n, '\\\\u\&')
432
+ }
433
+ string.force_encoding(::Encoding::UTF_8)
434
+ string
435
+ rescue => e
436
+ raise GeneratorError, "Caught #{e.class}: #{e}"
437
+ end
438
+ else
439
+ def utf8_to_json(string) # :nodoc:
440
+ string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
441
+ end
442
+
443
+ def utf8_to_json_ascii(string) # :nodoc:
444
+ string = string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
445
+ string.gsub!(/(
446
+ (?:
447
+ [\xc2-\xdf][\x80-\xbf] |
448
+ [\xe0-\xef][\x80-\xbf]{2} |
449
+ [\xf0-\xf4][\x80-\xbf]{3}
450
+ )+ |
451
+ [\x80-\xc1\xf5-\xff] # invalid
452
+ )/nx) { |c|
453
+ c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
454
+ s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
455
+ s.gsub!(/.{4}/n, '\\\\u\&')
456
+ }
457
+ string
458
+ rescue => e
459
+ raise GeneratorError, "Caught #{e.class}: #{e}"
460
+ end
461
+ end
462
+ module_function :utf8_to_json, :utf8_to_json_ascii
463
+
464
+ module Pure
465
+ module Generator
466
+ # This class is used to create State instances, that are use to hold data
467
+ # while generating a JSON text from a Ruby data structure.
468
+ class State
469
+ # Creates a State object from _opts_, which ought to be Hash to create
470
+ # a new State instance configured by _opts_, something else to create
471
+ # an unconfigured instance. If _opts_ is a State object, it is just
472
+ # returned.
473
+ def self.from_state(opts)
474
+ case
475
+ when self === opts
476
+ opts
477
+ when opts.respond_to?(:to_hash)
478
+ new(opts.to_hash)
479
+ when opts.respond_to?(:to_h)
480
+ new(opts.to_h)
481
+ else
482
+ SAFE_STATE_PROTOTYPE.dup
483
+ end
484
+ end
485
+
486
+ # Instantiates a new State object, configured by _opts_.
487
+ #
488
+ # _opts_ can have the following keys:
489
+ #
490
+ # * *indent*: a string used to indent levels (default: ''),
491
+ # * *space*: a string that is put after, a : or , delimiter (default: ''),
492
+ # * *space_before*: a string that is put before a : pair delimiter (default: ''),
493
+ # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
494
+ # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
495
+ # * *check_circular*: is deprecated now, use the :max_nesting option instead,
496
+ # * *max_nesting*: sets the maximum level of data structure nesting in
497
+ # the generated JSON, max_nesting = 0 if no maximum should be checked.
498
+ # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
499
+ # generated, otherwise an exception is thrown, if these values are
500
+ # encountered. This options defaults to false.
501
+ # * *quirks_mode*: Enables quirks_mode for parser, that is for example
502
+ # generating single JSON values instead of documents is possible.
503
+ def initialize(opts = {})
504
+ @indent = ''
505
+ @space = ''
506
+ @space_before = ''
507
+ @object_nl = ''
508
+ @array_nl = ''
509
+ @allow_nan = false
510
+ @ascii_only = false
511
+ @quirks_mode = false
512
+ @buffer_initial_length = 1024
513
+ configure opts
514
+ end
515
+
516
+ # This string is used to indent levels in the JSON text.
517
+ attr_accessor :indent
518
+
519
+ # This string is used to insert a space between the tokens in a JSON
520
+ # string.
521
+ attr_accessor :space
522
+
523
+ # This string is used to insert a space before the ':' in JSON objects.
524
+ attr_accessor :space_before
525
+
526
+ # This string is put at the end of a line that holds a JSON object (or
527
+ # Hash).
528
+ attr_accessor :object_nl
529
+
530
+ # This string is put at the end of a line that holds a JSON array.
531
+ attr_accessor :array_nl
532
+
533
+ # This integer returns the maximum level of data structure nesting in
534
+ # the generated JSON, max_nesting = 0 if no maximum is checked.
535
+ attr_accessor :max_nesting
536
+
537
+ # If this attribute is set to true, quirks mode is enabled, otherwise
538
+ # it's disabled.
539
+ attr_accessor :quirks_mode
540
+
541
+ # :stopdoc:
542
+ attr_reader :buffer_initial_length
543
+
544
+ def buffer_initial_length=(length)
545
+ if length > 0
546
+ @buffer_initial_length = length
547
+ end
548
+ end
549
+ # :startdoc:
550
+
551
+ # This integer returns the current depth data structure nesting in the
552
+ # generated JSON.
553
+ attr_accessor :depth
554
+
555
+ def check_max_nesting # :nodoc:
556
+ return if @max_nesting.zero?
557
+ current_nesting = depth + 1
558
+ current_nesting > @max_nesting and
559
+ raise NestingError, "nesting of #{current_nesting} is too deep"
560
+ end
561
+
562
+ # Returns true, if circular data structures are checked,
563
+ # otherwise returns false.
564
+ def check_circular?
565
+ !@max_nesting.zero?
566
+ end
567
+
568
+ # Returns true if NaN, Infinity, and -Infinity should be considered as
569
+ # valid JSON and output.
570
+ def allow_nan?
571
+ @allow_nan
572
+ end
573
+
574
+ # Returns true, if only ASCII characters should be generated. Otherwise
575
+ # returns false.
576
+ def ascii_only?
577
+ @ascii_only
578
+ end
579
+
580
+ # Returns true, if quirks mode is enabled. Otherwise returns false.
581
+ def quirks_mode?
582
+ @quirks_mode
583
+ end
584
+
585
+ # Configure this State instance with the Hash _opts_, and return
586
+ # itself.
587
+ def configure(opts)
588
+ @indent = opts[:indent] if opts.key?(:indent)
589
+ @space = opts[:space] if opts.key?(:space)
590
+ @space_before = opts[:space_before] if opts.key?(:space_before)
591
+ @object_nl = opts[:object_nl] if opts.key?(:object_nl)
592
+ @array_nl = opts[:array_nl] if opts.key?(:array_nl)
593
+ @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
594
+ @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
595
+ @depth = opts[:depth] || 0
596
+ @quirks_mode = opts[:quirks_mode] if opts.key?(:quirks_mode)
597
+ if !opts.key?(:max_nesting) # defaults to 19
598
+ @max_nesting = 19
599
+ elsif opts[:max_nesting]
600
+ @max_nesting = opts[:max_nesting]
601
+ else
602
+ @max_nesting = 0
603
+ end
604
+ self
605
+ end
606
+ alias merge configure
607
+
608
+ # Returns the configuration instance variables as a hash, that can be
609
+ # passed to the configure method.
610
+ def to_h
611
+ result = {}
612
+ for iv in %w[indent space space_before object_nl array_nl allow_nan max_nesting ascii_only quirks_mode buffer_initial_length depth]
613
+ result[iv.intern] = instance_variable_get("@#{iv}")
614
+ end
615
+ result
616
+ end
617
+
618
+ # Generates a valid JSON document from object +obj+ and returns the
619
+ # result. If no valid JSON document can be created this method raises a
620
+ # GeneratorError exception.
621
+ def generate(obj)
622
+ result = obj.to_json(self)
623
+ unless @quirks_mode
624
+ unless result =~ /\A\s*\[/ && result =~ /\]\s*\Z/ ||
625
+ result =~ /\A\s*\{/ && result =~ /\}\s*\Z/
626
+ then
627
+ raise GeneratorError, "only generation of JSON objects or arrays allowed"
628
+ end
629
+ end
630
+ result
631
+ end
632
+
633
+ # Return the value returned by method +name+.
634
+ def [](name)
635
+ __send__ name
636
+ end
637
+ end
638
+
639
+ module GeneratorMethods
640
+ module Object
641
+ # Converts this object to a string (calling #to_s), converts
642
+ # it to a JSON string, and returns the result. This is a fallback, if no
643
+ # special method #to_json was defined for some object.
644
+ def to_json(*) to_s.to_json end
645
+ end
646
+
647
+ module Hash
648
+ # Returns a JSON string containing a JSON object, that is unparsed from
649
+ # this Hash instance.
650
+ # _state_ is a JSON::State object, that can also be used to configure the
651
+ # produced JSON string output further.
652
+ # _depth_ is used to find out nesting depth, to indent accordingly.
653
+ def to_json(state = nil, *)
654
+ state = State.from_state(state)
655
+ state.check_max_nesting
656
+ json_transform(state)
657
+ end
658
+
659
+ private
660
+
661
+ def json_shift(state)
662
+ state.object_nl.empty? or return ''
663
+ state.indent * state.depth
664
+ end
665
+
666
+ def json_transform(state)
667
+ delim = ','
668
+ delim << state.object_nl
669
+ result = '{'
670
+ result << state.object_nl
671
+ depth = state.depth += 1
672
+ first = true
673
+ indent = !state.object_nl.empty?
674
+ each { |key,value|
675
+ result << delim unless first
676
+ result << state.indent * depth if indent
677
+ result << key.to_s.to_json(state)
678
+ result << state.space_before
679
+ result << ':'
680
+ result << state.space
681
+ result << value.to_json(state)
682
+ first = false
683
+ }
684
+ depth = state.depth -= 1
685
+ result << state.object_nl
686
+ result << state.indent * depth if indent if indent
687
+ result << '}'
688
+ result
689
+ end
690
+ end
691
+
692
+ module Array
693
+ # Returns a JSON string containing a JSON array, that is unparsed from
694
+ # this Array instance.
695
+ # _state_ is a JSON::State object, that can also be used to configure the
696
+ # produced JSON string output further.
697
+ def to_json(state = nil, *)
698
+ state = State.from_state(state)
699
+ state.check_max_nesting
700
+ json_transform(state)
701
+ end
702
+
703
+ private
704
+
705
+ def json_transform(state)
706
+ delim = ','
707
+ delim << state.array_nl
708
+ result = '['
709
+ result << state.array_nl
710
+ depth = state.depth += 1
711
+ first = true
712
+ indent = !state.array_nl.empty?
713
+ each { |value|
714
+ result << delim unless first
715
+ result << state.indent * depth if indent
716
+ result << value.to_json(state)
717
+ first = false
718
+ }
719
+ depth = state.depth -= 1
720
+ result << state.array_nl
721
+ result << state.indent * depth if indent
722
+ result << ']'
723
+ end
724
+ end
725
+
726
+ module Integer
727
+ # Returns a JSON string representation for this Integer number.
728
+ def to_json(*) to_s end
729
+ end
730
+
731
+ module Float
732
+ # Returns a JSON string representation for this Float number.
733
+ def to_json(state = nil, *)
734
+ state = State.from_state(state)
735
+ case
736
+ when infinite?
737
+ if state.allow_nan?
738
+ to_s
739
+ else
740
+ raise GeneratorError, "#{self} not allowed in JSON"
741
+ end
742
+ when nan?
743
+ if state.allow_nan?
744
+ to_s
745
+ else
746
+ raise GeneratorError, "#{self} not allowed in JSON"
747
+ end
748
+ else
749
+ to_s
750
+ end
751
+ end
752
+ end
753
+
754
+ module String
755
+ if defined?(::Encoding)
756
+ # This string should be encoded with UTF-8 A call to this method
757
+ # returns a JSON string encoded with UTF16 big endian characters as
758
+ # \u????.
759
+ def to_json(state = nil, *args)
760
+ state = State.from_state(state)
761
+ if encoding == ::Encoding::UTF_8
762
+ string = self
763
+ else
764
+ string = encode(::Encoding::UTF_8)
765
+ end
766
+ if state.ascii_only?
767
+ '"' << JSON.utf8_to_json_ascii(string) << '"'
768
+ else
769
+ '"' << JSON.utf8_to_json(string) << '"'
770
+ end
771
+ end
772
+ else
773
+ # This string should be encoded with UTF-8 A call to this method
774
+ # returns a JSON string encoded with UTF16 big endian characters as
775
+ # \u????.
776
+ def to_json(state = nil, *args)
777
+ state = State.from_state(state)
778
+ if state.ascii_only?
779
+ '"' << JSON.utf8_to_json_ascii(self) << '"'
780
+ else
781
+ '"' << JSON.utf8_to_json(self) << '"'
782
+ end
783
+ end
784
+ end
785
+
786
+ # Module that holds the extinding methods if, the String module is
787
+ # included.
788
+ module Extend
789
+ # Raw Strings are JSON Objects (the raw bytes are stored in an
790
+ # array for the key "raw"). The Ruby String can be created by this
791
+ # module method.
792
+ def json_create(o)
793
+ o['raw'].pack('C*')
794
+ end
795
+ end
796
+
797
+ # Extends _modul_ with the String::Extend module.
798
+ def self.included(modul)
799
+ modul.extend Extend
800
+ end
801
+
802
+ # This method creates a raw object hash, that can be nested into
803
+ # other data structures and will be unparsed as a raw string. This
804
+ # method should be used, if you want to convert raw strings to JSON
805
+ # instead of UTF-8 strings, e. g. binary data.
806
+ def to_json_raw_object
807
+ {
808
+ JSON.create_id => self.class.name,
809
+ 'raw' => self.unpack('C*'),
810
+ }
811
+ end
812
+
813
+ # This method creates a JSON text from the result of
814
+ # a call to to_json_raw_object of this String.
815
+ def to_json_raw(*args)
816
+ to_json_raw_object.to_json(*args)
817
+ end
818
+ end
819
+
820
+ module TrueClass
821
+ # Returns a JSON string for true: 'true'.
822
+ def to_json(*) 'true' end
823
+ end
824
+
825
+ module FalseClass
826
+ # Returns a JSON string for false: 'false'.
827
+ def to_json(*) 'false' end
828
+ end
829
+
830
+ module NilClass
831
+ # Returns a JSON string for nil: 'null'.
832
+ def to_json(*) 'null' end
833
+ end
834
+ end
835
+ end
836
+ end
837
+ end
838
+
839
+ module JSON
840
+ class << self
841
+ # If _object_ is string-like, parse the string and return the parsed result
842
+ # as a Ruby data structure. Otherwise generate a JSON text from the Ruby
843
+ # data structure object and return it.
844
+ #
845
+ # The _opts_ argument is passed through to generate/parse respectively. See
846
+ # generate and parse for their documentation.
847
+ def [](object, opts = {})
848
+ if object.respond_to? :to_str
849
+ JSON.parse(object.to_str, opts)
850
+ else
851
+ JSON.generate(object, opts)
852
+ end
853
+ end
854
+
855
+ # Returns the JSON parser class that is used by JSON. This is either
856
+ # JSON::Ext::Parser or JSON::Pure::Parser.
857
+ attr_reader :parser
858
+
859
+ # Set the JSON parser class _parser_ to be used by JSON.
860
+ def parser=(parser) # :nodoc:
861
+ @parser = parser
862
+ remove_const :Parser if JSON.const_defined_in?(self, :Parser)
863
+ const_set :Parser, parser
864
+ end
865
+
866
+ # Return the constant located at _path_. The format of _path_ has to be
867
+ # either ::A::B::C or A::B::C. In any case, A has to be located at the top
868
+ # level (absolute namespace path?). If there doesn't exist a constant at
869
+ # the given path, an ArgumentError is raised.
870
+ def deep_const_get(path) # :nodoc:
871
+ path.to_s.split(/::/).inject(Object) do |p, c|
872
+ case
873
+ when c.empty? then p
874
+ when JSON.const_defined_in?(p, c) then p.const_get(c)
875
+ else
876
+ begin
877
+ p.const_missing(c)
878
+ rescue NameError => e
879
+ raise ArgumentError, "can't get const #{path}: #{e}"
880
+ end
881
+ end
882
+ end
883
+ end
884
+
885
+ # Set the module _generator_ to be used by JSON.
886
+ def generator=(generator) # :nodoc:
887
+ old, $VERBOSE = $VERBOSE, nil
888
+ @generator = generator
889
+ generator_methods = generator::GeneratorMethods
890
+ for const in generator_methods.constants
891
+ klass = deep_const_get(const)
892
+ modul = generator_methods.const_get(const)
893
+ klass.class_eval do
894
+ instance_methods(false).each do |m|
895
+ m.to_s == 'to_json' and remove_method m
896
+ end
897
+ include modul
898
+ end
899
+ end
900
+ self.state = generator::State
901
+ const_set :State, self.state
902
+ const_set :SAFE_STATE_PROTOTYPE, State.new
903
+ const_set :FAST_STATE_PROTOTYPE, State.new(
904
+ :indent => '',
905
+ :space => '',
906
+ :object_nl => "",
907
+ :array_nl => "",
908
+ :max_nesting => false
909
+ )
910
+ const_set :PRETTY_STATE_PROTOTYPE, State.new(
911
+ :indent => ' ',
912
+ :space => ' ',
913
+ :object_nl => "\n",
914
+ :array_nl => "\n"
915
+ )
916
+ ensure
917
+ $VERBOSE = old
918
+ end
919
+
920
+ # Returns the JSON generator module that is used by JSON. This is
921
+ # either JSON::Ext::Generator or JSON::Pure::Generator.
922
+ attr_reader :generator
923
+
924
+ # Returns the JSON generator state class that is used by JSON. This is
925
+ # either JSON::Ext::Generator::State or JSON::Pure::Generator::State.
926
+ attr_accessor :state
927
+
928
+ # This is create identifier, which is used to decide if the _json_create_
929
+ # hook of a class should be called. It defaults to 'json_class'.
930
+ attr_accessor :create_id
931
+ end
932
+ self.create_id = 'json_class'
933
+
934
+ NaN = 0.0/0
935
+
936
+ Infinity = 1.0/0
937
+
938
+ MinusInfinity = -Infinity
939
+
940
+ # The base exception for JSON errors.
941
+ class JSONError < StandardError; end
942
+
943
+ # This exception is raised if a parser error occurs.
944
+ class ParserError < JSONError; end
945
+
946
+ # This exception is raised if the nesting of parsed data structures is too
947
+ # deep.
948
+ class NestingError < ParserError; end
949
+
950
+ # :stopdoc:
951
+ class CircularDatastructure < NestingError; end
952
+ # :startdoc:
953
+
954
+ # This exception is raised if a generator or unparser error occurs.
955
+ class GeneratorError < JSONError; end
956
+ # For backwards compatibility
957
+ UnparserError = GeneratorError
958
+
959
+ # This exception is raised if the required unicode support is missing on the
960
+ # system. Usually this means that the iconv library is not installed.
961
+ class MissingUnicodeSupport < JSONError; end
962
+
963
+ module_function
964
+
965
+ # Parse the JSON document _source_ into a Ruby data structure and return it.
966
+ #
967
+ # _opts_ can have the following
968
+ # keys:
969
+ # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
970
+ # structures. Disable depth checking with :max_nesting => false. It defaults
971
+ # to 19.
972
+ # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
973
+ # defiance of RFC 4627 to be parsed by the Parser. This option defaults
974
+ # to false.
975
+ # * *symbolize_names*: If set to true, returns symbols for the names
976
+ # (keys) in a JSON object. Otherwise strings are returned. Strings are
977
+ # the default.
978
+ # * *create_additions*: If set to false, the Parser doesn't create
979
+ # additions even if a matching class and create_id was found. This option
980
+ # defaults to true.
981
+ # * *object_class*: Defaults to Hash
982
+ # * *array_class*: Defaults to Array
983
+ def parse(source, opts = {})
984
+ Parser.new(source, opts).parse
985
+ end
986
+
987
+ # Parse the JSON document _source_ into a Ruby data structure and return it.
988
+ # The bang version of the parse method defaults to the more dangerous values
989
+ # for the _opts_ hash, so be sure only to parse trusted _source_ documents.
990
+ #
991
+ # _opts_ can have the following keys:
992
+ # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
993
+ # structures. Enable depth checking with :max_nesting => anInteger. The parse!
994
+ # methods defaults to not doing max depth checking: This can be dangerous
995
+ # if someone wants to fill up your stack.
996
+ # * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in
997
+ # defiance of RFC 4627 to be parsed by the Parser. This option defaults
998
+ # to true.
999
+ # * *create_additions*: If set to false, the Parser doesn't create
1000
+ # additions even if a matching class and create_id was found. This option
1001
+ # defaults to true.
1002
+ def parse!(source, opts = {})
1003
+ opts = {
1004
+ :max_nesting => false,
1005
+ :allow_nan => true
1006
+ }.update(opts)
1007
+ Parser.new(source, opts).parse
1008
+ end
1009
+
1010
+ # Generate a JSON document from the Ruby data structure _obj_ and return
1011
+ # it. _state_ is * a JSON::State object,
1012
+ # * or a Hash like object (responding to to_hash),
1013
+ # * an object convertible into a hash by a to_h method,
1014
+ # that is used as or to configure a State object.
1015
+ #
1016
+ # It defaults to a state object, that creates the shortest possible JSON text
1017
+ # in one line, checks for circular data structures and doesn't allow NaN,
1018
+ # Infinity, and -Infinity.
1019
+ #
1020
+ # A _state_ hash can have the following keys:
1021
+ # * *indent*: a string used to indent levels (default: ''),
1022
+ # * *space*: a string that is put after, a : or , delimiter (default: ''),
1023
+ # * *space_before*: a string that is put before a : pair delimiter (default: ''),
1024
+ # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
1025
+ # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
1026
+ # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
1027
+ # generated, otherwise an exception is thrown if these values are
1028
+ # encountered. This options defaults to false.
1029
+ # * *max_nesting*: The maximum depth of nesting allowed in the data
1030
+ # structures from which JSON is to be generated. Disable depth checking
1031
+ # with :max_nesting => false, it defaults to 19.
1032
+ #
1033
+ # See also the fast_generate for the fastest creation method with the least
1034
+ # amount of sanity checks, and the pretty_generate method for some
1035
+ # defaults for pretty output.
1036
+ def generate(obj, opts = nil)
1037
+ if State === opts
1038
+ state, opts = opts, nil
1039
+ else
1040
+ state = SAFE_STATE_PROTOTYPE.dup
1041
+ end
1042
+ if opts
1043
+ if opts.respond_to? :to_hash
1044
+ opts = opts.to_hash
1045
+ elsif opts.respond_to? :to_h
1046
+ opts = opts.to_h
1047
+ else
1048
+ raise TypeError, "can't convert #{opts.class} into Hash"
1049
+ end
1050
+ state = state.configure(opts)
1051
+ end
1052
+ state.generate(obj)
1053
+ end
1054
+
1055
+ # :stopdoc:
1056
+ # I want to deprecate these later, so I'll first be silent about them, and
1057
+ # later delete them.
1058
+ alias unparse generate
1059
+ module_function :unparse
1060
+ # :startdoc:
1061
+
1062
+ # Generate a JSON document from the Ruby data structure _obj_ and return it.
1063
+ # This method disables the checks for circles in Ruby objects.
1064
+ #
1065
+ # *WARNING*: Be careful not to pass any Ruby data structures with circles as
1066
+ # _obj_ argument because this will cause JSON to go into an infinite loop.
1067
+ def fast_generate(obj, opts = nil)
1068
+ if State === opts
1069
+ state, opts = opts, nil
1070
+ else
1071
+ state = FAST_STATE_PROTOTYPE.dup
1072
+ end
1073
+ if opts
1074
+ if opts.respond_to? :to_hash
1075
+ opts = opts.to_hash
1076
+ elsif opts.respond_to? :to_h
1077
+ opts = opts.to_h
1078
+ else
1079
+ raise TypeError, "can't convert #{opts.class} into Hash"
1080
+ end
1081
+ state.configure(opts)
1082
+ end
1083
+ state.generate(obj)
1084
+ end
1085
+
1086
+ # :stopdoc:
1087
+ # I want to deprecate these later, so I'll first be silent about them, and later delete them.
1088
+ alias fast_unparse fast_generate
1089
+ module_function :fast_unparse
1090
+ # :startdoc:
1091
+
1092
+ # Generate a JSON document from the Ruby data structure _obj_ and return it.
1093
+ # The returned document is a prettier form of the document returned by
1094
+ # #unparse.
1095
+ #
1096
+ # The _opts_ argument can be used to configure the generator. See the
1097
+ # generate method for a more detailed explanation.
1098
+ def pretty_generate(obj, opts = nil)
1099
+ if State === opts
1100
+ state, opts = opts, nil
1101
+ else
1102
+ state = PRETTY_STATE_PROTOTYPE.dup
1103
+ end
1104
+ if opts
1105
+ if opts.respond_to? :to_hash
1106
+ opts = opts.to_hash
1107
+ elsif opts.respond_to? :to_h
1108
+ opts = opts.to_h
1109
+ else
1110
+ raise TypeError, "can't convert #{opts.class} into Hash"
1111
+ end
1112
+ state.configure(opts)
1113
+ end
1114
+ state.generate(obj)
1115
+ end
1116
+
1117
+ # :stopdoc:
1118
+ # I want to deprecate these later, so I'll first be silent about them, and later delete them.
1119
+ alias pretty_unparse pretty_generate
1120
+ module_function :pretty_unparse
1121
+ # :startdoc:
1122
+
1123
+ class << self
1124
+ # The global default options for the JSON.load method:
1125
+ # :max_nesting: false
1126
+ # :allow_nan: true
1127
+ # :quirks_mode: true
1128
+ attr_accessor :load_default_options
1129
+ end
1130
+ self.load_default_options = {
1131
+ :max_nesting => false,
1132
+ :allow_nan => true,
1133
+ :quirks_mode => true,
1134
+ }
1135
+
1136
+ # Load a ruby data structure from a JSON _source_ and return it. A source can
1137
+ # either be a string-like object, an IO-like object, or an object responding
1138
+ # to the read method. If _proc_ was given, it will be called with any nested
1139
+ # Ruby object as an argument recursively in depth first order. The default
1140
+ # options for the parser can be changed via the load_default_options method.
1141
+ #
1142
+ # This method is part of the implementation of the load/dump interface of
1143
+ # Marshal and YAML.
1144
+ def load(source, proc = nil)
1145
+ opts = load_default_options
1146
+ if source.respond_to? :to_str
1147
+ source = source.to_str
1148
+ elsif source.respond_to? :to_io
1149
+ source = source.to_io.read
1150
+ elsif source.respond_to?(:read)
1151
+ source = source.read
1152
+ end
1153
+ if opts[:quirks_mode] && (source.nil? || source.empty?)
1154
+ source = 'null'
1155
+ end
1156
+ result = parse(source, opts)
1157
+ recurse_proc(result, &proc) if proc
1158
+ result
1159
+ end
1160
+
1161
+ # Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_
1162
+ def recurse_proc(result, &proc)
1163
+ case result
1164
+ when Array
1165
+ result.each { |x| recurse_proc x, &proc }
1166
+ proc.call result
1167
+ when Hash
1168
+ result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
1169
+ proc.call result
1170
+ else
1171
+ proc.call result
1172
+ end
1173
+ end
1174
+
1175
+ alias restore load
1176
+ module_function :restore
1177
+
1178
+ class << self
1179
+ # The global default options for the JSON.dump method:
1180
+ # :max_nesting: false
1181
+ # :allow_nan: true
1182
+ # :quirks_mode: true
1183
+ attr_accessor :dump_default_options
1184
+ end
1185
+ self.dump_default_options = {
1186
+ :max_nesting => false,
1187
+ :allow_nan => true,
1188
+ :quirks_mode => true,
1189
+ }
1190
+
1191
+ # Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns
1192
+ # the result.
1193
+ #
1194
+ # If anIO (an IO-like object or an object that responds to the write method)
1195
+ # was given, the resulting JSON is written to it.
1196
+ #
1197
+ # If the number of nested arrays or objects exceeds _limit_, an ArgumentError
1198
+ # exception is raised. This argument is similar (but not exactly the
1199
+ # same!) to the _limit_ argument in Marshal.dump.
1200
+ #
1201
+ # The default options for the generator can be changed via the
1202
+ # dump_default_options method.
1203
+ #
1204
+ # This method is part of the implementation of the load/dump interface of
1205
+ # Marshal and YAML.
1206
+ def dump(obj, anIO = nil, limit = nil)
1207
+ if anIO and limit.nil?
1208
+ anIO = anIO.to_io if anIO.respond_to?(:to_io)
1209
+ unless anIO.respond_to?(:write)
1210
+ limit = anIO
1211
+ anIO = nil
1212
+ end
1213
+ end
1214
+ opts = JSON.dump_default_options
1215
+ limit and opts.update(:max_nesting => limit)
1216
+ result = generate(obj, opts)
1217
+ if anIO
1218
+ anIO.write result
1219
+ anIO
1220
+ else
1221
+ result
1222
+ end
1223
+ rescue JSON::NestingError
1224
+ raise ArgumentError, "exceed depth limit"
1225
+ end
1226
+
1227
+ # Swap consecutive bytes of _string_ in place.
1228
+ def self.swap!(string) # :nodoc:
1229
+ 0.upto(string.size / 2) do |i|
1230
+ break unless string[2 * i + 1]
1231
+ string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i]
1232
+ end
1233
+ string
1234
+ end
1235
+
1236
+ # Shortuct for iconv.
1237
+ if ::String.method_defined?(:encode)
1238
+ # Encodes string using Ruby's _String.encode_
1239
+ def self.iconv(to, from, string)
1240
+ string.encode(to, from)
1241
+ end
1242
+ else
1243
+ require 'iconv'
1244
+ # Encodes string using _iconv_ library
1245
+ def self.iconv(to, from, string)
1246
+ Iconv.conv(to, from, string)
1247
+ end
1248
+ end
1249
+
1250
+ if ::Object.method(:const_defined?).arity == 1
1251
+ def self.const_defined_in?(modul, constant)
1252
+ modul.const_defined?(constant)
1253
+ end
1254
+ else
1255
+ def self.const_defined_in?(modul, constant)
1256
+ modul.const_defined?(constant, false)
1257
+ end
1258
+ end
1259
+ end
1260
+
1261
+ module ::Kernel
1262
+ private
1263
+
1264
+ # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
1265
+ # one line.
1266
+ def j(*objs)
1267
+ objs.each do |obj|
1268
+ puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
1269
+ end
1270
+ nil
1271
+ end
1272
+
1273
+ # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with
1274
+ # indentation and over many lines.
1275
+ def jj(*objs)
1276
+ objs.each do |obj|
1277
+ puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
1278
+ end
1279
+ nil
1280
+ end
1281
+
1282
+ # If _object_ is string-like, parse the string and return the parsed result as
1283
+ # a Ruby data structure. Otherwise, generate a JSON text from the Ruby data
1284
+ # structure object and return it.
1285
+ #
1286
+ # The _opts_ argument is passed through to generate/parse respectively. See
1287
+ # generate and parse for their documentation.
1288
+ def JSON(object, *args)
1289
+ if object.respond_to? :to_str
1290
+ JSON.parse(object.to_str, args.first)
1291
+ else
1292
+ JSON.generate(object, args.first)
1293
+ end
1294
+ end
1295
+ end
1296
+
1297
+ # Extends any Class to include _json_creatable?_ method.
1298
+ class ::Class
1299
+ # Returns true if this class can be used to create an instance
1300
+ # from a serialised JSON string. The class has to implement a class
1301
+ # method _json_create_ that expects a hash as first parameter. The hash
1302
+ # should include the required data.
1303
+ def json_creatable?
1304
+ respond_to?(:json_create)
1305
+ end
1306
+ end
1307
+
1308
+ JSON.generator = JSON::Pure::Generator
1309
+ JSON.parser = JSON::Pure::Parser
1310
+ require 'uri'
1311
+
1312
+ # It just gists.
1313
+ module Gist
1314
+ extend self
1315
+
1316
+ VERSION = '4.0.1'
1317
+
1318
+ # A list of clipboard commands with copy and paste support.
1319
+ CLIPBOARD_COMMANDS = {
1320
+ 'xclip' => 'xclip -o',
1321
+ 'xsel' => 'xsel -o',
1322
+ 'pbcopy' => 'pbpaste',
1323
+ 'putclip' => 'getclip'
1324
+ }
1325
+
1326
+ GITHUB_API_URL = URI("https://api.github.com/")
1327
+ GIT_IO_URL = URI("http://git.io")
1328
+
1329
+ GITHUB_BASE_PATH = ""
1330
+ GHE_BASE_PATH = "/api/v3"
1331
+
1332
+ URL_ENV_NAME = "GITHUB_URL"
1333
+
1334
+ USER_AGENT = "gist/#{VERSION} (Net::HTTP, #{RUBY_DESCRIPTION})"
1335
+
1336
+ # Exception tag for errors raised while gisting.
1337
+ module Error;
1338
+ def self.exception(*args)
1339
+ RuntimeError.new(*args).extend(self)
1340
+ end
1341
+ end
1342
+ class ClipboardError < RuntimeError; include Error end
1343
+
1344
+ # Upload a gist to https://gist.github.com
1345
+ #
1346
+ # @param [String] content the code you'd like to gist
1347
+ # @param [Hash] options more detailed options, see
1348
+ # the documentation for {multi_gist}
1349
+ #
1350
+ # @see http://developer.github.com/v3/gists/
1351
+ def gist(content, options = {})
1352
+ filename = options[:filename] || "a.rb"
1353
+ multi_gist({filename => content}, options)
1354
+ end
1355
+
1356
+ # Upload a gist to https://gist.github.com
1357
+ #
1358
+ # @param [Hash] files the code you'd like to gist: filename => content
1359
+ # @param [Hash] options more detailed options
1360
+ #
1361
+ # @option options [String] :description the description
1362
+ # @option options [Boolean] :public (false) is this gist public
1363
+ # @option options [Boolean] :anonymous (false) is this gist anonymous
1364
+ # @option options [String] :access_token (`File.read("~/.gist")`) The OAuth2 access token.
1365
+ # @option options [String] :update the URL or id of a gist to update
1366
+ # @option options [Boolean] :copy (false) Copy resulting URL to clipboard, if successful.
1367
+ # @option options [Boolean] :open (false) Open the resulting URL in a browser.
1368
+ # @option options [Symbol] :output (:all) The type of return value you'd like:
1369
+ # :html_url gives a String containing the url to the gist in a browser
1370
+ # :short_url gives a String contianing a git.io url that redirects to html_url
1371
+ # :javascript gives a String containing a script tag suitable for embedding the gist
1372
+ # :all gives a Hash containing the parsed json response from the server
1373
+ #
1374
+ # @return [String, Hash] the return value as configured by options[:output]
1375
+ # @raise [Gist::Error] if something went wrong
1376
+ #
1377
+ # @see http://developer.github.com/v3/gists/
1378
+ def multi_gist(files, options={})
1379
+ json = {}
1380
+
1381
+ json[:description] = options[:description] if options[:description]
1382
+ json[:public] = !!options[:public]
1383
+ json[:files] = {}
1384
+
1385
+ files.each_pair do |(name, content)|
1386
+ raise "Cannot gist empty files" if content.to_s.strip == ""
1387
+ json[:files][File.basename(name)] = {:content => content}
1388
+ end
1389
+
1390
+ existing_gist = options[:update].to_s.split("/").last
1391
+ if options[:anonymous]
1392
+ access_token = nil
1393
+ else
1394
+ access_token = (options[:access_token] || File.read(auth_token_file) rescue nil)
1395
+ end
1396
+
1397
+ url = "#{base_path}/gists"
1398
+ url << "/" << CGI.escape(existing_gist) if existing_gist.to_s != ''
1399
+ url << "?access_token=" << CGI.escape(access_token) if access_token.to_s != ''
1400
+
1401
+ request = Net::HTTP::Post.new(url)
1402
+ request.body = JSON.dump(json)
1403
+ request.content_type = 'application/json'
1404
+
1405
+ retried = false
1406
+
1407
+ begin
1408
+ response = http(api_url, request)
1409
+ if Net::HTTPSuccess === response
1410
+ on_success(response.body, options)
1411
+ else
1412
+ raise "Got #{response.class} from gist: #{response.body}"
1413
+ end
1414
+ rescue => e
1415
+ raise if retried
1416
+ retried = true
1417
+ retry
1418
+ end
1419
+
1420
+ rescue => e
1421
+ raise e.extend Error
1422
+ end
1423
+
1424
+ # Convert long github urls into short git.io ones
1425
+ #
1426
+ # @param [String] url
1427
+ # @return [String] shortened url, or long url if shortening fails
1428
+ def shorten(url)
1429
+ request = Net::HTTP::Post.new("/")
1430
+ request.set_form_data(:url => url)
1431
+ response = http(GIT_IO_URL, request)
1432
+ case response.code
1433
+ when "201"
1434
+ response['Location']
1435
+ else
1436
+ url
1437
+ end
1438
+ end
1439
+
1440
+ # Log the user into gist.
1441
+ #
1442
+ # This method asks the user for a username and password, and tries to obtain
1443
+ # and OAuth2 access token, which is then stored in ~/.gist
1444
+ #
1445
+ # @raise [Gist::Error] if something went wrong
1446
+ # @see http://developer.github.com/v3/oauth/
1447
+ def login!
1448
+ puts "Obtaining OAuth2 access_token from github."
1449
+ print "GitHub username: "
1450
+ username = $stdin.gets.strip
1451
+ print "GitHub password: "
1452
+ password = begin
1453
+ `stty -echo` rescue nil
1454
+ $stdin.gets.strip
1455
+ ensure
1456
+ `stty echo` rescue nil
1457
+ end
1458
+ puts ""
1459
+
1460
+ request = Net::HTTP::Post.new("#{base_path}/authorizations")
1461
+ request.body = JSON.dump({
1462
+ :scopes => [:gist],
1463
+ :note => "The gist gem",
1464
+ :note_url => "https://github.com/ConradIrwin/gist"
1465
+ })
1466
+ request.content_type = 'application/json'
1467
+ request.basic_auth(username, password)
1468
+
1469
+ response = http(api_url, request)
1470
+
1471
+ if Net::HTTPCreated === response
1472
+ File.open(auth_token_file, 'w') do |f|
1473
+ f.write JSON.parse(response.body)['token']
1474
+ end
1475
+ puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/applications"
1476
+ else
1477
+ raise "Got #{response.class} from gist: #{response.body}"
1478
+ end
1479
+ rescue => e
1480
+ raise e.extend Error
1481
+ end
1482
+
1483
+ # Return HTTP connection
1484
+ #
1485
+ # @param [URI::HTTP] The URI to which to connect
1486
+ # @return [Net::HTTP]
1487
+ def http_connection(uri)
1488
+ env = ENV['http_proxy'] || ENV['HTTP_PROXY']
1489
+ connection = if env
1490
+ proxy = URI(env)
1491
+ Net::HTTP::Proxy(proxy.host, proxy.port).new(uri.host, uri.port)
1492
+ else
1493
+ Net::HTTP.new(uri.host, uri.port)
1494
+ end
1495
+ if uri.scheme == "https"
1496
+ connection.use_ssl = true
1497
+ connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
1498
+ end
1499
+ connection.open_timeout = 10
1500
+ connection.read_timeout = 10
1501
+ connection
1502
+ end
1503
+
1504
+ # Run an HTTP operation
1505
+ #
1506
+ # @param [URI::HTTP] The URI to which to connect
1507
+ # @param [Net::HTTPRequest] The request to make
1508
+ # @return [Net::HTTPResponse]
1509
+ def http(url, request)
1510
+ request['User-Agent'] = USER_AGENT
1511
+
1512
+ http_connection(url).start do |http|
1513
+ http.request request
1514
+ end
1515
+ rescue Timeout::Error
1516
+ raise "Could not connect to #{api_url}"
1517
+ end
1518
+
1519
+ # Called after an HTTP response to gist to perform post-processing.
1520
+ #
1521
+ # @param [String] body the text body from the github api
1522
+ # @param [Hash] options more detailed options, see
1523
+ # the documentation for {multi_gist}
1524
+ def on_success(body, options={})
1525
+ json = JSON.parse(body)
1526
+
1527
+ output = case options[:output]
1528
+ when :javascript
1529
+ %Q{<script src="#{json['html_url']}.js"></script>}
1530
+ when :html_url
1531
+ json['html_url']
1532
+ when :short_url
1533
+ shorten(json['html_url'])
1534
+ else
1535
+ json
1536
+ end
1537
+
1538
+ Gist.copy(output.to_s) if options[:copy]
1539
+ Gist.open(json['html_url']) if options[:open]
1540
+
1541
+ output
1542
+ end
1543
+
1544
+ # Copy a string to the clipboard.
1545
+ #
1546
+ # @param [String] content
1547
+ # @raise [Gist::Error] if no clipboard integration could be found
1548
+ #
1549
+ def copy(content)
1550
+ IO.popen(clipboard_command(:copy), 'r+') { |clip| clip.print content }
1551
+
1552
+ unless paste == content
1553
+ message = 'Copying to clipboard failed.'
1554
+
1555
+ if ENV["TMUX"] && clipboard_command(:copy) == 'pbcopy'
1556
+ message << "\nIf you're running tmux on a mac, try http://robots.thoughtbot.com/post/19398560514/how-to-copy-and-paste-with-tmux-on-mac-os-x"
1557
+ end
1558
+
1559
+ raise Error, message
1560
+ end
1561
+ rescue Error => e
1562
+ raise ClipboardError, e.message + "\nAttempted to copy: #{content}"
1563
+ end
1564
+
1565
+ # Get a string from the clipboard.
1566
+ #
1567
+ # @param [String] content
1568
+ # @raise [Gist::Error] if no clipboard integration could be found
1569
+ def paste
1570
+ `#{clipboard_command(:paste)}`
1571
+ end
1572
+
1573
+ # Find command from PATH environment.
1574
+ #
1575
+ # @param [String] cmd command name to find
1576
+ # @param [String] options PATH environment variable
1577
+ # @return [String] the command found
1578
+ def which(cmd, path=ENV['PATH'])
1579
+ if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin|cygwin/
1580
+ path.split(File::PATH_SEPARATOR).each {|dir|
1581
+ f = File.join(dir, cmd+".exe")
1582
+ return f if File.executable?(f) && !File.directory?(f)
1583
+ }
1584
+ nil
1585
+ else
1586
+ return system("which #{cmd} > /dev/null 2>&1")
1587
+ end
1588
+ end
1589
+
1590
+ # Get the command to use for the clipboard action.
1591
+ #
1592
+ # @param [Symbol] action either :copy or :paste
1593
+ # @return [String] the command to run
1594
+ # @raise [Gist::ClipboardError] if no clipboard integration could be found
1595
+ def clipboard_command(action)
1596
+ command = CLIPBOARD_COMMANDS.keys.detect do |cmd|
1597
+ which cmd
1598
+ end
1599
+ raise ClipboardError, <<-EOT unless command
1600
+ Could not find copy command, tried:
1601
+ #{CLIPBOARD_COMMANDS.values.join(' || ')}
1602
+ EOT
1603
+ action == :copy ? command : CLIPBOARD_COMMANDS[command]
1604
+ end
1605
+
1606
+ # Open a URL in a browser.
1607
+ #
1608
+ # @param [String] url
1609
+ # @raise [RuntimeError] if no browser integration could be found
1610
+ #
1611
+ # This method was heavily inspired by defunkt's Gist#open,
1612
+ # @see https://github.com/defunkt/gist/blob/bca9b29/lib/gist.rb#L157
1613
+ def open(url)
1614
+ command = if ENV['BROWSER']
1615
+ ENV['BROWSER']
1616
+ elsif RUBY_PLATFORM =~ /darwin/
1617
+ 'open'
1618
+ elsif RUBY_PLATFORM =~ /linux/
1619
+ %w(
1620
+ sensible-browser
1621
+ firefox
1622
+ firefox-bin
1623
+ ).detect do |cmd|
1624
+ which cmd
1625
+ end
1626
+ elsif ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw|wince/i
1627
+ 'start ""'
1628
+ else
1629
+ raise "Could not work out how to use a browser."
1630
+ end
1631
+
1632
+ `#{command} #{url}`
1633
+ end
1634
+
1635
+ # Get the API base path
1636
+ def base_path
1637
+ ENV.key?(URL_ENV_NAME) ? GHE_BASE_PATH : GITHUB_BASE_PATH
1638
+ end
1639
+
1640
+ # Get the API URL
1641
+ def api_url
1642
+ ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_API_URL
1643
+ end
1644
+
1645
+ def auth_token_file
1646
+ if ENV.key?(URL_ENV_NAME)
1647
+ File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/[^a-z.]/, '')}"
1648
+ else
1649
+ File.expand_path "~/.gist"
1650
+ end
1651
+ end
1652
+
1653
+ def legacy_private_gister?
1654
+ return unless which('git')
1655
+ `git config --global gist.private` =~ /\Ayes|1|true|on\z/i
1656
+ end
1657
+
1658
+ def should_be_public?(options={})
1659
+ if options.key? :private
1660
+ !options[:private]
1661
+ else
1662
+ !Gist.legacy_private_gister?
1663
+ end
1664
+ end
1665
+ end
1666
+ #!/usr/bin/env ruby
1667
+
1668
+ require 'optparse'
1669
+
1670
+ # For the holdings of options.
1671
+ options = {}
1672
+ filenames = []
1673
+
1674
+ opts = OptionParser.new do |opts|
1675
+ executable_name = File.split($0)[1]
1676
+ opts.banner = <<-EOS
1677
+ Gist (v#{Gist::VERSION}) lets you upload to https://gist.github.com/
1678
+
1679
+ The content to be uploaded can be passed as a list of files, if none are
1680
+ specified STDIN will be read. The default filename for STDIN is "a.rb", and all
1681
+ filenames can be overridden by repeating the "-f" flag. The most useful reason
1682
+ to do this is to change the syntax highlighting.
1683
+
1684
+ If you'd like your gists to be associated with your GitHub account, so that you
1685
+ can edit them and find them in future, first use `gist --login` to obtain an
1686
+ Oauth2 access token. This is stored and used by gist in the future.
1687
+
1688
+ Private gists do not have guessable URLs and can be created with "-p", you can
1689
+ also set the description at the top of the gist by passing "-d".
1690
+
1691
+ Anonymous gists are not associated with your GitHub account, they can be created
1692
+ with "-a" even after you have used "gist --login".
1693
+
1694
+ If you would like to shorten the resulting gist URL, use the -s flag. This will
1695
+ use GitHub's URL shortener, git.io.
1696
+
1697
+ To copy the resulting URL to your clipboard you can use the -c option, or to
1698
+ just open it directly in your browser, use -o. Using the -e option will copy the
1699
+ embeddable URL to the clipboard. You can add `alias gist='gist -c'` to your
1700
+ shell's rc file to configure this behaviour by default.
1701
+
1702
+ Instead of creating a new gist, you can update an existing one by passing its ID
1703
+ or URL with "-u". For this to work, you must be logged in, and have created the
1704
+ original gist with the same GitHub account.
1705
+
1706
+ Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-d DESC] [-a] [-u URL] [-P] [-f NAME|-t EXT]* FILE*
1707
+ #{executable_name} --login
1708
+
1709
+ EOS
1710
+
1711
+ opts.on("--login", "Authenticate gist on this computer.") do
1712
+ Gist.login!
1713
+ exit
1714
+ end
1715
+
1716
+ opts.on("-f", "--filename [NAME.EXTENSION]", "Sets the filename and syntax type.") do |filename|
1717
+ filenames << filename
1718
+ options[:filename] = filename
1719
+ end
1720
+
1721
+ opts.on("-t", "--type [EXTENSION]", "Sets the file extension and syntax type.") do |extension|
1722
+ filenames << "foo.#{extension}"
1723
+ options[:filename] = "foo.#{extension}"
1724
+ end
1725
+
1726
+ opts.on("-p", "--private", "Makes your gist private.") do
1727
+ options[:private] = true
1728
+ end
1729
+
1730
+ opts.on("--no-private") do
1731
+ options[:private] = false
1732
+ end
1733
+
1734
+ opts.on("-d", "--description DESCRIPTION", "Adds a description to your gist.") do |description|
1735
+ options[:description] = description
1736
+ end
1737
+
1738
+ opts.on("-s", "--shorten", "Shorten the gist URL using git.io.") do |shorten|
1739
+ options[:shorten] = shorten
1740
+ end
1741
+
1742
+ opts.on("-u", "--update [ URL | ID ]", "Update an existing gist.") do |update|
1743
+ options[:update] = update
1744
+ end
1745
+
1746
+ opts.on("-a", "--anonymous", "Create an anonymous gist.") do
1747
+ options[:anonymous] = true
1748
+ end
1749
+
1750
+ opts.on("-c", "--copy", "Copy the resulting URL to the clipboard") do
1751
+ options[:copy] = true
1752
+ end
1753
+
1754
+ opts.on("-e", "--embed", "Copy the embed code for the gist to the clipboard") do
1755
+ options[:embed] = true
1756
+ options[:copy] = true
1757
+ end
1758
+
1759
+ opts.on("-o", "--open", "Open the resulting URL in a browser") do
1760
+ options[:open] = true
1761
+ end
1762
+
1763
+ opts.on("--no-open")
1764
+
1765
+ opts.on("-P", "--paste", "Paste from the clipboard to gist") do
1766
+ options[:paste] = true
1767
+ end
1768
+
1769
+ opts.on_tail("-h","--help", "Show this message.") do
1770
+ puts opts
1771
+ exit
1772
+ end
1773
+
1774
+ opts.on_tail("-v", "--version", "Print the version.") do
1775
+ puts "gist v#{Gist::VERSION}"
1776
+ exit
1777
+ end
1778
+
1779
+ end
1780
+ opts.parse!
1781
+
1782
+ begin
1783
+ options[:output] = if options[:embed] && options[:shorten]
1784
+ raise Gist::Error, "--embed does not make sense with --shorten"
1785
+ elsif options[:embed]
1786
+ :javascript
1787
+ elsif options[:shorten]
1788
+ :short_url
1789
+ else
1790
+ :html_url
1791
+ end
1792
+
1793
+ options[:public] = Gist.should_be_public?(options)
1794
+
1795
+ if options[:paste]
1796
+ puts Gist.gist(Gist.paste, options)
1797
+ else
1798
+ to_read = ARGV.empty? ? ['-'] : ARGV
1799
+ files = {}
1800
+ to_read.zip(filenames).each do |(file, name)|
1801
+ files[name || file] =
1802
+ begin
1803
+ if file == '-'
1804
+ $stderr.puts "(type a gist. <ctrl-c> to cancel, <ctrl-d> when done)" if $stdin.tty?
1805
+ STDIN.read
1806
+ else
1807
+ File.read(File.expand_path(file))
1808
+ end
1809
+ rescue => e
1810
+ raise e.extend(Gist::Error)
1811
+ end
1812
+ end
1813
+
1814
+ puts Gist.multi_gist(files, options)
1815
+ end
1816
+ rescue Gist::Error => e
1817
+ puts "Error: #{e.message}"
1818
+ exit 1
1819
+ rescue Interrupt
1820
+ # bye!
1821
+ end