gist 4.0.0 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
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