fig 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/Changes +94 -0
  2. data/lib/fig.rb +1 -1
  3. data/lib/fig/command.rb +18 -7
  4. data/lib/fig/command/action/dump_package_definition_for_command_line.rb +2 -2
  5. data/lib/fig/command/action/dump_package_definition_parsed.rb +2 -2
  6. data/lib/fig/command/action/dump_package_definition_text.rb +2 -2
  7. data/lib/fig/command/action/source_package.rb +65 -0
  8. data/lib/fig/command/options.rb +64 -15
  9. data/lib/fig/command/options/parser.rb +24 -14
  10. data/lib/fig/command/package_applier.rb +32 -7
  11. data/lib/fig/command/package_loader.rb +16 -7
  12. data/lib/fig/external_program.rb +72 -0
  13. data/lib/fig/figrc.rb +1 -1
  14. data/lib/fig/grammar/v0.rb +2 -2
  15. data/lib/fig/grammar/v0.treetop +2 -2
  16. data/lib/fig/grammar/v1.rb +17 -1737
  17. data/lib/fig/grammar/v1.treetop +6 -217
  18. data/lib/fig/grammar/v1_base.rb +1750 -0
  19. data/lib/fig/grammar/v1_base.treetop +229 -0
  20. data/lib/fig/grammar/v2.rb +508 -0
  21. data/lib/fig/grammar/v2.treetop +65 -0
  22. data/lib/fig/grammar_monkey_patches.rb +7 -0
  23. data/lib/fig/no_such_package_config_error.rb +3 -1
  24. data/lib/fig/not_yet_parsed_package.rb +27 -0
  25. data/lib/fig/operating_system.rb +5 -5
  26. data/lib/fig/package.rb +20 -2
  27. data/lib/fig/package_definition_text_assembler.rb +2 -1
  28. data/lib/fig/package_descriptor.rb +11 -4
  29. data/lib/fig/parser.rb +44 -58
  30. data/lib/fig/parser_package_build_state.rb +39 -4
  31. data/lib/fig/protocol/file.rb +2 -2
  32. data/lib/fig/protocol/ftp.rb +15 -10
  33. data/lib/fig/protocol/http.rb +1 -1
  34. data/lib/fig/protocol/netrc_enabled.rb +29 -16
  35. data/lib/fig/protocol/sftp.rb +19 -12
  36. data/lib/fig/repository.rb +33 -21
  37. data/lib/fig/repository_package_publisher.rb +129 -8
  38. data/lib/fig/runtime_environment.rb +114 -28
  39. data/lib/fig/statement/include.rb +21 -4
  40. data/lib/fig/statement/include_file.rb +94 -0
  41. data/lib/fig/unparser.rb +15 -7
  42. data/lib/fig/unparser/v1.rb +2 -80
  43. data/lib/fig/unparser/v1_base.rb +85 -0
  44. data/lib/fig/unparser/v2.rb +55 -0
  45. data/lib/fig/working_directory_maintainer.rb +12 -0
  46. metadata +61 -51
@@ -28,7 +28,7 @@ class Fig::Protocol::File
28
28
 
29
29
  # Determine whether we need to update something. Returns nil to indicate
30
30
  # "don't know".
31
- def path_up_to_date?(uri, path)
31
+ def path_up_to_date?(uri, path, prompt_for_login)
32
32
  begin
33
33
  unescaped_path = CGI.unescape uri.path
34
34
  if ::File.mtime(unescaped_path) <= ::File.mtime(path)
@@ -43,7 +43,7 @@ class Fig::Protocol::File
43
43
 
44
44
  # Returns whether the file was not downloaded because the file already
45
45
  # exists and is already up-to-date.
46
- def download(uri, path)
46
+ def download(uri, path, prompt_for_login)
47
47
  begin
48
48
  unescaped_path = CGI.unescape uri.path
49
49
  FileUtils.cp(unescaped_path, path)
@@ -17,11 +17,12 @@ class Fig::Protocol::FTP
17
17
 
18
18
  def initialize(login)
19
19
  @login = login
20
+ initialize_netrc
20
21
  end
21
22
 
22
23
  def download_list(uri)
23
24
  ftp = Net::FTP.new(uri.host)
