fig 0.1.67 → 0.1.69

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