fig 0.1.62 → 0.1.64

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 (43) hide show
  1. data/Changes +156 -0
  2. data/VERSION +1 -1
  3. data/bin/fig +9 -2
  4. data/bin/fig-debug +9 -2
  5. data/lib/fig/applicationconfiguration.rb +3 -2
  6. data/lib/fig/atexit.rb +37 -0
  7. data/lib/fig/backtrace.rb +23 -6
  8. data/lib/fig/command.rb +131 -31
  9. data/lib/fig/command/coveragesupport.rb +40 -0
  10. data/lib/fig/command/listing.rb +8 -8
  11. data/lib/fig/command/optionerror.rb +8 -0
  12. data/lib/fig/{options.rb → command/options.rb} +248 -144
  13. data/lib/fig/command/packageload.rb +161 -62
  14. data/lib/fig/configfileerror.rb +2 -0
  15. data/lib/fig/environment.rb +350 -246
  16. data/lib/fig/environmentvariables/casesensitive.rb +1 -1
  17. data/lib/fig/figrc.rb +78 -78
  18. data/lib/fig/grammar.treetop +204 -219
  19. data/lib/fig/log4rconfigerror.rb +2 -0
  20. data/lib/fig/operatingsystem.rb +382 -334
  21. data/lib/fig/package.rb +11 -33
  22. data/lib/fig/packagecache.rb +1 -1
  23. data/lib/fig/packagedescriptor.rb +103 -21
  24. data/lib/fig/packagedescriptorparseerror.rb +16 -0
  25. data/lib/fig/parser.rb +36 -19
  26. data/lib/fig/parserpackagebuildstate.rb +56 -0
  27. data/lib/fig/repository.rb +504 -259
  28. data/lib/fig/statement.rb +30 -12
  29. data/lib/fig/statement/archive.rb +8 -5
  30. data/lib/fig/statement/asset.rb +19 -0
  31. data/lib/fig/statement/command.rb +2 -2
  32. data/lib/fig/statement/configuration.rb +20 -20
  33. data/lib/fig/statement/include.rb +13 -34
  34. data/lib/fig/statement/override.rb +21 -7
  35. data/lib/fig/statement/path.rb +22 -2
  36. data/lib/fig/statement/resource.rb +14 -4
  37. data/lib/fig/statement/retrieve.rb +34 -4
  38. data/lib/fig/statement/set.rb +22 -2
  39. data/lib/fig/workingdirectorymaintainer.rb +197 -0
  40. data/lib/fig/workingdirectorymetadata.rb +45 -0
  41. metadata +52 -46
  42. data/lib/fig/retriever.rb +0 -141
  43. data/lib/fig/statement/publish.rb +0 -15
@@ -37,7 +37,14 @@ class Fig::Package
37
37
  end
38
38
 
39
39
  descriptor = Fig::PackageDescriptor.new(@name, @version, config_name)
40
- message = 'Configuration not found: ' + descriptor.to_string()
40
+ config_description = nil
41
+ if @name.nil? and @version.nil?
42
+ config_description = config_name
43
+ else
44
+ config_description = descriptor.to_string(:use_default_config)
45
+ end
46
+
47
+ message = %Q<There is no "#{config_description}" config.>
41
48
 
42
49
  raise Fig::NoSuchPackageConfigError.new(message, descriptor)
43
50
  end
@@ -58,15 +65,7 @@ class Fig::Package
58
65
  end
59
66
 
60
67
  def retrieves
61
- retrieves = {}
62
-
63
- statements.each do
64
- |statement|
65
-
66
- retrieves[statement.var] = statement.path if statement.is_a?(Fig::Statement::Retrieve)
67
- end
68
-
69
- return retrieves
68
+ return @statements.select { |statement| statement.is_a?(Fig::Statement::Retrieve) }
70
69
  end
71
70
 
72
71
  def archive_urls
@@ -98,6 +97,8 @@ class Fig::Package
98
97
 
99
98
  if statement.is_a?(Fig::Statement::Include)
100
99
  descriptors << statement.resolved_dependency_descriptor(self, backtrace)
100
+ elsif statement.is_a?(Fig::Statement::Override)
101
+ backtrace.add_override(statement)
101
102
  end
102
103
  end
103
104
 
@@ -114,22 +115,6 @@ class Fig::Package
114
115
  return
115
116
  end
116
117
 
117
- # Block will receive a Package and a Statement.
118
- def walk_statements_following_package_dependencies(repository, &block)
119
- @statements.each do |statement|
120
- yield self, statement
121
- statement.walk_statements_following_package_dependencies(
122
- repository, self, nil, &block
123
- )
124
- end
125
-
126
- return
127
- end
128
-
129
- def unparse
130
- return @statements.map { |statement| statement.unparse('') }.join("\n")
131
- end
132
-
133
118
  def ==(other)
134
119
  return false if other.nil?
135
120
 
@@ -182,10 +167,3 @@ class Fig::Package
182
167
  return mine <=> others
183
168
  end
184
169
  end
185
-
186
- # TODO: get this out of the global namespace
187
- def unparse_statements(indent, prefix, statements, suffix)
188
- body = @statements.map { |statement| statement.unparse(indent+' ') }.join("\n")
189
-
190
- return ["\n#{indent}#{prefix}", body, "#{indent}#{suffix}"].join("\n")
191
- end
@@ -1,6 +1,6 @@
1
1
  module Fig; end
2
2
 
3
- # Simple double-level cache of Packages.
3
+ # Simple double-level (name, version) cache of Packages.
4
4
  class Fig::PackageCache
5
5
  def initialize()
