fig 0.1.77 → 0.1.79

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.
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