fig 0.1.77 → 0.1.79
Sign up to get free protection for your applications and to get access to all the features.
- data/Changes +58 -1
- data/bin/fig +1 -1
- data/lib/fig.rb +1 -1
- data/lib/fig/command.rb +3 -3
- data/lib/fig/command/action/dump_package_definition_parsed.rb +5 -4
- data/lib/fig/command/action/list_variables/all_configs.rb +2 -5
- data/lib/fig/command/action/publish_local.rb +1 -1
- data/lib/fig/command/action/role/list_variables_in_a_tree.rb +2 -5
- data/lib/fig/command/action/run_command_line.rb +10 -3
- data/lib/fig/command/action/run_command_statement.rb +1 -1
- data/lib/fig/command/options.rb +8 -7
- data/lib/fig/command/options/parser.rb +1 -1
- data/lib/fig/environment_variables/case_insensitive.rb +1 -1
- data/lib/fig/environment_variables/case_sensitive.rb +1 -1
- data/lib/fig/figrc.rb +10 -10
- data/lib/fig/{not_found_error.rb → file_not_found_error.rb} +1 -1
- data/lib/fig/grammar/v0.rb +174 -173
- data/lib/fig/grammar/v0.treetop +27 -21
- data/lib/fig/grammar/v1.rb +477 -171
- data/lib/fig/grammar/v1.treetop +34 -22
- data/lib/fig/operating_system.rb +139 -60
- data/lib/fig/package.rb +8 -4
- data/lib/fig/package_definition_text_assembler.rb +31 -23
- data/lib/fig/parser.rb +3 -5
- data/lib/fig/parser_package_build_state.rb +15 -14
- data/lib/fig/repository.rb +41 -28
- data/lib/fig/repository_package_publisher.rb +20 -25
- data/lib/fig/runtime_environment.rb +136 -87
- data/lib/fig/statement.rb +15 -116
- data/lib/fig/statement/archive.rb +6 -4
- data/lib/fig/statement/asset.rb +50 -35
- data/lib/fig/statement/command.rb +6 -2
- data/lib/fig/statement/configuration.rb +10 -2
- data/lib/fig/statement/environment_variable.rb +35 -0
- data/lib/fig/statement/grammar_version.rb +6 -2
- data/lib/fig/statement/include.rb +6 -2
- data/lib/fig/statement/override.rb +6 -2
- data/lib/fig/statement/path.rb +7 -8
- data/lib/fig/statement/resource.rb +7 -3
- data/lib/fig/statement/retrieve.rb +10 -2
- data/lib/fig/statement/set.rb +7 -8
- data/lib/fig/string_tokenizer.rb +195 -0
- data/lib/fig/tokenized_string.rb +22 -0
- data/lib/fig/tokenized_string/plain_segment.rb +24 -0
- data/lib/fig/tokenized_string/token.rb +18 -0
- data/lib/fig/unparser.rb +84 -1
- data/lib/fig/unparser/v0.rb +4 -0
- data/lib/fig/unparser/v1.rb +7 -7
- data/lib/fig/url.rb +12 -1
- data/lib/fig/{url_access_error.rb → url_access_disallowed_error.rb} +2 -2
- metadata +129 -128
- data/lib/fig/grammar/v0_asset_location.rb +0 -162
- data/lib/fig/grammar/v0_ish.rb +0 -1356
- data/lib/fig/grammar/v1_asset_location.rb +0 -162
- data/lib/fig/grammar/v2.rb +0 -1478
data/lib/fig/grammar/v1.treetop
CHANGED
@@ -45,29 +45,29 @@ module Fig
|
|
45
45
|
end
|
46
46
|
|
47
47
|
rule archive
|
48
|
-
statement_start:'archive' ws_or_comment+
|
48
|
+
statement_start:'archive' ws_or_comment+ location:asset_location {
|
49
49
|
def to_package_statement(build_state)
|
50
50
|
return build_state.new_asset_statement(
|
51
|
-
Statement::Archive, statement_start,
|
51
|
+
Statement::Archive, statement_start, location
|
52
52
|
)
|
53
53
|
end
|
54
54
|
}
|
55
55
|
end
|
56
56
|
|
57
57
|
rule resource
|
58
|
-
statement_start:'resource' ws_or_comment+
|
58
|
+
statement_start:'resource' ws_or_comment+ location:asset_location {
|
59
59
|
def to_package_statement(build_state)
|
60
60
|
return build_state.new_asset_statement(
|
61
|
-
Statement::Resource, statement_start,
|
61
|
+
Statement::Resource, statement_start, location
|
62
62
|
)
|
63
63
|
end
|
64
64
|
}
|
65
65
|
end
|
66
66
|
|
67
|
-
rule
|
68
|
-
'"' [^"]* '"' /
|
69
|
-
"'" [^']* "'" /
|
70
|
-
[
|
67
|
+
rule asset_location
|
68
|
+
'"' ( [^"\\] / '\\' . )* '"' /
|
69
|
+
"'" ( [^'\\] / '\\' . )* "'" /
|
70
|
+
[^\s#]+
|
71
71
|
end
|
72
72
|
|
73
73
|
rule retrieve
|
@@ -79,6 +79,8 @@ module Fig
|
|
79
79
|
end
|
80
80
|
|
81
81
|
rule retrieve_path
|
82
|
+
# TODO: this should match asset location rules, but handling of
|
83
|
+
# "[package]" substitution is going to be interesting.
|
82
84
|
[a-zA-Z0-9_/.\[\]-]+
|
83
85
|
end
|
84
86
|
|
@@ -130,44 +132,54 @@ module Fig
|
|
130
132
|
}
|
131
133
|
end
|
132
134
|
|
133
|
-
rule
|
134
|
-
|
135
|
+
rule environment_variable_name
|
136
|
+
[a-zA-Z0-9_]+
|
137
|
+
end
|
138
|
+
|
139
|
+
rule set
|
140
|
+
statement_start:'set' ws_or_comment+ environment_variable_name_value {
|
135
141
|
def to_config_statement(build_state)
|
136
142
|
return build_state.new_environment_variable_statement(
|
137
|
-
Statement::
|
143
|
+
Statement::Set, statement_start, environment_variable_name_value
|
138
144
|
)
|
139
145
|
end
|
140
146
|
}
|
141
147
|
end
|
142
148
|
|
143
|
-
rule
|
144
|
-
|
145
|
-
end
|
146
|
-
|
147
|
-
rule set
|
148
|
-
statement_start:'set' ws_or_comment+ name_value:[\S]+ {
|
149
|
+
rule path
|
150
|
+
statement_start:('add' / 'append' / 'path') ws_or_comment+ environment_variable_name_value {
|
149
151
|
def to_config_statement(build_state)
|
150
152
|
return build_state.new_environment_variable_statement(
|
151
|
-
Statement::
|
153
|
+
Statement::Path, statement_start, environment_variable_name_value
|
152
154
|
)
|
153
155
|
end
|
154
156
|
}
|
155
157
|
end
|
156
158
|
|
159
|
+
rule environment_variable_name_value
|
160
|
+
# Should result in somewhat reasonable handling by Treetop when
|
161
|
+
# encountering mis-quoted constructs.
|
162
|
+
[^\s#\\"]* '"' ( [^"\\] / '\\' . )* '"' /
|
163
|
+
[^\s#\\']* "'" ( [^'\\] / '\\' . )* "'" /
|
164
|
+
[^\s#]+
|
165
|
+
end
|
166
|
+
|
157
167
|
rule command
|
158
|
-
statement_start:'command' ws_or_comment+
|
168
|
+
statement_start:'command' ws_or_comment+ command_line {
|
159
169
|
def to_config_statement(build_state)
|
160
|
-
return build_state.new_command_statement(
|
170
|
+
return build_state.new_command_statement(
|
171
|
+
statement_start, command_line
|
172
|
+
)
|
161
173
|
end
|
162
174
|
}
|
163
175
|
end
|
164
176
|
|
165
|
-
rule
|
177
|
+
rule command_line
|
166
178
|
'"' value:[^"]* '"'
|
167
179
|
end
|
168
180
|
|
169
181
|
rule descriptor_string
|
170
|
-
[
|
182
|
+
[^\s#]+
|
171
183
|
end
|
172
184
|
end
|
173
185
|
end
|
data/lib/fig/operating_system.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'cgi'
|
1
2
|
require 'fileutils'
|
2
3
|
require 'find'
|
3
4
|
# Must specify absolute path of ::Archive when using
|
@@ -16,15 +17,54 @@ require 'highline/import'
|
|
16
17
|
require 'fig/at_exit'
|
17
18
|
require 'fig/environment_variables/case_insensitive'
|
18
19
|
require 'fig/environment_variables/case_sensitive'
|
20
|
+
require 'fig/file_not_found_error'
|
19
21
|
require 'fig/logging'
|
20
22
|
require 'fig/network_error'
|
21
|
-
require 'fig/not_found_error'
|
22
23
|
|
23
24
|
module Fig; end
|
24
25
|
|
25
26
|
# Does things requiring real O/S interaction, primarilly taking care of file
|
26
27
|
# transfers and running external commands.
|
27
28
|
class Fig::OperatingSystem
|
29
|
+
WINDOWS_FILE_NAME_ILLEGAL_CHARACTERS = %w[ \\ / : * ? " < > | ]
|
30
|
+
UNIX_FILE_NAME_ILLEGAL_CHARACTERS = %w[ / ]
|
31
|
+
|
32
|
+
def self.windows?
|
33
|
+
RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.java?
|
37
|
+
RUBY_PLATFORM == 'java'
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.unix?
|
41
|
+
!windows?
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.file_name_illegal_characters()
|
45
|
+
if Fig::OperatingSystem.windows?
|
46
|
+
return WINDOWS_FILE_NAME_ILLEGAL_CHARACTERS
|
47
|
+
end
|
48
|
+
|
49
|
+
return UNIX_FILE_NAME_ILLEGAL_CHARACTERS
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.wrap_variable_name_with_shell_expansion(variable_name)
|
53
|
+
if Fig::OperatingSystem.windows?
|
54
|
+
return "%#{variable_name}%"
|
55
|
+
else
|
56
|
+
return "$#{variable_name}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.get_environment_variables(initial_values = nil)
|
61
|
+
if Fig::OperatingSystem.windows?
|
62
|
+
return Fig::EnvironmentVariables::CaseInsensitive.new(initial_values)
|
63
|
+
end
|
64
|
+
|
65
|
+
return Fig::EnvironmentVariables::CaseSensitive.new(initial_values)
|
66
|
+
end
|
67
|
+
|
28
68
|
def initialize(login)
|
29
69
|
@login = login
|
30
70
|
@username = ENV['FIG_USERNAME']
|
@@ -67,10 +107,6 @@ class Fig::OperatingSystem
|
|
67
107
|
File.open(path, 'wb') { |f| f.binmode; f << content }
|
68
108
|
end
|
69
109
|
|
70
|
-
SUCCESS = 0
|
71
|
-
NOT_MODIFIED = 3
|
72
|
-
NOT_FOUND = 4
|
73
|
-
|
74
110
|
def strip_paths_for_list(ls_output, packages, path)
|
75
111
|
if not ls_output.nil?
|
76
112
|
ls_output = ls_output.gsub(path + '/', '').gsub(path, '').split("\n")
|
@@ -106,12 +142,13 @@ class Fig::OperatingSystem
|
|
106
142
|
packages
|
107
143
|
when 'file'
|
108
144
|
packages = []
|
109
|
-
|
145
|
+
unescaped_path = CGI.unescape uri.path
|
146
|
+
return packages if ! File.exist?(unescaped_path)
|
110
147
|
|
111
148
|
ls = ''
|
112
|
-
Find.find(
|
149
|
+
Find.find(unescaped_path) { |file| ls << file.to_s; ls << "\n" }
|
113
150
|
|
114
|
-
strip_paths_for_list(ls, packages,
|
151
|
+
strip_paths_for_list(ls, packages, unescaped_path)
|
115
152
|
return packages
|
116
153
|
else
|
117
154
|
Fig::Logging.fatal "Protocol not supported: #{url}"
|
@@ -158,6 +195,49 @@ class Fig::OperatingSystem
|
|
158
195
|
all_packages.flatten.sort
|
159
196
|
end
|
160
197
|
|
198
|
+
# Determine whether we need to update something. Returns nil to indicate
|
199
|
+
# "don't know".
|
200
|
+
def path_up_to_date?(url, path)
|
201
|
+
return false if ! File.exist? path
|
202
|
+
|
203
|
+
uri = URI.parse(url)
|
204
|
+
case uri.scheme
|
205
|
+
when 'ftp'
|
206
|
+
begin
|
207
|
+
ftp = Net::FTP.new(uri.host)
|
208
|
+
ftp_login(ftp, uri.host)
|
209
|
+
|
210
|
+
if ftp.mtime(uri.path) <= File.mtime(path)
|
211
|
+
return true
|
212
|
+
end
|
213
|
+
|
214
|
+
return false
|
215
|
+
rescue Net::FTPPermError => error
|
216
|
+
Fig::Logging.debug error.message
|
217
|
+
raise Fig::FileNotFoundError.new error.message, url
|
218
|
+
rescue SocketError => error
|
219
|
+
Fig::Logging.debug error.message
|
220
|
+
raise Fig::FileNotFoundError.new error.message, url
|
221
|
+
end
|
222
|
+
when 'http'
|
223
|
+
return nil # Not implemented
|
224
|
+
when 'ssh'
|
225
|
+
when 'file'
|
226
|
+
begin
|
227
|
+
unescaped_path = CGI.unescape uri.path
|
228
|
+
if File.mtime(unescaped_path) <= File.mtime(path)
|
229
|
+
return true
|
230
|
+
end
|
231
|
+
|
232
|
+
return false
|
233
|
+
rescue Errno::ENOENT => error
|
234
|
+
raise Fig::FileNotFoundError.new error.message, url
|
235
|
+
end
|
236
|
+
else
|
237
|
+
raise_unknown_protocol(url)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
161
241
|
# Returns whether the file was not downloaded because the file already
|
162
242
|
# exists and is already up-to-date.
|
163
243
|
def download(url, path)
|
@@ -179,10 +259,10 @@ class Fig::OperatingSystem
|
|
179
259
|
end
|
180
260
|
rescue Net::FTPPermError => error
|
181
261
|
Fig::Logging.debug error.message
|
182
|
-
raise Fig::
|
262
|
+
raise Fig::FileNotFoundError.new error.message, url
|
183
263
|
rescue SocketError => error
|
184
264
|
Fig::Logging.debug error.message
|
185
|
-
raise Fig::
|
265
|
+
raise Fig::FileNotFoundError.new error.message, url
|
186
266
|
end
|
187
267
|
when 'http'
|
188
268
|
log_download(url, path)
|
@@ -193,29 +273,29 @@ class Fig::OperatingSystem
|
|
193
273
|
download_via_http_get(url, file)
|
194
274
|
rescue SystemCallError => error
|
195
275
|
Fig::Logging.debug error.message
|
196
|
-
raise Fig::
|
276
|
+
raise Fig::FileNotFoundError.new error.message, url
|
197
277
|
rescue SocketError => error
|
198
278
|
Fig::Logging.debug error.message
|
199
|
-
raise Fig::
|
279
|
+
raise Fig::FileNotFoundError.new error.message, url
|
200
280
|
end
|
201
281
|
end
|
202
282
|
when 'ssh'
|
203
283
|
# TODO need better way to do conditional download
|
204
284
|
timestamp = File.exist?(path) ? File.mtime(path).to_i : 0
|
205
285
|
# Requires that remote installation of fig be at the same location as the local machine.
|
206
|
-
|
286
|
+
command = `which fig-download`.strip + " #{timestamp} #{uri.path}"
|
207
287
|
log_download(url, path)
|
208
|
-
ssh_download(uri.user, uri.host, path,
|
288
|
+
ssh_download(uri.user, uri.host, path, command)
|
209
289
|
when 'file'
|
210
290
|
begin
|
211
|
-
|
291
|
+
unescaped_path = CGI.unescape uri.path
|
292
|
+
FileUtils.cp(unescaped_path, path)
|
212
293
|
return true
|
213
294
|
rescue Errno::ENOENT => error
|
214
|
-
raise Fig::
|
295
|
+
raise Fig::FileNotFoundError.new error.message, url
|
215
296
|
end
|
216
297
|
else
|
217
|
-
|
218
|
-
raise Fig::NetworkError.new("Unknown protocol: #{url}")
|
298
|
+
raise_unknown_protocol(url)
|
219
299
|
end
|
220
300
|
end
|
221
301
|
|
@@ -223,7 +303,7 @@ class Fig::OperatingSystem
|
|
223
303
|
def download_resource(url, download_directory)
|
224
304
|
FileUtils.mkdir_p(download_directory)
|
225
305
|
|
226
|
-
basename = URI.parse(url).path.split('/').last
|
306
|
+
basename = CGI.unescape URI.parse(url).path.split('/').last
|
227
307
|
path = File.join(download_directory, basename)
|
228
308
|
|
229
309
|
download(url, path)
|
@@ -234,7 +314,7 @@ class Fig::OperatingSystem
|
|
234
314
|
def download_and_unpack_archive(url, download_directory)
|
235
315
|
basename, path = download_resource(url, download_directory)
|
236
316
|
|
237
|
-
case
|
317
|
+
case path
|
238
318
|
when /\.tar\.gz$/
|
239
319
|
unpack_archive(download_directory, path)
|
240
320
|
when /\.tgz$/
|
@@ -265,8 +345,8 @@ class Fig::OperatingSystem
|
|
265
345
|
ftp_root_dirs = ftp_uri.path.split('/')
|
266
346
|
remote_publish_path = uri.path[0, uri.path.rindex('/')]
|
267
347
|
remote_publish_dirs = remote_publish_path.split('/')
|
268
|
-
# Use array subtraction to deduce which project/version folder to upload
|
269
|
-
# i.e. [1,2,3] - [2,3,4] = [1]
|
348
|
+
# Use array subtraction to deduce which project/version folder to upload
|
349
|
+
# to, i.e. [1,2,3] - [2,3,4] = [1]
|
270
350
|
remote_project_dirs = remote_publish_dirs - ftp_root_dirs
|
271
351
|
Net::FTP.open(uri.host) do |ftp|
|
272
352
|
ftp_login(ftp, uri.host)
|
@@ -284,11 +364,11 @@ class Fig::OperatingSystem
|
|
284
364
|
ftp.putbinaryfile(local_file)
|
285
365
|
end
|
286
366
|
when 'file'
|
287
|
-
|
288
|
-
FileUtils.
|
367
|
+
unescaped_path = CGI.unescape uri.path
|
368
|
+
FileUtils.mkdir_p(File.dirname(unescaped_path))
|
369
|
+
FileUtils.cp(local_file, unescaped_path)
|
289
370
|
else
|
290
|
-
|
291
|
-
raise Fig::NetworkError.new("Unknown protocol: #{uri}")
|
371
|
+
raise_unknown_protocol(uri)
|
292
372
|
end
|
293
373
|
end
|
294
374
|
|
@@ -370,58 +450,48 @@ class Fig::OperatingSystem
|
|
370
450
|
end
|
371
451
|
end
|
372
452
|
|
373
|
-
def
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
end
|
380
|
-
|
381
|
-
def self.unix?
|
382
|
-
!windows?
|
453
|
+
def shell_exec(command)
|
454
|
+
if Fig::OperatingSystem.windows?
|
455
|
+
plain_exec( [ ENV['ComSpec'], '/c', command.join(' ') ] )
|
456
|
+
else
|
457
|
+
plain_exec( [ ENV['SHELL'], '-c', command.join(' ') ] )
|
458
|
+
end
|
383
459
|
end
|
384
460
|
|
385
|
-
def
|
461
|
+
def plain_exec(command)
|
386
462
|
# Kernel#exec won't run Kernel#at_exit handlers.
|
387
463
|
Fig::AtExit.execute()
|
388
464
|
if ENV['FIG_COVERAGE']
|
389
465
|
SimpleCov.at_exit.call
|
390
466
|
end
|
391
467
|
|
392
|
-
|
393
|
-
Kernel.exec(ENV['ComSpec'], '/c', cmd.join(' '))
|
394
|
-
else
|
395
|
-
Kernel.exec(ENV['SHELL'], '-c', cmd.join(' '))
|
396
|
-
end
|
468
|
+
Kernel.exec(*command)
|
397
469
|
end
|
398
470
|
|
399
|
-
|
400
|
-
|
401
|
-
|
471
|
+
# *sigh* Apparently Ruby < v1.9.3 does some wacko thing with single argument
|
472
|
+
# exec that causes it to not invoke the shell, so we've got this mess.
|
473
|
+
def plain_or_shell_exec(command)
|
474
|
+
if command.size > 1
|
475
|
+
plain_exec(command)
|
402
476
|
else
|
403
|
-
|
477
|
+
shell_exec(command)
|
404
478
|
end
|
405
479
|
end
|
406
480
|
|
407
|
-
def self.get_environment_variables(initial_values = nil)
|
408
|
-
if Fig::OperatingSystem.windows?
|
409
|
-
return Fig::EnvironmentVariables::CaseInsensitive.new(initial_values)
|
410
|
-
end
|
411
|
-
|
412
|
-
return Fig::EnvironmentVariables::CaseSensitive.new(initial_values)
|
413
|
-
end
|
414
|
-
|
415
481
|
private
|
416
482
|
|
483
|
+
SUCCESS = 0
|
484
|
+
NOT_MODIFIED = 3
|
485
|
+
NOT_FOUND = 4
|
486
|
+
|
417
487
|
# path = The local path the file should be downloaded to.
|
418
|
-
#
|
419
|
-
def ssh_download(user, host, path,
|
488
|
+
# command = The command to be run on the remote host.
|
489
|
+
def ssh_download(user, host, path, command)
|
420
490
|
return_code = nil
|
421
491
|
tempfile = Tempfile.new('tmp')
|
422
492
|
Net::SSH.start(host, user) do |ssh|
|
423
493
|
ssh.open_channel do |channel|
|
424
|
-
channel.exec(
|
494
|
+
channel.exec(command)
|
425
495
|
channel.on_data() { |ch, data| tempfile << data }
|
426
496
|
channel.on_extended_data() { |ch, type, data| Fig::Logging.error "SSH Download ERROR: #{data}" }
|
427
497
|
channel.on_request('exit-status') { |ch, request|
|
@@ -438,7 +508,7 @@ class Fig::OperatingSystem
|
|
438
508
|
return false
|
439
509
|
when NOT_FOUND
|
440
510
|
tempfile.delete
|
441
|
-
raise Fig::
|
511
|
+
raise Fig::FileNotFoundError.new 'Remote path not found', path
|
442
512
|
when SUCCESS
|
443
513
|
FileUtils.mv(tempfile.path, path)
|
444
514
|
return true
|
@@ -463,7 +533,7 @@ class Fig::OperatingSystem
|
|
463
533
|
def download_via_http_get(uri_string, file, redirection_limit = 10)
|
464
534
|
if redirection_limit < 1
|
465
535
|
Fig::Logging.debug 'Too many HTTP redirects.'
|
466
|
-
raise Fig::
|
536
|
+
raise Fig::FileNotFoundError.new 'Too many HTTP redirects.', uri_string
|
467
537
|
end
|
468
538
|
|
469
539
|
response = Net::HTTP.get_response(URI(uri_string))
|
@@ -477,7 +547,7 @@ class Fig::OperatingSystem
|
|
477
547
|
download_via_http_get(location, file, limit - 1)
|
478
548
|
else
|
479
549
|
Fig::Logging.debug "Download failed: #{response.code} #{response.message}."
|
480
|
-
raise Fig::
|
550
|
+
raise Fig::FileNotFoundError.new(
|
481
551
|
"Download failed: #{response.code} #{response.message}.", uri_string
|
482
552
|
)
|
483
553
|
end
|
@@ -485,6 +555,15 @@ class Fig::OperatingSystem
|
|
485
555
|
return
|
486
556
|
end
|
487
557
|
|
558
|
+
def raise_unknown_protocol(url)
|
559
|
+
Fig::Logging.fatal %Q<Don't know how to handle the protocol in "#{url}".>
|
560
|
+
raise Fig::NetworkError.new(
|
561
|
+
%Q<Don't know how to handle the protocol in "#{url}".>
|
562
|
+
)
|
563
|
+
|
564
|
+
return
|
565
|
+
end
|
566
|
+
|
488
567
|
def log_download(url, path)
|
489
568
|
Fig::Logging.debug "Downloading #{url} to #{path}."
|
490
569
|
end
|