fig 0.1.67 → 0.1.69

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,187 @@
1
+ require 'optparse'
2
+
3
+ require 'fig/command/option_error'
4
+
5
+ module Fig; end
6
+ class Fig::Command; end
7
+ class Fig::Command::Options; end
8
+
9
+ # Command-line processing.
10
+ class Fig::Command::Options::Parser
11
+ # This class knows way too much about how OptionParser works.
12
+
13
+ USAGE = <<-EOF
14
+ Usage:
15
+
16
+ fig [...] [DESCRIPTOR] [--update | --update-if-missing] [-- COMMAND]
17
+ fig [...] [DESCRIPTOR] [--update | --update-if-missing] [--command-extra-args VALUES]
18
+
19
+ fig {--publish | --publish-local} DESCRIPTOR
20
+ [--resource PATH]
21
+ [--archive PATH]
22
+ [--include DESCRIPTOR]
23
+ [--override DESCRIPTOR]
24
+ [--force]
25
+ [...]
26
+
27
+ fig --clean DESCRIPTOR [...]
28
+
29
+ fig --get VARIABLE [DESCRIPTOR] [...]
30
+ fig --list-configs [DESCRIPTOR] [...]
31
+ fig --list-dependencies [--list-tree] [--list-all-configs] [DESCRIPTOR] [...]
32
+ fig --list-variables [--list-tree] [--list-all-configs] [DESCRIPTOR] [...]
33
+ fig {--list-local | --list-remote} [...]
34
+
35
+ fig {--version | --help}
36
+
37
+
38
+ A DESCRIPTOR looks like <package name>[/<version>][:<config>] e.g. "foo",
39
+ "foo/1.2.3", and "foo/1.2.3:default". Whether ":<config>" and "/<version>" are
40
+ required or allowed is dependent upon what your are doing.
41
+
42
+ Standard options (represented as "[...]" above):
43
+
44
+ [--set VARIABLE=VALUE]
45
+ [--append VARIABLE=VALUE]
46
+ [--file PATH] [--no-file]
47
+ [--config CONFIG]
48
+ [--login]
49
+ [--log-level LEVEL] [--log-config PATH]
50
+ [--figrc PATH] [--no-figrc]
51
+ [--suppress-warning-include-statement-missing-version]
52
+
53
+ Environment variables:
54
+
55
+ FIG_REMOTE_URL (required),
56
+ FIG_HOME (path to local repository cache, defaults to $HOME/.fighome).
57
+ EOF
58
+
59
+ def initialize()
60
+ @switches = {}
61
+ @argument_description = {}
62
+ @parser = OptionParser.new
63
+
64
+ @parser.banner = "#{USAGE}\n"
65
+ end
66
+
67
+ def add_argument_description(options, description)
68
+ if options.is_a? Array
69
+ options.each do
70
+ |option|
71
+
72
+ @argument_description[option] = description
73
+ end
74
+ else
75
+ @argument_description[options] = description
76
+ end
77
+
78
+ return
79
+ end
80
+
81
+ def on_head(*arguments, &block)
82
+ switch_array = make_switch_array(arguments, block)
83
+
84
+ return if not switch_array
85
+
86
+ @parser.top.prepend(*switch_array)
87
+
88
+ return
89
+ end
90
+
91
+ def on(*arguments, &block)
92
+ switch_array = make_switch_array(arguments, block)
93
+
94
+ return if not switch_array
95
+
96
+ @parser.top.append(*switch_array)
97
+
98
+ return
99
+ end
100
+
101
+ def on_tail(*arguments, &block)
102
+ switch_array = make_switch_array(arguments, block)
103
+
104
+ return if not switch_array
105
+
106
+ @parser.base.append(*switch_array)
107
+
108
+ return
109
+ end
110
+
111
+ def help()
112
+ return @parser.help
113
+ end
114
+
115
+ def parse!(argv)
116
+ begin
117
+ @parser.parse!(argv)
118
+ rescue OptionParser::InvalidArgument => error
119
+ raise_invalid_argument(error.args[0], error.args[1])
120
+ rescue OptionParser::MissingArgument => error
121
+ raise_missing_argument(error.args[0])
122
+ rescue OptionParser::InvalidOption => error
123
+ raise Fig::Command::OptionError.new(
124
+ "Unknown option #{error.args[0]}.\n\n#{USAGE}"
125
+ )
126
+ rescue OptionParser::ParseError => error
127
+ raise Fig::Command::OptionError.new(error.to_s)
128
+ end
129
+
130
+ return
131
+ end
132
+
133
+ def raise_invalid_argument(option, value)
134
+ # *sigh* OptionParser does not raise MissingArgument for the case of an
135
+ # option with a required value being followed by another option. It
136
+ # assigns the next option as the value instead. E.g. for
137
+ #
138
+ # fig --set --get FOO
139
+ #
140
+ # it assigns "--get" as the value of the "--set" option.
141
+ if @switches.has_key? value
142
+ raise_missing_argument(option)
143
+ end
144
+
145
+ description = @argument_description[option]
146
+ if description.nil?
147
+ description = ''
148
+ else
149
+ description = ' ' + description
150
+ end
151
+
152
+ raise Fig::Command::OptionError.new(
153
+ %Q<Invalid value for #{option}: "#{value}".#{description}>
154
+ )
155
+ end
156
+
157
+ private
158
+
159
+ def make_switch_array(arguments, block)
160
+ # From the OptionParser code, the contents of the array:
161
+ #
162
+ # +switch+:: OptionParser::Switch instance to be inserted.
163
+ # +short_opts+:: List of short style options.
164
+ # +long_opts+:: List of long style options.
165
+ # +nolong_opts+:: List of long style options with "no-" prefix.
166
+ #
167
+ # Why returning this data separate from the Switch object is necessary, I
168
+ # do not understand.
169
+
170
+ switch_array = @parser.make_switch(arguments, block)
171
+ switch = switch_array[0]
172
+
173
+ options = [switch.long, switch.short].flatten
174
+
175
+ return if options.any? {|option| @switches.has_key? option}
176
+
177
+ options.each {|option| @switches[option] = switch}
178
+
179
+ return switch_array
180
+ end
181
+
182
+ def raise_missing_argument(option)
183
+ raise Fig::Command::OptionError.new(
184
+ "Please provide a value for #{option}."
185
+ )
186
+ end
187
+ end
@@ -45,14 +45,14 @@ module Fig::Logging
45
45
  when / [.] ya?ml \z /x
