fig 0.1.62 → 0.1.64

Sign up to get free protection for your applications and to get access to all the features.
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