aapis-gist 5.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +3 -0
- data/Gemfile +3 -0
- data/LICENSE.MIT +7 -0
- data/README.md +196 -0
- data/Rakefile +44 -0
- data/bin/gist +211 -0
- data/build/gist +2091 -0
- data/build/gist.1 +277 -0
- data/gist.gemspec +21 -0
- data/lib/gist.rb +591 -0
- data/spec/auth_token_file_spec.rb +61 -0
- data/spec/clipboard_spec.rb +40 -0
- data/spec/ghe_spec.rb +91 -0
- data/spec/gist_spec.rb +23 -0
- data/spec/proxy_spec.rb +21 -0
- data/spec/rawify_spec.rb +21 -0
- data/spec/shorten_spec.rb +24 -0
- data/spec/spec_helper.rb +19 -0
- data/vendor/json.rb +1304 -0
- metadata +124 -0
data/build/gist.1
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
.\" generated with Ronn/v0.7.3
|
2
|
+
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
3
|
+
.
|
4
|
+
.TH "GIST" "1" "May 2018" "" "Gist manual"
|
5
|
+
.
|
6
|
+
.SH "NAME"
|
7
|
+
\fBgist\fR \- upload code to https://gist\.github\.com
|
8
|
+
.
|
9
|
+
.SH "Synopsis"
|
10
|
+
The gist gem provides a \fBgist\fR command that you can use from your terminal to upload content to https://gist\.github\.com/\.
|
11
|
+
.
|
12
|
+
.SH "Installation"
|
13
|
+
.
|
14
|
+
.IP "\(bu" 4
|
15
|
+
If you have ruby installed:
|
16
|
+
.
|
17
|
+
.IP
|
18
|
+
gem install gist
|
19
|
+
.
|
20
|
+
.IP "\(bu" 4
|
21
|
+
If you\'re using Bundler:
|
22
|
+
.
|
23
|
+
.IP
|
24
|
+
source :rubygems gem \'gist\'
|
25
|
+
.
|
26
|
+
.IP "\(bu" 4
|
27
|
+
For OS X, gist lives in Homebrew
|
28
|
+
.
|
29
|
+
.IP
|
30
|
+
brew install gist
|
31
|
+
.
|
32
|
+
.IP "" 0
|
33
|
+
.
|
34
|
+
.SH "Command"
|
35
|
+
.
|
36
|
+
.IP "\(bu" 4
|
37
|
+
To upload the contents of \fBa\.rb\fR just:
|
38
|
+
.
|
39
|
+
.IP
|
40
|
+
gist a\.rb
|
41
|
+
.
|
42
|
+
.IP "\(bu" 4
|
43
|
+
Upload multiple files:
|
44
|
+
.
|
45
|
+
.IP
|
46
|
+
gist a b c gist *\.rb
|
47
|
+
.
|
48
|
+
.IP "\(bu" 4
|
49
|
+
By default it reads from STDIN, and you can set a filename with \fB\-f\fR\.
|
50
|
+
.
|
51
|
+
.IP
|
52
|
+
gist \-f test\.rb <a\.rb
|
53
|
+
.
|
54
|
+
.IP "\(bu" 4
|
55
|
+
Alternatively, you can just paste from the clipboard:
|
56
|
+
.
|
57
|
+
.IP
|
58
|
+
gist \-P
|
59
|
+
.
|
60
|
+
.IP "\(bu" 4
|
61
|
+
Use \fB\-p\fR to make the gist private:
|
62
|
+
.
|
63
|
+
.IP
|
64
|
+
gist \-p a\.rb
|
65
|
+
.
|
66
|
+
.IP "\(bu" 4
|
67
|
+
Use \fB\-d\fR to add a description:
|
68
|
+
.
|
69
|
+
.IP
|
70
|
+
gist \-d "Random rbx bug" a\.rb
|
71
|
+
.
|
72
|
+
.IP "\(bu" 4
|
73
|
+
You can update existing gists with \fB\-u\fR:
|
74
|
+
.
|
75
|
+
.IP
|
76
|
+
gist \-u GIST_ID FILE_NAME gist \-u 42f2c239d2eb57299408 test\.txt
|
77
|
+
.
|
78
|
+
.IP "\(bu" 4
|
79
|
+
If you\'d like to copy the resulting URL to your clipboard, use \fB\-c\fR\.
|
80
|
+
.
|
81
|
+
.IP
|
82
|
+
gist \-c <a\.rb
|
83
|
+
.
|
84
|
+
.IP "\(bu" 4
|
85
|
+
If you\'d like to copy the resulting embeddable URL to your clipboard, use \fB\-e\fR\.
|
86
|
+
.
|
87
|
+
.IP
|
88
|
+
gist \-e <a\.rb
|
89
|
+
.
|
90
|
+
.IP "\(bu" 4
|
91
|
+
And you can just ask gist to open a browser window directly with \fB\-o\fR\.
|
92
|
+
.
|
93
|
+
.IP
|
94
|
+
gist \-o <a\.rb
|
95
|
+
.
|
96
|
+
.IP "\(bu" 4
|
97
|
+
To list (public gists or all gists for authed user) gists for user
|
98
|
+
.
|
99
|
+
.IP
|
100
|
+
gist \-l : all gists for authed user gist \-l defunkt : list defunkt\'s public gists
|
101
|
+
.
|
102
|
+
.IP "" 0
|
103
|
+
.
|
104
|
+
.P
|
105
|
+
To read a gist and print it to STDOUT
|
106
|
+
.
|
107
|
+
.IP "" 4
|
108
|
+
.
|
109
|
+
.nf
|
110
|
+
|
111
|
+
gist \-r GIST_ID
|
112
|
+
gist \-r 374130
|
113
|
+
.
|
114
|
+
.fi
|
115
|
+
.
|
116
|
+
.IP "" 0
|
117
|
+
.
|
118
|
+
.IP "\(bu" 4
|
119
|
+
See \fBgist \-\-help\fR for more detail\.
|
120
|
+
.
|
121
|
+
.IP "" 0
|
122
|
+
.
|
123
|
+
.SH "Login"
|
124
|
+
If you want to associate your gists with your GitHub account, you need to login with gist\. It doesn\'t store your username and password, it just uses them to get an OAuth2 token (with the "gist" permission)\.
|
125
|
+
.
|
126
|
+
.IP "" 4
|
127
|
+
.
|
128
|
+
.nf
|
129
|
+
|
130
|
+
gist \-\-login
|
131
|
+
Obtaining OAuth2 access_token from github\.
|
132
|
+
GitHub username: ConradIrwin
|
133
|
+
GitHub password:
|
134
|
+
2\-factor auth code:
|
135
|
+
Success! https://github\.com/settings/tokens
|
136
|
+
.
|
137
|
+
.fi
|
138
|
+
.
|
139
|
+
.IP "" 0
|
140
|
+
.
|
141
|
+
.P
|
142
|
+
This token is stored in \fB~/\.gist\fR and used for all future gisting\. If you need to you can revoke it from https://github\.com/settings/tokens, or just delete the file\.
|
143
|
+
.
|
144
|
+
.IP "\(bu" 4
|
145
|
+
After you\'ve done this, you can still upload gists anonymously with \fB\-a\fR\.
|
146
|
+
.
|
147
|
+
.IP
|
148
|
+
gist \-a a\.rb
|
149
|
+
.
|
150
|
+
.IP "" 0
|
151
|
+
.
|
152
|
+
.P
|
153
|
+
If you have a complicated authorization requirement you can manually create a token file by pasting a Github token with only the \fBgist\fR permission into a file called \fB~/\.gist\fR\. You can create one from https://github\.com/settings/tokens
|
154
|
+
.
|
155
|
+
.P
|
156
|
+
This file should contain only the token (~40 hex characters), and to make it easier to edit, can optionally have a final newline (\en or \er\en)\.
|
157
|
+
.
|
158
|
+
.P
|
159
|
+
For example, one way to create this file would be to run:
|
160
|
+
.
|
161
|
+
.IP "" 4
|
162
|
+
.
|
163
|
+
.nf
|
164
|
+
|
165
|
+
echo MY_SECRET_TOKEN > ~/\.gist
|
166
|
+
.
|
167
|
+
.fi
|
168
|
+
.
|
169
|
+
.IP "" 0
|
170
|
+
.
|
171
|
+
.SS "GitHub Enterprise"
|
172
|
+
If you\'d like \fBgist\fR to use your locally installed GitHub Enterprise \fIhttps://enterprise\.github\.com/\fR, you need to export the \fBGITHUB_URL\fR environment variable (usually done in your \fB~/\.bashrc\fR)\.
|
173
|
+
.
|
174
|
+
.IP "" 4
|
175
|
+
.
|
176
|
+
.nf
|
177
|
+
|
178
|
+
export GITHUB_URL=http://github\.internal\.example\.com/
|
179
|
+
.
|
180
|
+
.fi
|
181
|
+
.
|
182
|
+
.IP "" 0
|
183
|
+
.
|
184
|
+
.P
|
185
|
+
Once you\'ve done this and restarted your terminal (or run \fBsource ~/\.bashrc\fR), gist will automatically use github enterprise instead of the public github\.com
|
186
|
+
.
|
187
|
+
.P
|
188
|
+
Your token for GitHub Enterprise will be stored in \fB\.gist\.<protocol>\.<server\.name>[\.<port>]\fR (e\.g\. \fB~/\.gist\.http\.github\.internal\.example\.com\fR for the GITHUB_URL example above) instead of \fB~/\.gist\fR\.
|
189
|
+
.
|
190
|
+
.P
|
191
|
+
If you have multiple servers or use Enterprise and public GitHub often, you can work around this by creating scripts that set the env var and then run \fBgist\fR\. Keep in mind that to use the public GitHub you must unset the env var\. Just setting it to the public URL will not work\. Use \fBunset GITHUB_URL\fR
|
192
|
+
.
|
193
|
+
.SS "Token file format"
|
194
|
+
If you cannot use passwords, as most Enterprise installations do, you can generate the token via the web interface and then simply save the string in the correct file\. Avoid line breaks or you might see: \fB$ gist \-l Error: Bad credentials\fR
|
195
|
+
.
|
196
|
+
.TP
|
197
|
+
You can also use Gist as a library from inside your ruby code:
|
198
|
+
.
|
199
|
+
.IP
|
200
|
+
Gist\.gist("Look\.at(:my => \'awesome\')\.code")
|
201
|
+
.
|
202
|
+
.P
|
203
|
+
If you need more advanced features you can also pass:
|
204
|
+
.
|
205
|
+
.IP "\(bu" 4
|
206
|
+
\fB:access_token\fR to authenticate using OAuth2 (default is `File\.read("~/\.gist"))\.
|
207
|
+
.
|
208
|
+
.IP "\(bu" 4
|
209
|
+
\fB:filename\fR to change the syntax highlighting (default is \fBa\.rb\fR)\.
|
210
|
+
.
|
211
|
+
.IP "\(bu" 4
|
212
|
+
\fB:public\fR if you want your gist to have a guessable url\.
|
213
|
+
.
|
214
|
+
.IP "\(bu" 4
|
215
|
+
\fB:description\fR to add a description to your gist\.
|
216
|
+
.
|
217
|
+
.IP "\(bu" 4
|
218
|
+
\fB:update\fR to update an existing gist (can be a URL or an id)\.
|
219
|
+
.
|
220
|
+
.IP "\(bu" 4
|
221
|
+
\fB:anonymous\fR to submit an anonymous gist (default is false)\.
|
222
|
+
.
|
223
|
+
.IP "\(bu" 4
|
224
|
+
\fB:copy\fR to copy the resulting URL to the clipboard (default is false)\.
|
225
|
+
.
|
226
|
+
.IP "\(bu" 4
|
227
|
+
\fB:open\fR to open the resulting URL in a browser (default is false)\.
|
228
|
+
.
|
229
|
+
.IP "" 0
|
230
|
+
.
|
231
|
+
.P
|
232
|
+
NOTE: The access_token must have the "gist" scope\.
|
233
|
+
.
|
234
|
+
.IP "\(bu" 4
|
235
|
+
If you want to upload multiple files in the same gist, you can:
|
236
|
+
.
|
237
|
+
.IP
|
238
|
+
Gist\.multi_gist("a\.rb" => "Foo\.bar", "a\.py" => "Foo\.bar")
|
239
|
+
.
|
240
|
+
.IP "\(bu" 4
|
241
|
+
If you\'d rather use gist\'s builtin access_token, then you can force the user to obtain one by calling:
|
242
|
+
.
|
243
|
+
.IP
|
244
|
+
Gist\.login!
|
245
|
+
.
|
246
|
+
.IP "\(bu" 4
|
247
|
+
This will take them through the process of obtaining an OAuth2 token, and storing it in \fB~/\.gist\fR, where it can later be read by \fBGist\.gist\fR
|
248
|
+
.
|
249
|
+
.IP "" 0
|
250
|
+
.
|
251
|
+
.SH "Configuration"
|
252
|
+
.
|
253
|
+
.IP "\(bu" 4
|
254
|
+
If you\'d like \fB\-o\fR or \fB\-c\fR to be the default when you use the gist executable, add an alias to your \fB~/\.bashrc\fR (or equivalent)\. For example:
|
255
|
+
.
|
256
|
+
.IP
|
257
|
+
alias gist=\'gist \-c\'
|
258
|
+
.
|
259
|
+
.IP "\(bu" 4
|
260
|
+
If you\'d prefer gist to open a different browser, then you can export the BROWSER environment variable:
|
261
|
+
.
|
262
|
+
.IP
|
263
|
+
export BROWSER=google\-chrome
|
264
|
+
.
|
265
|
+
.IP "" 0
|
266
|
+
.
|
267
|
+
.P
|
268
|
+
If clipboard or browser integration don\'t work on your platform, please file a bug or (more ideally) a pull request\.
|
269
|
+
.
|
270
|
+
.P
|
271
|
+
If you need to use an HTTP proxy to access the internet, export the \fBHTTP_PROXY\fR or \fBhttp_proxy\fR environment variable and gist will use it\.
|
272
|
+
.
|
273
|
+
.SH "Meta\-fu"
|
274
|
+
Thanks to @defunkt and @indirect for writing and maintaining versions 1 through 3\. Thanks to @rking and @ConradIrwin for maintaining version 4\.
|
275
|
+
.
|
276
|
+
.P
|
277
|
+
Licensed under the MIT license\. Bug\-reports, and pull requests are welcome\.
|
data/gist.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require './lib/gist'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'aapis-gist'
|
5
|
+
s.version = Gist::VERSION
|
6
|
+
s.summary = 'Just allows you to upload gists'
|
7
|
+
s.description = 'Provides a single function (Gist.gist) that uploads a gist.'
|
8
|
+
s.homepage = 'https://github.com/defunkt/gist'
|
9
|
+
s.email = ['conrad.irwin@gmail.com', 'rkingist@sharpsaw.org', 'rpriebe@me.com']
|
10
|
+
s.authors = ['Conrad Irwin', '☈king', 'aapis']
|
11
|
+
s.license = 'MIT'
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
|
15
|
+
s.executables << 'gist'
|
16
|
+
|
17
|
+
s.add_development_dependency 'rake'
|
18
|
+
s.add_development_dependency 'ronn'
|
19
|
+
s.add_development_dependency 'webmock'
|
20
|
+
s.add_development_dependency 'rspec', '>3'
|
21
|
+
end
|
data/lib/gist.rb
ADDED
@@ -0,0 +1,591 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'cgi'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'json'
|
7
|
+
rescue LoadError
|
8
|
+
require File.join File.dirname(File.dirname(__FILE__)), 'vendor', 'json.rb'
|
9
|
+
end
|
10
|
+
|
11
|
+
# It just gists.
|
12
|
+
module Gist
|
13
|
+
extend self
|
14
|
+
|
15
|
+
VERSION = '5.1.1'
|
16
|
+
|
17
|
+
# A list of clipboard commands with copy and paste support.
|
18
|
+
CLIPBOARD_COMMANDS = {
|
19
|
+
'pbcopy' => 'pbpaste',
|
20
|
+
'xclip' => 'xclip -o',
|
21
|
+
'xsel -i' => 'xsel -o',
|
22
|
+
'putclip' => 'getclip',
|
23
|
+
}
|
24
|
+
|
25
|
+
GITHUB_API_URL = URI("https://api.github.com/")
|
26
|
+
GIT_IO_URL = URI("https://git.io")
|
27
|
+
|
28
|
+
GITHUB_BASE_PATH = ""
|
29
|
+
GHE_BASE_PATH = "/api/v3"
|
30
|
+
|
31
|
+
URL_ENV_NAME = "GITHUB_URL"
|
32
|
+
|
33
|
+
USER_AGENT = "gist/#{VERSION} (Net::HTTP, #{RUBY_DESCRIPTION})"
|
34
|
+
|
35
|
+
# Exception tag for errors raised while gisting.
|
36
|
+
module Error;
|
37
|
+
def self.exception(*args)
|
38
|
+
RuntimeError.new(*args).extend(self)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
class ClipboardError < RuntimeError; include Error end
|
42
|
+
|
43
|
+
# helper module for authentication token actions
|
44
|
+
module AuthTokenFile
|
45
|
+
def self.filename
|
46
|
+
if ENV.key?(URL_ENV_NAME)
|
47
|
+
File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/:/, '.').gsub(/[^a-z0-9.]/, '')}"
|
48
|
+
else
|
49
|
+
File.expand_path "~/.gist"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.read
|
54
|
+
File.read(filename).chomp
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.write(token)
|
58
|
+
File.open(filename, 'w', 0600) do |f|
|
59
|
+
f.write token
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# auth token for authentication
|
65
|
+
#
|
66
|
+
# @return [String] string value of access token or `nil`, if not found
|
67
|
+
def auth_token
|
68
|
+
@token ||= AuthTokenFile.read rescue nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Upload a gist to https://gist.github.com
|
72
|
+
#
|
73
|
+
# @param [String] content the code you'd like to gist
|
74
|
+
# @param [Hash] options more detailed options, see
|
75
|
+
# the documentation for {multi_gist}
|
76
|
+
#
|
77
|
+
# @see http://developer.github.com/v3/gists/
|
78
|
+
def gist(content, options = {})
|
79
|
+
filename = options[:filename] || default_filename
|
80
|
+
multi_gist({filename => content}, options)
|
81
|
+
end
|
82
|
+
|
83
|
+
def default_filename
|
84
|
+
"gistfile1.txt"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Upload a gist to https://gist.github.com
|
88
|
+
#
|
89
|
+
# @param [Hash] files the code you'd like to gist: filename => content
|
90
|
+
# @param [Hash] options more detailed options
|
91
|
+
#
|
92
|
+
# @option options [String] :description the description
|
93
|
+
# @option options [Boolean] :public (false) is this gist public
|
94
|
+
# @option options [Boolean] :anonymous (false) is this gist anonymous
|
95
|
+
# @option options [String] :access_token (`File.read("~/.gist")`) The OAuth2 access token.
|
96
|
+
# @option options [String] :update the URL or id of a gist to update
|
97
|
+
# @option options [Boolean] :copy (false) Copy resulting URL to clipboard, if successful.
|
98
|
+
# @option options [Boolean] :open (false) Open the resulting URL in a browser.
|
99
|
+
# @option options [Boolean] :skip_empty (false) Skip gisting empty files.
|
100
|
+
# @option options [Symbol] :output (:all) The type of return value you'd like:
|
101
|
+
# :html_url gives a String containing the url to the gist in a browser
|
102
|
+
# :short_url gives a String contianing a git.io url that redirects to html_url
|
103
|
+
# :javascript gives a String containing a script tag suitable for embedding the gist
|
104
|
+
# :all gives a Hash containing the parsed json response from the server
|
105
|
+
#
|
106
|
+
# @return [String, Hash] the return value as configured by options[:output]
|
107
|
+
# @raise [Gist::Error] if something went wrong
|
108
|
+
#
|
109
|
+
# @see http://developer.github.com/v3/gists/
|
110
|
+
def multi_gist(files, options={})
|
111
|
+
if options[:anonymous]
|
112
|
+
raise 'Anonymous gists are no longer supported. Please log in with `gist --login`. ' \
|
113
|
+
'(GitHub now requires credentials to gist https://bit.ly/2GBBxKw)'
|
114
|
+
else
|
115
|
+
access_token = (options[:access_token] || auth_token())
|
116
|
+
end
|
117
|
+
|
118
|
+
json = {}
|
119
|
+
|
120
|
+
json[:description] = options[:description] if options[:description]
|
121
|
+
json[:public] = !!options[:public]
|
122
|
+
json[:files] = {}
|
123
|
+
|
124
|
+
files.each_pair do |(name, content)|
|
125
|
+
if content.to_s.strip == ""
|
126
|
+
raise "Cannot gist empty files" unless options[:skip_empty]
|
127
|
+
else
|
128
|
+
name = name == "-" ? default_filename : File.basename(name)
|
129
|
+
json[:files][name] = {:content => content}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
return if json[:files].empty? && options[:skip_empty]
|
134
|
+
|
135
|
+
existing_gist = options[:update].to_s.split("/").last
|
136
|
+
|
137
|
+
url = "#{base_path}/gists"
|
138
|
+
url << "/" << CGI.escape(existing_gist) if existing_gist.to_s != ''
|
139
|
+
|
140
|
+
request = Net::HTTP::Post.new(url)
|
141
|
+
request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
|
142
|
+
request.body = JSON.dump(json)
|
143
|
+
request.content_type = 'application/json'
|
144
|
+
|
145
|
+
retried = false
|
146
|
+
|
147
|
+
begin
|
148
|
+
response = http(api_url, request)
|
149
|
+
if Net::HTTPSuccess === response
|
150
|
+
on_success(response.body, options)
|
151
|
+
else
|
152
|
+
raise "Got #{response.class} from gist: #{response.body}"
|
153
|
+
end
|
154
|
+
rescue => e
|
155
|
+
raise if retried
|
156
|
+
retried = true
|
157
|
+
retry
|
158
|
+
end
|
159
|
+
|
160
|
+
rescue => e
|
161
|
+
raise e.extend Error
|
162
|
+
end
|
163
|
+
|
164
|
+
# List all gists(private also) for authenticated user
|
165
|
+
# otherwise list public gists for given username (optional argument)
|
166
|
+
#
|
167
|
+
# @param [String] user
|
168
|
+
# @deprecated
|
169
|
+
#
|
170
|
+
# see https://developer.github.com/v3/gists/#list-gists
|
171
|
+
def list_gists(user = "")
|
172
|
+
url = "#{base_path}"
|
173
|
+
|
174
|
+
if user == ""
|
175
|
+
access_token = auth_token()
|
176
|
+
if access_token.to_s != ''
|
177
|
+
url << "/gists"
|
178
|
+
|
179
|
+
request = Net::HTTP::Get.new(url)
|
180
|
+
request['Authorization'] = "token #{access_token}"
|
181
|
+
response = http(api_url, request)
|
182
|
+
|
183
|
+
pretty_gist(response)
|
184
|
+
|
185
|
+
else
|
186
|
+
raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists."
|
187
|
+
end
|
188
|
+
|
189
|
+
else
|
190
|
+
url << "/users/#{user}/gists"
|
191
|
+
|
192
|
+
request = Net::HTTP::Get.new(url)
|
193
|
+
response = http(api_url, request)
|
194
|
+
|
195
|
+
pretty_gist(response)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def list_all_gists(user = "")
|
200
|
+
url = "#{base_path}"
|
201
|
+
|
202
|
+
if user == ""
|
203
|
+
access_token = auth_token()
|
204
|
+
if access_token.to_s != ''
|
205
|
+
url << "/gists?per_page=100"
|
206
|
+
get_gist_pages(url, access_token)
|
207
|
+
else
|
208
|
+
raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists."
|
209
|
+
end
|
210
|
+
|
211
|
+
else
|
212
|
+
url << "/users/#{user}/gists?per_page=100"
|
213
|
+
get_gist_pages(url)
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
def gist_exists?(id)
|
219
|
+
url = "#{base_path}/gists/#{id}"
|
220
|
+
|
221
|
+
access_token = auth_token()
|
222
|
+
|
223
|
+
request = Net::HTTP::Get.new(url)
|
224
|
+
request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
|
225
|
+
response = http(api_url, request)
|
226
|
+
|
227
|
+
if response.code == '200'
|
228
|
+
JSON.parse(response.body)
|
229
|
+
else
|
230
|
+
raise Error, "Gist with id of #{id} does not exist."
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def read_gist(id, file_name=nil)
|
235
|
+
url = "#{base_path}/gists/#{id}"
|
236
|
+
|
237
|
+
access_token = auth_token()
|
238
|
+
|
239
|
+
request = Net::HTTP::Get.new(url)
|
240
|
+
request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
|
241
|
+
response = http(api_url, request)
|
242
|
+
|
243
|
+
if response.code == '200'
|
244
|
+
body = JSON.parse(response.body)
|
245
|
+
files = body["files"]
|
246
|
+
|
247
|
+
if file_name
|
248
|
+
file = files[file_name]
|
249
|
+
raise Error, "Gist with id of #{id} and file #{file_name} does not exist." unless file
|
250
|
+
else
|
251
|
+
file = files.values.first
|
252
|
+
end
|
253
|
+
|
254
|
+
puts file["content"]
|
255
|
+
else
|
256
|
+
raise Error, "Gist with id of #{id} does not exist."
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def delete_gist(id)
|
261
|
+
id = id.split("/").last
|
262
|
+
url = "#{base_path}/gists/#{id}"
|
263
|
+
|
264
|
+
access_token = auth_token()
|
265
|
+
if access_token.to_s != ''
|
266
|
+
request = Net::HTTP::Delete.new(url)
|
267
|
+
request["Authorization"] = "token #{access_token}"
|
268
|
+
response = http(api_url, request)
|
269
|
+
else
|
270
|
+
raise Error, "Not authenticated. Use 'gist --login' to login."
|
271
|
+
end
|
272
|
+
|
273
|
+
if response.code == '204'
|
274
|
+
puts "Deleted!"
|
275
|
+
else
|
276
|
+
raise Error, "Gist with id of #{id} does not exist."
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def get_gist_pages(url, access_token = "")
|
281
|
+
|
282
|
+
request = Net::HTTP::Get.new(url)
|
283
|
+
request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
|
284
|
+
response = http(api_url, request)
|
285
|
+
pretty_gist(response)
|
286
|
+
|
287
|
+
link_header = response.header['link']
|
288
|
+
|
289
|
+
if link_header
|
290
|
+
links = Hash[ link_header.gsub(/(<|>|")/, "").split(',').map { |link| link.split('; rel=') } ].invert
|
291
|
+
get_gist_pages(links['next'], access_token) if links['next']
|
292
|
+
end
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
# return prettified string result of response body for all gists
|
297
|
+
#
|
298
|
+
# @params [Net::HTTPResponse] response
|
299
|
+
# @return [String] prettified result of listing all gists
|
300
|
+
#
|
301
|
+
# see https://developer.github.com/v3/gists/#response
|
302
|
+
def pretty_gist(response)
|
303
|
+
body = JSON.parse(response.body)
|
304
|
+
if response.code == '200'
|
305
|
+
body.each do |gist|
|
306
|
+
description = "#{gist['description'] || gist['files'].keys.join(" ")} #{gist['public'] ? '' : '(secret)'}"
|
307
|
+
puts "#{gist['html_url']} #{description.tr("\n", " ")}\n"
|
308
|
+
$stdout.flush
|
309
|
+
end
|
310
|
+
|
311
|
+
else
|
312
|
+
raise Error, body['message']
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# Convert long github urls into short git.io ones
|
317
|
+
#
|
318
|
+
# @param [String] url
|
319
|
+
# @return [String] shortened url, or long url if shortening fails
|
320
|
+
def shorten(url)
|
321
|
+
request = Net::HTTP::Post.new("/create")
|
322
|
+
request.set_form_data(:url => url)
|
323
|
+
response = http(GIT_IO_URL, request)
|
324
|
+
case response.code
|
325
|
+
when "200"
|
326
|
+
URI.join(GIT_IO_URL, response.body).to_s
|
327
|
+
when "201"
|
328
|
+
response['Location']
|
329
|
+
else
|
330
|
+
url
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Convert github url into raw file url
|
335
|
+
#
|
336
|
+
# Unfortunately the url returns from github's api is legacy,
|
337
|
+
# we have to taking a HTTPRedirection before appending it
|
338
|
+
# with '/raw'. Let's looking forward for github's api fix :)
|
339
|
+
#
|
340
|
+
# @param [String] url
|
341
|
+
# @return [String] the raw file url
|
342
|
+
def rawify(url)
|
343
|
+
uri = URI(url)
|
344
|
+
request = Net::HTTP::Get.new(uri.path)
|
345
|
+
response = http(uri, request)
|
346
|
+
if Net::HTTPSuccess === response
|
347
|
+
url + '/raw'
|
348
|
+
elsif Net::HTTPRedirection === response
|
349
|
+
rawify(response.header['location'])
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# Log the user into gist.
|
354
|
+
#
|
355
|
+
# This method asks the user for a username and password, and tries to obtain
|
356
|
+
# and OAuth2 access token, which is then stored in ~/.gist
|
357
|
+
#
|
358
|
+
# @raise [Gist::Error] if something went wrong
|
359
|
+
# @param [Hash] credentials login details
|
360
|
+
# @option credentials [String] :username
|
361
|
+
# @option credentials [String] :password
|
362
|
+
# @see http://developer.github.com/v3/oauth/
|
363
|
+
def login!(credentials={})
|
364
|
+
puts "Obtaining OAuth2 access_token from GitHub."
|
365
|
+
loop do
|
366
|
+
print "GitHub username: "
|
367
|
+
username = credentials[:username] || $stdin.gets.strip
|
368
|
+
print "GitHub password: "
|
369
|
+
password = credentials[:password] || begin
|
370
|
+
`stty -echo` rescue nil
|
371
|
+
$stdin.gets.strip
|
372
|
+
ensure
|
373
|
+
`stty echo` rescue nil
|
374
|
+
end
|
375
|
+
puts ""
|
376
|
+
|
377
|
+
request = Net::HTTP::Post.new("#{base_path}/authorizations")
|
378
|
+
request.body = JSON.dump({
|
379
|
+
:scopes => [:gist],
|
380
|
+
:note => "The gist gem (#{Time.now})",
|
381
|
+
:note_url => "https://github.com/ConradIrwin/gist"
|
382
|
+
})
|
383
|
+
request.content_type = 'application/json'
|
384
|
+
request.basic_auth(username, password)
|
385
|
+
|
386
|
+
response = http(api_url, request)
|
387
|
+
|
388
|
+
if Net::HTTPUnauthorized === response && response['X-GitHub-OTP']
|
389
|
+
print "2-factor auth code: "
|
390
|
+
twofa_code = $stdin.gets.strip
|
391
|
+
puts ""
|
392
|
+
|
393
|
+
request['X-GitHub-OTP'] = twofa_code
|
394
|
+
response = http(api_url, request)
|
395
|
+
end
|
396
|
+
|
397
|
+
if Net::HTTPCreated === response
|
398
|
+
AuthTokenFile.write JSON.parse(response.body)['token']
|
399
|
+
puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/tokens"
|
400
|
+
return
|
401
|
+
elsif Net::HTTPUnauthorized === response
|
402
|
+
puts "Error: #{JSON.parse(response.body)['message']}"
|
403
|
+
next
|
404
|
+
else
|
405
|
+
raise "Got #{response.class} from gist: #{response.body}"
|
406
|
+
end
|
407
|
+
end
|
408
|
+
rescue => e
|
409
|
+
raise e.extend Error
|
410
|
+
end
|
411
|
+
|
412
|
+
# Return HTTP connection
|
413
|
+
#
|
414
|
+
# @param [URI::HTTP] The URI to which to connect
|
415
|
+
# @return [Net::HTTP]
|
416
|
+
def http_connection(uri)
|
417
|
+
env = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
418
|
+
connection = if env
|
419
|
+
proxy = URI(env)
|
420
|
+
Net::HTTP::Proxy(proxy.host, proxy.port).new(uri.host, uri.port)
|
421
|
+
else
|
422
|
+
Net::HTTP.new(uri.host, uri.port)
|
423
|
+
end
|
424
|
+
if uri.scheme == "https"
|
425
|
+
connection.use_ssl = true
|
426
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
427
|
+
end
|
428
|
+
connection.open_timeout = 10
|
429
|
+
connection.read_timeout = 10
|
430
|
+
connection
|
431
|
+
end
|
432
|
+
|
433
|
+
# Run an HTTP operation
|
434
|
+
#
|
435
|
+
# @param [URI::HTTP] The URI to which to connect
|
436
|
+
# @param [Net::HTTPRequest] The request to make
|
437
|
+
# @return [Net::HTTPResponse]
|
438
|
+
def http(url, request)
|
439
|
+
request['User-Agent'] = USER_AGENT
|
440
|
+
|
441
|
+
http_connection(url).start do |http|
|
442
|
+
http.request request
|
443
|
+
end
|
444
|
+
rescue Timeout::Error
|
445
|
+
raise "Could not connect to #{api_url}"
|
446
|
+
end
|
447
|
+
|
448
|
+
# Called after an HTTP response to gist to perform post-processing.
|
449
|
+
#
|
450
|
+
# @param [String] body the text body from the github api
|
451
|
+
# @param [Hash] options more detailed options, see
|
452
|
+
# the documentation for {multi_gist}
|
453
|
+
def on_success(body, options={})
|
454
|
+
json = JSON.parse(body)
|
455
|
+
|
456
|
+
output = case options[:output]
|
457
|
+
when :javascript
|
458
|
+
%Q{<script src="#{json['html_url']}.js"></script>}
|
459
|
+
when :html_url
|
460
|
+
json['html_url']
|
461
|
+
when :raw_url
|
462
|
+
rawify(json['html_url'])
|
463
|
+
when :short_url
|
464
|
+
shorten(json['html_url'])
|
465
|
+
when :short_raw_url
|
466
|
+
shorten(rawify(json['html_url']))
|
467
|
+
else
|
468
|
+
json
|
469
|
+
end
|
470
|
+
|
471
|
+
Gist.copy(output.to_s) if options[:copy]
|
472
|
+
Gist.open(json['html_url']) if options[:open]
|
473
|
+
|
474
|
+
output
|
475
|
+
end
|
476
|
+
|
477
|
+
# Copy a string to the clipboard.
|
478
|
+
#
|
479
|
+
# @param [String] content
|
480
|
+
# @raise [Gist::Error] if no clipboard integration could be found
|
481
|
+
#
|
482
|
+
def copy(content)
|
483
|
+
IO.popen(clipboard_command(:copy), 'r+') { |clip| clip.print content }
|
484
|
+
|
485
|
+
unless paste == content
|
486
|
+
message = 'Copying to clipboard failed.'
|
487
|
+
|
488
|
+
if ENV["TMUX"] && clipboard_command(:copy) == 'pbcopy'
|
489
|
+
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"
|
490
|
+
end
|
491
|
+
|
492
|
+
raise Error, message
|
493
|
+
end
|
494
|
+
rescue Error => e
|
495
|
+
raise ClipboardError, e.message + "\nAttempted to copy: #{content}"
|
496
|
+
end
|
497
|
+
|
498
|
+
# Get a string from the clipboard.
|
499
|
+
#
|
500
|
+
# @param [String] content
|
501
|
+
# @raise [Gist::Error] if no clipboard integration could be found
|
502
|
+
def paste
|
503
|
+
`#{clipboard_command(:paste)}`
|
504
|
+
end
|
505
|
+
|
506
|
+
# Find command from PATH environment.
|
507
|
+
#
|
508
|
+
# @param [String] cmd command name to find
|
509
|
+
# @param [String] options PATH environment variable
|
510
|
+
# @return [String] the command found
|
511
|
+
def which(cmd, path=ENV['PATH'])
|
512
|
+
if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin|cygwin/
|
513
|
+
path.split(File::PATH_SEPARATOR).each {|dir|
|
514
|
+
f = File.join(dir, cmd+".exe")
|
515
|
+
return f if File.executable?(f) && !File.directory?(f)
|
516
|
+
}
|
517
|
+
nil
|
518
|
+
else
|
519
|
+
return system("which #{cmd} > /dev/null 2>&1")
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
# Get the command to use for the clipboard action.
|
524
|
+
#
|
525
|
+
# @param [Symbol] action either :copy or :paste
|
526
|
+
# @return [String] the command to run
|
527
|
+
# @raise [Gist::ClipboardError] if no clipboard integration could be found
|
528
|
+
def clipboard_command(action)
|
529
|
+
command = CLIPBOARD_COMMANDS.keys.detect do |cmd|
|
530
|
+
which cmd
|
531
|
+
end
|
532
|
+
raise ClipboardError, <<-EOT unless command
|
533
|
+
Could not find copy command, tried:
|
534
|
+
#{CLIPBOARD_COMMANDS.values.join(' || ')}
|
535
|
+
EOT
|
536
|
+
action == :copy ? command : CLIPBOARD_COMMANDS[command]
|
537
|
+
end
|
538
|
+
|
539
|
+
# Open a URL in a browser.
|
540
|
+
#
|
541
|
+
# @param [String] url
|
542
|
+
# @raise [RuntimeError] if no browser integration could be found
|
543
|
+
#
|
544
|
+
# This method was heavily inspired by defunkt's Gist#open,
|
545
|
+
# @see https://github.com/defunkt/gist/blob/bca9b29/lib/gist.rb#L157
|
546
|
+
def open(url)
|
547
|
+
command = if ENV['BROWSER']
|
548
|
+
ENV['BROWSER']
|
549
|
+
elsif RUBY_PLATFORM =~ /darwin/
|
550
|
+
'open'
|
551
|
+
elsif RUBY_PLATFORM =~ /linux/
|
552
|
+
%w(
|
553
|
+
sensible-browser
|
554
|
+
xdg-open
|
555
|
+
firefox
|
556
|
+
firefox-bin
|
557
|
+
).detect do |cmd|
|
558
|
+
which cmd
|
559
|
+
end
|
560
|
+
elsif ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw|wince/i
|
561
|
+
'start ""'
|
562
|
+
else
|
563
|
+
raise "Could not work out how to use a browser."
|
564
|
+
end
|
565
|
+
|
566
|
+
`#{command} #{url}`
|
567
|
+
end
|
568
|
+
|
569
|
+
# Get the API base path
|
570
|
+
def base_path
|
571
|
+
ENV.key?(URL_ENV_NAME) ? GHE_BASE_PATH : GITHUB_BASE_PATH
|
572
|
+
end
|
573
|
+
|
574
|
+
# Get the API URL
|
575
|
+
def api_url
|
576
|
+
ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_API_URL
|
577
|
+
end
|
578
|
+
|
579
|
+
def legacy_private_gister?
|
580
|
+
return unless which('git')
|
581
|
+
`git config --global gist.private` =~ /\Ayes|1|true|on\z/i
|
582
|
+
end
|
583
|
+
|
584
|
+
def should_be_public?(options={})
|
585
|
+
if options.key? :private
|
586
|
+
!options[:private]
|
587
|
+
else
|
588
|
+
!Gist.legacy_private_gister?
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|