46
46
  Log4r::YamlConfigurator.load_yaml_file(config_file)
47
47
  else
48
- raise ConfigFileError, %Q<Don't know what format #{config_file} is in.>, config_file
48
+ raise Fig::ConfigFileError, %Q<Don't know what format #{config_file} is in.>, config_file
49
49
  end
50
50
 
51
51
  if Log4r::Logger['fig'].nil?
52
52
  $stderr.puts %q<A value was provided for --log-config but no "fig" logger was defined.>
53
53
  end
54
54
  rescue Log4r::ConfigError, ArgumentError => exception
55
- raise Log4rConfigError.new(config_file, exception)
55
+ raise Fig::Log4rConfigError.new(config_file, exception)
56
56
  end
57
57
  end
58
58
 
@@ -242,7 +242,7 @@ class Fig::OperatingSystem
242
242
  end
243
243
  end
244
244
 
245
- def upload(local_file, remote_file, user)
245
+ def upload(local_file, remote_file)
246
246
  Fig::Logging.debug "Uploading #{local_file} to #{remote_file}."
247
247
  uri = URI.parse(remote_file)
248
248
  case uri.scheme
@@ -20,15 +20,15 @@ class Fig::Package
20
20
  DEFAULT_CONFIG = 'default'
21
21
 
22
22
  attr_reader :name, :version, :directory, :statements
23
- attr_accessor :backtrace
23
+ attr_accessor :backtrace, :unparsed_text
24
24
 
