fig 0.1.77 → 0.1.79

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/Changes +58 -1
  2. data/bin/fig +1 -1
  3. data/lib/fig.rb +1 -1
  4. data/lib/fig/command.rb +3 -3
  5. data/lib/fig/command/action/dump_package_definition_parsed.rb +5 -4
  6. data/lib/fig/command/action/list_variables/all_configs.rb +2 -5
  7. data/lib/fig/command/action/publish_local.rb +1 -1
  8. data/lib/fig/command/action/role/list_variables_in_a_tree.rb +2 -5
  9. data/lib/fig/command/action/run_command_line.rb +10 -3
  10. data/lib/fig/command/action/run_command_statement.rb +1 -1
  11. data/lib/fig/command/options.rb +8 -7
  12. data/lib/fig/command/options/parser.rb +1 -1
  13. data/lib/fig/environment_variables/case_insensitive.rb +1 -1
  14. data/lib/fig/environment_variables/case_sensitive.rb +1 -1
  15. data/lib/fig/figrc.rb +10 -10
  16. data/lib/fig/{not_found_error.rb → file_not_found_error.rb} +1 -1
  17. data/lib/fig/grammar/v0.rb +174 -173
  18. data/lib/fig/grammar/v0.treetop +27 -21
  19. data/lib/fig/grammar/v1.rb +477 -171
  20. data/lib/fig/grammar/v1.treetop +34 -22
  21. data/lib/fig/operating_system.rb +139 -60
  22. data/lib/fig/package.rb +8 -4
  23. data/lib/fig/package_definition_text_assembler.rb +31 -23
  24. data/lib/fig/parser.rb +3 -5
  25. data/lib/fig/parser_package_build_state.rb +15 -14
  26. data/lib/fig/repository.rb +41 -28
  27. data/lib/fig/repository_package_publisher.rb +20 -25
  28. data/lib/fig/runtime_environment.rb +136 -87
  29. data/lib/fig/statement.rb +15 -116
  30. data/lib/fig/statement/archive.rb +6 -4
  31. data/lib/fig/statement/asset.rb +50 -35
  32. data/lib/fig/statement/command.rb +6 -2
  33. data/lib/fig/statement/configuration.rb +10 -2
  34. data/lib/fig/statement/environment_variable.rb +35 -0
  35. data/lib/fig/statement/grammar_version.rb +6 -2
  36. data/lib/fig/statement/include.rb +6 -2
  37. data/lib/fig/statement/override.rb +6 -2
  38. data/lib/fig/statement/path.rb +7 -8
  39. data/lib/fig/statement/resource.rb +7 -3
  40. data/lib/fig/statement/retrieve.rb +10 -2
  41. data/lib/fig/statement/set.rb +7 -8
  42. data/lib/fig/string_tokenizer.rb +195 -0
  43. data/lib/fig/tokenized_string.rb +22 -0
  44. data/lib/fig/tokenized_string/plain_segment.rb +24 -0
  45. data/lib/fig/tokenized_string/token.rb +18 -0
  46. data/lib/fig/unparser.rb +84 -1
  47. data/lib/fig/unparser/v0.rb +4 -0
  48. data/lib/fig/unparser/v1.rb +7 -7
  49. data/lib/fig/url.rb +12 -1
  50. data/lib/fig/{url_access_error.rb → url_access_disallowed_error.rb} +2 -2
  51. metadata +129 -128
  52. data/lib/fig/grammar/v0_asset_location.rb +0 -162
  53. data/lib/fig/grammar/v0_ish.rb +0 -1356
  54. data/lib/fig/grammar/v1_asset_location.rb +0 -162
  55. data/lib/fig/grammar/v2.rb +0 -1478
@@ -45,29 +45,29 @@ module Fig
45
45
  end
46
46
 
47
47
  rule archive
48
- statement_start:'archive' ws_or_comment+ url:asset_url {
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, url
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+ url:asset_url {
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, url
61
+ Statement::Resource, statement_start, location
62
62
  )
63
63
  end
64
64
  }
65
65
  end
66
66
 
67
- rule asset_url
68
- '"' [^"]* '"' /
69
- "'" [^']* "'" /
70
- [\S]+
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 path
134
- statement_start:('add' / 'append' / 'path') ws_or_comment+ name_value:[\S]+ {
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::Path, statement_start, name_value
143
+ Statement::Set, statement_start, environment_variable_name_value
138
144
  )
139
145
  end
140
146
  }
141
147
  end
142
148
 
143
- rule environment_variable_name
144
- [a-zA-Z0-9_]+
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::Set, statement_start, name_value
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+ string {
168
+ statement_start:'command' ws_or_comment+ command_line {
159
169
  def to_config_statement(build_state)
160
- return build_state.new_command_statement(statement_start, string)
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 string
177
+ rule command_line
166
178
  '"' value:[^"]* '"'
167
179
  end
168
180
 
169
181
  rule descriptor_string
170
- [\S]+
182
+ [^\s#]+
171
183
  end
172
184
  end
173
185
  end
@@ -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
- return packages if ! File.exist?(uri.path)
145
+ unescaped_path = CGI.unescape uri.path
146
+ return packages if ! File.exist?(unescaped_path)
110
147
 
111
148
  ls = ''
112
- Find.find(uri.path) { |file| ls << file.to_s; ls << "\n" }
149
+ Find.find(unescaped_path) { |file| ls << file.to_s; ls << "\n" }
113
150
 
114
- strip_paths_for_list(ls, packages, uri.path)
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::NotFoundError.new error.message, url
262
+ raise Fig::FileNotFoundError.new error.message, url
183
263
  rescue SocketError => error
184
264
  Fig::Logging.debug error.message
185
- raise Fig::NotFoundError.new error.message, url
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::NotFoundError.new error.message, url
276
+ raise Fig::FileNotFoundError.new error.message, url
197
277
  rescue SocketError => error
198
278
  Fig::Logging.debug error.message
199
- raise Fig::NotFoundError.new error.message, url
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
- cmd = `which fig-download`.strip + " #{timestamp} #{uri.path}"
286
+ command = `which fig-download`.strip + " #{timestamp} #{uri.path}"
207
287
  log_download(url, path)
208
- ssh_download(uri.user, uri.host, path, cmd)
288
+ ssh_download(uri.user, uri.host, path, command)
209
289
  when 'file'
210
290
  begin
211
- FileUtils.cp(uri.path, path)
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::NotFoundError.new error.message, url
295
+ raise Fig::FileNotFoundError.new error.message, url
215
296
  end
216
297
  else
217
- Fig::Logging.fatal "Unknown protocol: #{url}"
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 basename
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 to,
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
- FileUtils.mkdir_p(File.dirname(uri.path))
288
- FileUtils.cp(local_file, uri.path)
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
- Fig::Logging.fatal "Unknown protocol: #{uri}"
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 self.windows?
374
- RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
375
- end
376
-
377
- def self.java?
378
- RUBY_PLATFORM == 'java'
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 shell_exec(cmd)
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
- if Fig::OperatingSystem.windows?
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
- def self.wrap_variable_name_with_shell_expansion(variable_name)
400
- if Fig::OperatingSystem.windows?
401
- return "%#{variable_name}%"
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
- return "$#{variable_name}"
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
- # cmd = The command to be run on the remote host.
419
- def ssh_download(user, host, path, cmd)
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(cmd)
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::NotFoundError.new 'Remote path not found', path
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::NotFoundError.new 'Too many HTTP redirects.', uri_string
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::NotFoundError.new(
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