aapis-gist 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/build/gist ADDED
@@ -0,0 +1,2091 @@
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 'uri'
7
+
8
+ begin
9
+ require 'strscan'
10
+
11
+ module JSON
12
+ module Pure
13
+ # This class implements the JSON parser that is used to parse a JSON string
14
+ # into a Ruby data structure.
15
+ class Parser < StringScanner
16
+ STRING = /" ((?:[^\x0-\x1f"\\] |
17
+ # escaped special characters:
18
+ \\["\\\/bfnrt] |
19
+ \\u[0-9a-fA-F]{4} |
20
+ # match all but escaped special characters:
21
+ \\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\xff])*)
22
+ "/nx
23
+ INTEGER = /(-?0|-?[1-9]\d*)/
24
+ FLOAT = /(-?
25
+ (?:0|[1-9]\d*)
26
+ (?:
27
+ \.\d+(?i:e[+-]?\d+) |
28
+ \.\d+ |
29
+ (?i:e[+-]?\d+)
30
+ )
31
+ )/x
32
+ NAN = /NaN/
33
+ INFINITY = /Infinity/
34
+ MINUS_INFINITY = /-Infinity/
35
+ OBJECT_OPEN = /\{/
36
+ OBJECT_CLOSE = /\}/
37
+ ARRAY_OPEN = /\[/
38
+ ARRAY_CLOSE = /\]/
39
+ PAIR_DELIMITER = /:/
40
+ COLLECTION_DELIMITER = /,/
41
+ TRUE = /true/
42
+ FALSE = /false/
43
+ NULL = /null/
44
+ IGNORE = %r(
45
+ (?:
46
+ //[^\n\r]*[\n\r]| # line comments
47
+ /\* # c-style comments
48
+ (?:
49
+ [^*/]| # normal chars
50
+ /[^*]| # slashes that do not start a nested comment
51
+ \*[^/]| # asterisks that do not end this comment
52
+ /(?=\*/) # single slash before this comment's end
53
+ )*
54
+ \*/ # the End of this comment
55
+ |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr
56
+ )+
57
+ )mx
58
+
59
+ UNPARSED = Object.new
60
+
61
+ # Creates a new JSON::Pure::Parser instance for the string _source_.
62
+ #
63
+ # It will be configured by the _opts_ hash. _opts_ can have the following
64
+ # keys:
65
+ # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
66
+ # structures. Disable depth checking with :max_nesting => false|nil|0,
67
+ # it defaults to 19.
68
+ # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
69
+ # defiance of RFC 4627 to be parsed by the Parser. This option defaults
70
+ # to false.
71
+ # * *symbolize_names*: If set to true, returns symbols for the names
72
+ # (keys) in a JSON object. Otherwise strings are returned, which is also
73
+ # the default.
74
+ # * *create_additions*: If set to false, the Parser doesn't create
75
+ # additions even if a matchin class and create_id was found. This option
76
+ # defaults to true.
77
+ # * *object_class*: Defaults to Hash
78
+ # * *array_class*: Defaults to Array
79
+ # * *quirks_mode*: Enables quirks_mode for parser, that is for example
80
+ # parsing single JSON values instead of documents is possible.
81
+ def initialize(source, opts = {})
82
+ opts ||= {}
83
+ unless @quirks_mode = opts[:quirks_mode]
84
+ source = convert_encoding source
85
+ end
86
+ super source
87
+ if !opts.key?(:max_nesting) # defaults to 19
88
+ @max_nesting = 19
89
+ elsif opts[:max_nesting]
90
+ @max_nesting = opts[:max_nesting]
91
+ else
92
+ @max_nesting = 0
93
+ end
94
+ @allow_nan = !!opts[:allow_nan]
95
+ @symbolize_names = !!opts[:symbolize_names]
96
+ if opts.key?(:create_additions)
97
+ @create_additions = !!opts[:create_additions]
98
+ else
99
+ @create_additions = true
100
+ end
101
+ @create_id = @create_additions ? JSON.create_id : nil
102
+ @object_class = opts[:object_class] || Hash
103
+ @array_class = opts[:array_class] || Array
104
+ @match_string = opts[:match_string]
105
+ end
106
+
107
+ alias source string
108
+
109
+ def quirks_mode?
110
+ !!@quirks_mode
111
+ end
112
+
113
+ def reset
114
+ super
115
+ @current_nesting = 0
116
+ end
117
+
118
+ # Parses the current JSON string _source_ and returns the complete data
119
+ # structure as a result.
120
+ def parse
121
+ reset
122
+ obj = nil
123
+ if @quirks_mode
124
+ while !eos? && skip(IGNORE)
125
+ end
126
+ if eos?
127
+ raise ParserError, "source did not contain any JSON!"
128
+ else
129
+ obj = parse_value
130
+ obj == UNPARSED and raise ParserError, "source did not contain any JSON!"
131
+ end
132
+ else
133
+ until eos?
134
+ case
135
+ when scan(OBJECT_OPEN)
136
+ obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
137
+ @current_nesting = 1
138
+ obj = parse_object
139
+ when scan(ARRAY_OPEN)
140
+ obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
141
+ @current_nesting = 1
142
+ obj = parse_array
143
+ when skip(IGNORE)
144
+ ;
145
+ else
146
+ raise ParserError, "source '#{peek(20)}' not in JSON!"
147
+ end
148
+ end
149
+ obj or raise ParserError, "source did not contain any JSON!"
150
+ end
151
+ obj
152
+ end
153
+
154
+ private
155
+
156
+ def convert_encoding(source)
157
+ if source.respond_to?(:to_str)
158
+ source = source.to_str
159
+ else
160
+ raise TypeError, "#{source.inspect} is not like a string"
161
+ end
162
+ if defined?(::Encoding)
163
+ if source.encoding == ::Encoding::ASCII_8BIT
164
+ b = source[0, 4].bytes.to_a
165
+ source =
166
+ case
167
+ when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
168
+ source.dup.force_encoding(::Encoding::UTF_32BE).encode!(::Encoding::UTF_8)
169
+ when b.size >= 4 && b[0] == 0 && b[2] == 0
170
+ source.dup.force_encoding(::Encoding::UTF_16BE).encode!(::Encoding::UTF_8)
171
+ when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
172
+ source.dup.force_encoding(::Encoding::UTF_32LE).encode!(::Encoding::UTF_8)
173
+ when b.size >= 4 && b[1] == 0 && b[3] == 0
174
+ source.dup.force_encoding(::Encoding::UTF_16LE).encode!(::Encoding::UTF_8)
175
+ else
176
+ source.dup
177
+ end
178
+ else
179
+ source = source.encode(::Encoding::UTF_8)
180
+ end
181
+ source.force_encoding(::Encoding::ASCII_8BIT)
182
+ else
183
+ b = source
184
+ source =
185
+ case
186
+ when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
187
+ JSON.iconv('utf-8', 'utf-32be', b)
188
+ when b.size >= 4 && b[0] == 0 && b[2] == 0
189
+ JSON.iconv('utf-8', 'utf-16be', b)
190
+ when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
191
+ JSON.iconv('utf-8', 'utf-32le', b)
192
+ when b.size >= 4 && b[1] == 0 && b[3] == 0
193
+ JSON.iconv('utf-8', 'utf-16le', b)
194
+ else
195
+ b
196
+ end
197
+ end
198
+ source
199
+ end
200
+
201
+ # Unescape characters in strings.
202
+ UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
203
+ UNESCAPE_MAP.update({
204
+ ?" => '"',
205
+ ?\\ => '\\',
206
+ ?/ => '/',
207
+ ?b => "\b",
208
+ ?f => "\f",
209
+ ?n => "\n",
210
+ ?r => "\r",
211
+ ?t => "\t",
212
+ ?u => nil,
213
+ })
214
+
215
+ EMPTY_8BIT_STRING = ''
216
+ if ::String.method_defined?(:encode)
217
+ EMPTY_8BIT_STRING.force_encoding Encoding::ASCII_8BIT
218
+ end
219
+
220
+ def parse_string
221
+ if scan(STRING)
222
+ return '' if self[1].empty?
223
+ string = self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
224
+ if u = UNESCAPE_MAP[$&[1]]
225
+ u
226
+ else # \uXXXX
227
+ bytes = EMPTY_8BIT_STRING.dup
228
+ i = 0
229
+ while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
230
+ bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
231
+ i += 1
232
+ end
233
+ JSON.iconv('utf-8', 'utf-16be', bytes)
234
+ end
235
+ end
236
+ if string.respond_to?(:force_encoding)
237
+ string.force_encoding(::Encoding::UTF_8)
238
+ end
239
+ if @create_additions and @match_string
240
+ for (regexp, klass) in @match_string
241
+ klass.json_creatable? or next
242
+ string =~ regexp and return klass.json_create(string)
243
+ end
244
+ end
245
+ string
246
+ else
247
+ UNPARSED
248
+ end
249
+ rescue => e
250
+ raise ParserError, "Caught #{e.class} at '#{peek(20)}': #{e}"
251
+ end
252
+
253
+ def parse_value
254
+ case
255
+ when scan(FLOAT)
256
+ Float(self[1])
257
+ when scan(INTEGER)
258
+ Integer(self[1])
259
+ when scan(TRUE)
260
+ true
261
+ when scan(FALSE)
262
+ false
263
+ when scan(NULL)
264
+ nil
265
+ when (string = parse_string) != UNPARSED
266
+ string
267
+ when scan(ARRAY_OPEN)
268
+ @current_nesting += 1
269
+ ary = parse_array
270
+ @current_nesting -= 1
271
+ ary
272
+ when scan(OBJECT_OPEN)
273
+ @current_nesting += 1
274
+ obj = parse_object
275
+ @current_nesting -= 1
276
+ obj
277
+ when @allow_nan && scan(NAN)
278
+ NaN
279
+ when @allow_nan && scan(INFINITY)
280
+ Infinity
281
+ when @allow_nan && scan(MINUS_INFINITY)
282
+ MinusInfinity
283
+ else
284
+ UNPARSED
285
+ end
286
+ end
287
+
288
+ def parse_array
289
+ raise NestingError, "nesting of #@current_nesting is too deep" if
290
+ @max_nesting.nonzero? && @current_nesting > @max_nesting
291
+ result = @array_class.new
292
+ delim = false
293
+ until eos?
294
+ case
295
+ when (value = parse_value) != UNPARSED
296
+ delim = false
297
+ result << value
298
+ skip(IGNORE)
299
+ if scan(COLLECTION_DELIMITER)
300
+ delim = true
301
+ elsif match?(ARRAY_CLOSE)
302
+ ;
303
+ else
304
+ raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
305
+ end
306
+ when scan(ARRAY_CLOSE)
307
+ if delim
308
+ raise ParserError, "expected next element in array at '#{peek(20)}'!"
309
+ end
310
+ break
311
+ when skip(IGNORE)
312
+ ;
313
+ else
314
+ raise ParserError, "unexpected token in array at '#{peek(20)}'!"
315
+ end
316
+ end
317
+ result
318
+ end
319
+
320
+ def parse_object
321
+ raise NestingError, "nesting of #@current_nesting is too deep" if
322
+ @max_nesting.nonzero? && @current_nesting > @max_nesting
323
+ result = @object_class.new
324
+ delim = false
325
+ until eos?
326
+ case
327
+ when (string = parse_string) != UNPARSED
328
+ skip(IGNORE)
329
+ unless scan(PAIR_DELIMITER)
330
+ raise ParserError, "expected ':' in object at '#{peek(20)}'!"
331
+ end
332
+ skip(IGNORE)
333
+ unless (value = parse_value).equal? UNPARSED
334
+ result[@symbolize_names ? string.to_sym : string] = value
335
+ delim = false
336
+ skip(IGNORE)
337
+ if scan(COLLECTION_DELIMITER)
338
+ delim = true
339
+ elsif match?(OBJECT_CLOSE)
340
+ ;
341
+ else
342
+ raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
343
+ end
344
+ else
345
+ raise ParserError, "expected value in object at '#{peek(20)}'!"
346
+ end
347
+ when scan(OBJECT_CLOSE)
348
+ if delim
349
+ raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
350
+ end
351
+ if @create_additions and klassname = result[@create_id]
352
+ klass = JSON.deep_const_get klassname
353
+ break unless klass and klass.json_creatable?
354
+ result = klass.json_create(result)
355
+ end
356
+ break
357
+ when skip(IGNORE)
358
+ ;
359
+ else
360
+ raise ParserError, "unexpected token in object at '#{peek(20)}'!"
361
+ end
362
+ end
363
+ result
364
+ end
365
+ end
366
+ end
367
+ end
368
+
369
+ module JSON
370
+ MAP = {
371
+ "\x0" => '\u0000',
372
+ "\x1" => '\u0001',
373
+ "\x2" => '\u0002',
374
+ "\x3" => '\u0003',
375
+ "\x4" => '\u0004',
376
+ "\x5" => '\u0005',
377
+ "\x6" => '\u0006',
378
+ "\x7" => '\u0007',
379
+ "\b" => '\b',
380
+ "\t" => '\t',
381
+ "\n" => '\n',
382
+ "\xb" => '\u000b',
383
+ "\f" => '\f',
384
+ "\r" => '\r',
385
+ "\xe" => '\u000e',
386
+ "\xf" => '\u000f',
387
+ "\x10" => '\u0010',
388
+ "\x11" => '\u0011',
389
+ "\x12" => '\u0012',
390
+ "\x13" => '\u0013',
391
+ "\x14" => '\u0014',
392
+ "\x15" => '\u0015',
393
+ "\x16" => '\u0016',
394
+ "\x17" => '\u0017',
395
+ "\x18" => '\u0018',
396
+ "\x19" => '\u0019',
397
+ "\x1a" => '\u001a',
398
+ "\x1b" => '\u001b',
399
+ "\x1c" => '\u001c',
400
+ "\x1d" => '\u001d',
401
+ "\x1e" => '\u001e',
402
+ "\x1f" => '\u001f',
403
+ '"' => '\"',
404
+ '\\' => '\\\\',
405
+ } # :nodoc:
406
+
407
+ # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
408
+ # UTF16 big endian characters as \u????, and return it.
409
+ if defined?(::Encoding)
410
+ def utf8_to_json(string) # :nodoc:
411
+ string = string.dup
412
+ string << '' # XXX workaround: avoid buffer sharing
413
+ string.force_encoding(::Encoding::ASCII_8BIT)
414
+ string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
415
+ string.force_encoding(::Encoding::UTF_8)
416
+ string
417
+ end
418
+
419
+ def utf8_to_json_ascii(string) # :nodoc:
420
+ string = string.dup
421
+ string << '' # XXX workaround: avoid buffer sharing
422
+ string.force_encoding(::Encoding::ASCII_8BIT)
423
+ string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
424
+ string.gsub!(/(
425
+ (?:
426
+ [\xc2-\xdf][\x80-\xbf] |
427
+ [\xe0-\xef][\x80-\xbf]{2} |
428
+ [\xf0-\xf4][\x80-\xbf]{3}
429
+ )+ |
430
+ [\x80-\xc1\xf5-\xff] # invalid
431
+ )/nx) { |c|
432
+ c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
433
+ s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
434
+ s.gsub!(/.{4}/n, '\\\\u\&')
435
+ }
436
+ string.force_encoding(::Encoding::UTF_8)
437
+ string
438
+ rescue => e
439
+ raise GeneratorError, "Caught #{e.class}: #{e}"
440
+ end
441
+ else
442
+ def utf8_to_json(string) # :nodoc:
443
+ string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
444
+ end
445
+
446
+ def utf8_to_json_ascii(string) # :nodoc:
447
+ string = string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
448
+ string.gsub!(/(
449
+ (?:
450
+ [\xc2-\xdf][\x80-\xbf] |
451
+ [\xe0-\xef][\x80-\xbf]{2} |
452
+ [\xf0-\xf4][\x80-\xbf]{3}
453
+ )+ |
454
+ [\x80-\xc1\xf5-\xff] # invalid
455
+ )/nx) { |c|
456
+ c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
457
+ s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
458
+ s.gsub!(/.{4}/n, '\\\\u\&')
459
+ }
460
+ string
461
+ rescue => e
462
+ raise GeneratorError, "Caught #{e.class}: #{e}"
463
+ end
464
+ end
465
+ module_function :utf8_to_json, :utf8_to_json_ascii
466
+
467
+ module Pure
468
+ module Generator
469
+ # This class is used to create State instances, that are use to hold data
470
+ # while generating a JSON text from a Ruby data structure.
471
+ class State
472
+ # Creates a State object from _opts_, which ought to be Hash to create
473
+ # a new State instance configured by _opts_, something else to create
474
+ # an unconfigured instance. If _opts_ is a State object, it is just
475
+ # returned.
476
+ def self.from_state(opts)
477
+ case
478
+ when self === opts
479
+ opts
480
+ when opts.respond_to?(:to_hash)
481
+ new(opts.to_hash)
482
+ when opts.respond_to?(:to_h)
483
+ new(opts.to_h)
484
+ else
485
+ SAFE_STATE_PROTOTYPE.dup
486
+ end
487
+ end
488
+
489
+ # Instantiates a new State object, configured by _opts_.
490
+ #
491
+ # _opts_ can have the following keys:
492
+ #
493
+ # * *indent*: a string used to indent levels (default: ''),
494
+ # * *space*: a string that is put after, a : or , delimiter (default: ''),
495
+ # * *space_before*: a string that is put before a : pair delimiter (default: ''),
496
+ # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
497
+ # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
498
+ # * *check_circular*: is deprecated now, use the :max_nesting option instead,
499
+ # * *max_nesting*: sets the maximum level of data structure nesting in
500
+ # the generated JSON, max_nesting = 0 if no maximum should be checked.
501
+ # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
502
+ # generated, otherwise an exception is thrown, if these values are
503
+ # encountered. This options defaults to false.
504
+ # * *quirks_mode*: Enables quirks_mode for parser, that is for example
505
+ # generating single JSON values instead of documents is possible.
506
+ def initialize(opts = {})
507
+ @indent = ''
508
+ @space = ''
509
+ @space_before = ''
510
+ @object_nl = ''
511
+ @array_nl = ''
512
+ @allow_nan = false
513
+ @ascii_only = false
514
+ @quirks_mode = false
515
+ @buffer_initial_length = 1024
516
+ configure opts
517
+ end
518
+
519
+ # This string is used to indent levels in the JSON text.
520
+ attr_accessor :indent
521
+
522
+ # This string is used to insert a space between the tokens in a JSON
523
+ # string.
524
+ attr_accessor :space
525
+
526
+ # This string is used to insert a space before the ':' in JSON objects.
527
+ attr_accessor :space_before
528
+
529
+ # This string is put at the end of a line that holds a JSON object (or
530
+ # Hash).
531
+ attr_accessor :object_nl
532
+
533
+ # This string is put at the end of a line that holds a JSON array.
534
+ attr_accessor :array_nl
535
+
536
+ # This integer returns the maximum level of data structure nesting in
537
+ # the generated JSON, max_nesting = 0 if no maximum is checked.
538
+ attr_accessor :max_nesting
539
+
540
+ # If this attribute is set to true, quirks mode is enabled, otherwise
541
+ # it's disabled.
542
+ attr_accessor :quirks_mode
543
+
544
+ # :stopdoc:
545
+ attr_reader :buffer_initial_length
546
+
547
+ def buffer_initial_length=(length)
548
+ if length > 0
549
+ @buffer_initial_length = length
550
+ end
551
+ end
552
+ # :startdoc:
553
+
554
+ # This integer returns the current depth data structure nesting in the
555
+ # generated JSON.
556
+ attr_accessor :depth
557
+
558
+ def check_max_nesting # :nodoc:
559
+ return if @max_nesting.zero?
560
+ current_nesting = depth + 1
561
+ current_nesting > @max_nesting and
562
+ raise NestingError, "nesting of #{current_nesting} is too deep"
563
+ end
564
+
565
+ # Returns true, if circular data structures are checked,
566
+ # otherwise returns false.
567
+ def check_circular?
568
+ !@max_nesting.zero?
569
+ end
570
+
571
+ # Returns true if NaN, Infinity, and -Infinity should be considered as
572
+ # valid JSON and output.
573
+ def allow_nan?
574
+ @allow_nan
575
+ end
576
+
577
+ # Returns true, if only ASCII characters should be generated. Otherwise
578
+ # returns false.
579
+ def ascii_only?
580
+ @ascii_only
581
+ end
582
+
583
+ # Returns true, if quirks mode is enabled. Otherwise returns false.
584
+ def quirks_mode?
585
+ @quirks_mode
586
+ end
587
+
588
+ # Configure this State instance with the Hash _opts_, and return
589
+ # itself.
590
+ def configure(opts)
591
+ @indent = opts[:indent] if opts.key?(:indent)
592
+ @space = opts[:space] if opts.key?(:space)
593
+ @space_before = opts[:space_before] if opts.key?(:space_before)
594
+ @object_nl = opts[:object_nl] if opts.key?(:object_nl)
595
+ @array_nl = opts[:array_nl] if opts.key?(:array_nl)
596
+ @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
597
+ @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
598
+ @depth = opts[:depth] || 0
599
+ @quirks_mode = opts[:quirks_mode] if opts.key?(:quirks_mode)
600
+ if !opts.key?(:max_nesting) # defaults to 19
601
+ @max_nesting = 19
602
+ elsif opts[:max_nesting]
603
+ @max_nesting = opts[:max_nesting]
604
+ else
605
+ @max_nesting = 0
606
+ end
607
+ self
608
+ end
609
+ alias merge configure
610
+
611
+ # Returns the configuration instance variables as a hash, that can be
612
+ # passed to the configure method.
613
+ def to_h
614
+ result = {}
615
+ for iv in %w[indent space space_before object_nl array_nl allow_nan max_nesting ascii_only quirks_mode buffer_initial_length depth]
616
+ result[iv.intern] = instance_variable_get("@#{iv}")
617
+ end
618
+ result
619
+ end
620
+
621
+ # Generates a valid JSON document from object +obj+ and returns the
622
+ # result. If no valid JSON document can be created this method raises a
623
+ # GeneratorError exception.
624
+ def generate(obj)
625
+ result = obj.to_json(self)
626
+ unless @quirks_mode
627
+ unless result =~ /\A\s*\[/ && result =~ /\]\s*\Z/ ||
628
+ result =~ /\A\s*\{/ && result =~ /\}\s*\Z/
629
+ then
630
+ raise GeneratorError, "only generation of JSON objects or arrays allowed"
631
+ end
632
+ end
633
+ result
634
+ end
635
+
636
+ # Return the value returned by method +name+.
637
+ def [](name)
638
+ __send__ name
639
+ end
640
+ end
641
+
642
+ module GeneratorMethods
643
+ module Object
644
+ # Converts this object to a string (calling #to_s), converts
645
+ # it to a JSON string, and returns the result. This is a fallback, if no
646
+ # special method #to_json was defined for some object.
647
+ def to_json(*) to_s.to_json end
648
+ end
649
+
650
+ module Hash
651
+ # Returns a JSON string containing a JSON object, that is unparsed from
652
+ # this Hash instance.
653
+ # _state_ is a JSON::State object, that can also be used to configure the
654
+ # produced JSON string output further.
655
+ # _depth_ is used to find out nesting depth, to indent accordingly.
656
+ def to_json(state = nil, *)
657
+ state = State.from_state(state)
658
+ state.check_max_nesting
659
+ json_transform(state)
660
+ end
661
+
662
+ private
663
+
664
+ def json_shift(state)
665
+ state.object_nl.empty? or return ''
666
+ state.indent * state.depth
667
+ end
668
+
669
+ def json_transform(state)
670
+ delim = ','
671
+ delim << state.object_nl
672
+ result = '{'
673
+ result << state.object_nl
674
+ depth = state.depth += 1
675
+ first = true
676
+ indent = !state.object_nl.empty?
677
+ each { |key,value|
678
+ result << delim unless first
679
+ result << state.indent * depth if indent
680
+ result << key.to_s.to_json(state)
681
+ result << state.space_before
682
+ result << ':'
683
+ result << state.space
684
+ result << value.to_json(state)
685
+ first = false
686
+ }
687
+ depth = state.depth -= 1
688
+ result << state.object_nl
689
+ result << state.indent * depth if indent if indent
690
+ result << '}'
691
+ result
692
+ end
693
+ end
694
+
695
+ module Array
696
+ # Returns a JSON string containing a JSON array, that is unparsed from
697
+ # this Array instance.
698
+ # _state_ is a JSON::State object, that can also be used to configure the
699
+ # produced JSON string output further.
700
+ def to_json(state = nil, *)
701
+ state = State.from_state(state)
702
+ state.check_max_nesting
703
+ json_transform(state)
704
+ end
705
+
706
+ private
707
+
708
+ def json_transform(state)
709
+ delim = ','
710
+ delim << state.array_nl
711
+ result = '['
712
+ result << state.array_nl
713
+ depth = state.depth += 1
714
+ first = true
715
+ indent = !state.array_nl.empty?
716
+ each { |value|
717
+ result << delim unless first
718
+ result << state.indent * depth if indent
719
+ result << value.to_json(state)
720
+ first = false
721
+ }
722
+ depth = state.depth -= 1
723
+ result << state.array_nl
724
+ result << state.indent * depth if indent
725
+ result << ']'
726
+ end
727
+ end
728
+
729
+ module Integer
730
+ # Returns a JSON string representation for this Integer number.
731
+ def to_json(*) to_s end
732
+ end
733
+
734
+ module Float
735
+ # Returns a JSON string representation for this Float number.
736
+ def to_json(state = nil, *)
737
+ state = State.from_state(state)
738
+ case
739
+ when infinite?
740
+ if state.allow_nan?
741
+ to_s
742
+ else
743
+ raise GeneratorError, "#{self} not allowed in JSON"
744
+ end
745
+ when nan?
746
+ if state.allow_nan?
747
+ to_s
748
+ else
749
+ raise GeneratorError, "#{self} not allowed in JSON"
750
+ end
751
+ else
752
+ to_s
753
+ end
754
+ end
755
+ end
756
+
757
+ module String
758
+ if defined?(::Encoding)
759
+ # This string should be encoded with UTF-8 A call to this method
760
+ # returns a JSON string encoded with UTF16 big endian characters as
761
+ # \u????.
762
+ def to_json(state = nil, *args)
763
+ state = State.from_state(state)
764
+ if encoding == ::Encoding::UTF_8
765
+ string = self
766
+ else
767
+ string = encode(::Encoding::UTF_8)
768
+ end
769
+ if state.ascii_only?
770
+ '"' << JSON.utf8_to_json_ascii(string) << '"'
771
+ else
772
+ '"' << JSON.utf8_to_json(string) << '"'
773
+ end
774
+ end
775
+ else
776
+ # This string should be encoded with UTF-8 A call to this method
777
+ # returns a JSON string encoded with UTF16 big endian characters as
778
+ # \u????.
779
+ def to_json(state = nil, *args)
780
+ state = State.from_state(state)
781
+ if state.ascii_only?
782
+ '"' << JSON.utf8_to_json_ascii(self) << '"'
783
+ else
784
+ '"' << JSON.utf8_to_json(self) << '"'
785
+ end
786
+ end
787
+ end
788
+
789
+ # Module that holds the extinding methods if, the String module is
790
+ # included.
791
+ module Extend
792
+ # Raw Strings are JSON Objects (the raw bytes are stored in an
793
+ # array for the key "raw"). The Ruby String can be created by this
794
+ # module method.
795
+ def json_create(o)
796
+ o['raw'].pack('C*')
797
+ end
798
+ end
799
+
800
+ # Extends _modul_ with the String::Extend module.
801
+ def self.included(modul)
802
+ modul.extend Extend
803
+ end
804
+
805
+ # This method creates a raw object hash, that can be nested into
806
+ # other data structures and will be unparsed as a raw string. This
807
+ # method should be used, if you want to convert raw strings to JSON
808
+ # instead of UTF-8 strings, e. g. binary data.
809
+ def to_json_raw_object
810
+ {
811
+ JSON.create_id => self.class.name,
812
+ 'raw' => self.unpack('C*'),
813
+ }
814
+ end
815
+
816
+ # This method creates a JSON text from the result of
817
+ # a call to to_json_raw_object of this String.
818
+ def to_json_raw(*args)
819
+ to_json_raw_object.to_json(*args)
820
+ end
821
+ end
822
+
823
+ module TrueClass
824
+ # Returns a JSON string for true: 'true'.
825
+ def to_json(*) 'true' end
826
+ end
827
+
828
+ module FalseClass
829
+ # Returns a JSON string for false: 'false'.
830
+ def to_json(*) 'false' end
831
+ end
832
+
833
+ module NilClass
834
+ # Returns a JSON string for nil: 'null'.
835
+ def to_json(*) 'null' end
836
+ end
837
+ end
838
+ end
839
+ end
840
+ end
841
+
842
+ module JSON
843
+ class << self
844
+ # If _object_ is string-like, parse the string and return the parsed result
845
+ # as a Ruby data structure. Otherwise generate a JSON text from the Ruby
846
+ # data structure object and return it.
847
+ #
848
+ # The _opts_ argument is passed through to generate/parse respectively. See
849
+ # generate and parse for their documentation.
850
+ def [](object, opts = {})
851
+ if object.respond_to? :to_str
852
+ JSON.parse(object.to_str, opts)
853
+ else
854
+ JSON.generate(object, opts)
855
+ end
856
+ end
857
+
858
+ # Returns the JSON parser class that is used by JSON. This is either
859
+ # JSON::Ext::Parser or JSON::Pure::Parser.
860
+ attr_reader :parser
861
+
862
+ # Set the JSON parser class _parser_ to be used by JSON.
863
+ def parser=(parser) # :nodoc:
864
+ @parser = parser
865
+ remove_const :Parser if JSON.const_defined_in?(self, :Parser)
866
+ const_set :Parser, parser
867
+ end
868
+
869
+ # Return the constant located at _path_. The format of _path_ has to be
870
+ # either ::A::B::C or A::B::C. In any case, A has to be located at the top
871
+ # level (absolute namespace path?). If there doesn't exist a constant at
872
+ # the given path, an ArgumentError is raised.
873
+ def deep_const_get(path) # :nodoc:
874
+ path.to_s.split(/::/).inject(Object) do |p, c|
875
+ case
876
+ when c.empty? then p
877
+ when JSON.const_defined_in?(p, c) then p.const_get(c)
878
+ else
879
+ begin
880
+ p.const_missing(c)
881
+ rescue NameError => e
882
+ raise ArgumentError, "can't get const #{path}: #{e}"
883
+ end
884
+ end
885
+ end
886
+ end
887
+
888
+ # Set the module _generator_ to be used by JSON.
889
+ def generator=(generator) # :nodoc:
890
+ old, $VERBOSE = $VERBOSE, nil
891
+ @generator = generator
892
+ generator_methods = generator::GeneratorMethods
893
+ for const in generator_methods.constants
894
+ klass = deep_const_get(const)
895
+ modul = generator_methods.const_get(const)
896
+ klass.class_eval do
897
+ instance_methods(false).each do |m|
898
+ m.to_s == 'to_json' and remove_method m
899
+ end
900
+ include modul
901
+ end
902
+ end
903
+ self.state = generator::State
904
+ const_set :State, self.state
905
+ const_set :SAFE_STATE_PROTOTYPE, State.new
906
+ const_set :FAST_STATE_PROTOTYPE, State.new(
907
+ :indent => '',
908
+ :space => '',
909
+ :object_nl => "",
910
+ :array_nl => "",
911
+ :max_nesting => false
912
+ )
913
+ const_set :PRETTY_STATE_PROTOTYPE, State.new(
914
+ :indent => ' ',
915
+ :space => ' ',
916
+ :object_nl => "\n",
917
+ :array_nl => "\n"
918
+ )
919
+ ensure
920
+ $VERBOSE = old
921
+ end
922
+
923
+ # Returns the JSON generator module that is used by JSON. This is
924
+ # either JSON::Ext::Generator or JSON::Pure::Generator.
925
+ attr_reader :generator
926
+
927
+ # Returns the JSON generator state class that is used by JSON. This is
928
+ # either JSON::Ext::Generator::State or JSON::Pure::Generator::State.
929
+ attr_accessor :state
930
+
931
+ # This is create identifier, which is used to decide if the _json_create_
932
+ # hook of a class should be called. It defaults to 'json_class'.
933
+ attr_accessor :create_id
934
+ end
935
+ self.create_id = 'json_class'
936
+
937
+ NaN = 0.0/0
938
+
939
+ Infinity = 1.0/0
940
+
941
+ MinusInfinity = -Infinity
942
+
943
+ # The base exception for JSON errors.
944
+ class JSONError < StandardError; end
945
+
946
+ # This exception is raised if a parser error occurs.
947
+ class ParserError < JSONError; end
948
+
949
+ # This exception is raised if the nesting of parsed data structures is too
950
+ # deep.
951
+ class NestingError < ParserError; end
952
+
953
+ # :stopdoc:
954
+ class CircularDatastructure < NestingError; end
955
+ # :startdoc:
956
+
957
+ # This exception is raised if a generator or unparser error occurs.
958
+ class GeneratorError < JSONError; end
959
+ # For backwards compatibility
960
+ UnparserError = GeneratorError
961
+
962
+ # This exception is raised if the required unicode support is missing on the
963
+ # system. Usually this means that the iconv library is not installed.
964
+ class MissingUnicodeSupport < JSONError; end
965
+
966
+ module_function
967
+
968
+ # Parse the JSON document _source_ into a Ruby data structure and return it.
969
+ #
970
+ # _opts_ can have the following
971
+ # keys:
972
+ # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
973
+ # structures. Disable depth checking with :max_nesting => false. It defaults
974
+ # to 19.
975
+ # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
976
+ # defiance of RFC 4627 to be parsed by the Parser. This option defaults
977
+ # to false.
978
+ # * *symbolize_names*: If set to true, returns symbols for the names
979
+ # (keys) in a JSON object. Otherwise strings are returned. Strings are
980
+ # the default.
981
+ # * *create_additions*: If set to false, the Parser doesn't create
982
+ # additions even if a matching class and create_id was found. This option
983
+ # defaults to true.
984
+ # * *object_class*: Defaults to Hash
985
+ # * *array_class*: Defaults to Array
986
+ def parse(source, opts = {})
987
+ Parser.new(source, opts).parse
988
+ end
989
+
990
+ # Parse the JSON document _source_ into a Ruby data structure and return it.
991
+ # The bang version of the parse method defaults to the more dangerous values
992
+ # for the _opts_ hash, so be sure only to parse trusted _source_ documents.
993
+ #
994
+ # _opts_ can have the following keys:
995
+ # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
996
+ # structures. Enable depth checking with :max_nesting => anInteger. The parse!
997
+ # methods defaults to not doing max depth checking: This can be dangerous
998
+ # if someone wants to fill up your stack.
999
+ # * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in
1000
+ # defiance of RFC 4627 to be parsed by the Parser. This option defaults
1001
+ # to true.
1002
+ # * *create_additions*: If set to false, the Parser doesn't create
1003
+ # additions even if a matching class and create_id was found. This option
1004
+ # defaults to true.
1005
+ def parse!(source, opts = {})
1006
+ opts = {
1007
+ :max_nesting => false,
1008
+ :allow_nan => true
1009
+ }.update(opts)
1010
+ Parser.new(source, opts).parse
1011
+ end
1012
+
1013
+ # Generate a JSON document from the Ruby data structure _obj_ and return
1014
+ # it. _state_ is * a JSON::State object,
1015
+ # * or a Hash like object (responding to to_hash),
1016
+ # * an object convertible into a hash by a to_h method,
1017
+ # that is used as or to configure a State object.
1018
+ #
1019
+ # It defaults to a state object, that creates the shortest possible JSON text
1020
+ # in one line, checks for circular data structures and doesn't allow NaN,
1021
+ # Infinity, and -Infinity.
1022
+ #
1023
+ # A _state_ hash can have the following keys:
1024
+ # * *indent*: a string used to indent levels (default: ''),
1025
+ # * *space*: a string that is put after, a : or , delimiter (default: ''),
1026
+ # * *space_before*: a string that is put before a : pair delimiter (default: ''),
1027
+ # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
1028
+ # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
1029
+ # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
1030
+ # generated, otherwise an exception is thrown if these values are
1031
+ # encountered. This options defaults to false.
1032
+ # * *max_nesting*: The maximum depth of nesting allowed in the data
1033
+ # structures from which JSON is to be generated. Disable depth checking
1034
+ # with :max_nesting => false, it defaults to 19.
1035
+ #
1036
+ # See also the fast_generate for the fastest creation method with the least
1037
+ # amount of sanity checks, and the pretty_generate method for some
1038
+ # defaults for pretty output.
1039
+ def generate(obj, opts = nil)
1040
+ if State === opts
1041
+ state, opts = opts, nil
1042
+ else
1043
+ state = SAFE_STATE_PROTOTYPE.dup
1044
+ end
1045
+ if opts
1046
+ if opts.respond_to? :to_hash
1047
+ opts = opts.to_hash
1048
+ elsif opts.respond_to? :to_h
1049
+ opts = opts.to_h
1050
+ else
1051
+ raise TypeError, "can't convert #{opts.class} into Hash"
1052
+ end
1053
+ state = state.configure(opts)
1054
+ end
1055
+ state.generate(obj)
1056
+ end
1057
+
1058
+ # :stopdoc:
1059
+ # I want to deprecate these later, so I'll first be silent about them, and
1060
+ # later delete them.
1061
+ alias unparse generate
1062
+ module_function :unparse
1063
+ # :startdoc:
1064
+
1065
+ # Generate a JSON document from the Ruby data structure _obj_ and return it.
1066
+ # This method disables the checks for circles in Ruby objects.
1067
+ #
1068
+ # *WARNING*: Be careful not to pass any Ruby data structures with circles as
1069
+ # _obj_ argument because this will cause JSON to go into an infinite loop.
1070
+ def fast_generate(obj, opts = nil)
1071
+ if State === opts
1072
+ state, opts = opts, nil
1073
+ else
1074
+ state = FAST_STATE_PROTOTYPE.dup
1075
+ end
1076
+ if opts
1077
+ if opts.respond_to? :to_hash
1078
+ opts = opts.to_hash
1079
+ elsif opts.respond_to? :to_h
1080
+ opts = opts.to_h
1081
+ else
1082
+ raise TypeError, "can't convert #{opts.class} into Hash"
1083
+ end
1084
+ state.configure(opts)
1085
+ end
1086
+ state.generate(obj)
1087
+ end
1088
+
1089
+ # :stopdoc:
1090
+ # I want to deprecate these later, so I'll first be silent about them, and later delete them.
1091
+ alias fast_unparse fast_generate
1092
+ module_function :fast_unparse
1093
+ # :startdoc:
1094
+
1095
+ # Generate a JSON document from the Ruby data structure _obj_ and return it.
1096
+ # The returned document is a prettier form of the document returned by
1097
+ # #unparse.
1098
+ #
1099
+ # The _opts_ argument can be used to configure the generator. See the
1100
+ # generate method for a more detailed explanation.
1101
+ def pretty_generate(obj, opts = nil)
1102
+ if State === opts
1103
+ state, opts = opts, nil
1104
+ else
1105
+ state = PRETTY_STATE_PROTOTYPE.dup
1106
+ end
1107
+ if opts
1108
+ if opts.respond_to? :to_hash
1109
+ opts = opts.to_hash
1110
+ elsif opts.respond_to? :to_h
1111
+ opts = opts.to_h
1112
+ else
1113
+ raise TypeError, "can't convert #{opts.class} into Hash"
1114
+ end
1115
+ state.configure(opts)
1116
+ end
1117
+ state.generate(obj)
1118
+ end
1119
+
1120
+ # :stopdoc:
1121
+ # I want to deprecate these later, so I'll first be silent about them, and later delete them.
1122
+ alias pretty_unparse pretty_generate
1123
+ module_function :pretty_unparse
1124
+ # :startdoc:
1125
+
1126
+ class << self
1127
+ # The global default options for the JSON.load method:
1128
+ # :max_nesting: false
1129
+ # :allow_nan: true
1130
+ # :quirks_mode: true
1131
+ attr_accessor :load_default_options
1132
+ end
1133
+ self.load_default_options = {
1134
+ :max_nesting => false,
1135
+ :allow_nan => true,
1136
+ :quirks_mode => true,
1137
+ }
1138
+
1139
+ # Load a ruby data structure from a JSON _source_ and return it. A source can
1140
+ # either be a string-like object, an IO-like object, or an object responding
1141
+ # to the read method. If _proc_ was given, it will be called with any nested
1142
+ # Ruby object as an argument recursively in depth first order. The default
1143
+ # options for the parser can be changed via the load_default_options method.
1144
+ #
1145
+ # This method is part of the implementation of the load/dump interface of
1146
+ # Marshal and YAML.
1147
+ def load(source, proc = nil)
1148
+ opts = load_default_options
1149
+ if source.respond_to? :to_str
1150
+ source = source.to_str
1151
+ elsif source.respond_to? :to_io
1152
+ source = source.to_io.read
1153
+ elsif source.respond_to?(:read)
1154
+ source = source.read
1155
+ end
1156
+ if opts[:quirks_mode] && (source.nil? || source.empty?)
1157
+ source = 'null'
1158
+ end
1159
+ result = parse(source, opts)
1160
+ recurse_proc(result, &proc) if proc
1161
+ result
1162
+ end
1163
+
1164
+ # Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_
1165
+ def recurse_proc(result, &proc)
1166
+ case result
1167
+ when Array
1168
+ result.each { |x| recurse_proc x, &proc }
1169
+ proc.call result
1170
+ when Hash
1171
+ result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
1172
+ proc.call result
1173
+ else
1174
+ proc.call result
1175
+ end
1176
+ end
1177
+
1178
+ alias restore load
1179
+ module_function :restore
1180
+
1181
+ class << self
1182
+ # The global default options for the JSON.dump method:
1183
+ # :max_nesting: false
1184
+ # :allow_nan: true
1185
+ # :quirks_mode: true
1186
+ attr_accessor :dump_default_options
1187
+ end
1188
+ self.dump_default_options = {
1189
+ :max_nesting => false,
1190
+ :allow_nan => true,
1191
+ :quirks_mode => true,
1192
+ }
1193
+
1194
+ # Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns
1195
+ # the result.
1196
+ #
1197
+ # If anIO (an IO-like object or an object that responds to the write method)
1198
+ # was given, the resulting JSON is written to it.
1199
+ #
1200
+ # If the number of nested arrays or objects exceeds _limit_, an ArgumentError
1201
+ # exception is raised. This argument is similar (but not exactly the
1202
+ # same!) to the _limit_ argument in Marshal.dump.
1203
+ #
1204
+ # The default options for the generator can be changed via the
1205
+ # dump_default_options method.
1206
+ #
1207
+ # This method is part of the implementation of the load/dump interface of
1208
+ # Marshal and YAML.
1209
+ def dump(obj, anIO = nil, limit = nil)
1210
+ if anIO and limit.nil?
1211
+ anIO = anIO.to_io if anIO.respond_to?(:to_io)
1212
+ unless anIO.respond_to?(:write)
1213
+ limit = anIO
1214
+ anIO = nil
1215
+ end
1216
+ end
1217
+ opts = JSON.dump_default_options
1218
+ limit and opts.update(:max_nesting => limit)
1219
+ result = generate(obj, opts)
1220
+ if anIO
1221
+ anIO.write result
1222
+ anIO
1223
+ else
1224
+ result
1225
+ end
1226
+ rescue JSON::NestingError
1227
+ raise ArgumentError, "exceed depth limit"
1228
+ end
1229
+
1230
+ # Swap consecutive bytes of _string_ in place.
1231
+ def self.swap!(string) # :nodoc:
1232
+ 0.upto(string.size / 2) do |i|
1233
+ break unless string[2 * i + 1]
1234
+ string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i]
1235
+ end
1236
+ string
1237
+ end
1238
+
1239
+ # Shortuct for iconv.
1240
+ if ::String.method_defined?(:encode)
1241
+ # Encodes string using Ruby's _String.encode_
1242
+ def self.iconv(to, from, string)
1243
+ string.encode(to, from)
1244
+ end
1245
+ else
1246
+ require 'iconv'
1247
+ # Encodes string using _iconv_ library
1248
+ def self.iconv(to, from, string)
1249
+ Iconv.conv(to, from, string)
1250
+ end
1251
+ end
1252
+
1253
+ if ::Object.method(:const_defined?).arity == 1
1254
+ def self.const_defined_in?(modul, constant)
1255
+ modul.const_defined?(constant)
1256
+ end
1257
+ else
1258
+ def self.const_defined_in?(modul, constant)
1259
+ modul.const_defined?(constant, false)
1260
+ end
1261
+ end
1262
+ end
1263
+
1264
+ module ::Kernel
1265
+ private
1266
+
1267
+ # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
1268
+ # one line.
1269
+ def j(*objs)
1270
+ objs.each do |obj|
1271
+ puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
1272
+ end
1273
+ nil
1274
+ end
1275
+
1276
+ # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with
1277
+ # indentation and over many lines.
1278
+ def jj(*objs)
1279
+ objs.each do |obj|
1280
+ puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
1281
+ end
1282
+ nil
1283
+ end
1284
+
1285
+ # If _object_ is string-like, parse the string and return the parsed result as
1286
+ # a Ruby data structure. Otherwise, generate a JSON text from the Ruby data
1287
+ # structure object and return it.
1288
+ #
1289
+ # The _opts_ argument is passed through to generate/parse respectively. See
1290
+ # generate and parse for their documentation.
1291
+ def JSON(object, *args)
1292
+ if object.respond_to? :to_str
1293
+ JSON.parse(object.to_str, args.first)
1294
+ else
1295
+ JSON.generate(object, args.first)
1296
+ end
1297
+ end
1298
+ end
1299
+
1300
+ # Extends any Class to include _json_creatable?_ method.
1301
+ class ::Class
1302
+ # Returns true if this class can be used to create an instance
1303
+ # from a serialised JSON string. The class has to implement a class
1304
+ # method _json_create_ that expects a hash as first parameter. The hash
1305
+ # should include the required data.
1306
+ def json_creatable?
1307
+ respond_to?(:json_create)
1308
+ end
1309
+ end
1310
+
1311
+ JSON.generator = JSON::Pure::Generator
1312
+ JSON.parser = JSON::Pure::Parser
1313
+ rescue LoadError
1314
+ require File.join File.dirname(File.dirname(__FILE__)), 'vendor', 'json.rb'
1315
+ end
1316
+
1317
+ # It just gists.
1318
+ module Gist
1319
+ extend self
1320
+
1321
+ VERSION = '5.1.0'
1322
+
1323
+ # A list of clipboard commands with copy and paste support.
1324
+ CLIPBOARD_COMMANDS = {
1325
+ 'pbcopy' => 'pbpaste',
1326
+ 'xclip' => 'xclip -o',
1327
+ 'xsel -i' => 'xsel -o',
1328
+ 'putclip' => 'getclip',
1329
+ }
1330
+
1331
+ GITHUB_API_URL = URI("https://api.github.com/")
1332
+ GIT_IO_URL = URI("https://git.io")
1333
+
1334
+ GITHUB_BASE_PATH = ""
1335
+ GHE_BASE_PATH = "/api/v3"
1336
+
1337
+ URL_ENV_NAME = "GITHUB_URL"
1338
+
1339
+ USER_AGENT = "gist/#{VERSION} (Net::HTTP, #{RUBY_DESCRIPTION})"
1340
+
1341
+ # Exception tag for errors raised while gisting.
1342
+ module Error;
1343
+ def self.exception(*args)
1344
+ RuntimeError.new(*args).extend(self)
1345
+ end
1346
+ end
1347
+ class ClipboardError < RuntimeError; include Error end
1348
+
1349
+ # helper module for authentication token actions
1350
+ module AuthTokenFile
1351
+ def self.filename
1352
+ if ENV.key?(URL_ENV_NAME)
1353
+ File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/:/, '.').gsub(/[^a-z0-9.]/, '')}"
1354
+ else
1355
+ File.expand_path "~/.gist"
1356
+ end
1357
+ end
1358
+
1359
+ def self.read
1360
+ File.read(filename).chomp
1361
+ end
1362
+
1363
+ def self.write(token)
1364
+ File.open(filename, 'w', 0600) do |f|
1365
+ f.write token
1366
+ end
1367
+ end
1368
+ end
1369
+
1370
+ # auth token for authentication
1371
+ #
1372
+ # @return [String] string value of access token or `nil`, if not found
1373
+ def auth_token
1374
+ @token ||= AuthTokenFile.read rescue nil
1375
+ end
1376
+
1377
+ # Upload a gist to https://gist.github.com
1378
+ #
1379
+ # @param [String] content the code you'd like to gist
1380
+ # @param [Hash] options more detailed options, see
1381
+ # the documentation for {multi_gist}
1382
+ #
1383
+ # @see http://developer.github.com/v3/gists/
1384
+ def gist(content, options = {})
1385
+ filename = options[:filename] || default_filename
1386
+ multi_gist({filename => content}, options)
1387
+ end
1388
+
1389
+ def default_filename
1390
+ "gistfile1.txt"
1391
+ end
1392
+
1393
+ # Upload a gist to https://gist.github.com
1394
+ #
1395
+ # @param [Hash] files the code you'd like to gist: filename => content
1396
+ # @param [Hash] options more detailed options
1397
+ #
1398
+ # @option options [String] :description the description
1399
+ # @option options [Boolean] :public (false) is this gist public
1400
+ # @option options [Boolean] :anonymous (false) is this gist anonymous
1401
+ # @option options [String] :access_token (`File.read("~/.gist")`) The OAuth2 access token.
1402
+ # @option options [String] :update the URL or id of a gist to update
1403
+ # @option options [Boolean] :copy (false) Copy resulting URL to clipboard, if successful.
1404
+ # @option options [Boolean] :open (false) Open the resulting URL in a browser.
1405
+ # @option options [Boolean] :skip_empty (false) Skip gisting empty files.
1406
+ # @option options [Symbol] :output (:all) The type of return value you'd like:
1407
+ # :html_url gives a String containing the url to the gist in a browser
1408
+ # :short_url gives a String contianing a git.io url that redirects to html_url
1409
+ # :javascript gives a String containing a script tag suitable for embedding the gist
1410
+ # :all gives a Hash containing the parsed json response from the server
1411
+ #
1412
+ # @return [String, Hash] the return value as configured by options[:output]
1413
+ # @raise [Gist::Error] if something went wrong
1414
+ #
1415
+ # @see http://developer.github.com/v3/gists/
1416
+ def multi_gist(files, options={})
1417
+ if options[:anonymous]
1418
+ raise 'Anonymous gists are no longer supported. Please log in with `gist --login`. ' \
1419
+ '(GitHub now requires credentials to gist https://bit.ly/2GBBxKw)'
1420
+ else
1421
+ access_token = (options[:access_token] || auth_token())
1422
+ end
1423
+
1424
+ json = {}
1425
+
1426
+ json[:description] = options[:description] if options[:description]
1427
+ json[:public] = !!options[:public]
1428
+ json[:files] = {}
1429
+
1430
+ files.each_pair do |(name, content)|
1431
+ if content.to_s.strip == ""
1432
+ raise "Cannot gist empty files" unless options[:skip_empty]
1433
+ else
1434
+ name = name == "-" ? default_filename : File.basename(name)
1435
+ json[:files][name] = {:content => content}
1436
+ end
1437
+ end
1438
+
1439
+ return if json[:files].empty? && options[:skip_empty]
1440
+
1441
+ existing_gist = options[:update].to_s.split("/").last
1442
+
1443
+ url = "#{base_path}/gists"
1444
+ url << "/" << CGI.escape(existing_gist) if existing_gist.to_s != ''
1445
+
1446
+ request = Net::HTTP::Post.new(url)
1447
+ request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
1448
+ request.body = JSON.dump(json)
1449
+ request.content_type = 'application/json'
1450
+
1451
+ retried = false
1452
+
1453
+ begin
1454
+ response = http(api_url, request)
1455
+ if Net::HTTPSuccess === response
1456
+ on_success(response.body, options)
1457
+ else
1458
+ raise "Got #{response.class} from gist: #{response.body}"
1459
+ end
1460
+ rescue => e
1461
+ raise if retried
1462
+ retried = true
1463
+ retry
1464
+ end
1465
+
1466
+ rescue => e
1467
+ raise e.extend Error
1468
+ end
1469
+
1470
+ # List all gists(private also) for authenticated user
1471
+ # otherwise list public gists for given username (optional argument)
1472
+ #
1473
+ # @param [String] user
1474
+ # @deprecated
1475
+ #
1476
+ # see https://developer.github.com/v3/gists/#list-gists
1477
+ def list_gists(user = "")
1478
+ url = "#{base_path}"
1479
+
1480
+ if user == ""
1481
+ access_token = auth_token()
1482
+ if access_token.to_s != ''
1483
+ url << "/gists"
1484
+
1485
+ request = Net::HTTP::Get.new(url)
1486
+ request['Authorization'] = "token #{access_token}"
1487
+ response = http(api_url, request)
1488
+
1489
+ pretty_gist(response)
1490
+
1491
+ else
1492
+ raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists."
1493
+ end
1494
+
1495
+ else
1496
+ url << "/users/#{user}/gists"
1497
+
1498
+ request = Net::HTTP::Get.new(url)
1499
+ response = http(api_url, request)
1500
+
1501
+ pretty_gist(response)
1502
+ end
1503
+ end
1504
+
1505
+ def list_all_gists(user = "")
1506
+ url = "#{base_path}"
1507
+
1508
+ if user == ""
1509
+ access_token = auth_token()
1510
+ if access_token.to_s != ''
1511
+ url << "/gists?per_page=100"
1512
+ get_gist_pages(url, access_token)
1513
+ else
1514
+ raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists."
1515
+ end
1516
+
1517
+ else
1518
+ url << "/users/#{user}/gists?per_page=100"
1519
+ get_gist_pages(url)
1520
+ end
1521
+
1522
+ end
1523
+
1524
+ def read_gist(id, file_name=nil)
1525
+ url = "#{base_path}/gists/#{id}"
1526
+
1527
+ access_token = auth_token()
1528
+
1529
+ request = Net::HTTP::Get.new(url)
1530
+ request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
1531
+ response = http(api_url, request)
1532
+
1533
+ if response.code == '200'
1534
+ body = JSON.parse(response.body)
1535
+ files = body["files"]
1536
+
1537
+ if file_name
1538
+ file = files[file_name]
1539
+ raise Error, "Gist with id of #{id} and file #{file_name} does not exist." unless file
1540
+ else
1541
+ file = files.values.first
1542
+ end
1543
+
1544
+ puts file["content"]
1545
+ else
1546
+ raise Error, "Gist with id of #{id} does not exist."
1547
+ end
1548
+ end
1549
+
1550
+ def delete_gist(id)
1551
+ id = id.split("/").last
1552
+ url = "#{base_path}/gists/#{id}"
1553
+
1554
+ access_token = auth_token()
1555
+ if access_token.to_s != ''
1556
+ request = Net::HTTP::Delete.new(url)
1557
+ request["Authorization"] = "token #{access_token}"
1558
+ response = http(api_url, request)
1559
+ else
1560
+ raise Error, "Not authenticated. Use 'gist --login' to login."
1561
+ end
1562
+
1563
+ if response.code == '204'
1564
+ puts "Deleted!"
1565
+ else
1566
+ raise Error, "Gist with id of #{id} does not exist."
1567
+ end
1568
+ end
1569
+
1570
+ def get_gist_pages(url, access_token = "")
1571
+
1572
+ request = Net::HTTP::Get.new(url)
1573
+ request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
1574
+ response = http(api_url, request)
1575
+ pretty_gist(response)
1576
+
1577
+ link_header = response.header['link']
1578
+
1579
+ if link_header
1580
+ links = Hash[ link_header.gsub(/(<|>|")/, "").split(',').map { |link| link.split('; rel=') } ].invert
1581
+ get_gist_pages(links['next'], access_token) if links['next']
1582
+ end
1583
+
1584
+ end
1585
+
1586
+ # return prettified string result of response body for all gists
1587
+ #
1588
+ # @params [Net::HTTPResponse] response
1589
+ # @return [String] prettified result of listing all gists
1590
+ #
1591
+ # see https://developer.github.com/v3/gists/#response
1592
+ def pretty_gist(response)
1593
+ body = JSON.parse(response.body)
1594
+ if response.code == '200'
1595
+ body.each do |gist|
1596
+ description = "#{gist['description'] || gist['files'].keys.join(" ")} #{gist['public'] ? '' : '(secret)'}"
1597
+ puts "#{gist['html_url']} #{description.tr("\n", " ")}\n"
1598
+ $stdout.flush
1599
+ end
1600
+
1601
+ else
1602
+ raise Error, body['message']
1603
+ end
1604
+ end
1605
+
1606
+ # Convert long github urls into short git.io ones
1607
+ #
1608
+ # @param [String] url
1609
+ # @return [String] shortened url, or long url if shortening fails
1610
+ def shorten(url)
1611
+ request = Net::HTTP::Post.new("/create")
1612
+ request.set_form_data(:url => url)
1613
+ response = http(GIT_IO_URL, request)
1614
+ case response.code
1615
+ when "200"
1616
+ URI.join(GIT_IO_URL, response.body).to_s
1617
+ when "201"
1618
+ response['Location']
1619
+ else
1620
+ url
1621
+ end
1622
+ end
1623
+
1624
+ # Convert github url into raw file url
1625
+ #
1626
+ # Unfortunately the url returns from github's api is legacy,
1627
+ # we have to taking a HTTPRedirection before appending it
1628
+ # with '/raw'. Let's looking forward for github's api fix :)
1629
+ #
1630
+ # @param [String] url
1631
+ # @return [String] the raw file url
1632
+ def rawify(url)
1633
+ uri = URI(url)
1634
+ request = Net::HTTP::Get.new(uri.path)
1635
+ response = http(uri, request)
1636
+ if Net::HTTPSuccess === response
1637
+ url + '/raw'
1638
+ elsif Net::HTTPRedirection === response
1639
+ rawify(response.header['location'])
1640
+ end
1641
+ end
1642
+
1643
+ # Log the user into gist.
1644
+ #
1645
+ # This method asks the user for a username and password, and tries to obtain
1646
+ # and OAuth2 access token, which is then stored in ~/.gist
1647
+ #
1648
+ # @raise [Gist::Error] if something went wrong
1649
+ # @param [Hash] credentials login details
1650
+ # @option credentials [String] :username
1651
+ # @option credentials [String] :password
1652
+ # @see http://developer.github.com/v3/oauth/
1653
+ def login!(credentials={})
1654
+ puts "Obtaining OAuth2 access_token from GitHub."
1655
+ loop do
1656
+ print "GitHub username: "
1657
+ username = credentials[:username] || $stdin.gets.strip
1658
+ print "GitHub password: "
1659
+ password = credentials[:password] || begin
1660
+ `stty -echo` rescue nil
1661
+ $stdin.gets.strip
1662
+ ensure
1663
+ `stty echo` rescue nil
1664
+ end
1665
+ puts ""
1666
+
1667
+ request = Net::HTTP::Post.new("#{base_path}/authorizations")
1668
+ request.body = JSON.dump({
1669
+ :scopes => [:gist],
1670
+ :note => "The gist gem (#{Time.now})",
1671
+ :note_url => "https://github.com/ConradIrwin/gist"
1672
+ })
1673
+ request.content_type = 'application/json'
1674
+ request.basic_auth(username, password)
1675
+
1676
+ response = http(api_url, request)
1677
+
1678
+ if Net::HTTPUnauthorized === response && response['X-GitHub-OTP']
1679
+ print "2-factor auth code: "
1680
+ twofa_code = $stdin.gets.strip
1681
+ puts ""
1682
+
1683
+ request['X-GitHub-OTP'] = twofa_code
1684
+ response = http(api_url, request)
1685
+ end
1686
+
1687
+ if Net::HTTPCreated === response
1688
+ AuthTokenFile.write JSON.parse(response.body)['token']
1689
+ puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/tokens"
1690
+ return
1691
+ elsif Net::HTTPUnauthorized === response
1692
+ puts "Error: #{JSON.parse(response.body)['message']}"
1693
+ next
1694
+ else
1695
+ raise "Got #{response.class} from gist: #{response.body}"
1696
+ end
1697
+ end
1698
+ rescue => e
1699
+ raise e.extend Error
1700
+ end
1701
+
1702
+ # Return HTTP connection
1703
+ #
1704
+ # @param [URI::HTTP] The URI to which to connect
1705
+ # @return [Net::HTTP]
1706
+ def http_connection(uri)
1707
+ env = ENV['http_proxy'] || ENV['HTTP_PROXY']
1708
+ connection = if env
1709
+ proxy = URI(env)
1710
+ Net::HTTP::Proxy(proxy.host, proxy.port).new(uri.host, uri.port)
1711
+ else
1712
+ Net::HTTP.new(uri.host, uri.port)
1713
+ end
1714
+ if uri.scheme == "https"
1715
+ connection.use_ssl = true
1716
+ connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
1717
+ end
1718
+ connection.open_timeout = 10
1719
+ connection.read_timeout = 10
1720
+ connection
1721
+ end
1722
+
1723
+ # Run an HTTP operation
1724
+ #
1725
+ # @param [URI::HTTP] The URI to which to connect
1726
+ # @param [Net::HTTPRequest] The request to make
1727
+ # @return [Net::HTTPResponse]
1728
+ def http(url, request)
1729
+ request['User-Agent'] = USER_AGENT
1730
+
1731
+ http_connection(url).start do |http|
1732
+ http.request request
1733
+ end
1734
+ rescue Timeout::Error
1735
+ raise "Could not connect to #{api_url}"
1736
+ end
1737
+
1738
+ # Called after an HTTP response to gist to perform post-processing.
1739
+ #
1740
+ # @param [String] body the text body from the github api
1741
+ # @param [Hash] options more detailed options, see
1742
+ # the documentation for {multi_gist}
1743
+ def on_success(body, options={})
1744
+ json = JSON.parse(body)
1745
+
1746
+ output = case options[:output]
1747
+ when :javascript
1748
+ %Q{<script src="#{json['html_url']}.js"></script>}
1749
+ when :html_url
1750
+ json['html_url']
1751
+ when :raw_url
1752
+ rawify(json['html_url'])
1753
+ when :short_url
1754
+ shorten(json['html_url'])
1755
+ when :short_raw_url
1756
+ shorten(rawify(json['html_url']))
1757
+ else
1758
+ json
1759
+ end
1760
+
1761
+ Gist.copy(output.to_s) if options[:copy]
1762
+ Gist.open(json['html_url']) if options[:open]
1763
+
1764
+ output
1765
+ end
1766
+
1767
+ # Copy a string to the clipboard.
1768
+ #
1769
+ # @param [String] content
1770
+ # @raise [Gist::Error] if no clipboard integration could be found
1771
+ #
1772
+ def copy(content)
1773
+ IO.popen(clipboard_command(:copy), 'r+') { |clip| clip.print content }
1774
+
1775
+ unless paste == content
1776
+ message = 'Copying to clipboard failed.'
1777
+
1778
+ if ENV["TMUX"] && clipboard_command(:copy) == 'pbcopy'
1779
+ 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"
1780
+ end
1781
+
1782
+ raise Error, message
1783
+ end
1784
+ rescue Error => e
1785
+ raise ClipboardError, e.message + "\nAttempted to copy: #{content}"
1786
+ end
1787
+
1788
+ # Get a string from the clipboard.
1789
+ #
1790
+ # @param [String] content
1791
+ # @raise [Gist::Error] if no clipboard integration could be found
1792
+ def paste
1793
+ `#{clipboard_command(:paste)}`
1794
+ end
1795
+
1796
+ # Find command from PATH environment.
1797
+ #
1798
+ # @param [String] cmd command name to find
1799
+ # @param [String] options PATH environment variable
1800
+ # @return [String] the command found
1801
+ def which(cmd, path=ENV['PATH'])
1802
+ if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin|cygwin/
1803
+ path.split(File::PATH_SEPARATOR).each {|dir|
1804
+ f = File.join(dir, cmd+".exe")
1805
+ return f if File.executable?(f) && !File.directory?(f)
1806
+ }
1807
+ nil
1808
+ else
1809
+ return system("which #{cmd} > /dev/null 2>&1")
1810
+ end
1811
+ end
1812
+
1813
+ # Get the command to use for the clipboard action.
1814
+ #
1815
+ # @param [Symbol] action either :copy or :paste
1816
+ # @return [String] the command to run
1817
+ # @raise [Gist::ClipboardError] if no clipboard integration could be found
1818
+ def clipboard_command(action)
1819
+ command = CLIPBOARD_COMMANDS.keys.detect do |cmd|
1820
+ which cmd
1821
+ end
1822
+ raise ClipboardError, <<-EOT unless command
1823
+ Could not find copy command, tried:
1824
+ #{CLIPBOARD_COMMANDS.values.join(' || ')}
1825
+ EOT
1826
+ action == :copy ? command : CLIPBOARD_COMMANDS[command]
1827
+ end
1828
+
1829
+ # Open a URL in a browser.
1830
+ #
1831
+ # @param [String] url
1832
+ # @raise [RuntimeError] if no browser integration could be found
1833
+ #
1834
+ # This method was heavily inspired by defunkt's Gist#open,
1835
+ # @see https://github.com/defunkt/gist/blob/bca9b29/lib/gist.rb#L157
1836
+ def open(url)
1837
+ command = if ENV['BROWSER']
1838
+ ENV['BROWSER']
1839
+ elsif RUBY_PLATFORM =~ /darwin/
1840
+ 'open'
1841
+ elsif RUBY_PLATFORM =~ /linux/
1842
+ %w(
1843
+ sensible-browser
1844
+ xdg-open
1845
+ firefox
1846
+ firefox-bin
1847
+ ).detect do |cmd|
1848
+ which cmd
1849
+ end
1850
+ elsif ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw|wince/i
1851
+ 'start ""'
1852
+ else
1853
+ raise "Could not work out how to use a browser."
1854
+ end
1855
+
1856
+ `#{command} #{url}`
1857
+ end
1858
+
1859
+ # Get the API base path
1860
+ def base_path
1861
+ ENV.key?(URL_ENV_NAME) ? GHE_BASE_PATH : GITHUB_BASE_PATH
1862
+ end
1863
+
1864
+ # Get the API URL
1865
+ def api_url
1866
+ ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_API_URL
1867
+ end
1868
+
1869
+ def legacy_private_gister?
1870
+ return unless which('git')
1871
+ `git config --global gist.private` =~ /\Ayes|1|true|on\z/i
1872
+ end
1873
+
1874
+ def should_be_public?(options={})
1875
+ if options.key? :private
1876
+ !options[:private]
1877
+ else
1878
+ !Gist.legacy_private_gister?
1879
+ end
1880
+ end
1881
+ end
1882
+ #!/usr/bin/env ruby
1883
+
1884
+ # Silence Ctrl-C's
1885
+ trap('INT'){ exit 1 }
1886
+
1887
+ if Signal.list.include? 'PIPE'
1888
+ trap('PIPE', 'EXIT')
1889
+ end
1890
+
1891
+ require 'optparse'
1892
+
1893
+ # For the holdings of options.
1894
+ options = {}
1895
+ filenames = []
1896
+
1897
+ OptionParser.new do |opts|
1898
+ executable_name = File.split($0)[1]
1899
+ opts.banner = <<-EOS
1900
+ Gist (v#{Gist::VERSION}) lets you upload to https://gist.github.com/
1901
+
1902
+ The content to be uploaded can be passed as a list of files, if none are
1903
+ specified STDIN will be read. The default filename for STDIN is "a.rb", and all
1904
+ filenames can be overridden by repeating the "-f" flag. The most useful reason
1905
+ to do this is to change the syntax highlighting.
1906
+
1907
+ All gists must to be associated with a GitHub account, so you will need to login with
1908
+ `gist --login` to obtain an OAuth2 access token. This is stored and used by gist in the future.
1909
+
1910
+ Private gists do not have guessable URLs and can be created with "-p", you can
1911
+ also set the description at the top of the gist by passing "-d".
1912
+
1913
+ If you would like to shorten the resulting gist URL, use the -s flag. This will
1914
+ use GitHub's URL shortener, git.io. You can also use -R to get the link to the
1915
+ raw gist.
1916
+
1917
+ To copy the resulting URL to your clipboard you can use the -c option, or to
1918
+ just open it directly in your browser, use -o. Using the -e option will copy the
1919
+ embeddable URL to the clipboard. You can add `alias gist='gist -c'` to your
1920
+ shell's rc file to configure this behaviour by default.
1921
+
1922
+ Instead of creating a new gist, you can update an existing one by passing its ID
1923
+ or URL with "-u". For this to work, you must be logged in, and have created the
1924
+ original gist with the same GitHub account.
1925
+
1926
+ If you want to skip empty files, use the --skip-empty flag. If all files are
1927
+ empty no gist will be created.
1928
+
1929
+ Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-R] [-d DESC] [-u URL]
1930
+ [--skip-empty] [-P] [-f NAME|-t EXT]* FILE*
1931
+ #{executable_name} --login
1932
+ #{executable_name} [-l|-r]
1933
+
1934
+ EOS
1935
+
1936
+ opts.on("--login", "Authenticate gist on this computer.") do
1937
+ Gist.login!
1938
+ exit
1939
+ end
1940
+
1941
+ opts.on("-f", "--filename [NAME.EXTENSION]", "Sets the filename and syntax type.") do |filename|
1942
+ filenames << filename
1943
+ options[:filename] = filename
1944
+ end
1945
+
1946
+ opts.on("-t", "--type [EXTENSION]", "Sets the file extension and syntax type.") do |extension|
1947
+ filenames << "foo.#{extension}"
1948
+ options[:filename] = "foo.#{extension}"
1949
+ end
1950
+
1951
+ opts.on("-p", "--private", "Makes your gist private.") do
1952
+ options[:private] = true
1953
+ end
1954
+
1955
+ opts.on("--no-private") do
1956
+ options[:private] = false
1957
+ end
1958
+
1959
+ opts.on("-d", "--description DESCRIPTION", "Adds a description to your gist.") do |description|
1960
+ options[:description] = description
1961
+ end
1962
+
1963
+ opts.on("-s", "--shorten", "Shorten the gist URL using git.io.") do |shorten|
1964
+ options[:shorten] = shorten
1965
+ end
1966
+
1967
+ opts.on("-u", "--update [ URL | ID ]", "Update an existing gist.") do |update|
1968
+ options[:update] = update
1969
+ end
1970
+
1971
+ opts.on("-c", "--copy", "Copy the resulting URL to the clipboard") do
1972
+ options[:copy] = true
1973
+ end
1974
+
1975
+ opts.on("-e", "--embed", "Copy the embed code for the gist to the clipboard") do
1976
+ options[:embed] = true
1977
+ options[:copy] = true
1978
+ end
1979
+
1980
+ opts.on("-o", "--open", "Open the resulting URL in a browser") do
1981
+ options[:open] = true
1982
+ end
1983
+
1984
+ opts.on("--no-open")
1985
+
1986
+ opts.on("--skip-empty", "Skip gisting empty files") do
1987
+ options[:skip_empty] = true
1988
+ end
1989
+
1990
+ opts.on("-P", "--paste", "Paste from the clipboard to gist") do
1991
+ options[:paste] = true
1992
+ end
1993
+
1994
+ opts.on("-R", "--raw", "Display raw URL of the new gist") do
1995
+ options[:raw] = true
1996
+ end
1997
+
1998
+ opts.on("-l", "--list [USER]", "List all gists for user") do |user|
1999
+ options[:list] = user
2000
+ end
2001
+
2002
+ opts.on("-r", "--read ID [FILENAME]", "Read a gist and print out the contents") do |id|
2003
+ options[:read] = id
2004
+ end
2005
+
2006
+ opts.on("--delete [ URL | ID ]", "Delete a gist") do |id|
2007
+ options[:delete] = id
2008
+ end
2009
+
2010
+ opts.on_tail("-h","--help", "Show this message.") do
2011
+ puts opts
2012
+ exit
2013
+ end
2014
+
2015
+ opts.on_tail("-v", "--version", "Print the version.") do
2016
+ puts "gist v#{Gist::VERSION}"
2017
+ exit
2018
+ end
2019
+
2020
+ end.parse!
2021
+
2022
+ begin
2023
+ if Gist.auth_token.nil?
2024
+ puts 'Please log in with `gist --login`. ' \
2025
+ '(GitHub now requires credentials to gist https://bit.ly/2GBBxKw)'
2026
+ exit(1)
2027
+ end
2028
+
2029
+ options[:output] = if options[:embed] && options[:shorten]
2030
+ raise Gist::Error, "--embed does not make sense with --shorten"
2031
+ elsif options[:embed]
2032
+ :javascript
2033
+ elsif options[:shorten] and options[:raw]
2034
+ :short_raw_url
2035
+ elsif options[:shorten]
2036
+ :short_url
2037
+ elsif options[:raw]
2038
+ :raw_url
2039
+ else
2040
+ :html_url
2041
+ end
2042
+
2043
+ options[:public] = Gist.should_be_public?(options)
2044
+
2045
+ if options.key? :list
2046
+ if options[:list]
2047
+ Gist.list_all_gists(options[:list])
2048
+ else
2049
+ Gist.list_all_gists
2050
+ end
2051
+ exit
2052
+ end
2053
+
2054
+ if options.key? :read
2055
+ file_name = ARGV.first
2056
+ Gist.read_gist(options[:read], file_name)
2057
+ exit
2058
+ end
2059
+
2060
+ if options.key? :delete
2061
+ Gist.delete_gist(options[:delete])
2062
+ exit
2063
+ end
2064
+
2065
+ if options[:paste]
2066
+ puts Gist.gist(Gist.paste, options)
2067
+ else
2068
+ to_read = ARGV.empty? ? ['-'] : ARGV
2069
+ files = {}
2070
+ to_read.zip(filenames).each do |(file, name)|
2071
+ files[name || file] =
2072
+ begin
2073
+ if file == '-'
2074
+ $stderr.puts "(type a gist. <ctrl-c> to cancel, <ctrl-d> when done)" if $stdin.tty?
2075
+ STDIN.read
2076
+ else
2077
+ File.read(File.expand_path(file))
2078
+ end
2079
+ rescue => e
2080
+ raise e.extend(Gist::Error)
2081
+ end
2082
+ end
2083
+
2084
+ output = Gist.multi_gist(files, options)
2085
+ puts output if output
2086
+ end
2087
+
2088
+ rescue Gist::Error => e
2089
+ puts "Error: #{e.message}"
2090
+ exit 1
2091
+ end