25
25
  def initialize(name, version, directory, statements)
26
- @name = name
27
- @version = version
28
- @directory = directory
29
- @statements = statements
26
+ @name = name
27
+ @version = version
28
+ @directory = directory
29
+ @statements = statements
30
30
  @applied_config_names = []
31
- @backtrace = nil
31
+ @backtrace = nil
32
32
  end
33
33
 
34
34
  def [](config_name)
@@ -21,13 +21,13 @@ class Fig::Parser
21
21
  @check_include_versions = check_include_versions
22
22
  end
23
23
 
24
- def parse_package(descriptor, directory, source_description, input)
24
+ def parse_package(descriptor, directory, source_description, unparsed_text)
25
25
  # Bye bye comments.
26
- input = input.gsub(/#.*$/, '')
26
+ stripped_text = unparsed_text.gsub(/#.*$/, '')
27
27
 
28
28
  # Extra space at the end because most of the rules in the grammar require
29
29
  # trailing whitespace.
30
- result = @treetop_parser.parse(input + ' ')
30
+ result = @treetop_parser.parse(stripped_text + ' ')
31
31
 
32
32
  extended_description =
33
33
  extend_source_description(directory, source_description)
@@ -40,6 +40,7 @@ class Fig::Parser
40
40
  directory,
41
41
  Fig::ParserPackageBuildState.new(descriptor, extended_description)
42
42
  )
43
+ package.unparsed_text = unparsed_text
43
44
 
44
45
  check_for_bad_urls(package, descriptor)
45
46
  check_for_multiple_command_statements(package)
@@ -1,3 +1,4 @@
1
+ require 'fileutils'
1
2
  require 'set'
2
3
  require 'socket'
3
4
  require 'sys/admin'
@@ -5,16 +6,13 @@ require 'tmpdir'
5
6
 
6
7
  require 'fig'
7
8
  require 'fig/at_exit'
8
- require 'fig/command'
9
9
  require 'fig/logging'
10
10
  require 'fig/not_found_error'
11
11
  require 'fig/package_cache'
12
12
  require 'fig/package_descriptor'
13
13
  require 'fig/parser'
14
14
  require 'fig/repository_error'
15
- require 'fig/statement/archive'
16
- require 'fig/statement/resource'
17
- require 'fig/url_access_error'
15
+ require 'fig/repository_package_publisher'
18
16
 
19
17
  module Fig; end
20
18
 
@@ -22,6 +20,7 @@ module Fig; end
22
20
  # defers remote operations to others.
23
21
  class Fig::Repository
24
22
  METADATA_SUBDIRECTORY = '_meta'
23
+ PACKAGE_FILE_IN_REPO = '.fig'
25
24
  RESOURCES_FILE = 'resources.tar.gz'
26
25
  VERSION_FILE_NAME = 'repository-format-version'
27
26
  VERSION_SUPPORTED = 1
@@ -34,13 +33,13 @@ class Fig::Repository
34
33
  os,
35
34
  local_repository_directory,
36
35
  application_config,
37
- remote_repository_user,
36
+ publish_listeners,
38
37
  check_include_versions
39
38
  )
40
39
  @operating_system = os
41
40
  @local_repository_directory = local_repository_directory
42
41
  @application_config = application_config
43
- @remote_repository_user = remote_repository_user
42
+ @publish_listeners = publish_listeners
44
43
 
45
44
  @parser = Fig::Parser.new(application_config, check_include_versions)
46
45
 
@@ -123,38 +122,31 @@ class Fig::Repository
123
122
  return
124
123
  end
125
124
 
126
- def publish_package(package_statements, descriptor, local_only)
125
+ def publish_package(
126
+ package_statements, descriptor, local_only, source_package, was_forced
127
+ )
127
128
  check_local_repository_format()
128
129
  if not local_only
129
130
  check_remote_repository_format()
130
131
  end
131
132
 