24
- ftp_login(ftp, uri.host)
25
+ ftp_login(ftp, uri.host, :prompt_for_login)
25
26
  ftp.chdir(uri.path)
26
27
  dirs = ftp.nlst
27
28
  ftp.close
@@ -31,10 +32,10 @@ class Fig::Protocol::FTP
31
32
 
32
33
  # Determine whether we need to update something. Returns nil to indicate
33
34
  # "don't know".
34
- def path_up_to_date?(uri, path)
35
+ def path_up_to_date?(uri, path, prompt_for_login)
35
36
  begin
36
37
  ftp = Net::FTP.new(uri.host)
37
- ftp_login(ftp, uri.host)
38
+ ftp_login(ftp, uri.host, prompt_for_login)
38
39
 
39
40
  if ftp.mtime(uri.path) <= ::File.mtime(path)
40
41
  return true
@@ -52,10 +53,10 @@ class Fig::Protocol::FTP
52
53
 
53
54
  # Returns whether the file was not downloaded because the file already
54
55
  # exists and is already up-to-date.
55
- def download(uri, path)
56
+ def download(uri, path, prompt_for_login)
56
57
  begin
57
58
  ftp = Net::FTP.new(uri.host)
58
- ftp_login(ftp, uri.host)
59
+ ftp_login(ftp, uri.host, prompt_for_login)
59
60
 
60
61
  if ::File.exist?(path) && ftp.mtime(uri.path) <= ::File.mtime(path)
61
62
  Fig::Logging.debug "#{path} is up to date."
@@ -87,7 +88,7 @@ class Fig::Protocol::FTP
87
88
  # to, i.e. [1,2,3] - [2,3,4] = [1]
88
89
  remote_project_dirs = remote_publish_dirs - ftp_root_dirs
89
90
  Net::FTP.open(uri.host) do |ftp|
90
- ftp_login(ftp, uri.host)
91
+ ftp_login(ftp, uri.host, :prompt_for_login)
91
92
  # Assume that the FIG_REMOTE_URL path exists.
92
93
  ftp.chdir(ftp_root_path)
93
94
  remote_project_dirs.each do |dir|
@@ -105,11 +106,15 @@ class Fig::Protocol::FTP
105
106
 
106
107
  private
107
108
 
108
- def ftp_login(ftp, host)
109
+ def ftp_login(ftp, host, prompt_for_login)
109
110
  begin
110
111
  if @login
111
- load_authentication_for host
112
- ftp.login get_username, get_password
112
+ authentication = get_authentication_for host, prompt_for_login
113
+ if authentication
114
+ ftp.login authentication.username, authentication.password
115
+ else
116
+ ftp.login
117
+ end
113
118
  else
114
119
  ftp.login
115
120
  end
@@ -131,7 +136,7 @@ class Fig::Protocol::FTP
131
136
  threads << Thread.new do
132
137
  packages = all_packages[num]
133
138
  ftp = Net::FTP.new(uri.host)
134
- ftp_login(ftp, uri.host)
139
+ ftp_login(ftp, uri.host, :prompt_for_login)
135
140
  ftp.chdir(uri.path)
136
141
  pos = num
137
142
  while pos < dirs.length
@@ -15,7 +15,7 @@ class Fig::Protocol::HTTP
15
15
 
16
16
  # Returns whether the file was not downloaded because the file already
17
17
  # exists and is already up-to-date.
18
- def download(uri, path)
18
+ def download(uri, path, prompt_for_login)
19
19
  log_download(uri, path)
20
20
  ::File.open(path, 'wb') do |file|
21
21
  file.binmode
@@ -10,33 +10,46 @@ module Fig::Protocol; end
10
10
  module Fig::Protocol::NetRCEnabled
11
11
  private
12
12
 
13
- def get_username()
14
- @username ||= HighLine.new.ask('Username: ') { |q| q.echo = true }
15
- return @username
16
- end
13
+ NetRCEntry = Struct.new :username, :password
17
14
 
18
- def get_password()
19
- @password ||= HighLine.new.ask('Password: ') { |q| q.echo = false }
20
- return @password
21
- end
15
+ def initialize_netrc()
16
+ @netrc_entries_by_host = {}
22
17
 
23
- def load_authentication_for(host)
24
- return if @username || @password
18
+ return
19
+ end
25
20
 
