nimbu 0.1
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.
- data/bin/nimbu +12 -0
- data/lib/nimbu.rb +6 -0
- data/lib/nimbu/auth.rb +268 -0
- data/lib/nimbu/cli.rb +12 -0
- data/lib/nimbu/client.rb +269 -0
- data/lib/nimbu/client/rendezvous.rb +76 -0
- data/lib/nimbu/command.rb +189 -0
- data/lib/nimbu/command/auth.rb +38 -0
- data/lib/nimbu/command/base.rb +212 -0
- data/lib/nimbu/command/help.rb +139 -0
- data/lib/nimbu/command/helpers.rb +382 -0
- data/lib/nimbu/command/init.rb +38 -0
- data/lib/nimbu/command/server.rb +22 -0
- data/lib/nimbu/command/themes.rb +86 -0
- data/lib/nimbu/helpers.rb +382 -0
- data/lib/nimbu/server/base.rb +82 -0
- data/lib/nimbu/server/views/index.haml +1 -0
- data/lib/nimbu/version.rb +3 -0
- data/lib/vendor/nimbu/okjson.rb +557 -0
- metadata +121 -0
data/bin/nimbu
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# resolve bin path, ignoring symlinks
|
4
|
+
require "pathname"
|
5
|
+
bin_file = Pathname.new(__FILE__).realpath
|
6
|
+
|
7
|
+
# add self to libpath
|
8
|
+
$:.unshift File.expand_path("../../lib", bin_file)
|
9
|
+
|
10
|
+
# start up the CLI
|
11
|
+
require "nimbu/cli"
|
12
|
+
Nimbu::CLI.start(*ARGV)
|
data/lib/nimbu.rb
ADDED
data/lib/nimbu/auth.rb
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "nimbu"
|
3
|
+
require "nimbu/client"
|
4
|
+
require "nimbu/helpers"
|
5
|
+
|
6
|
+
class Nimbu::Auth
|
7
|
+
class << self
|
8
|
+
|
9
|
+
include Nimbu::Helpers
|
10
|
+
attr_accessor :credentials
|
11
|
+
attr_accessor :configuration
|
12
|
+
|
13
|
+
def client
|
14
|
+
@client ||= begin
|
15
|
+
client = Nimbu::Client.new(user, password, host)
|
16
|
+
client.on_warning { |msg| self.display("\n#{msg}\n\n") }
|
17
|
+
client
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def login
|
22
|
+
delete_credentials
|
23
|
+
get_credentials
|
24
|
+
end
|
25
|
+
|
26
|
+
def logout
|
27
|
+
delete_credentials
|
28
|
+
end
|
29
|
+
|
30
|
+
# just a stub; will raise if not authenticated
|
31
|
+
def check
|
32
|
+
client.list
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_host
|
36
|
+
"getnimbu.com"
|
37
|
+
end
|
38
|
+
|
39
|
+
def host
|
40
|
+
@host ||= ENV['NIMBU_HOST'] || get_nimbu_host
|
41
|
+
end
|
42
|
+
|
43
|
+
def theme
|
44
|
+
@theme ||= ENV['NIMBU_THEME'] || get_nimbu_theme
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_nimbu_host
|
48
|
+
get_configuration[:hostname]
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_nimbu_theme
|
52
|
+
get_configuration[:theme]
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_configuration # :nodoc:
|
56
|
+
@configuration ||= (read_configuration || ask_for_and_save_configuration)
|
57
|
+
end
|
58
|
+
|
59
|
+
def ask_for_and_save_configuration
|
60
|
+
@configuration = ask_for_configuration
|
61
|
+
write_configuration
|
62
|
+
@configuration
|
63
|
+
end
|
64
|
+
|
65
|
+
def configuration_file
|
66
|
+
"#{Dir.pwd}/nimbu.yml"
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete_configuration
|
70
|
+
FileUtils.rm_f(configuration_file)
|
71
|
+
@host = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def ask_for_configuration
|
75
|
+
puts "What is the hostname for this Nimbu site?"
|
76
|
+
print "Hostname: "
|
77
|
+
hostname = ask
|
78
|
+
|
79
|
+
puts "What is the theme you are developing in this directory?"
|
80
|
+
print "Theme (i.e. default): "
|
81
|
+
theme = ask
|
82
|
+
|
83
|
+
{:hostname => hostname, :theme => theme}
|
84
|
+
end
|
85
|
+
|
86
|
+
def read_configuration
|
87
|
+
File.exists?(configuration_file) and YAML::load(File.open( configuration_file ))
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_configuration
|
91
|
+
FileUtils.mkdir_p(File.dirname(configuration_file))
|
92
|
+
File.open(configuration_file, 'w') {|credentials| credentials.puts(YAML.dump(self.configuration))}
|
93
|
+
FileUtils.chmod(0700, File.dirname(configuration_file))
|
94
|
+
FileUtils.chmod(0600, configuration_file)
|
95
|
+
end
|
96
|
+
|
97
|
+
def reauthorize
|
98
|
+
@credentials = ask_for_and_save_credentials
|
99
|
+
end
|
100
|
+
|
101
|
+
def user # :nodoc:
|
102
|
+
get_credentials[0]
|
103
|
+
end
|
104
|
+
|
105
|
+
def password # :nodoc:
|
106
|
+
get_credentials[1]
|
107
|
+
end
|
108
|
+
|
109
|
+
def api_key
|
110
|
+
Nimbu::Client.auth(user, password)["api_key"]
|
111
|
+
end
|
112
|
+
|
113
|
+
def credentials_file
|
114
|
+
if host == default_host
|
115
|
+
"#{home_directory}/.nimbu/credentials"
|
116
|
+
else
|
117
|
+
"#{home_directory}/.nimbu/credentials.#{CGI.escape(host)}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def get_credentials # :nodoc:
|
122
|
+
@credentials ||= (read_credentials || ask_for_and_save_credentials)
|
123
|
+
end
|
124
|
+
|
125
|
+
def delete_credentials
|
126
|
+
FileUtils.rm_f(credentials_file)
|
127
|
+
@client, @credentials = nil, nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def read_credentials
|
131
|
+
if ENV['NIMBU_API_KEY']
|
132
|
+
['', ENV['NIMBU_API_KEY']]
|
133
|
+
else
|
134
|
+
File.exists?(credentials_file) and File.read(credentials_file).split("\n")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def write_credentials
|
139
|
+
FileUtils.mkdir_p(File.dirname(credentials_file))
|
140
|
+
File.open(credentials_file, 'w') {|credentials| credentials.puts(self.credentials)}
|
141
|
+
FileUtils.chmod(0700, File.dirname(credentials_file))
|
142
|
+
FileUtils.chmod(0600, credentials_file)
|
143
|
+
end
|
144
|
+
|
145
|
+
def echo_off
|
146
|
+
with_tty do
|
147
|
+
system "stty -echo"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def echo_on
|
152
|
+
with_tty do
|
153
|
+
system "stty echo"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def ask_for_credentials
|
158
|
+
puts "Enter your Nimbu credentials."
|
159
|
+
|
160
|
+
print "Email: "
|
161
|
+
user = ask
|
162
|
+
|
163
|
+
print "Password: "
|
164
|
+
password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
|
165
|
+
api_key = Nimbu::Client.auth(user, password)['api_key']
|
166
|
+
|
167
|
+
[user, api_key]
|
168
|
+
end
|
169
|
+
|
170
|
+
def ask_for_password_on_windows
|
171
|
+
require "Win32API"
|
172
|
+
char = nil
|
173
|
+
password = ''
|
174
|
+
|
175
|
+
while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
|
176
|
+
break if char == 10 || char == 13 # received carriage return or newline
|
177
|
+
if char == 127 || char == 8 # backspace and delete
|
178
|
+
password.slice!(-1, 1)
|
179
|
+
else
|
180
|
+
# windows might throw a -1 at us so make sure to handle RangeError
|
181
|
+
(password << char.chr) rescue RangeError
|
182
|
+
end
|
183
|
+
end
|
184
|
+
puts
|
185
|
+
return password
|
186
|
+
end
|
187
|
+
|
188
|
+
def ask_for_password
|
189
|
+
echo_off
|
190
|
+
trap("INT") do
|
191
|
+
echo_on
|
192
|
+
exit
|
193
|
+
end
|
194
|
+
password = ask
|
195
|
+
puts
|
196
|
+
echo_on
|
197
|
+
return password
|
198
|
+
end
|
199
|
+
|
200
|
+
def ask_for_and_save_credentials
|
201
|
+
begin
|
202
|
+
@credentials = ask_for_credentials
|
203
|
+
write_credentials
|
204
|
+
check
|
205
|
+
rescue ::RestClient::Unauthorized, ::RestClient::ResourceNotFound => e
|
206
|
+
delete_credentials
|
207
|
+
display "Authentication failed."
|
208
|
+
retry if retry_login?
|
209
|
+
exit 1
|
210
|
+
rescue Exception => e
|
211
|
+
delete_credentials
|
212
|
+
raise e
|
213
|
+
end
|
214
|
+
@credentials
|
215
|
+
end
|
216
|
+
|
217
|
+
def check_for_associated_ssh_key
|
218
|
+
return unless client.keys.empty?
|
219
|
+
associate_or_generate_ssh_key
|
220
|
+
end
|
221
|
+
|
222
|
+
def associate_or_generate_ssh_key
|
223
|
+
public_keys = Dir.glob("#{home_directory}/.ssh/*.pub").sort
|
224
|
+
|
225
|
+
case public_keys.length
|
226
|
+
when 0 then
|
227
|
+
display "Could not find an existing public key."
|
228
|
+
display "Would you like to generate one? [Yn] ", false
|
229
|
+
unless ask.strip.downcase == "n"
|
230
|
+
display "Generating new SSH public key."
|
231
|
+
generate_ssh_key("id_rsa")
|
232
|
+
associate_key("#{home_directory}/.ssh/id_rsa.pub")
|
233
|
+
end
|
234
|
+
when 1 then
|
235
|
+
display "Found existing public key: #{public_keys.first}"
|
236
|
+
associate_key(public_keys.first)
|
237
|
+
else
|
238
|
+
display "Found the following SSH public keys:"
|
239
|
+
public_keys.each_with_index do |key, index|
|
240
|
+
display "#{index+1}) #{File.basename(key)}"
|
241
|
+
end
|
242
|
+
display "Which would you like to use with your Nimbu account? ", false
|
243
|
+
chosen = public_keys[ask.to_i-1] rescue error("Invalid choice")
|
244
|
+
associate_key(chosen)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def generate_ssh_key(keyfile)
|
249
|
+
ssh_dir = File.join(home_directory, ".ssh")
|
250
|
+
unless File.exists?(ssh_dir)
|
251
|
+
FileUtils.mkdir_p ssh_dir
|
252
|
+
File.chmod(0700, ssh_dir)
|
253
|
+
end
|
254
|
+
`ssh-keygen -t rsa -N "" -f \"#{home_directory}/.ssh/#{keyfile}\" 2>&1`
|
255
|
+
end
|
256
|
+
|
257
|
+
def associate_key(key)
|
258
|
+
display "Uploading SSH public key #{key}"
|
259
|
+
client.add_key(File.read(key))
|
260
|
+
end
|
261
|
+
|
262
|
+
def retry_login?
|
263
|
+
@login_attempts ||= 0
|
264
|
+
@login_attempts += 1
|
265
|
+
@login_attempts < 3
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
data/lib/nimbu/cli.rb
ADDED
data/lib/nimbu/client.rb
ADDED
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'uri'
|
4
|
+
require 'time'
|
5
|
+
require 'nimbu/auth'
|
6
|
+
require 'nimbu/helpers'
|
7
|
+
require 'nimbu/version'
|
8
|
+
|
9
|
+
# A Ruby class to call the Nimbu REST API. You might use this if you want to
|
10
|
+
# manage your Nimbu apps from within a Ruby program, such as Capistrano.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# require 'nimbu'
|
15
|
+
# nimbu = Nimbu::Client.new('me@example.com', 'mypass')
|
16
|
+
# nimbu.create('myapp')
|
17
|
+
#
|
18
|
+
class Nimbu::Client
|
19
|
+
|
20
|
+
include Nimbu::Helpers
|
21
|
+
extend Nimbu::Helpers
|
22
|
+
|
23
|
+
def self.version
|
24
|
+
Nimbu::VERSION
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.gem_version_string
|
28
|
+
"nimbu-gem/#{version}"
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :host, :user, :password
|
32
|
+
|
33
|
+
def self.auth(user, password, host=Nimbu::Auth.host)
|
34
|
+
client = new(user, password, host)
|
35
|
+
json_decode client.post('/login', { :user => {:email => user, :password => password }}, :accept => 'json').to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(user, password, host=Nimbu::Auth.host)
|
39
|
+
@user = user
|
40
|
+
@password = password
|
41
|
+
@host = host
|
42
|
+
end
|
43
|
+
|
44
|
+
# Show a list of sites
|
45
|
+
def list
|
46
|
+
doc = xml(get('/sites').to_s)
|
47
|
+
doc.elements.to_a("//sites/site").map do |a|
|
48
|
+
name = a.elements.to_a("name").first
|
49
|
+
owner = a.elements.to_a("domain").first
|
50
|
+
[name.text, owner.text]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Show info such as mode, custom domain, and collaborators on an app.
|
55
|
+
def info(name_or_domain)
|
56
|
+
raise ArgumentError.new("name_or_domain is required for info") unless name_or_domain
|
57
|
+
name_or_domain = name_or_domain.gsub(/^(http:\/\/)?(www\.)?/, '')
|
58
|
+
doc = xml(get("/apps/#{name_or_domain}").to_s)
|
59
|
+
attrs = hash_from_xml_doc(doc)[:app]
|
60
|
+
attrs.merge!(:collaborators => list_collaborators(attrs[:name]))
|
61
|
+
attrs.merge!(:addons => installed_addons(attrs[:name]))
|
62
|
+
end
|
63
|
+
|
64
|
+
def on_warning(&blk)
|
65
|
+
@warning_callback = blk
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_template(params)
|
69
|
+
post("/engine/template",params)
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_page(params)
|
73
|
+
post("/engine/render",params)
|
74
|
+
end
|
75
|
+
|
76
|
+
def list_themes
|
77
|
+
get("/themes")
|
78
|
+
end
|
79
|
+
|
80
|
+
def show_theme_contents(theme)
|
81
|
+
get("/themes/#{theme}/list")
|
82
|
+
end
|
83
|
+
|
84
|
+
def fetch_theme_layout(theme,id)
|
85
|
+
get("/themes/#{theme}/layouts/#{id}")
|
86
|
+
end
|
87
|
+
|
88
|
+
def fetch_theme_template(theme,id)
|
89
|
+
get("/themes/#{theme}/templates/#{id}")
|
90
|
+
end
|
91
|
+
|
92
|
+
def fetch_theme_assets(theme,id)
|
93
|
+
get("/themes/#{theme}/assets/#{id}")
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
##################
|
98
|
+
|
99
|
+
def resource(uri, options={})
|
100
|
+
if http_proxy
|
101
|
+
RestClient.proxy = http_proxy
|
102
|
+
end
|
103
|
+
resource = RestClient::Resource.new(realize_full_uri(uri), options.merge(:user => password))
|
104
|
+
resource
|
105
|
+
end
|
106
|
+
|
107
|
+
def get(uri, extra_headers={}) # :nodoc:
|
108
|
+
process(:get, uri, extra_headers)
|
109
|
+
end
|
110
|
+
|
111
|
+
def post(uri, payload="", extra_headers={}) # :nodoc:
|
112
|
+
process(:post, uri, extra_headers, payload)
|
113
|
+
end
|
114
|
+
|
115
|
+
def put(uri, payload, extra_headers={}) # :nodoc:
|
116
|
+
process(:put, uri, extra_headers, payload)
|
117
|
+
end
|
118
|
+
|
119
|
+
def delete(uri, extra_headers={}) # :nodoc:
|
120
|
+
process(:delete, uri, extra_headers)
|
121
|
+
end
|
122
|
+
|
123
|
+
def process(method, uri, extra_headers={}, payload=nil)
|
124
|
+
headers = nimbu_headers.merge(extra_headers)
|
125
|
+
args = [method, payload, headers].compact
|
126
|
+
|
127
|
+
resource_options = default_resource_options_for_uri(uri)
|
128
|
+
|
129
|
+
begin
|
130
|
+
response = resource(uri, resource_options).send(*args)
|
131
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError
|
132
|
+
host = URI.parse(realize_full_uri(uri)).host
|
133
|
+
error "Unable to connect to #{host}"
|
134
|
+
rescue RestClient::SSLCertificateNotVerified => ex
|
135
|
+
host = URI.parse(realize_full_uri(uri)).host
|
136
|
+
error "WARNING: Unable to verify SSL certificate for #{host}\nTo disable SSL verification, run with HEROKU_SSL_VERIFY=disable"
|
137
|
+
end
|
138
|
+
|
139
|
+
extract_warning(response)
|
140
|
+
response
|
141
|
+
end
|
142
|
+
|
143
|
+
def extract_warning(response)
|
144
|
+
return unless response
|
145
|
+
if response.headers[:x_nimbu_warning] && @warning_callback
|
146
|
+
warning = response.headers[:x_nimbu_warning]
|
147
|
+
@displayed_warnings ||= {}
|
148
|
+
unless @displayed_warnings[warning]
|
149
|
+
@warning_callback.call(warning)
|
150
|
+
@displayed_warnings[warning] = true
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def nimbu_headers # :nodoc:
|
156
|
+
{
|
157
|
+
'X-Nimbu-API-Version' => '1',
|
158
|
+
'User-Agent' => self.class.gem_version_string,
|
159
|
+
'X-Ruby-Version' => RUBY_VERSION,
|
160
|
+
'X-Ruby-Platform' => RUBY_PLATFORM
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
def xml(raw) # :nodoc:
|
165
|
+
REXML::Document.new(raw)
|
166
|
+
end
|
167
|
+
|
168
|
+
def escape(value) # :nodoc:
|
169
|
+
escaped = URI.escape(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
170
|
+
escaped.gsub('.', '%2E') # not covered by the previous URI.escape
|
171
|
+
end
|
172
|
+
|
173
|
+
module JSON
|
174
|
+
def self.parse(json)
|
175
|
+
json_decode(json)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def configure_addon(action, app_name, addon, config = {})
|
182
|
+
response = update_addon action,
|
183
|
+
addon_path(app_name, addon),
|
184
|
+
config
|
185
|
+
|
186
|
+
json_decode(response.to_s) unless response.to_s.empty?
|
187
|
+
end
|
188
|
+
|
189
|
+
def addon_path(app_name, addon)
|
190
|
+
"/apps/#{app_name}/addons/#{escape(addon)}"
|
191
|
+
end
|
192
|
+
|
193
|
+
def update_addon(action, path, config)
|
194
|
+
params = { :config => config }
|
195
|
+
app = params[:config].delete(:confirm)
|
196
|
+
headers = { :accept => 'application/json' }
|
197
|
+
params.merge!(:confirm => app) if app
|
198
|
+
|
199
|
+
case action
|
200
|
+
when :install
|
201
|
+
post path, params, headers
|
202
|
+
when :upgrade
|
203
|
+
put path, params, headers
|
204
|
+
when :uninstall
|
205
|
+
confirm = app ? "confirm=#{app}" : ''
|
206
|
+
delete "#{path}?#{confirm}", headers
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def realize_full_uri(given)
|
211
|
+
full_host = (host =~ /^http/) ? host : "http://#{host}"
|
212
|
+
host = URI.parse(full_host)
|
213
|
+
uri = URI.parse(given)
|
214
|
+
uri.host ||= host.host
|
215
|
+
uri.scheme ||= host.scheme || "http"
|
216
|
+
uri.path = "/api/v1" + ((uri.path[0..0] == "/") ? uri.path : "/#{uri.path}")
|
217
|
+
uri.port = host.port if full_host =~ /\:\d+/
|
218
|
+
uri.to_s
|
219
|
+
end
|
220
|
+
|
221
|
+
def default_resource_options_for_uri(uri)
|
222
|
+
if ENV["HEROKU_SSL_VERIFY"] == "disable"
|
223
|
+
{}
|
224
|
+
elsif realize_full_uri(uri) =~ %r|^https://api.getnimbu.com|
|
225
|
+
{ :verify_ssl => OpenSSL::SSL::VERIFY_PEER, :ssl_ca_file => local_ca_file }
|
226
|
+
else
|
227
|
+
{}
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def local_ca_file
|
232
|
+
File.expand_path("../../../data/cacert.pem", __FILE__)
|
233
|
+
end
|
234
|
+
|
235
|
+
def hash_from_xml_doc(elements)
|
236
|
+
elements.inject({}) do |hash, e|
|
237
|
+
next(hash) unless e.respond_to?(:children)
|
238
|
+
hash.update(e.name.gsub("-","_").to_sym => case e.children.length
|
239
|
+
when 0 then nil
|
240
|
+
when 1 then e.text
|
241
|
+
else hash_from_xml_doc(e.children)
|
242
|
+
end)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def http_proxy
|
247
|
+
proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
|
248
|
+
if proxy && !proxy.empty?
|
249
|
+
unless /^[^:]+:\/\// =~ proxy
|
250
|
+
proxy = "http://" + proxy
|
251
|
+
end
|
252
|
+
proxy
|
253
|
+
else
|
254
|
+
nil
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def https_proxy
|
259
|
+
proxy = ENV['HTTPS_PROXY'] || ENV['https_proxy']
|
260
|
+
if proxy && !proxy.empty?
|
261
|
+
unless /^[^:]+:\/\// =~ proxy
|
262
|
+
proxy = "https://" + proxy
|
263
|
+
end
|
264
|
+
proxy
|
265
|
+
else
|
266
|
+
nil
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|