132
- validate_asset_names(package_statements)
133
-
134
- temp_dir = publish_temp_dir()
135
- @operating_system.delete_and_recreate_directory(temp_dir)
136
- local_dir = local_dir_for_package(descriptor)
137
- @operating_system.delete_and_recreate_directory(local_dir)
138
- fig_file = File.join(temp_dir, PACKAGE_FILE_IN_REPO)
139
- content = publish_package_content_and_derive_dot_fig_contents(
140
- package_statements, descriptor, local_dir, local_only
141
- )
142
- @operating_system.write(fig_file, content)
143
-
144
- if not local_only
145
- @operating_system.upload(
146
- fig_file,
147
- remote_fig_file_for_package(descriptor),
148
- @remote_repository_user
149
- )
150
- end
151
- @operating_system.copy(
152
- fig_file, local_fig_file_for_package(descriptor)
153
- )
154
-
155
- FileUtils.rm_rf(temp_dir)
156
-
157
- return true
133
+ publisher = Fig::RepositoryPackagePublisher.new
134
+ publisher.operating_system = @operating_system
135
+ publisher.publish_listeners = @publish_listeners
136
+ publisher.package_statements = package_statements
137
+ publisher.descriptor = descriptor
138
+ publisher.source_package = source_package
139
+ publisher.was_forced = was_forced
140
+ publisher.base_temp_dir = base_temp_dir
141
+ publisher.local_dir_for_package = local_dir_for_package(descriptor)
142
+ publisher.remote_dir_for_package = remote_dir_for_package(descriptor)
143
+ publisher.local_only = local_only
144
+ publisher.local_fig_file_for_package =
145
+ local_fig_file_for_package(descriptor)
146
+ publisher.remote_fig_file_for_package =
147
+ remote_fig_file_for_package(descriptor)
148
+
149
+ return publisher.publish_package()
158
150
  end
159
151
 
160
152
  def update_unconditionally()
@@ -167,12 +159,8 @@ class Fig::Repository
167
159
 
168
160
  private
169
161
 
170
- PACKAGE_FILE_IN_REPO = '.fig'
171
-
172
162
  def initialize_local_repository()
173
- if not File.exist?(@local_repository_directory)
174
- Dir.mkdir(@local_repository_directory)
175
- end
163
+ FileUtils.mkdir_p(@local_repository_directory)
176
164
 
177
165
  version_file = local_version_file()
178
166
  if not File.exist?(version_file)
@@ -261,31 +249,6 @@ class Fig::Repository
261
249
  return version_string.to_i()
262
250
  end
263
251
 
264
- def validate_asset_names(package_statements)
265
- asset_statements = package_statements.select { |s| s.is_asset? }
266
-
267
- asset_names = Set.new()
268
- asset_statements.each do
269
- |statement|
270
-
271
- asset_name = statement.asset_name()
272
- if not asset_name.nil?
273
- if asset_name == RESOURCES_FILE
274
- Fig::Logging.fatal \
275
- %Q<You cannot have an asset with the name "#{RESOURCES_FILE}"#{statement.position_string()} due to Fig implementation details.>
276
- end
277
-
278
- if asset_names.include?(asset_name)
279
- Fig::Logging.fatal \
280
- %Q<Found multiple archives with the name "#{asset_name}"#{statement.position_string()}. If these were allowed, archives would overwrite each other.>
281
- raise Fig::RepositoryError.new
282
- else
283
- asset_names.add(asset_name)
284
- end
285
- end
286
- end
287
- end
288
-
289
252
  def remote_repository_url()
290
253
  return @application_config.remote_repository_url()
291
254
  end
@@ -326,12 +289,16 @@ class Fig::Repository
326
289
  end
327
290
 
328
291
  def install_package(descriptor, temp_dir)
292
+ remote_fig_file = remote_fig_file_for_package(descriptor)
293
+ local_dir = local_dir_for_package(descriptor)
294
+ local_fig_file = fig_file_for_package_download(local_dir)
295
+ return if not @operating_system.download(remote_fig_file, local_fig_file)
296
+
329
297
  @operating_system.delete_and_recreate_directory(temp_dir)
330
298
 