26
- @username ||= ENV['FIG_USERNAME']
27
- @password ||= ENV['FIG_PASSWORD']
28
- return if @username || @password
21
+ def get_authentication_for(host, prompt_if_missing)
22
+ if @netrc_entries_by_host.include? host
23
+ return @netrc_entries_by_host[host]
24
+ end
29
25
 
26
+ entry = nil
30
27
  begin
31
28
  login_data = Net::Netrc.locate host
32
29
  if login_data
33
- @username = login_data.login
34
- @password = login_data.password
30
+ entry = NetRCEntry.new login_data.login, login_data.password
31
+ elsif prompt_if_missing
32
+ entry = get_authentication_from_user(host)
35
33
  end
36
34
  rescue SecurityError => error
37
35
  raise Fig::UserInputError.new error.message
38
36
  end
39
37
 
40
- return
38
+ @netrc_entries_by_host[host] = entry
39
+
40
+ return entry
41
+ end
42
+
43
+ def get_authentication_from_user(host)
44
+ username =
45
+ ENV['FIG_USERNAME'] ||
46
+ HighLine.new.ask("Username for #{host}: ") { |q| q.echo = true }
47
+ password =
48
+ ENV['FIG_PASSWORD'] ||
49
+ HighLine.new.ask("Password for #{username}@#{host}: ") {
50
+ |q| q.echo = false
51
+ }
52
+
53
+ return NetRCEntry.new username, password
41
54
  end
42
55
  end
@@ -14,10 +14,14 @@ class Fig::Protocol::SFTP
14
14
  include Fig::Protocol
15
15
  include Fig::Protocol::NetRCEnabled
16
16
 
17
+ def initialize()
18
+ initialize_netrc
19
+ end
20
+
17
21
  def download_list(uri)
18
22
  package_versions = []
19
23
 
20
- sftp_run(uri) do
24
+ sftp_run(uri, :prompt_for_login) do
21
25
  |connection|
22
26
 
23
27
  connection.dir.foreach uri.path do
@@ -48,11 +52,11 @@ class Fig::Protocol::SFTP
48
52
 
49
53
  # Determine whether we need to update something. Returns nil to indicate
50
54
  # "don't know".
51
- def path_up_to_date?(uri, path)
52
- sftp_run(uri) do
55
+ def path_up_to_date?(uri, path, prompt_for_login)
56
+ sftp_run(uri, prompt_for_login) do
53
57
  |connection|
54
58
 
55
- return connection.stat!(uri.path).mtime <= ::File.mtime(path)
59
+ return connection.stat!(uri.path).mtime.to_f <= ::File.mtime(path).to_f
56
60
  end
57
61
 
58
62
  return nil
@@ -60,8 +64,8 @@ class Fig::Protocol::SFTP
60
64
 
61
65
  # Returns whether the file was not downloaded because the file already
62
66
  # exists and is already up-to-date.
63
- def download(uri, path)
64
- sftp_run(uri) do
67
+ def download(uri, path, prompt_for_login)
68
+ sftp_run(uri, prompt_for_login) do
65
69
  |connection|
66
70
 
67
71
  begin
@@ -70,7 +74,7 @@ class Fig::Protocol::SFTP
70
74
  # when the remote path does not exist.
71
75
  stat = connection.stat!(uri.path)
72
76
 
73
- if ::File.exist?(path) && stat.mtime <= ::File.mtime(path)
77
+ if ::File.exist?(path) && stat.mtime.to_f <= ::File.mtime(path).to_f
74
78
  Fig::Logging.debug "#{path} is up to date."
75
79
  return false
76
80
  else
@@ -91,7 +95,7 @@ class Fig::Protocol::SFTP
91
95
  end
92
96
 
93
97
  def upload(local_file, uri)
94
- sftp_run(uri) do
98
+ sftp_run(uri, :prompt_for_login) do
95
99
  |connection|
96
100
 
97
101
  ensure_directory_exists connection, ::File.dirname(uri.path)
@@ -103,19 +107,22 @@ class Fig::Protocol::SFTP
103
107
 
104
108
  private
105
109
 
106
- def sftp_run(uri, &block)
110
+ def sftp_run(uri, prompt_for_login, &block)
107
111
  host = uri.host
108
112
 