6
6
  @packages = {}
@@ -1,10 +1,12 @@
1
+ require 'fig/packagedescriptorparseerror'
2
+
1
3
  module Fig; end
2
4
 
3
- # Parsed representation of a package (name/version:config).
5
+ # Parsed representation of a package specification, i.e. "name/version:config".
4
6
  class Fig::PackageDescriptor
5
7
  include Comparable
6
8
 
7
- attr_reader :name, :version, :config
9
+ attr_reader :name, :version, :config, :original_string
8
10
 
9
11
  def self.format(name, version, config, use_default_config = false)
10
12
  string = name || ''
@@ -24,34 +26,45 @@ class Fig::PackageDescriptor
24
26
  return string
25
27
  end
26
28
 
27
- def self.parse(raw_string)
29
+ def self.parse(raw_string, options = {})
30
+ filled_in_options = {}
31
+ filled_in_options.merge!(options)
32
+ filled_in_options[:original_string] = raw_string
33
+ filled_in_options[:require_at_least_one_component] = true
34
+
28
35
  # Additional checks in validate_component() will take care of the looseness
29
36
  # of the regexes. These just need to ensure that the entire string gets
30
37
  # assigned to one component or another.
31
-
32
- self.new(
38
+ return self.new(
33
39
  raw_string =~ %r< \A ( [^:/]+ ) >x ? $1 : nil,
34
40
  raw_string =~ %r< \A [^/]* / ( [^:]+ ) >x ? $1 : nil,
35
- raw_string =~ %r< \A [^:]* : ( .+ ) \z >x ? $1 : nil
41
+ raw_string =~ %r< \A [^:]* : ( .+ ) \z >x ? $1 : nil,
42
+ filled_in_options
36
43
  )
37
44
  end
38
45
 
39
- def initialize(name, version, config)
40
- validate_component name, 'name'
41
- validate_component version, 'version'
42
- validate_component config, 'config'
43
-
44
- @name = name
45
- @version = version
46
- @config = config
47
- end
48
-
49
- def validate_component(value, name)
50
- return if value.nil?
51
-
52
- return if value =~ / \A [a-zA-Z0-9_.-]+ \z /x
46
+ # Options are:
47
+ #
48
+ # :name => { :required | :forbidden }
49
+ # :version => { :required | :forbidden }
50
+ # :config => { :required | :forbidden }
51
+ # :original_string => the unparsed form
52
+ # :require_at_least_one_component => should we have at least one of
53
+ # name, version, and config
54
+ # :validation_context => what the descriptor is for
55
+ # :source_description => where the descriptor came from,
56
+ # most likely the result of invoking
57
+ # Fig::Statement.position_description().
58
+ def initialize(name, version, config, options = {})
59
+ @name = name
60
+ @version = version
61
+ @config = config
62
+ @original_string = options[:original_string]
53
63
 
54
- raise %Q<Invalid #{name} for package descriptor: "#{value}".>
64
+ validate_component name, 'name', :name, options
65
+ validate_component version, 'version', :version, options
66
+ validate_component config, 'config', :config, options
67
+ validate_across_components options
55
68
  end
56
69
 
57
70
  # Specifically not named :to_s because it doesn't act like that should.
@@ -64,4 +77,73 @@ class Fig::PackageDescriptor
64
77
  def <=>(other)
65
78
  return to_string() <=> other.to_string()
66
79
  end
80
+
81
+ private
82
+
83
+ def validate_component(
84
+ value, name, presence_requirement_symbol, options
85
+ )
86
+ validate_component_format(value, name, options)
87
+
88
+ case options[presence_requirement_symbol]
89
+ when :required
90
+ if value.nil?
91
+ throw_presence_exception(name, presence_requirement_symbol, options)
92
+ end
93
+ when :forbidden
94
+ if ! value.nil?
95
+ throw_presence_exception(name, presence_requirement_symbol, options)
96
+ end
97
+ else
98
+ # No requirements
99
+ end
100
+
101
+ return
102
+ end
103
+
104
+ def validate_component_format(value, name, options)
105
+ return if value.nil?
106
+
107
+ return if value =~ / \A [a-zA-Z0-9_.-]+ \z /x
108
+
109
+ raise Fig::PackageDescriptorParseError.new(
110
+ %Q<Invalid #{name} ("#{value}")#{standard_exception_suffix(options)}>,
111
+ @original_string
112
+ )
113
+ end
114
+
115
+ def throw_presence_exception(name, presence_requirement_symbol, options)
116
+ presence = options[presence_requirement_symbol]
117
+ raise Fig::PackageDescriptorParseError.new(
118
+ "Package #{name} #{presence}#{standard_exception_suffix(options)}",
119
+ @original_string
120
+ )
121
+ end
122
+
123
+ def validate_across_components(options)
124
+ if ! @version.nil? && @name.nil?
125
+ raise Fig::PackageDescriptorParseError.new(
126
+ "Cannot specify a version without a name#{standard_exception_suffix(options)}",
127
+ @original_string
128
+ )
129
+ end
130
+
131
+ return if not options[:require_at_least_one_component]
132
+ if @name.nil? && @version.nil? && @config.nil?
133
+ raise Fig::PackageDescriptorParseError.new(
134
+ "Must specify at least one of name, version, and config#{standard_exception_suffix(options)}",
135
+ @original_string
136
+ )
137
+ end
138
+
139
+ return
140
+ end
141
+
142
+ def standard_exception_suffix(options)
143
+ original_string = @original_string.nil? ? '' : %Q< ("#{@original_string}")>
144
+ context = options[:validation_context] || ''
145
+ source_description = options[:source_description] || ''
146
+
147
+ return " in package descriptor#{original_string}#{context}#{source_description}."
148
+ end
67
149
  end
@@ -0,0 +1,16 @@
1
+ require 'fig/userinputerror'
2
+
3
+ module Fig
4
+ # Could not turn a string into a PackageDescriptor.
5
+ class PackageDescriptorParseError < UserInputError
6
+ attr_accessor :original_string
7
+
8
+ def initialize(message, original_string)
9
+ super(message)
10
+
11
+ @file = original_string
12
+
13
+ return
14
+ end
15
+ end
16
+ end
@@ -1,10 +1,11 @@
1
- require 'polyglot'
2
1
  require 'treetop'
3
2
 
4
- require 'fig/grammar'
3
+ require 'fig/grammar' # this is grammar.treetop, not grammar.rb.
5
4
  require 'fig/logging'
6
5
  require 'fig/packageparseerror'
6
+ require 'fig/parserpackagebuildstate'
7
7
  require 'fig/repository'
8
+ require 'fig/statement'
8
9
  require 'fig/urlaccesserror'
9
10
  require 'fig/userinputerror'
10
11
 
@@ -13,36 +14,32 @@ module Fig; end
13
14
  # Parses .fig files (wrapping the Treetop-generated parser object) and deals
14
15
  # with a few restrictions on them.
15
16
  class Fig::Parser
16
- def self.node_location(node)
17
- offset_from_start_of_file = node.interval.first
18
- input = node.input
19
-
20
- return [
21
- input.line_of(offset_from_start_of_file),
22
- input.column_of(offset_from_start_of_file)
23
- ]
24
- end
25
-
26
17
  def initialize(application_config, check_include_versions)
27
- @parser = Fig::FigParser.new
18
+ # Fig::FigParser class is synthesized by Treetop.
19
+ @treetop_parser = Fig::FigParser.new
28
20
  @application_config = application_config
29
21
  @check_include_versions = check_include_versions
30
22
  end
31
23
 
32
- def parse_package(descriptor, directory, input)
24
+ def parse_package(descriptor, directory, source_description, input)
33
25
  # Bye bye comments.
34
26
  input = input.gsub(/#.*$/, '')
35
27
 
36
28
  # Extra space at the end because most of the rules in the grammar require
37
29
  # trailing whitespace.
38
- result = @parser.parse(input + ' ')
30
+ result = @treetop_parser.parse(input + ' ')
31
+
32
+ extended_description =
33
+ extend_source_description(directory, source_description)
39
34
 
40
35
  if result.nil?
41
- Fig::Logging.fatal "#{directory}: #{@parser.failure_reason}"
42
- raise Fig::PackageParseError.new("#{directory}: #{@parser.failure_reason}")
36
+ raise_parse_error(extended_description)
43
37
  end
44
38
 
45
- package = result.to_package(descriptor, directory)
39
+ package = result.to_package(
40
+ directory,
41
+ Fig::ParserPackageBuildState.new(descriptor, extended_description)
42
+ )
46
43
 
47
44
  check_for_bad_urls(package, descriptor)
48
45
  check_for_multiple_command_statements(package)
@@ -53,6 +50,26 @@ class Fig::Parser
53
50
 
54
51
  private
55
52
 
53
+ def extend_source_description(directory, original_description)
54
+ if original_description
55
+ extended = original_description
56
+ if directory != '.'
57
+ extended << " (#{directory})"
58
+ end
59
+
60
+ return extended
61
+ end
62
+
63
+ return directory
64
+ end
65
+
66
+ def raise_parse_error(extended_description)
67
+ message = extended_description
68
+ message << ": #{@treetop_parser.failure_reason}"
69
+
70
+ raise Fig::PackageParseError.new(message)
71
+ end
72
+
56
73
  def check_for_bad_urls(package, descriptor)
57
74
  bad_urls = []
58
75
  package.walk_statements do |statement|
@@ -73,7 +90,7 @@ class Fig::Parser
73
90
  package.walk_statements do |statement|
74
91
  if statement.is_a?(Fig::Statement::Command)
75
92
  if command_processed == true
76
- raise Fig::UserInputError.new(
93
+ raise Fig::PackageParseError.new(
77
94
  %Q<Found a second "command" statement within a "config" block#{statement.position_string()}.>
78
95
  )
79
96
  end
@@ -0,0 +1,56 @@
1
+ require 'fig/packageparseerror'
2
+ require 'fig/statement'
3
+
4
+ module Fig; end
5
+
6
+ class Fig::ParserPackageBuildState
7
+ attr_reader :descriptor
8
+ attr_reader :source_description
9
+
10
+ def initialize(descriptor, source_description)
11
+ @descriptor = descriptor
12
+ @source_description = source_description
13
+ end
14
+
15
+ def node_location(node)
16
+ offset_from_start_of_file = node.interval.first
17
+ input = node.input
18
+
19
+ return [
20
+ input.line_of(offset_from_start_of_file),
21
+ input.column_of(offset_from_start_of_file)
22
+ ]
23
+ end
24
+
25
+ # This method is necessary due to ruby v1.8 not allowing array splat
26
+ # notation, i.e. Fig::Statement.position_description(*node_location(node),
27
+ # source_description)
28
+ def node_location_description(node)
29
+ location = node_location(node)
30
+
31
+ return Fig::Statement.position_description(
32
+ location[0], location[1], source_description
33
+ )
34
+ end
35
+
36
+ def new_environment_variable_statement(
37
+ statement_class, keyword_node, value_node
38
+ )
39
+ name, value = statement_class.parse_name_value(value_node.text_value) {
40
+ raise_invalid_value_parse_error(
41
+ keyword_node,
42
+ value_node,
43
+ statement_class.const_get(:ARGUMENT_DESCRIPTION)
44
+ )
45
+ }
46
+ return statement_class.new(
47
+ node_location(keyword_node), source_description, name, value
48
+ )
49
+ end
50
+
51
+ def raise_invalid_value_parse_error(keyword_node, value_node, description)
52
+ raise Fig::PackageParseError.new(
53
+ %Q<Invalid value for #{keyword_node.text_value} statement: "#{value_node.text_value}" #{description}#{node_location_description(value_node)}>
54
+ )
55
+ end
56
+ end
@@ -1,6 +1,10 @@
1
+ require 'set'
1
2
  require 'socket'
2
3
  require 'sys/admin'
4
+ require 'tmpdir'
3
5
 
6
+ require 'fig/atexit'
7
+ require 'fig/command'
4
8
  require 'fig/logging'
5
9
  require 'fig/notfounderror'
6
10
  require 'fig/packagecache'
@@ -11,353 +15,594 @@ require 'fig/statement/archive'
11
15
  require 'fig/statement/resource'
12
16
  require 'fig/urlaccesserror'
13
17
 
14
- module Fig
15
- # Overall management of a repository. Handles local operations itself;
16
- # defers remote operations to others.
17
- class Repository
18
- METADATA_SUBDIRECTORY = '_meta'
18
+ module Fig; end
19
19
 
20
- def self.is_url?(url)
21
- not (/ftp:\/\/|http:\/\/|file:\/\/|ssh:\/\// =~ url).nil?
22
- end
20
+ # Overall management of a repository. Handles local operations itself;
21
+ # defers remote operations to others.
22
+ class Fig::Repository
23
+ METADATA_SUBDIRECTORY = '_meta'
24
+ RESOURCES_FILE = 'resources.tar.gz'
25
+ VERSION_FILE_NAME = 'repository-format-version'
26
+ VERSION_SUPPORTED = 1
23
27
 
24
- def initialize(
25
- os,
26
- local_repository_dir,
27
- remote_repository_url,
28
- application_config,
29
- remote_repository_user,
30
- update,
31
- update_if_missing,
32
- check_include_versions
33
- )
34
- @operating_system = os
35
- @local_repository_dir = local_repository_dir
36
- @remote_repository_url = remote_repository_url
37
- @remote_repository_user = remote_repository_user
38
- @update = update
39
- @update_if_missing = update_if_missing
28
+ def self.is_url?(url)
29
+ not (/ftp:\/\/|http:\/\/|file:\/\/|ssh:\/\// =~ url).nil?
30
+ end
40
31
 
41
- @parser = Parser.new(application_config, check_include_versions)
32
+ def initialize(
33
+ os,
34
+ local_repository_directory,
35
+ application_config,
36
+ remote_repository_user,
37
+ update,
38
+ update_if_missing,
39
+ check_include_versions
40
+ )
41
+ @operating_system = os
42
+ @local_repository_directory = local_repository_directory
43
+ @application_config = application_config
44
+ @remote_repository_user = remote_repository_user
45
+ @update = update
46
+ @update_if_missing = update_if_missing
47
+
48
+ @parser = Fig::Parser.new(application_config, check_include_versions)
49
+
50
+ initialize_local_repository()
51
+ reset_cached_data()
52
+ end
42
53
 
43
- reset_cached_data()
44
- end
54
+ def reset_cached_data()
55
+ @package_cache = Fig::PackageCache.new()
56
+ end
45
57
 
46
- def reset_cached_data()
47
- @packages = PackageCache.new()
48
- end
58
+ def list_packages
59
+ check_local_repository_format()
49
60
 
50
- def list_packages
51
- results = []
52
- if File.exist?(@local_repository_dir)
53
- @operating_system.list(@local_repository_dir).each do |name|
54
- @operating_system.list(File.join(@local_repository_dir, name)).each do |version|
55
- results << PackageDescriptor.format(name, version, nil)
56
- end
61
+ results = []
62
+ if File.exist?(local_package_directory())
63
+ @operating_system.list(local_package_directory()).each do |name|
64
+ @operating_system.list(File.join(local_package_directory(), name)).each do
65
+ |version|
66
+ results << Fig::PackageDescriptor.format(name, version, nil)
57
67
  end
58
68
  end
59
-
60
- return results
61
69
  end
62
70
 
63
- def list_remote_packages
64
- paths = @operating_system.download_list(@remote_repository_url)
71
+ return results
72
+ end
65
73
 
66
- return paths.reject { |path| path =~ %r< ^ #{METADATA_SUBDIRECTORY} / >xs }
67
- end
74
+ def list_remote_packages
75
+ check_remote_repository_format()
68
76
 
69
- def get_package(
70
- descriptor,
71
- allow_any_version = false
72
- )
73
- if not descriptor.version
74
- if allow_any_version
75
- package = @packages.get_any_version_of_package(descriptor.name)
76
- if package
77
- Logging.warn(
78
- "Picked version #{package.version} of #{package.name} at random."
79
- )
80
- return package
81
- end
82
- end
77
+ paths = @operating_system.download_list(remote_repository_url())
78
+
79
+ return paths.reject { |path| path =~ %r< ^ #{METADATA_SUBDIRECTORY} / >xs }
80
+ end
83
81
 
84
- raise RepositoryError.new(
85
- %Q<Cannot retrieve "#{descriptor.name}" without a version.>
86
- )
82
+ def get_package(
83
+ descriptor,
84
+ allow_any_version = false
85
+ )
86
+ check_local_repository_format()
87
+
88
+ if not descriptor.version
89
+ if allow_any_version
90
+ package = @package_cache.get_any_version_of_package(descriptor.name)
91
+ if package
92
+ Fig::Logging.warn(
93
+ "Picked version #{package.version} of #{package.name} at random."
94
+ )
95
+ return package
96
+ end
87
97
  end
88
98
 
89
- package = @packages.get_package(descriptor.name, descriptor.version)
90
- return package if package
99
+ raise Fig::RepositoryError.new(
100
+ %Q<Cannot retrieve "#{descriptor.name}" without a version.>
101
+ )
102
+ end
91
103
 
92
- Logging.debug \
93
- "Considering #{PackageDescriptor.format(descriptor.name, descriptor.version, nil)}."
104
+ package = @package_cache.get_package(descriptor.name, descriptor.version)
105
+ return package if package
94
106
 
95
- if should_update?(descriptor)
96
- update_package(descriptor)
97
- end
107
+ Fig::Logging.debug \
108
+ "Considering #{Fig::PackageDescriptor.format(descriptor.name, descriptor.version, nil)}."
109
+
110
+ if should_update?(descriptor)
111
+ check_remote_repository_format()
98
112
 
99
- return read_local_package(descriptor)
113
+ update_package(descriptor)
100
114
  end
101
115
 
102
- def clean(descriptor)
103
- @packages.remove_package(descriptor.name, descriptor.version)
116
+ return read_local_package(descriptor)
117
+ end
118
+
119
+ def clean(descriptor)
120
+ check_local_repository_format()
104
121
 
105
- dir = File.join(@local_repository_dir, descriptor.name)
106
- dir = File.join(dir, descriptor.version) if descriptor.version
122
+ @package_cache.remove_package(descriptor.name, descriptor.version)
107
123
 
108
- FileUtils.rm_rf(dir)
124
+ FileUtils.rm_rf(local_dir_for_package(descriptor))
109
125
 
110
- return
126
+ return
127
+ end
128
+
129
+ def publish_package(package_statements, descriptor, local_only)
130
+ check_local_repository_format()
131
+ if not local_only
132
+ check_remote_repository_format()
111
133
  end
112
134
 
113
- def publish_package(package_statements, descriptor, local_only)
114
- temp_dir = temp_dir_for_package(descriptor)
115
- @operating_system.clear_directory(temp_dir)
116
- local_dir = local_dir_for_package(descriptor)
117
- @operating_system.clear_directory(local_dir)
118
- fig_file = File.join(temp_dir, '.fig')
119
- content = derive_package_content(
120
- package_statements, descriptor, local_dir, local_only
121
- )
122
- @operating_system.write(fig_file, content.join("\n").strip)
135
+ validate_asset_names(package_statements)
136
+
137
+ temp_dir = publish_temp_dir()
138
+ @operating_system.delete_and_recreate_directory(temp_dir)
139
+ local_dir = local_dir_for_package(descriptor)
140
+ @operating_system.delete_and_recreate_directory(local_dir)
141
+ fig_file = File.join(temp_dir, PACKAGE_FILE_IN_REPO)
142
+ content = publish_package_content_and_derive_dot_fig_contents(
143
+ package_statements, descriptor, local_dir, local_only
144
+ )
145
+ @operating_system.write(fig_file, content)
146
+
147
+ if not local_only
123
148
  @operating_system.upload(
124
149
  fig_file,
125
150
  remote_fig_file_for_package(descriptor),
126
151
  @remote_repository_user
127
- ) unless local_only
128
- @operating_system.copy(
129
- fig_file, local_fig_file_for_package(descriptor)
130
152
  )
153
+ end
154
+ @operating_system.copy(
155
+ fig_file, local_fig_file_for_package(descriptor)
156
+ )
131
157
 
132
- FileUtils.rm_rf(temp_dir)
158
+ FileUtils.rm_rf(temp_dir)
159
+
160
+ return true
161
+ end
162
+
163
+ def updating?
164
+ return @update || @update_if_missing
165
+ end
166
+
167
+ private
168
+
169
+ PACKAGE_FILE_IN_REPO = '.fig'
170
+
171
+ def initialize_local_repository()
172
+ if not File.exist?(@local_repository_directory)
173
+ Dir.mkdir(@local_repository_directory)
133
174
  end
134
175
 
135
- def updating?
136
- return @update || @update_if_missing
176
+ version_file = local_version_file()
177
+ if not File.exist?(version_file)
178
+ File.open(version_file, 'w') { |handle| handle.write(VERSION_SUPPORTED) }
137
179
  end
138
180
 
139
- private
181
+ return
182
+ end
140
183
 
141
- def should_update?(descriptor)
142
- return true if @update
184
+ def check_local_repository_format()
185
+ check_repository_format('Local', local_repository_version())
143
186
 
144
- return @update_if_missing && package_missing?(descriptor)
145
- end
187
+ return
188
+ end
189
+
190
+ def check_remote_repository_format()
191
+ check_repository_format('Remote', remote_repository_version())
192
+
193
+ return
194
+ end
146
195
 
147
- def read_local_package(descriptor)
148
- directory = local_dir_for_package(descriptor)
149
- return read_package_from_directory(directory, descriptor)
196
+ def check_repository_format(name, version)
197
+ if version != VERSION_SUPPORTED
198
+ Fig::Logging.fatal \
199
+ "#{name} repository is in version #{version} format. This version of fig can only deal with repositories in version #{VERSION_SUPPORTED} format."
200
+ raise Fig::RepositoryError.new
150
201
  end
151
202
 
152
- def bundle_resources(package_statements)
153
- resources = []
154
- new_package_statements = package_statements.reject do |statement|
155
- if (
156
- statement.is_a?(Statement::Resource) &&
157
- ! Repository.is_url?(statement.url)
158
- )
159
- resources << statement.url
160
- true
161
- else
162
- false
163
- end
164
- end
203
+ return
204
+ end
165
205
 
166
- if resources.size > 0
167
- resources = expand_globs_from(resources)
168
- file = 'resources.tar.gz'
169
- @operating_system.create_archive(file, resources)
170
- new_package_statements.unshift(Statement::Archive.new(nil, file))
171
- at_exit { File.delete(file) }
172
- end
206
+ def local_repository_version()
207
+ if @local_repository_version.nil?
208
+ version_file = local_version_file()
173
209
 
174
- return new_package_statements
210
+ @local_repository_version =
211
+ parse_repository_version(version_file, version_file)
175
212
  end
176
213
 
177
- def install_package(descriptor)
178
- temp_dir = nil
214
+ return @local_repository_version
215
+ end
216
+
217
+ def local_version_file()
218
+ return File.join(@local_repository_directory, VERSION_FILE_NAME)
219
+ end
179
220
 
221
+ def local_package_directory()
222
+ return File.expand_path(File.join(@local_repository_directory, 'repos'))
223
+ end
224
+
225
+ def remote_repository_version()
226
+ if @remote_repository_version.nil?
227
+ temp_dir = base_temp_dir()
228
+ @operating_system.delete_and_recreate_directory(temp_dir)
229
+ remote_version_file = "#{remote_repository_url()}/#{VERSION_FILE_NAME}"
230
+ local_version_file = File.join(temp_dir, "remote-#{VERSION_FILE_NAME}")
180
231
  begin
181
- package = read_local_package(descriptor)
182
- temp_dir = temp_dir_for_package(descriptor)
183
- @operating_system.clear_directory(temp_dir)
184
- package.archive_urls.each do |archive_url|
185
- if not Repository.is_url?(archive_url)
186
- archive_url = remote_dir_for_package(descriptor) + '/' + archive_url
187
- end
188
- @operating_system.download_archive(archive_url, File.join(temp_dir))
189
- end
190
- package.resource_urls.each do |resource_url|
191
- if not Repository.is_url?(resource_url)
192
- resource_url =
193
- remote_dir_for_package(descriptor) + '/' + resource_url
194
- end
195
- @operating_system.download_resource(resource_url, File.join(temp_dir))
196
- end
197
- local_dir = local_dir_for_package(descriptor)
198
- @operating_system.clear_directory(local_dir)
199
- # some packages contain no files, only a fig file.
200
- if not (package.archive_urls.empty? && package.resource_urls.empty?)
201
- FileUtils.mv(Dir.glob(File.join(temp_dir, '*')), local_dir)
202
- end
203
- write_local_package(descriptor, package)
204
- rescue
205
- Logging.fatal 'Install failed, cleaning up.'
206
- delete_local_package(descriptor)
207
- raise RepositoryError.new
208
- ensure
209
- if temp_dir
210
- FileUtils.rm_rf(temp_dir)
232
+ @operating_system.download(remote_version_file, local_version_file)
233
+ rescue Fig::NotFoundError
234
+ # The download may create an empty file, so get rid of it.
235
+ if File.exist?(local_version_file)
236
+ File.unlink(local_version_file)
211
237
  end
212
238
  end
239
+
240
+ @remote_repository_version =
241
+ parse_repository_version(local_version_file, remote_version_file)
213
242
  end
214
243
 
215
- def update_package(descriptor)
216
- remote_fig_file = remote_fig_file_for_package(descriptor)
217
- local_fig_file = local_fig_file_for_package(descriptor)
218
- begin
219
- if @operating_system.download(remote_fig_file, local_fig_file)
220
- install_package(descriptor)
221
- end
222
- rescue NotFoundError
223
- Logging.fatal "Package not found in remote repository: #{descriptor.to_string()}"
224
- delete_local_package(descriptor)
225
- raise RepositoryError.new
226
- end
244
+ return @remote_repository_version
245
+ end
246
+
247
+ def parse_repository_version(version_file, description)
248
+ if not File.exist?(version_file)
249
+ return 1 # Since there was no version file early in Fig development.
227
250
  end
228
251
 
229
- # 'resources' is an Array of fileglob patterns: ['tmp/foo/file1',
230
- # 'tmp/foo/*.jar']
231
- def expand_globs_from(resources)
232
- expanded_files = []
233
- resources.each {|f| expanded_files.concat(Dir.glob(f))}
234
- expanded_files
252
+ version_string = IO.read(version_file)
253
+ version_string.strip!()
254
+ if version_string !~ / \A \d+ \z /x
255
+ Fig::Logging.fatal \
256
+ %Q<Could not parse the contents of "#{description}" ("#{version_string}") as a version.>
257
+ raise Fig::RepositoryError.new
235
258
  end
236
259
 
237
- def read_package_from_directory(dir, descriptor)
238
- file = nil
239
- dot_fig_file = File.join(dir, '.fig')
240
- if File.exist?(dot_fig_file)
241
- file = dot_fig_file
242
- else
243
- package_dot_fig_file = File.join(dir, 'package.fig')
244
- if not File.exist?(package_dot_fig_file)
245
- Logging.fatal %Q<Fig file not found for package "#{descriptor.name || '<unnamed>'}". Looked for "#{dot_fig_file}" and "#{package_dot_fig_file}" and found neither.>
246
- raise RepositoryError.new
260
+ return version_string.to_i()
261
+ end
262
+
263
+ def validate_asset_names(package_statements)
264
+ asset_statements = package_statements.select { |s| s.is_asset? }
265
+
266
+ asset_names = Set.new()
267
+ asset_statements.each do
268
+ |statement|
269
+
270
+ asset_name = statement.asset_name()
271
+ if not asset_name.nil?
272
+ if asset_name == RESOURCES_FILE
273
+ Fig::Logging.fatal \
274
+ %Q<You cannot have an asset with the name "#{RESOURCES_FILE}"#{statement.position_string()} due to Fig implementation details.>
247
275
  end
248
276
 
249
- file = package_dot_fig_file
277
+ if asset_names.include?(asset_name)
278
+ Fig::Logging.fatal \
279
+ %Q<Found multiple archives with the name "#{asset_name}"#{statement.position_string()}. If these were allowed, archives would overwrite each other.>
280
+ raise Fig::RepositoryError.new
281
+ else
282
+ asset_names.add(asset_name)
283
+ end
250
284
  end
251
-
252
- return read_package_from_file(file, descriptor)
253
285
  end
286
+ end
254
287
 
255
- def read_package_from_file(file_name, descriptor)
256
- if not File.exist?(file_name)
257
- Logging.fatal "Package not found: #{descriptor.to_string()}"
258
- raise RepositoryError.new
259
- end
260
- content = File.read(file_name)
288
+ def remote_repository_url()
289
+ return @application_config.remote_repository_url()
290
+ end
261
291
 
262
- package = @parser.parse_package(
263
- descriptor, File.dirname(file_name), content
264
- )
292
+ def should_update?(descriptor)
293
+ return true if @update
265
294
 
266
- @packages.add_package(package)
295
+ return @update_if_missing && package_missing?(descriptor)
296
+ end
267
297
 
268
- return package
269
- end
298
+ def read_local_package(descriptor)
299
+ directory = local_dir_for_package(descriptor)
300
+ return read_package_from_directory(directory, descriptor)
301
+ end
270
302
 
271
- def delete_local_package(descriptor)
272
- FileUtils.rm_rf(local_dir_for_package(descriptor))
273
- end
303
+ def update_package(descriptor)
304
+ temp_dir = package_download_temp_dir(descriptor)
305
+ begin
306
+ install_package(descriptor, temp_dir)
307
+ rescue Fig::NotFoundError
308
+ Fig::Logging.fatal \
309
+ "Package not found in remote repository: #{descriptor.to_string()}"
274
310
 
275
- def write_local_package(descriptor, package)
276
- file = local_fig_file_for_package(descriptor)
277
- @operating_system.write(file, package.unparse)
278
- end
311
+ delete_local_package(descriptor)
279
312
 
280
- def remote_fig_file_for_package(descriptor)
281
- "#{@remote_repository_url}/#{descriptor.name}/#{descriptor.version}/.fig"
282
- end
313
+ raise Fig::RepositoryError.new
314
+ rescue StandardError => exception
315
+ Fig::Logging.debug exception
316
+ Fig::Logging.fatal 'Install failed, cleaning up.'
283
317
 
284
- def local_fig_file_for_package(descriptor)
285
- File.join(local_dir_for_package(descriptor), '.fig')
318
+ delete_local_package(descriptor)
319
+
320
+ raise Fig::RepositoryError.new
321
+ ensure
322
+ FileUtils.rm_rf(temp_dir)
286
323
  end
287
324
 
288
- def remote_dir_for_package(descriptor)
289
- "#{@remote_repository_url}/#{descriptor.name}/#{descriptor.version}"
325
+ return
326
+ end
327
+
328
+ def install_package(descriptor, temp_dir)
329
+ @operating_system.delete_and_recreate_directory(temp_dir)
330
+
331
+ remote_fig_file = remote_fig_file_for_package(descriptor)
332
+ local_fig_file = fig_file_for_package_download(temp_dir)
333
+
334
+ return if not @operating_system.download(remote_fig_file, local_fig_file)
335
+
336
+ package = read_package_from_directory(temp_dir, descriptor)
337
+
338
+ package.archive_urls.each do |archive_url|
339
+ if not Fig::Repository.is_url?(archive_url)
340
+ archive_url = remote_dir_for_package(descriptor) + '/' + archive_url
341
+ end
342
+ @operating_system.download_and_unpack_archive(archive_url, temp_dir)
343
+ end
344
+ package.resource_urls.each do |resource_url|
345
+ if not Fig::Repository.is_url?(resource_url)
346
+ resource_url =
347
+ remote_dir_for_package(descriptor) + '/' + resource_url
348
+ end
349
+ @operating_system.download_resource(resource_url, temp_dir)
290
350
  end
291
351
 
292
- def local_dir_for_package(descriptor)
293
- return File.join(
294
- @local_repository_dir, descriptor.name, descriptor.version
295
- )
352
+ local_dir = local_dir_for_package(descriptor)
353
+ FileUtils.rm_rf(local_dir)
354
+ FileUtils.mkdir_p( File.dirname(local_dir) )
355
+ FileUtils.mv(temp_dir, local_dir)
356
+
357
+ return
358
+ end
359
+
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
296
374
  end
297
375
 
298
- def temp_dir_for_package(descriptor)
299
- File.join(@local_repository_dir, 'tmp')
376
+ return expanded_files
377
+ end
378
+
379
+ def read_package_from_directory(directory, descriptor)
380
+ dot_fig_file = File.join(directory, PACKAGE_FILE_IN_REPO)
381
+ if not File.exist?(dot_fig_file)
382
+ Fig::Logging.fatal %Q<Fig file not found for package "#{descriptor.name || '<unnamed>'}". There is nothing in "#{dot_fig_file}".>
383
+ raise Fig::RepositoryError.new
300
384
  end
301
385
 
302
- def package_missing?(descriptor)
303
- not File.exist?(local_fig_file_for_package(descriptor))
386
+ return read_package_from_file(dot_fig_file, descriptor)
387
+ end
388
+
389
+ def read_package_from_file(file_name, descriptor)
390
+ if not File.exist?(file_name)
391
+ Fig::Logging.fatal "Package not found: #{descriptor.to_string()}"
392
+ raise Fig::RepositoryError.new
304
393
  end
394
+ content = File.read(file_name)
395
+
396
+ package = @parser.parse_package(
397
+ descriptor, File.dirname(file_name), descriptor.to_string(), content
398
+ )
399
+
400
+ @package_cache.add_package(package)
401
+
402
+ return package
403
+ end
404
+
405
+ def delete_local_package(descriptor)
406
+ FileUtils.rm_rf(local_dir_for_package(descriptor))
407
+ end
408
+
409
+ def remote_fig_file_for_package(descriptor)
410
+ "#{remote_dir_for_package(descriptor)}/#{PACKAGE_FILE_IN_REPO}"
411
+ end
412
+
413
+ def local_fig_file_for_package(descriptor)
414
+ File.join(local_dir_for_package(descriptor), PACKAGE_FILE_IN_REPO)
415
+ end
416
+
417
+ def fig_file_for_package_download(package_download_dir)
418
+ File.join(package_download_dir, PACKAGE_FILE_IN_REPO)
419
+ end
420
+
421
+ def remote_dir_for_package(descriptor)
422
+ "#{remote_repository_url()}/#{descriptor.name}/#{descriptor.version}"
423
+ end
424
+
425
+ def local_dir_for_package(descriptor)
426
+ return File.join(
427
+ local_package_directory(), descriptor.name, descriptor.version
428
+ )
429
+ end
430
+
431
+ def base_temp_dir()
432
+ File.join(@local_repository_directory, 'tmp')
433
+ end
434
+
435
+ def publish_temp_dir()
436
+ File.join(base_temp_dir(), 'publish')
437
+ end
438
+
439
+ def package_download_temp_dir(descriptor)
440
+ base_directory = File.join(base_temp_dir(), 'package-download')
441
+ FileUtils.mkdir_p(base_directory)
442
+
443
+ return Dir.mktmpdir(
444
+ "#{descriptor.name}.version.#{descriptor.version}+", base_directory
445
+ )
446
+ end
447
+
448
+ def package_missing?(descriptor)
449
+ not File.exist?(local_fig_file_for_package(descriptor))
450
+ end
305
451
 
306
- def derive_package_content(
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(
307
459
  package_statements, descriptor, local_dir, local_only
308
460
  )
309
- header_strings = derive_package_metadata_comments(descriptor)
310
- resource_statement_strings = derive_package_resources(
311
- package_statements, descriptor, local_dir, local_only
312
- )
313
461
 
314
- return [header_strings, resource_statement_strings].flatten()
315
- end
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()
316
468
 
317
- def derive_package_metadata_comments(descriptor)
318
- now = Time.now()
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
319
474
 
320
- return [
321
- %Q<# Publishing information for #{descriptor.to_string()}:>,
475
+ if asset_strings.empty?
476
+ asset_summary = [
322
477
  %q<#>,
323
- %Q<# Time: #{now} (epoch: #{now.to_i()})>,
324
- %Q<# User: #{Sys::Admin.get_login()}>,
325
- %Q<# Host: #{Socket.gethostname()}>,
326
- %Q<# Args: "#{ARGV.join %q[", "]}">,
327
- %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
328
486
  ]
329
487
  end
330
488
 
331
- def derive_package_resources(
332
- package_statements, descriptor, local_dir, local_only
333
- )
334
- return bundle_resources(package_statements).map do |statement|
335
- if statement.is_a?(Statement::Publish)
336
- nil
337
- elsif statement.is_a?(Statement::Archive) || statement.is_a?(Statement::Resource)
338
- if statement.is_a?(Statement::Resource) && ! Repository.is_url?(statement.url)
339
- archive_name = statement.url
340
- archive_remote = "#{remote_dir_for_package(descriptor)}/#{statement.url}"
341
- else
342
- archive_name = statement.url.split('/').last
343
- archive_remote = "#{remote_dir_for_package(descriptor)}/#{archive_name}"
344
- end
345
- if Repository.is_url?(statement.url)
346
- archive_local = File.join(temp_dir, archive_name)
347
- @operating_system.download(statement.url, archive_local)
348
- else
349
- archive_local = statement.url
350
- end
351
- @operating_system.upload(archive_local, archive_remote, @remote_repository_user) unless local_only
352
- @operating_system.copy(archive_local, local_dir + '/' + archive_name)
353
- if statement.is_a?(Statement::Archive)
354
- @operating_system.unpack_archive(local_dir, archive_name)
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::Command.get_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
355
525
  end
356
- statement.class.new(nil, archive_name).unparse('')
357
526
  else
358
- statement.unparse('')
527
+ asset_local = statement.url
528
+ check_asset_path(asset_local)
359
529
  end
360
- end.select { |s| not s.nil? }
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
361
546
  end
362
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
363
608
  end