331
- remote_fig_file = remote_fig_file_for_package(descriptor)
332
- local_fig_file = fig_file_for_package_download(temp_dir)
299
+ temp_fig_file = fig_file_for_package_download(temp_dir)
333
300
 
334
- return if not @operating_system.download(remote_fig_file, local_fig_file)
301
+ @operating_system.download(remote_fig_file, temp_fig_file)
335
302
 
336
303
  package = read_package_from_directory(temp_dir, descriptor)
337
304
 
@@ -349,7 +316,6 @@ class Fig::Repository
349
316
  @operating_system.download_resource(resource_url, temp_dir)
350
317
  end
351
318
 
352
- local_dir = local_dir_for_package(descriptor)
353
319
  FileUtils.rm_rf(local_dir)
354
320
  FileUtils.mkdir_p( File.dirname(local_dir) )
355
321
  FileUtils.mv(temp_dir, local_dir)
@@ -357,25 +323,6 @@ class Fig::Repository
357
323
  return
358
324
  end
359
325
 
360
- # 'resources' is an Array of fileglob patterns: ['tmp/foo/file1',
361
- # 'tmp/foo/*.jar']
362
- def expand_globs_from(resources)
363
- expanded_files = []
364
-
365
- resources.each do
366
- |path|
367
-
368
- globbed_files = Dir.glob(path)
369
- if globbed_files.empty?
370
- expanded_files << path
371
- else
372
- expanded_files.concat(globbed_files)
373
- end
374
- end
375
-
376
- return expanded_files
377
- end
378
-
379
326
  def read_package_from_directory(directory, descriptor)
380
327
  dot_fig_file = File.join(directory, PACKAGE_FILE_IN_REPO)
381
328
  if not File.exist?(dot_fig_file)
@@ -432,10 +379,6 @@ class Fig::Repository
432
379
  File.join(@local_repository_directory, 'tmp')
433
380
  end
434
381
 
435
- def publish_temp_dir()
436
- File.join(base_temp_dir(), 'publish')
437
- end
438
-
439
382
  def package_download_temp_dir(descriptor)
440
383
  base_directory = File.join(base_temp_dir(), 'package-download')
441
384
  FileUtils.mkdir_p(base_directory)
@@ -448,161 +391,4 @@ class Fig::Repository
448
391
  def package_missing?(descriptor)
449
392
  not File.exist?(local_fig_file_for_package(descriptor))
450
393
  end