109
- load_authentication_for host
113
+ authentication = get_authentication_for host, prompt_for_login
114
+ if ! authentication
115
+ raise Fig::NetworkError.new "No authentication information for #{host}."
116
+ end
110
117
 
111
118
  begin
112
- options = {:password => get_password}
119
+ options = {:password => authentication.password}
113
120
  port = uri.port
114
121
  if port
115
122
  options[:port] = port
116
123
  end
117
124
 
118
- Net::SFTP.start(host, get_username, options, &block)
125
+ Net::SFTP.start(host, authentication.username, options, &block)
119
126
  rescue Net::SSH::Exception => error
120
127
  raise Fig::NetworkError.new error.message
121
128
  rescue Net::SFTP::Exception => error
@@ -8,6 +8,7 @@ require 'fig'
8
8
  require 'fig/at_exit'
9
9
  require 'fig/file_not_found_error'
10
10
  require 'fig/logging'
11
+ require 'fig/not_yet_parsed_package'
11
12
  require 'fig/package_cache'
12
13
  require 'fig/package_descriptor'
13
14
  require 'fig/parser'
@@ -28,19 +29,22 @@ class Fig::Repository
28
29
  REMOTE_VERSION_SUPPORTED = 1
29
30
 
30
31
  def initialize(
31
- os,
32
+ application_configuration,
33
+ options,
34
+ operating_system,
32
35
  local_repository_directory,
33
- application_config,
34
- publish_listeners,
35
- check_include_versions
36
+ remote_repository_url,
37
+ parser,
38
+ publish_listeners
36
39
  )
37
- @operating_system = os
40
+ @application_configuration = application_configuration
41
+ @options = options
42
+ @operating_system = operating_system
38
43
  @local_repository_directory = local_repository_directory
39
- @application_config = application_config
44
+ @remote_repository_url = remote_repository_url
45
+ @parser = parser
40
46
  @publish_listeners = publish_listeners
41
47
 
42
- @parser = Fig::Parser.new(application_config, check_include_versions)
43
-
44
48
  initialize_local_repository()
45
49
  reset_cached_data()
46
50
  end
@@ -134,6 +138,8 @@ class Fig::Repository
134
138
  end
135
139
 
136
140
  publisher = Fig::RepositoryPackagePublisher.new
141
+ publisher.application_configuration = @application_configuration
142
+ publisher.options = @options
137
143
  publisher.operating_system = @operating_system
138
144
  publisher.publish_listeners = @publish_listeners
139
145
  publisher.package_statements = package_statements
@@ -165,6 +171,8 @@ class Fig::Repository
165
171
 
166
172
  private
167
173
 
174
+ attr_reader :remote_repository_url
175
+
168
176
  def initialize_local_repository()
169
177
  FileUtils.mkdir_p(@local_repository_directory)
170
178
 
@@ -237,7 +245,9 @@ class Fig::Repository
237
245
  )
238
246
  local_version_file = File.join(temp_dir, "remote-#{VERSION_FILE_NAME}")
239
247
  begin
240
- @operating_system.download(remote_version_file, local_version_file)
248
+ @operating_system.download(
249
+ remote_version_file, local_version_file, :prompt_for_login
250
+ )
241
251
  rescue Fig::FileNotFoundError
242
252
  # The download may create an empty file, so get rid of it.
243
253
  if File.exist?(local_version_file)
@@ -268,10 +278,6 @@ class Fig::Repository
268
278
  return version_string.to_i()
269
279
  end
270
280
 
271
- def remote_repository_url()
272
- return @application_config.remote_repository_url()
273
- end
274
-
275
281
  def should_update?(descriptor)
276
282
  return true if @update_condition == :unconditionally
277
283
 
@@ -308,7 +314,9 @@ class Fig::Repository
308
314
  package_directory = local_directory_for_package(descriptor)
309
315
  local_fig_file = fig_file_for_package_download(package_directory)
310
316
 
311
- if @operating_system.path_up_to_date? remote_fig_file, local_fig_file
317
+ if @operating_system.path_up_to_date?(
318
+ remote_fig_file, local_fig_file, :prompt_for_login
319
+ )
312
320
  Fig::Logging.debug \
313
321
  "Skipping update of #{descriptor.to_string} because it looks like #{local_fig_file} is up-to-date."
