gist 4.6.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,10 +5,10 @@ describe '...' do
5
5
  MOCK_USER = 'foo'
6
6
  MOCK_PASSWORD = 'bar'
7
7
 
8
- MOCK_AUTHZ_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_USER}:#{MOCK_PASSWORD}@#{MOCK_GHE_HOST}/api/v3/"
8
+ MOCK_AUTHZ_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_GHE_HOST}/api/v3/"
9
9
  MOCK_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_GHE_HOST}/api/v3/"
10
- MOCK_AUTHZ_GITHUB_URL = "https://#{MOCK_USER}:#{MOCK_PASSWORD}@api.github.com/"
11
10
  MOCK_GITHUB_URL = "https://api.github.com/"
11
+ MOCK_LOGIN_URL = "https://github.com/"
12
12
 
13
13
  before do
14
14
  @saved_env = ENV[Gist::URL_ENV_NAME]
@@ -20,8 +20,15 @@ describe '...' do
20
20
  # stub requests for /authorizations
21
21
  stub_request(:post, /#{MOCK_AUTHZ_GHE_URL}authorizations/).
22
22
  to_return(:status => 201, :body => '{"token": "asdf"}')
23
- stub_request(:post, /#{MOCK_AUTHZ_GITHUB_URL}authorizations/).
23
+ stub_request(:post, /#{MOCK_GITHUB_URL}authorizations/).
24
+ with(headers: {'Authorization'=>'Basic Zm9vOmJhcg=='}).
24
25
  to_return(:status => 201, :body => '{"token": "asdf"}')
26
+
27
+ stub_request(:post, /#{MOCK_LOGIN_URL}login\/device\/code/).
28
+ to_return(:status => 200, :body => '{"interval": "0.1", "user_code":"XXXX-XXXX", "device_code": "xxxx", "verification_uri": "https://github.com/login/device"}')
29
+
30
+ stub_request(:post, /#{MOCK_LOGIN_URL}login\/oauth\/access_token/).
31
+ to_return(:status => 200, :body => '{"access_token":"zzzz"}')
25
32
  end
26
33
 
27
34
  after do
@@ -48,7 +55,7 @@ describe '...' do
48
55
 
49
56
  Gist.login!
50
57
 
51
- assert_requested(:post, /#{MOCK_AUTHZ_GITHUB_URL}authorizations/)
58
+ assert_requested(:post, /#{MOCK_LOGIN_URL}login\/oauth\/access_token/)
52
59
  end
53
60
 
54
61
  it "should access to #{MOCK_GHE_HOST} when $#{Gist::URL_ENV_NAME} was set" do
@@ -65,7 +72,7 @@ describe '...' do
65
72
  $stdin = StringIO.new "#{MOCK_USER}_wrong\n#{MOCK_PASSWORD}_wrong\n"
66
73
  Gist.login! :username => MOCK_USER, :password => MOCK_PASSWORD
67
74
 
68
- assert_requested(:post, /#{MOCK_AUTHZ_GITHUB_URL}authorizations/)
75
+ assert_requested(:post, /#{MOCK_GITHUB_URL}authorizations/)
69
76
  end
70
77
 
71
78
  end
@@ -6,7 +6,16 @@ describe '...' do
6
6
  end
7
7
 
8
8
  it "should return the raw file url" do
9
- Gist.gist("Test gist", :output => :raw_url, :anonymous => true).should == "https://gist.github.com/anonymous/XXXXXX/raw"
9
+ Gist.gist("Test gist", :output => :raw_url, :anonymous => false).should == "https://gist.github.com/anonymous/XXXXXX/raw"
10
+ end
11
+
12
+ it 'should raise an error when trying to do operations without being logged in' do
13
+ error_msg = 'Anonymous gists are no longer supported. Please log in with `gist --login`. ' \
14
+ '(GitHub now requires credentials to gist https://bit.ly/2GBBxKw)'
15
+
16
+ expect do
17
+ Gist.gist("Test gist", output: :raw_url, anonymous: true)
18
+ end.to raise_error(StandardError, error_msg)
10
19
  end
11
20
  end
12
21
 
@@ -5,11 +5,20 @@ describe '...' do
5
5
 
6
6
  it "should return a shortened version of the URL when response is 200" do
7
7
  stub_request(:post, "https://git.io/create").to_return(:status => 200, :body => 'XXXXXX')
8
- Gist.gist("Test gist", :output => :short_url, :anonymous => true).should == "https://git.io/XXXXXX"
8
+ Gist.gist("Test gist", :output => :short_url, anonymous: false).should == "https://git.io/XXXXXX"
9
9
  end
10
10
 
11
11
  it "should return a shortened version of the URL when response is 201" do
12
12
  stub_request(:post, "https://git.io/create").to_return(:status => 201, :headers => { 'Location' => 'https://git.io/XXXXXX' })
13
- Gist.gist("Test gist", :output => :short_url, :anonymous => true).should == "https://git.io/XXXXXX"
13
+ Gist.gist("Test gist", :output => :short_url, anonymous: false).should == "https://git.io/XXXXXX"
14
+ end
15
+
16
+ it 'should raise an error when trying to get short urls without being logged in' do
17
+ error_msg = 'Anonymous gists are no longer supported. Please log in with `gist --login`. ' \
18
+ '(GitHub now requires credentials to gist https://bit.ly/2GBBxKw)'
19
+
20
+ expect do
21
+ Gist.gist("Test gist", output: :short_url, anonymous: true)
22
+ end.to raise_error(StandardError, error_msg)
14
23
  end
15
24
  end
@@ -11,7 +11,7 @@ RSpec.configure do |config|
11
11
  mocks.syntax = :should
12
12
  end
13
13
  config.expect_with :rspec do |expectations|
14
- expectations.syntax = :should
14
+ expectations.syntax = [:should, :expect]
15
15
  end
16
16
  end
17
17
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gist
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.0
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Conrad Irwin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-05-16 00:00:00.000000000 Z
12
+ date: 2020-08-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -76,15 +76,12 @@ executables:
76
76
  extensions: []
77
77
  extra_rdoc_files: []
78
78
  files:
79
- - ".gitignore"
80
79
  - ".rspec"
81
80
  - Gemfile
82
81
  - LICENSE.MIT
83
82
  - README.md
84
83
  - Rakefile
85
84
  - bin/gist
86
- - build/gist
87
- - build/gist.1
88
85
  - gist.gemspec
89
86
  - lib/gist.rb
90
87
  - spec/auth_token_file_spec.rb
@@ -96,7 +93,6 @@ files:
96
93
  - spec/shorten_spec.rb
97
94
  - spec/spec_helper.rb
98
95
  - vendor/json.rb
99
- - vendor/netrc.rb
100
96
  homepage: https://github.com/defunkt/gist
101
97
  licenses:
102
98
  - MIT
@@ -116,8 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
112
  - !ruby/object:Gem::Version
117
113
  version: '0'
118
114
  requirements: []
119
- rubyforge_project:
120
- rubygems_version: 2.6.11
115
+ rubygems_version: 3.0.3
121
116
  signing_key:
122
117
  specification_version: 4
123
118
  summary: Just allows you to upload gists
data/.gitignore DELETED
@@ -1,8 +0,0 @@
1
- .rvmrc
2
- Gemfile.lock
3
-
4
- # OS X
5
- .DS_Store
6
- .yardoc
7
- doc
8
- *.gem
data/build/gist DELETED
@@ -1,2357 +0,0 @@
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
- begin
1318
-
1319
- # The netrc library (https://github.com/heroku/netrc) is reproduced below, along
1320
- # with its copyright and license:
1321
- # - https://raw.githubusercontent.com/heroku/netrc/262ef111/LICENSE.md
1322
- # - https://raw.githubusercontent.com/heroku/netrc/262ef111/lib/netrc.rb
1323
-
1324
- # The MIT License (MIT)
1325
- #
1326
- # Copyright (c) 2011-2014 [CONTRIBUTORS.md](https://github.com/geemus/netrc/blob/master/CONTRIBUTORS.md)
1327
- #
1328
- # Permission is hereby granted, free of charge, to any person obtaining a copy of
1329
- # this software and associated documentation files (the "Software"), to deal in
1330
- # the Software without restriction, including without limitation the rights to
1331
- # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
1332
- # the Software, and to permit persons to whom the Software is furnished to do so,
1333
- # subject to the following conditions:
1334
- #
1335
- # The above copyright notice and this permission notice shall be included in all
1336
- # copies or substantial portions of the Software.
1337
- #
1338
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1339
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
1340
- # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
1341
- # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
1342
- # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1343
- # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1344
-
1345
- require 'rbconfig'
1346
- require 'io/console'
1347
-
1348
- class Netrc
1349
- VERSION = "0.11.0"
1350
-
1351
- # see http://stackoverflow.com/questions/4871309/what-is-the-correct-way-to-detect-if-ruby-is-running-on-windows
1352
- WINDOWS = RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
1353
- CYGWIN = RbConfig::CONFIG["host_os"] =~ /cygwin/
1354
-
1355
- def self.default_path
1356
- File.join(ENV['NETRC'] || home_path, netrc_filename)
1357
- end
1358
-
1359
- def self.home_path
1360
- home = Dir.respond_to?(:home) ? Dir.home : ENV['HOME']
1361
-
1362
- if WINDOWS && !CYGWIN
1363
- home ||= File.join(ENV['HOMEDRIVE'], ENV['HOMEPATH']) if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
1364
- home ||= ENV['USERPROFILE']
1365
- # XXX: old stuff; most likely unnecessary
1366
- home = home.tr("\\", "/") unless home.nil?
1367
- end
1368
-
1369
- (home && File.readable?(home)) ? home : Dir.pwd
1370
- rescue ArgumentError
1371
- return Dir.pwd
1372
- end
1373
-
1374
- def self.netrc_filename
1375
- WINDOWS && !CYGWIN ? "_netrc" : ".netrc"
1376
- end
1377
-
1378
- def self.config
1379
- @config ||= {}
1380
- end
1381
-
1382
- def self.configure
1383
- yield(self.config) if block_given?
1384
- self.config
1385
- end
1386
-
1387
- def self.check_permissions(path)
1388
- perm = File.stat(path).mode & 0777
1389
- if perm != 0600 && !(WINDOWS) && !(Netrc.config[:allow_permissive_netrc_file])
1390
- raise Error, "Permission bits for '#{path}' should be 0600, but are "+perm.to_s(8)
1391
- end
1392
- end
1393
-
1394
- # Reads path and parses it as a .netrc file. If path doesn't
1395
- # exist, returns an empty object. Decrypt paths ending in .gpg.
1396
- def self.read(path=default_path)
1397
- check_permissions(path)
1398
- data = if path =~ /\.gpg$/
1399
- decrypted = if ENV['GPG_AGENT_INFO']
1400
- `gpg --batch --quiet --decrypt #{path}`
1401
- else
1402
- print "Enter passphrase for #{path}: "
1403
- STDIN.noecho do
1404
- `gpg --batch --passphrase-fd 0 --quiet --decrypt #{path}`
1405
- end
1406
- end
1407
- if $?.success?
1408
- decrypted
1409
- else
1410
- raise Error.new("Decrypting #{path} failed.") unless $?.success?
1411
- end
1412
- else
1413
- File.read(path)
1414
- end
1415
- new(path, parse(lex(data.lines.to_a)))
1416
- rescue Errno::ENOENT
1417
- new(path, parse(lex([])))
1418
- end
1419
-
1420
- class TokenArray < Array
1421
- def take
1422
- if length < 1
1423
- raise Error, "unexpected EOF"
1424
- end
1425
- shift
1426
- end
1427
-
1428
- def readto
1429
- l = []
1430
- while length > 0 && ! yield(self[0])
1431
- l << shift
1432
- end
1433
- return l.join
1434
- end
1435
- end
1436
-
1437
- def self.lex(lines)
1438
- tokens = TokenArray.new
1439
- for line in lines
1440
- content, comment = line.split(/(\s*#.*)/m)
1441
- content.each_char do |char|
1442
- case char
1443
- when /\s/
1444
- if tokens.last && tokens.last[-1..-1] =~ /\s/
1445
- tokens.last << char
1446
- else
1447
- tokens << char
1448
- end
1449
- else
1450
- if tokens.last && tokens.last[-1..-1] =~ /\S/
1451
- tokens.last << char
1452
- else
1453
- tokens << char
1454
- end
1455
- end
1456
- end
1457
- if comment
1458
- tokens << comment
1459
- end
1460
- end
1461
- tokens
1462
- end
1463
-
1464
- def self.skip?(s)
1465
- s =~ /^\s/
1466
- end
1467
-
1468
-
1469
-
1470
- # Returns two values, a header and a list of items.
1471
- # Each item is a tuple, containing some or all of:
1472
- # - machine keyword (including trailing whitespace+comments)
1473
- # - machine name
1474
- # - login keyword (including surrounding whitespace+comments)
1475
- # - login
1476
- # - password keyword (including surrounding whitespace+comments)
1477
- # - password
1478
- # - trailing chars
1479
- # This lets us change individual fields, then write out the file
1480
- # with all its original formatting.
1481
- def self.parse(ts)
1482
- cur, item = [], []
1483
-
1484
- unless ts.is_a?(TokenArray)
1485
- ts = TokenArray.new(ts)
1486
- end
1487
-
1488
- pre = ts.readto{|t| t == "machine" || t == "default"}
1489
-
1490
- while ts.length > 0
1491
- if ts[0] == 'default'
1492
- cur << ts.take.to_sym
1493
- cur << ''
1494
- else
1495
- cur << ts.take + ts.readto{|t| ! skip?(t)}
1496
- cur << ts.take
1497
- end
1498
-
1499
- if ts.include?('login')
1500
- cur << ts.readto{|t| t == "login"} + ts.take + ts.readto{|t| ! skip?(t)}
1501
- cur << ts.take
1502
- end
1503
-
1504
- if ts.include?('password')
1505
- cur << ts.readto{|t| t == "password"} + ts.take + ts.readto{|t| ! skip?(t)}
1506
- cur << ts.take
1507
- end
1508
-
1509
- cur << ts.readto{|t| t == "machine" || t == "default"}
1510
-
1511
- item << cur
1512
- cur = []
1513
- end
1514
-
1515
- [pre, item]
1516
- end
1517
-
1518
- def initialize(path, data)
1519
- @new_item_prefix = ''
1520
- @path = path
1521
- @pre, @data = data
1522
-
1523
- if @data && @data.last && :default == @data.last[0]
1524
- @default = @data.pop
1525
- else
1526
- @default = nil
1527
- end
1528
- end
1529
-
1530
- attr_accessor :new_item_prefix
1531
-
1532
- def [](k)
1533
- if item = @data.detect {|datum| datum[1] == k}
1534
- Entry.new(item[3], item[5])
1535
- elsif @default
1536
- Entry.new(@default[3], @default[5])
1537
- end
1538
- end
1539
-
1540
- def []=(k, info)
1541
- if item = @data.detect {|datum| datum[1] == k}
1542
- item[3], item[5] = info
1543
- else
1544
- @data << new_item(k, info[0], info[1])
1545
- end
1546
- end
1547
-
1548
- def length
1549
- @data.length
1550
- end
1551
-
1552
- def delete(key)
1553
- datum = nil
1554
- for value in @data
1555
- if value[1] == key
1556
- datum = value
1557
- break
1558
- end
1559
- end
1560
- @data.delete(datum)
1561
- end
1562
-
1563
- def each(&block)
1564
- @data.each(&block)
1565
- end
1566
-
1567
- def new_item(m, l, p)
1568
- [new_item_prefix+"machine ", m, "\n login ", l, "\n password ", p, "\n"]
1569
- end
1570
-
1571
- def save
1572
- if @path =~ /\.gpg$/
1573
- e = IO.popen("gpg -a --batch --default-recipient-self -e", "r+") do |gpg|
1574
- gpg.puts(unparse)
1575
- gpg.close_write
1576
- gpg.read
1577
- end
1578
- raise Error.new("Encrypting #{@path} failed.") unless $?.success?
1579
- File.open(@path, 'w', 0600) {|file| file.print(e)}
1580
- else
1581
- File.open(@path, 'w', 0600) {|file| file.print(unparse)}
1582
- end
1583
- end
1584
-
1585
- def unparse
1586
- @pre + @data.map do |datum|
1587
- datum = datum.join
1588
- unless datum[-1..-1] == "\n"
1589
- datum << "\n"
1590
- else
1591
- datum
1592
- end
1593
- end.join
1594
- end
1595
-
1596
- Entry = Struct.new(:login, :password) do
1597
- alias to_ary to_a
1598
- end
1599
-
1600
- end
1601
-
1602
- class Netrc::Error < ::StandardError
1603
- end
1604
- rescue LoadError
1605
- require File.join File.dirname(File.dirname(__FILE__)), 'vendor', 'netrc.rb'
1606
- end
1607
-
1608
- # It just gists.
1609
- module Gist
1610
- extend self
1611
-
1612
- VERSION = '4.6.0'
1613
-
1614
- # A list of clipboard commands with copy and paste support.
1615
- CLIPBOARD_COMMANDS = {
1616
- 'pbcopy' => 'pbpaste',
1617
- 'xclip' => 'xclip -o',
1618
- 'xsel -i' => 'xsel -o',
1619
- 'putclip' => 'getclip',
1620
- }
1621
-
1622
- GITHUB_API_URL = URI("https://api.github.com/")
1623
- GIT_IO_URL = URI("https://git.io")
1624
-
1625
- GITHUB_BASE_PATH = ""
1626
- GHE_BASE_PATH = "/api/v3"
1627
-
1628
- URL_ENV_NAME = "GITHUB_URL"
1629
-
1630
- USER_AGENT = "gist/#{VERSION} (Net::HTTP, #{RUBY_DESCRIPTION})"
1631
-
1632
- # Exception tag for errors raised while gisting.
1633
- module Error;
1634
- def self.exception(*args)
1635
- RuntimeError.new(*args).extend(self)
1636
- end
1637
- end
1638
- class ClipboardError < RuntimeError; include Error end
1639
-
1640
- # helper module for authentication token actions
1641
- module AuthTokenFile
1642
- def self.filename
1643
- if ENV.key?(URL_ENV_NAME)
1644
- File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/:/, '.').gsub(/[^a-z0-9.]/, '')}"
1645
- else
1646
- File.expand_path "~/.gist"
1647
- end
1648
- end
1649
-
1650
- def self.read
1651
- File.read(filename).chomp
1652
- end
1653
-
1654
- def self.write(token)
1655
- File.open(filename, 'w', 0600) do |f|
1656
- f.write token
1657
- end
1658
- end
1659
- end
1660
-
1661
- # auth token for authentication
1662
- #
1663
- # @return [String] string value of access token or `nil`, if not found
1664
- def auth_token
1665
- @token ||= AuthTokenFile.read rescue nil || Netrc.read[self.api_url.host][1]
1666
- end
1667
-
1668
- # Upload a gist to https://gist.github.com
1669
- #
1670
- # @param [String] content the code you'd like to gist
1671
- # @param [Hash] options more detailed options, see
1672
- # the documentation for {multi_gist}
1673
- #
1674
- # @see http://developer.github.com/v3/gists/
1675
- def gist(content, options = {})
1676
- filename = options[:filename] || "a.rb"
1677
- multi_gist({filename => content}, options)
1678
- end
1679
-
1680
- # Upload a gist to https://gist.github.com
1681
- #
1682
- # @param [Hash] files the code you'd like to gist: filename => content
1683
- # @param [Hash] options more detailed options
1684
- #
1685
- # @option options [String] :description the description
1686
- # @option options [Boolean] :public (false) is this gist public
1687
- # @option options [Boolean] :anonymous (false) is this gist anonymous
1688
- # @option options [String] :access_token (`File.read("~/.gist")`) The OAuth2 access token.
1689
- # @option options [String] :update the URL or id of a gist to update
1690
- # @option options [Boolean] :copy (false) Copy resulting URL to clipboard, if successful.
1691
- # @option options [Boolean] :open (false) Open the resulting URL in a browser.
1692
- # @option options [Symbol] :output (:all) The type of return value you'd like:
1693
- # :html_url gives a String containing the url to the gist in a browser
1694
- # :short_url gives a String contianing a git.io url that redirects to html_url
1695
- # :javascript gives a String containing a script tag suitable for embedding the gist
1696
- # :all gives a Hash containing the parsed json response from the server
1697
- #
1698
- # @return [String, Hash] the return value as configured by options[:output]
1699
- # @raise [Gist::Error] if something went wrong
1700
- #
1701
- # @see http://developer.github.com/v3/gists/
1702
- def multi_gist(files, options={})
1703
- json = {}
1704
-
1705
- json[:description] = options[:description] if options[:description]
1706
- json[:public] = !!options[:public]
1707
- json[:files] = {}
1708
-
1709
- files.each_pair do |(name, content)|
1710
- raise "Cannot gist empty files" if content.to_s.strip == ""
1711
- json[:files][File.basename(name)] = {:content => content}
1712
- end
1713
-
1714
- existing_gist = options[:update].to_s.split("/").last
1715
- if options[:anonymous]
1716
- access_token = nil
1717
- else
1718
- access_token = (options[:access_token] || auth_token())
1719
- end
1720
-
1721
- url = "#{base_path}/gists"
1722
- url << "/" << CGI.escape(existing_gist) if existing_gist.to_s != ''
1723
- url << "?access_token=" << CGI.escape(access_token) if access_token.to_s != ''
1724
-
1725
- request = Net::HTTP::Post.new(url)
1726
- request.body = JSON.dump(json)
1727
- request.content_type = 'application/json'
1728
-
1729
- retried = false
1730
-
1731
- begin
1732
- response = http(api_url, request)
1733
- if Net::HTTPSuccess === response
1734
- on_success(response.body, options)
1735
- else
1736
- raise "Got #{response.class} from gist: #{response.body}"
1737
- end
1738
- rescue => e
1739
- raise if retried
1740
- retried = true
1741
- retry
1742
- end
1743
-
1744
- rescue => e
1745
- raise e.extend Error
1746
- end
1747
-
1748
- # List all gists(private also) for authenticated user
1749
- # otherwise list public gists for given username (optional argument)
1750
- #
1751
- # @param [String] user
1752
- # @deprecated
1753
- #
1754
- # see https://developer.github.com/v3/gists/#list-gists
1755
- def list_gists(user = "")
1756
- url = "#{base_path}"
1757
-
1758
- if user == ""
1759
- access_token = auth_token()
1760
- if access_token.to_s != ''
1761
- url << "/gists?access_token=" << CGI.escape(access_token)
1762
-
1763
- request = Net::HTTP::Get.new(url)
1764
- response = http(api_url, request)
1765
-
1766
- pretty_gist(response)
1767
-
1768
- else
1769
- raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists."
1770
- end
1771
-
1772
- else
1773
- url << "/users/#{user}/gists"
1774
-
1775
- request = Net::HTTP::Get.new(url)
1776
- response = http(api_url, request)
1777
-
1778
- pretty_gist(response)
1779
- end
1780
- end
1781
-
1782
- def list_all_gists(user = "")
1783
- url = "#{base_path}"
1784
-
1785
- if user == ""
1786
- access_token = auth_token()
1787
- if access_token.to_s != ''
1788
- url << "/gists?per_page=100&access_token=" << CGI.escape(access_token)
1789
- get_gist_pages(url)
1790
- else
1791
- raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists."
1792
- end
1793
-
1794
- else
1795
- url << "/users/#{user}/gists?per_page=100"
1796
- get_gist_pages(url)
1797
- end
1798
-
1799
- end
1800
-
1801
- def read_gist(id, file_name=nil)
1802
- url = "#{base_path}/gists/#{id}"
1803
- request = Net::HTTP::Get.new(url)
1804
- response = http(api_url, request)
1805
-
1806
- if response.code == '200'
1807
- body = JSON.parse(response.body)
1808
- files = body["files"]
1809
-
1810
- if file_name
1811
- file = files[file_name]
1812
- raise Error, "Gist with id of #{id} and file #{file_name} does not exist." unless file
1813
- else
1814
- file = files.values.first
1815
- end
1816
-
1817
- puts file["content"]
1818
- else
1819
- raise Error, "Gist with id of #{id} does not exist."
1820
- end
1821
- end
1822
-
1823
- def delete_gist(id)
1824
- id = id.split("/").last
1825
- url = "#{base_path}/gists/#{id}"
1826
-
1827
- access_token = auth_token()
1828
- if access_token.to_s != ''
1829
- url << "?access_token=" << CGI.escape(access_token)
1830
-
1831
- request = Net::HTTP::Delete.new(url)
1832
- response = http(api_url, request)
1833
- else
1834
- raise Error, "Not authenticated. Use 'gist --login' to login."
1835
- end
1836
-
1837
- if response.code == '204'
1838
- puts "Deleted!"
1839
- else
1840
- raise Error, "Gist with id of #{id} does not exist."
1841
- end
1842
- end
1843
-
1844
- def get_gist_pages(url)
1845
-
1846
- request = Net::HTTP::Get.new(url)
1847
- response = http(api_url, request)
1848
- pretty_gist(response)
1849
-
1850
- link_header = response.header['link']
1851
-
1852
- if link_header
1853
- links = Hash[ link_header.gsub(/(<|>|")/, "").split(',').map { |link| link.split('; rel=') } ].invert
1854
- get_gist_pages(links['next']) if links['next']
1855
- end
1856
-
1857
- end
1858
-
1859
- # return prettified string result of response body for all gists
1860
- #
1861
- # @params [Net::HTTPResponse] response
1862
- # @return [String] prettified result of listing all gists
1863
- #
1864
- # see https://developer.github.com/v3/gists/#response
1865
- def pretty_gist(response)
1866
- body = JSON.parse(response.body)
1867
- if response.code == '200'
1868
- body.each do |gist|
1869
- description = "#{gist['description'] || gist['files'].keys.join(" ")} #{gist['public'] ? '' : '(secret)'}"
1870
- puts "#{gist['html_url']} #{description.tr("\n", " ")}\n"
1871
- $stdout.flush
1872
- end
1873
-
1874
- else
1875
- raise Error, body['message']
1876
- end
1877
- end
1878
-
1879
- # Convert long github urls into short git.io ones
1880
- #
1881
- # @param [String] url
1882
- # @return [String] shortened url, or long url if shortening fails
1883
- def shorten(url)
1884
- request = Net::HTTP::Post.new("/create")
1885
- request.set_form_data(:url => url)
1886
- response = http(GIT_IO_URL, request)
1887
- case response.code
1888
- when "200"
1889
- URI.join(GIT_IO_URL, response.body).to_s
1890
- when "201"
1891
- response['Location']
1892
- else
1893
- url
1894
- end
1895
- end
1896
-
1897
- # Convert github url into raw file url
1898
- #
1899
- # Unfortunately the url returns from github's api is legacy,
1900
- # we have to taking a HTTPRedirection before appending it
1901
- # with '/raw'. Let's looking forward for github's api fix :)
1902
- #
1903
- # @param [String] url
1904
- # @return [String] the raw file url
1905
- def rawify(url)
1906
- uri = URI(url)
1907
- request = Net::HTTP::Get.new(uri.path)
1908
- response = http(uri, request)
1909
- if Net::HTTPSuccess === response
1910
- url + '/raw'
1911
- elsif Net::HTTPRedirection === response
1912
- rawify(response.header['location'])
1913
- end
1914
- end
1915
-
1916
- # Log the user into gist.
1917
- #
1918
- # This method asks the user for a username and password, and tries to obtain
1919
- # and OAuth2 access token, which is then stored in ~/.gist
1920
- #
1921
- # @raise [Gist::Error] if something went wrong
1922
- # @param [Hash] credentials login details
1923
- # @option credentials [String] :username
1924
- # @option credentials [String] :password
1925
- # @see http://developer.github.com/v3/oauth/
1926
- def login!(credentials={})
1927
- puts "Obtaining OAuth2 access_token from github."
1928
- loop do
1929
- print "GitHub username: "
1930
- username = credentials[:username] || $stdin.gets.strip
1931
- print "GitHub password: "
1932
- password = credentials[:password] || begin
1933
- `stty -echo` rescue nil
1934
- $stdin.gets.strip
1935
- ensure
1936
- `stty echo` rescue nil
1937
- end
1938
- puts ""
1939
-
1940
- request = Net::HTTP::Post.new("#{base_path}/authorizations")
1941
- request.body = JSON.dump({
1942
- :scopes => [:gist],
1943
- :note => "The gist gem (#{Time.now})",
1944
- :note_url => "https://github.com/ConradIrwin/gist"
1945
- })
1946
- request.content_type = 'application/json'
1947
- request.basic_auth(username, password)
1948
-
1949
- response = http(api_url, request)
1950
-
1951
- if Net::HTTPUnauthorized === response && response['X-GitHub-OTP']
1952
- print "2-factor auth code: "
1953
- twofa_code = $stdin.gets.strip
1954
- puts ""
1955
-
1956
- request['X-GitHub-OTP'] = twofa_code
1957
- response = http(api_url, request)
1958
- end
1959
-
1960
- if Net::HTTPCreated === response
1961
- AuthTokenFile.write JSON.parse(response.body)['token']
1962
- puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/tokens"
1963
- return
1964
- elsif Net::HTTPUnauthorized === response
1965
- puts "Error: #{JSON.parse(response.body)['message']}"
1966
- next
1967
- else
1968
- raise "Got #{response.class} from gist: #{response.body}"
1969
- end
1970
- end
1971
- rescue => e
1972
- raise e.extend Error
1973
- end
1974
-
1975
- # Return HTTP connection
1976
- #
1977
- # @param [URI::HTTP] The URI to which to connect
1978
- # @return [Net::HTTP]
1979
- def http_connection(uri)
1980
- env = ENV['http_proxy'] || ENV['HTTP_PROXY']
1981
- connection = if env
1982
- proxy = URI(env)
1983
- Net::HTTP::Proxy(proxy.host, proxy.port).new(uri.host, uri.port)
1984
- else
1985
- Net::HTTP.new(uri.host, uri.port)
1986
- end
1987
- if uri.scheme == "https"
1988
- connection.use_ssl = true
1989
- connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
1990
- end
1991
- connection.open_timeout = 10
1992
- connection.read_timeout = 10
1993
- connection
1994
- end
1995
-
1996
- # Run an HTTP operation
1997
- #
1998
- # @param [URI::HTTP] The URI to which to connect
1999
- # @param [Net::HTTPRequest] The request to make
2000
- # @return [Net::HTTPResponse]
2001
- def http(url, request)
2002
- request['User-Agent'] = USER_AGENT
2003
-
2004
- http_connection(url).start do |http|
2005
- http.request request
2006
- end
2007
- rescue Timeout::Error
2008
- raise "Could not connect to #{api_url}"
2009
- end
2010
-
2011
- # Called after an HTTP response to gist to perform post-processing.
2012
- #
2013
- # @param [String] body the text body from the github api
2014
- # @param [Hash] options more detailed options, see
2015
- # the documentation for {multi_gist}
2016
- def on_success(body, options={})
2017
- json = JSON.parse(body)
2018
-
2019
- output = case options[:output]
2020
- when :javascript
2021
- %Q{<script src="#{json['html_url']}.js"></script>}
2022
- when :html_url
2023
- json['html_url']
2024
- when :raw_url
2025
- rawify(json['html_url'])
2026
- when :short_url
2027
- shorten(json['html_url'])
2028
- when :short_raw_url
2029
- shorten(rawify(json['html_url']))
2030
- else
2031
- json
2032
- end
2033
-
2034
- Gist.copy(output.to_s) if options[:copy]
2035
- Gist.open(json['html_url']) if options[:open]
2036
-
2037
- output
2038
- end
2039
-
2040
- # Copy a string to the clipboard.
2041
- #
2042
- # @param [String] content
2043
- # @raise [Gist::Error] if no clipboard integration could be found
2044
- #
2045
- def copy(content)
2046
- IO.popen(clipboard_command(:copy), 'r+') { |clip| clip.print content }
2047
-
2048
- unless paste == content
2049
- message = 'Copying to clipboard failed.'
2050
-
2051
- if ENV["TMUX"] && clipboard_command(:copy) == 'pbcopy'
2052
- 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"
2053
- end
2054
-
2055
- raise Error, message
2056
- end
2057
- rescue Error => e
2058
- raise ClipboardError, e.message + "\nAttempted to copy: #{content}"
2059
- end
2060
-
2061
- # Get a string from the clipboard.
2062
- #
2063
- # @param [String] content
2064
- # @raise [Gist::Error] if no clipboard integration could be found
2065
- def paste
2066
- `#{clipboard_command(:paste)}`
2067
- end
2068
-
2069
- # Find command from PATH environment.
2070
- #
2071
- # @param [String] cmd command name to find
2072
- # @param [String] options PATH environment variable
2073
- # @return [String] the command found
2074
- def which(cmd, path=ENV['PATH'])
2075
- if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin|cygwin/
2076
- path.split(File::PATH_SEPARATOR).each {|dir|
2077
- f = File.join(dir, cmd+".exe")
2078
- return f if File.executable?(f) && !File.directory?(f)
2079
- }
2080
- nil
2081
- else
2082
- return system("which #{cmd} > /dev/null 2>&1")
2083
- end
2084
- end
2085
-
2086
- # Get the command to use for the clipboard action.
2087
- #
2088
- # @param [Symbol] action either :copy or :paste
2089
- # @return [String] the command to run
2090
- # @raise [Gist::ClipboardError] if no clipboard integration could be found
2091
- def clipboard_command(action)
2092
- command = CLIPBOARD_COMMANDS.keys.detect do |cmd|
2093
- which cmd
2094
- end
2095
- raise ClipboardError, <<-EOT unless command
2096
- Could not find copy command, tried:
2097
- #{CLIPBOARD_COMMANDS.values.join(' || ')}
2098
- EOT
2099
- action == :copy ? command : CLIPBOARD_COMMANDS[command]
2100
- end
2101
-
2102
- # Open a URL in a browser.
2103
- #
2104
- # @param [String] url
2105
- # @raise [RuntimeError] if no browser integration could be found
2106
- #
2107
- # This method was heavily inspired by defunkt's Gist#open,
2108
- # @see https://github.com/defunkt/gist/blob/bca9b29/lib/gist.rb#L157
2109
- def open(url)
2110
- command = if ENV['BROWSER']
2111
- ENV['BROWSER']
2112
- elsif RUBY_PLATFORM =~ /darwin/
2113
- 'open'
2114
- elsif RUBY_PLATFORM =~ /linux/
2115
- %w(
2116
- sensible-browser
2117
- xdg-open
2118
- firefox
2119
- firefox-bin
2120
- ).detect do |cmd|
2121
- which cmd
2122
- end
2123
- elsif ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw|wince/i
2124
- 'start ""'
2125
- else
2126
- raise "Could not work out how to use a browser."
2127
- end
2128
-
2129
- `#{command} #{url}`
2130
- end
2131
-
2132
- # Get the API base path
2133
- def base_path
2134
- ENV.key?(URL_ENV_NAME) ? GHE_BASE_PATH : GITHUB_BASE_PATH
2135
- end
2136
-
2137
- # Get the API URL
2138
- def api_url
2139
- ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_API_URL
2140
- end
2141
-
2142
- def legacy_private_gister?
2143
- return unless which('git')
2144
- `git config --global gist.private` =~ /\Ayes|1|true|on\z/i
2145
- end
2146
-
2147
- def should_be_public?(options={})
2148
- if options.key? :private
2149
- !options[:private]
2150
- else
2151
- !Gist.legacy_private_gister?
2152
- end
2153
- end
2154
- end
2155
- #!/usr/bin/env ruby
2156
-
2157
- # Silence Ctrl-C's
2158
- trap('INT'){ exit 1 }
2159
-
2160
- if Signal.list.include? 'PIPE'
2161
- trap('PIPE', 'EXIT')
2162
- end
2163
-
2164
- require 'optparse'
2165
-
2166
- # For the holdings of options.
2167
- options = {}
2168
- filenames = []
2169
-
2170
- OptionParser.new do |opts|
2171
- executable_name = File.split($0)[1]
2172
- opts.banner = <<-EOS
2173
- Gist (v#{Gist::VERSION}) lets you upload to https://gist.github.com/
2174
-
2175
- The content to be uploaded can be passed as a list of files, if none are
2176
- specified STDIN will be read. The default filename for STDIN is "a.rb", and all
2177
- filenames can be overridden by repeating the "-f" flag. The most useful reason
2178
- to do this is to change the syntax highlighting.
2179
-
2180
- If you'd like your gists to be associated with your GitHub account, so that you
2181
- can edit them and find them in future, first use `gist --login` to obtain an
2182
- Oauth2 access token. This is stored and used by gist in the future.
2183
-
2184
- Private gists do not have guessable URLs and can be created with "-p", you can
2185
- also set the description at the top of the gist by passing "-d".
2186
-
2187
- Anonymous gists are not associated with your GitHub account, they can be created
2188
- with "-a" even after you have used "gist --login".
2189
-
2190
- If you would like to shorten the resulting gist URL, use the -s flag. This will
2191
- use GitHub's URL shortener, git.io. You can also use -R to get the link to the
2192
- raw gist.
2193
-
2194
- To copy the resulting URL to your clipboard you can use the -c option, or to
2195
- just open it directly in your browser, use -o. Using the -e option will copy the
2196
- embeddable URL to the clipboard. You can add `alias gist='gist -c'` to your
2197
- shell's rc file to configure this behaviour by default.
2198
-
2199
- Instead of creating a new gist, you can update an existing one by passing its ID
2200
- or URL with "-u". For this to work, you must be logged in, and have created the
2201
- original gist with the same GitHub account.
2202
-
2203
- Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-R] [-d DESC] [-a] [-u URL] [-P] [-f NAME|-t EXT]* FILE*
2204
- #{executable_name} --login
2205
- #{executable_name} [-l|-r]
2206
-
2207
- EOS
2208
-
2209
- opts.on("--login", "Authenticate gist on this computer.") do
2210
- Gist.login!
2211
- exit
2212
- end
2213
-
2214
- opts.on("-f", "--filename [NAME.EXTENSION]", "Sets the filename and syntax type.") do |filename|
2215
- filenames << filename
2216
- options[:filename] = filename
2217
- end
2218
-
2219
- opts.on("-t", "--type [EXTENSION]", "Sets the file extension and syntax type.") do |extension|
2220
- filenames << "foo.#{extension}"
2221
- options[:filename] = "foo.#{extension}"
2222
- end
2223
-
2224
- opts.on("-p", "--private", "Makes your gist private.") do
2225
- options[:private] = true
2226
- end
2227
-
2228
- opts.on("--no-private") do
2229
- options[:private] = false
2230
- end
2231
-
2232
- opts.on("-d", "--description DESCRIPTION", "Adds a description to your gist.") do |description|
2233
- options[:description] = description
2234
- end
2235
-
2236
- opts.on("-s", "--shorten", "Shorten the gist URL using git.io.") do |shorten|
2237
- options[:shorten] = shorten
2238
- end
2239
-
2240
- opts.on("-u", "--update [ URL | ID ]", "Update an existing gist.") do |update|
2241
- options[:update] = update
2242
- end
2243
-
2244
- opts.on("-a", "--anonymous", "Create an anonymous gist.") do
2245
- options[:anonymous] = true
2246
- end
2247
-
2248
- opts.on("-c", "--copy", "Copy the resulting URL to the clipboard") do
2249
- options[:copy] = true
2250
- end
2251
-
2252
- opts.on("-e", "--embed", "Copy the embed code for the gist to the clipboard") do
2253
- options[:embed] = true
2254
- options[:copy] = true
2255
- end
2256
-
2257
- opts.on("-o", "--open", "Open the resulting URL in a browser") do
2258
- options[:open] = true
2259
- end
2260
-
2261
- opts.on("--no-open")
2262
-
2263
- opts.on("-P", "--paste", "Paste from the clipboard to gist") do
2264
- options[:paste] = true
2265
- end
2266
-
2267
- opts.on("-R", "--raw", "Display raw URL of the new gist") do
2268
- options[:raw] = true
2269
- end
2270
-
2271
- opts.on("-l", "--list [USER]", "List all gists for user") do |user|
2272
- options[:list] = user
2273
- end
2274
-
2275
- opts.on("-r", "--read ID [FILENAME]", "Read a gist and print out the contents") do |id|
2276
- options[:read] = id
2277
- end
2278
-
2279
- opts.on("--delete [ URL | ID ]", "Delete a gist") do |id|
2280
- options[:delete] = id
2281
- end
2282
-
2283
- opts.on_tail("-h","--help", "Show this message.") do
2284
- puts opts
2285
- exit
2286
- end
2287
-
2288
- opts.on_tail("-v", "--version", "Print the version.") do
2289
- puts "gist v#{Gist::VERSION}"
2290
- exit
2291
- end
2292
-
2293
- end.parse!
2294
-
2295
- begin
2296
- options[:output] = if options[:embed] && options[:shorten]
2297
- raise Gist::Error, "--embed does not make sense with --shorten"
2298
- elsif options[:embed]
2299
- :javascript
2300
- elsif options[:shorten] and options[:raw]
2301
- :short_raw_url
2302
- elsif options[:shorten]
2303
- :short_url
2304
- elsif options[:raw]
2305
- :raw_url
2306
- else
2307
- :html_url
2308
- end
2309
-
2310
- options[:public] = Gist.should_be_public?(options)
2311
-
2312
- if options.key? :list
2313
- if options[:list]
2314
- Gist.list_all_gists(options[:list])
2315
- else
2316
- Gist.list_all_gists
2317
- end
2318
- exit
2319
- end
2320
-
2321
- if options.key? :read
2322
- file_name = ARGV.first
2323
- Gist.read_gist(options[:read], file_name)
2324
- exit
2325
- end
2326
-
2327
- if options.key? :delete
2328
- Gist.delete_gist(options[:delete])
2329
- exit
2330
- end
2331
-
2332
- if options[:paste]
2333
- puts Gist.gist(Gist.paste, options)
2334
- else
2335
- to_read = ARGV.empty? ? ['-'] : ARGV
2336
- files = {}
2337
- to_read.zip(filenames).each do |(file, name)|
2338
- files[name || file] =
2339
- begin
2340
- if file == '-'
2341
- $stderr.puts "(type a gist. <ctrl-c> to cancel, <ctrl-d> when done)" if $stdin.tty?
2342
- STDIN.read
2343
- else
2344
- File.read(File.expand_path(file))
2345
- end
2346
- rescue => e
2347
- raise e.extend(Gist::Error)
2348
- end
2349
- end
2350
-
2351
- puts Gist.multi_gist(files, options)
2352
- end
2353
-
2354
- rescue Gist::Error => e
2355
- puts "Error: #{e.message}"
2356
- exit 1
2357
- end