451
-
452
- def publish_package_content_and_derive_dot_fig_contents(
453
- package_statements, descriptor, local_dir, local_only
454
- )
455
- header_strings = derive_package_metadata_comments(
456
- package_statements, descriptor
457
- )
458
- deparsed_statement_strings = publish_package_content(
459
- package_statements, descriptor, local_dir, local_only
460
- )
461
-
462
- statement_strings = [header_strings, deparsed_statement_strings].flatten()
463
- return statement_strings.join("\n").gsub(/\n{3,}/, "\n\n").strip() + "\n"
464
- end
465
-
466
- def derive_package_metadata_comments(package_statements, descriptor)
467
- now = Time.now()
468
-
469
- asset_statements =
470
- package_statements.select { |statement| statement.is_asset? }
471
- asset_strings =
472
- asset_statements.collect { |statement| statement.unparse('# ') }
473
- asset_summary = nil
474
-
475
- if asset_strings.empty?
476
- asset_summary = [
477
- %q<#>,
478
- %q<# There were no asset statements in the unpublished package definition.>
479
- ]
480
- else
481
- asset_summary = [
482
- %q<#>,
483
- %q<# Original asset statements: >,
484
- %q<#>,
485
- asset_strings
486
- ]
487
- end
488
-
489
- return [
490
- %Q<# Publishing information for #{descriptor.to_string()}:>,
491
- %q<#>,
492
- %Q<# Time: #{now} (epoch: #{now.to_i()})>,
493
- %Q<# User: #{Sys::Admin.get_login()}>,
494
- %Q<# Host: #{Socket.gethostname()}>,
495
- %Q<# Args: "#{ARGV.join %q[", "]}">,
496
- %Q<# Fig: v#{Fig::VERSION}>,
497
- asset_summary,
498
- %Q<\n>,
499
- ].flatten()
500
- end
501
-
502
- # Deals with Archive and Resource statements. It downloads any remote
503
- # files (those where the statement references a URL as opposed to a local
504
- # file) and then copies all files into the local repository and the remote
505
- # repository (if not a local-only publish).
506
- #
507
- # Returns the deparsed strings for the resource statements with URLs
508
- # replaced with in-package paths.
509
- def publish_package_content(
510
- package_statements, descriptor, local_dir, local_only
511
- )
512
- return create_resource_archive(package_statements).map do |statement|
513
- if statement.is_asset?
514
- asset_name = statement.asset_name()
515
- asset_remote = "#{remote_dir_for_package(descriptor)}/#{asset_name}"
516
-
517
- if Fig::Repository.is_url?(statement.url)
518
- asset_local = File.join(publish_temp_dir(), asset_name)
519
-
520
- begin
521
- @operating_system.download(statement.url, asset_local)
522
- rescue Fig::NotFoundError
523
- Fig::Logging.fatal "Could not download #{statement.url}."
524
- raise Fig::RepositoryError.new
525
- end
526
- else
527
- asset_local = statement.url
528
- check_asset_path(asset_local)
529
- end
530
-
531
- if not local_only
532
- @operating_system.upload(
533
- asset_local, asset_remote, @remote_repository_user
534
- )
535
- end
536
-
537
- @operating_system.copy(asset_local, local_dir + '/' + asset_name)
538
- if statement.is_a?(Fig::Statement::Archive)
539
- @operating_system.unpack_archive(local_dir, asset_name)
540
- end
541
-
542
- statement.class.new(nil, nil, asset_name).unparse('')
543
- else
544
- statement.unparse('')
545
- end
546
- end
547
- end
548
-
549
- # Grabs all of the Resource statements that don't reference URLs, creates a
550
- # "resources.tar.gz" file containing all the referenced files, strips the
551
- # Resource statements out of the statements, replacing them with a single
552
- # Archive statement. Thus the caller should substitute its set of
553
- # statements with the return value.
554
- def create_resource_archive(package_statements)
555
- asset_paths = []
556
- new_package_statements = package_statements.reject do |statement|
557
- if (
558
- statement.is_a?(Fig::Statement::Resource) &&
559
- ! Fig::Repository.is_url?(statement.url)
560
- )
561
- asset_paths << statement.url
562
- true
563
- else
564
- false
565
- end
566
- end
567
-
568
- if asset_paths.size > 0
569
- asset_paths = expand_globs_from(asset_paths)
570
- check_asset_paths(asset_paths)
571
-
572
- file = RESOURCES_FILE
573
- @operating_system.create_archive(file, asset_paths)
574
- new_package_statements.unshift(
575
- Fig::Statement::Archive.new(nil, nil, file)
576
- )
577
- Fig::AtExit.add { File.delete(file) }
578
- end
579
-
580
- return new_package_statements
581
- end
582
-
583
- def check_asset_path(asset_path)
584
- if not File.exist?(asset_path)
585
- Fig::Logging.fatal "Could not find file #{asset_path}."
586
- raise Fig::RepositoryError.new
587
- end
588
-
589
- return
590
- end
591
-
592
- def check_asset_paths(asset_paths)
593
- non_existing_paths =
594
- asset_paths.select {|path| ! File.exist?(path) && ! File.symlink?(path) }
595
-
596
- if not non_existing_paths.empty?
597
- if non_existing_paths.size > 1
598
- Fig::Logging.fatal "Could not find files: #{ non_existing_paths.join(', ') }"
599
- else
600
- Fig::Logging.fatal "Could not find file #{non_existing_paths[0]}."
601
- end
602
-
603
- raise Fig::RepositoryError.new
604
- end
605
-
606
- return
607
- end
608
394
  end