314
322
  return
@@ -328,7 +336,9 @@ class Fig::Repository
328
336
  FileUtils.mkdir_p temporary_package
329
337
  end
330
338
 
331
- return if ! @operating_system.download(remote_fig_file, temp_fig_file)
339
+ return if ! @operating_system.download(
340
+ remote_fig_file, temp_fig_file, :prompt_for_login
341
+ )
332
342
 
333
343
  package = read_package_from_directory(temporary_package, descriptor)
334
344
 
@@ -402,12 +412,14 @@ class Fig::Repository
402
412
  end
403
413
  content = File.read(file_name)
404
414
 
405
- package = @parser.parse_package(
406
- descriptor,
407
- runtime_for_package(descriptor),
408
- descriptor.to_string(),
409
- content
410
- )
415
+ unparsed_package = Fig::NotYetParsedPackage.new
416
+ unparsed_package.descriptor = descriptor
417
+ unparsed_package.working_directory = unparsed_package.base_directory =
418
+ runtime_for_package(descriptor)
419
+ unparsed_package.source_description = descriptor.to_string()
420
+ unparsed_package.unparsed_text = content
421
+
422
+ package = @parser.parse_package(unparsed_package)
411
423
 
412
424
  @package_cache.add_package(package)
413
425
 
@@ -4,11 +4,12 @@ require 'socket'
4
4
  require 'sys/admin'
5
5
  require 'tmpdir'
6
6
 
7
- require 'fig/statement/synthetic_raw_text'
8
7
  require 'fig'
9
8
  require 'fig/at_exit'
9
+ require 'fig/external_program'
10
10
  require 'fig/file_not_found_error'
11
11
  require 'fig/logging'
12
+ require 'fig/not_yet_parsed_package'
12
13
  require 'fig/package_cache'
13
14
  require 'fig/package_definition_text_assembler'
14
15
  require 'fig/package_descriptor'
@@ -19,12 +20,16 @@ require 'fig/repository_error'
19
20
  require 'fig/statement/archive'
20
21
  require 'fig/statement/grammar_version'
21
22
  require 'fig/statement/resource'
23
+ require 'fig/statement/synthetic_raw_text'
22
24
  require 'fig/url'
25
+ require 'fig/user_input_error'
23
26
 
24
27
  module Fig; end
25
28
 
26
29
  # Handles package publishing for the Repository.
27
30
  class Fig::RepositoryPackagePublisher
31
+ attr_writer :application_configuration
32
+ attr_writer :options
28
33
  attr_writer :operating_system
29
34
  attr_writer :publish_listeners
30
35
  attr_writer :descriptor
@@ -123,12 +128,15 @@ class Fig::RepositoryPackagePublisher
123
128
 
124
129
  published_package = nil
125
130
  begin
126
- published_package = Fig::Parser.new(nil, false).parse_package(
127
- @descriptor,
128
- @runtime_for_package,
129
- '<package to be published>',
130
- file_content
131
- )
131
+ unparsed_package = Fig::NotYetParsedPackage.new
132
+ unparsed_package.descriptor = @descriptor
133
+ unparsed_package.working_directory = unparsed_package.base_directory =
134
+ @runtime_for_package
135
+ unparsed_package.source_description = '<package to be published>'
136
+ unparsed_package.unparsed_text = file_content
137
+
138
+ published_package =
139
+ Fig::Parser.new(nil, false).parse_package(unparsed_package)
132
140
  rescue Fig::PackageParseError => error
133
141
  raise \
134
142
  "Bug in code! Could not parse package definition to be published.\n" +
@@ -152,11 +160,124 @@ class Fig::RepositoryPackagePublisher
152
160
  @text_assembler.add_header %Q<# Host: #{@publish_host}>
153
161
  @text_assembler.add_header %Q<# Args: "#{ARGV.join %q[", "]}">
154
162
  @text_assembler.add_header %Q<# Fig: v#{Fig::VERSION}>
163
+
164
+ add_environment_variables_to_package_metadata
165
+ add_version_control_to_package_metadata
166
+
155
167
  @text_assembler.add_header %Q<\n>
156
168
 
157
169
  return
158
170
  end
159
171
 
172
+ def add_environment_variables_to_package_metadata()
173
+ variables = @application_configuration[
174
+ 'environment variables to include in comments in published packages'
175
+ ]
176
+ return if ! variables || variables.empty?
177
+
178
+ @text_assembler.add_header %q<#>
179
+ @text_assembler.add_header %q<# Values of some environment variables at time of publish:>
180
+ @text_assembler.add_header %q<#>
181
+ variables.each do
182
+ |variable|
183
+
184
+ value = ENV[variable]
185
+ if value.nil?
186
+ value = ' was unset.'
187
+ else
188
+ value = "=#{value}"
189
+ end
190
+
191
+ @text_assembler.add_header %Q<# #{variable}#{value}>
192
+ end
193
+
194
+ return
195
+ end
196
+
197
+ def add_version_control_to_package_metadata()
198
+ return if @options.suppress_vcs_comments_in_published_packages?
199
+
200
+ add_subversion_url_to_package_metadata()
201
+ add_git_url_to_package_metadata()
202
+
203
+ return
204
+ end
205
+
206
+ def add_subversion_url_to_package_metadata()
207
+ output = get_subversion_working_directory_info
208
+ return if not output =~ /^URL: +(.*\S)\s*$/
209
+ url = $1
210
+
211
+ @text_assembler.add_header %q<#>
212
+ @text_assembler.add_header(
213
+ %Q<# Publish happened in a Subversion working directory from\n# #{url}.>
214
+ )
215
+
216
+ return
217
+ end
218
+
219
+ def get_subversion_working_directory_info()
220
+ executable =
221
+ get_version_control_executable('FIG_SVN_EXECUTABLE', 'svn') or return
222
+ return run_version_control_command(
223
+ [executable, 'info'], 'Subversion', 'FIG_SVN_EXECUTABLE'
224
+ )
225
+ end
226
+
227
+ def run_version_control_command(command, version_control_name, variable)
228
+ begin
229
+ output, errors, result = Fig::ExternalProgram.capture command
230
+ rescue Errno::ENOENT => error
231
+ Fig::Logging.warn(
232
+ %Q<Could not run "#{command.join ' '}": #{error.message}. Set #{variable} to the path to use for #{version_control_name} or to the empty string to suppress #{version_control_name} support.>
233
+ )
234
+ return
235
+ end
236
+
237
+ if result && ! result.success?
238
+ Fig::Logging.debug(
239
+ %Q<Could not run "#{command.join ' '}": #{result}: #{errors}>
240
+ )
241
+
242
+ return
243
+ end
244
+
245
+ return output
246
+ end
247
+
248
+ def get_version_control_executable(variable, default)
249
+ executable = ENV[variable]
250
+ if ! executable || executable.empty?
251
+ return if ENV.include? variable
252
+ return default
253
+ end
254
+
255
+ return executable
256
+ end
257
+
258
+ def add_git_url_to_package_metadata()
259
+ url = get_git_origin_url or return
260
+ url.strip!
261
+ return if url.empty?
262
+
263
+ @text_assembler.add_header %q<#>
264
+ @text_assembler.add_header(
265
+ %Q<# Publish happened in a Git working directory from\n# #{url}.>
266
+ )
267
+
268
+ return
269
+ end
270
+
271
+ def get_git_origin_url()
272
+ executable =
273
+ get_version_control_executable('FIG_GIT_EXECUTABLE', 'git') or return
274
+ return run_version_control_command(
275
+ [executable, 'config', '--get', 'remote.origin.url'],
276
+ 'Git',
277
+ 'FIG_GIT_EXECUTABLE'
278
+ )
279
+ end
280
+
160
281
  # Deals with Archive and Resource statements. It downloads any remote
161
282
  # files (those where the statement references a URL as opposed to a local
162
283
  # file) and then copies all files into the local repository and the remote
@@ -256,7 +377,7 @@ class Fig::RepositoryPackagePublisher
256
377
  asset_local = File.join(publish_temp_dir(), asset_name)
257
378
 
258
379
  begin
259
- @operating_system.download(asset_statement.location, asset_local)
380
+ @operating_system.download(asset_statement.location, asset_local, false)
260
381
  rescue Fig::FileNotFoundError
261
382
  Fig::Logging.fatal "Could not download #{asset_statement.location}."
262
383
  raise Fig::RepositoryError.new