lib/fig/environment.rb in fig-0.1.62 vs lib/fig/environment.rb in fig-0.1.64
- old
+ new
@@ -8,325 +8,429 @@
require 'fig/statement/include'
require 'fig/statement/path'
require 'fig/statement/set'
require 'fig/userinputerror'
-module Fig
- # Manages the program's metadata, including packages and environment
- # variables, and sets things up for running commands (from "command"
- # statements in configuration files).
- class Environment
- DEFAULT_VERSION_NAME = 'current'
+module Fig; end
- def initialize(repository, variables_override, retriever)
- @repository = repository
- @variables = variables_override || OperatingSystem.get_environment_variables
- @retrieve_vars = {}
- @packages = {}
- @retriever = retriever
- end
+# Manages the program's metadata, including packages and environment
+# variables, and sets things up for running commands (from "command"
+# statements in definition files or from the command-line).
+class Fig::Environment
+ # Note: when reading this code, understand that the word "retrieve" is a
+ # noun and not a verb, e.g. "retrieve path" means the value of a retrieve
+ # statement and not the action of retrieving a path.
- # Returns the value of an envirionment variable
- def [](name)
- return @variables[name]
- end
+ def initialize(repository, variables_override, working_directory_maintainer)
+ @repository = repository
+ @variables =
+ variables_override || Fig::OperatingSystem.get_environment_variables()
+ @retrieves = {}
+ @packages = {}
+ @working_directory_maintainer = working_directory_maintainer
+ end
- def variables
- return @variables.clone
+ # Returns the value of an envirionment variable
+ def [](name)
+ return @variables[name]
+ end
+
+ def variables
+ return @variables.clone
+ end
+
+ # Indicates that the values from a particular environment variable path
+ # should be copied to a local directory.
+ def add_retrieve(retrieve_statement)
+ name = retrieve_statement.var
+ if @retrieves.has_key?(name)
+ Fig::Logging.warn \
+ %q<About to overwrite "#{name}" retrieve path of "#{@retrieves[name].path}" with "#{retrieve_statement.path}".>
end
- # Indicates that the values from a particular envrionment variable path
- def add_retrieve(name, path)
- @retrieve_vars[name] = path
+ @retrieves[name] = retrieve_statement
+ retrieve_statement.added_to_environment(true)
- return
+ return
+ end
+
+ def register_package(package)
+ name = package.name
+
+ if get_package(name)
+ raise_repository_error(
+ name.nil? \
+ ? %Q<There is already a package with the name "#{name}".> \
+ : %q<There is already an unnamed package.>,
+ nil,
+ package
+ )
end
- def register_package(package)
- name = package.name
+ @packages[name] = package
- if get_package(name)
- Logging.fatal %Q<There is already a package with the name "#{name}".>
- raise RepositoryError.new
- end
+ return
+ end
- @packages[name] = package
+ def get_package(name)
+ return @packages[name]
+ end
+ def apply_config(package, config_name, backtrace)
+ if package.applied_config_names.member?(config_name)
return
end
+ new_backtrace = backtrace ||
+ Fig::Backtrace.new(
+ nil,
+ Fig::PackageDescriptor.new(package.name, package.version, config_name)
+ )
- def get_package(name)
- return @packages[name]
+ config = package[config_name]
+ config.statements.each do
+ |statement|
+ apply_config_statement(package, statement, new_backtrace)
end
+ package.add_applied_config_name(config_name)
- def packages
- return @packages.values
+ return
+ end
+
+ def execute_shell(command)
+ @variables.with_environment do
+ yield command.map{|arg| expand_command_line_argument(arg, nil, nil)}
end
- def apply_config(package, config_name, backtrace)
- if package.applied_config_names.member?(config_name)
- return
- end
- new_backtrace = backtrace
+ return
+ end
- config = package[config_name]
- config.statements.each { |stmt| apply_config_statement(package, stmt, new_backtrace) }
- package.add_applied_config_name(config_name)
+ def execute_config(base_package, descriptor, args, &block)
+ config_name =
+ descriptor.config || find_config_name_in_package(descriptor.name)
- return
+ name = descriptor.name || base_package.name
+ package = lookup_package(
+ name,
+ descriptor.version,
+ Fig::Backtrace.new(
+ nil,
+ Fig::PackageDescriptor.new(name, descriptor.version, config_name)
+ )
+ )
+
+ command_statement = package[config_name].command_statement
+ if command_statement
+ execute_command(command_statement, args, package, nil, &block)
+ else
+ raise Fig::UserInputError.new(
+ %Q<The "#{package.to_s}" package with the "#{config_name}" configuration does not contain a command.>
+ )
end
- def execute_shell(command)
- @variables.with_environment do
- yield command.map{|arg| expand_command_line_argument(arg)}
- end
+ return
+ end
- return
+ # In order for this to work correctly, any Overrides need to be processed
+ # before any other kind of Statement. The Configuration class guarantees
+ # that those come first in its set of Statements.
+ def apply_config_statement(base_package, statement, backtrace)
+ case statement
+ when Fig::Statement::Path
+ prepend_variable(base_package, statement.name, statement.value, backtrace)
+ when Fig::Statement::Set
+ set_variable(base_package, statement.name, statement.value, backtrace)
+ when Fig::Statement::Include
+ include_config(base_package, statement.descriptor, backtrace)
+ when Fig::Statement::Override
+ backtrace.add_override(statement)
+ when Fig::Statement::Command
+ # Skip - has no effect on environment.
+ else
+ raise "Unexpected statement in a config block: #{statement.unparse('')}"
end
- def execute_command(command, args, package)
- @variables.with_environment do
- argument =
- expand_command_line_argument(
- "#{command.command} #{args.join(' ')}"
- )
+ return
+ end
- yield expand_path(argument, package).split(' ')
- end
+ def include_config(base_package, descriptor, backtrace)
+ resolved_descriptor = nil
- return
+ # Check to see if this include has been overridden.
+ if backtrace
+ override = backtrace.get_override(
+ descriptor.name || base_package.name
+ )
+ if override
+ resolved_descriptor =
+ Fig::PackageDescriptor.new(
+ descriptor.name, override, descriptor.config
+ )
+ end
end
+ resolved_descriptor ||= descriptor
- def find_config_name_in_package(name)
- package = get_package(name)
- if not package
- return Package::DEFAULT_CONFIG
- end
+ new_backtrace = Fig::Backtrace.new(backtrace, resolved_descriptor)
+ package = lookup_package(
+ resolved_descriptor.name || base_package.name,
+ resolved_descriptor.version,
+ new_backtrace
+ )
+ apply_config(
+ package,
+ resolved_descriptor.config || Fig::Package::DEFAULT_CONFIG,
+ new_backtrace
+ )
- return package.primary_config_name || Package::DEFAULT_CONFIG
+ return
+ end
+
+ def check_unused_retrieves()
+ @retrieves.keys().sort().each do
+ |name|
+
+ statement = @retrieves[name]
+ if statement.loaded_but_not_referenced?
+ Fig::Logging.warn \
+ %Q<The #{name} variable was never referenced or didn't need expansion, so "#{statement.unparse('')}"#{statement.position_string} was ignored.>
+ end
end
+ end
- def execute_config(base_package, descriptor, args, &block)
- config_name =
- descriptor.config || find_config_name_in_package(descriptor.name)
+ private
- name = descriptor.name || base_package.name
- package = lookup_package(
- name,
- descriptor.version,
- Backtrace.new(
- nil,
- PackageDescriptor.new(name, descriptor.version, config_name)
- )
+ def set_variable(base_package, name, value, backtrace)
+ expanded_value =
+ expand_variable_as_path_and_process_retrieves(
+ name, value, base_package, backtrace
)
+ @variables[name] = expanded_value
- command = package[config_name].command
- if command
- execute_command(command, args, package, &block)
- else
- raise UserInputError.new(%Q<The "#{package.to_s}" package with the "#{config_name}" configuration does not contain a command.>)
- end
+ if Fig::Logging.debug?
+ expanded_message =
+ expanded_value == value ? '' \
+ : %Q< (expanded from "#{value}")>
- return
+ Fig::Logging.debug(
+ %Q<Set #{name} to "#{expanded_value}"#{expanded_message}.>
+ )
end
- def apply_config_statement(base_package, statement, backtrace)
- case statement
- when Statement::Path
- prepend_variable(base_package, statement.name, statement.value)
- when Statement::Set
- set_variable(base_package, statement.name, statement.value)
- when Statement::Include
- include_config(
- base_package, statement.descriptor, statement.overrides, backtrace
- )
- when Statement::Command
- # ignore
- else
- fail "Unexpected statement: #{statement}"
- end
+ return
+ end
- return
+ def prepend_variable(base_package, name, value, backtrace)
+ expanded_value =
+ expand_variable_as_path_and_process_retrieves(
+ name, value, base_package, backtrace
+ )
+ @variables.prepend_variable(name, expanded_value)
+
+ if Fig::Logging.debug?
+ expanded_message =
+ expanded_value == value ? '' \
+ : %Q< ("#{value}" expanded to "#{expanded_value}")>
+
+ Fig::Logging.debug(
+ %Q<Prepending to #{name} resulted in "#{@variables[name]}"#{expanded_message}.>
+ )
end
- def include_config(base_package, descriptor, overrides, backtrace)
- resolved_descriptor = nil
+ return
+ end
- # Check to see if this include has been overridden.
- if backtrace
- override = backtrace.get_override(
- descriptor.name || base_package.name
+ def lookup_package(name, version, backtrace)
+ package = get_package(name)
+ if package.nil?
+ if not version
+ raise_repository_error(
+ "No version specified for #{name}.", backtrace, package
)
- if override
- resolved_descriptor =
- PackageDescriptor.new(
- descriptor.name, override, descriptor.config
- )
- end
end
- resolved_descriptor ||= descriptor
- new_backtrace = Backtrace.new(backtrace, resolved_descriptor)
- overrides.each do |override|
- new_backtrace.add_override(override.package_name, override.version)
- end
- package = lookup_package(
- resolved_descriptor.name || base_package.name,
- resolved_descriptor.version,
- new_backtrace
+ package = @repository.get_package(
+ Fig::PackageDescriptor.new(name, version, nil)
)
- apply_config(
- package,
- resolved_descriptor.config || Package::DEFAULT_CONFIG,
- new_backtrace
- )
+ package.backtrace = backtrace
+ @packages[name] = package
+ elsif version && version != package.version
+ raise_repository_error("Version mismatch: #{name}", backtrace, package)
+ end
- return
+ return package
+ end
+
+ def find_config_name_in_package(name)
+ package = get_package(name)
+ if not package
+ return Fig::Package::DEFAULT_CONFIG
end
- private
+ return package.primary_config_name || Fig::Package::DEFAULT_CONFIG
+ end
- def set_variable(base_package, name, value)
- @variables[name] = expand_and_retrieve_variable_value(base_package, name, value)
+ def execute_command(command_statement, args, package, backtrace)
+ @variables.with_environment do
+ argument =
+ expand_command_line_argument(
+ "#{command_statement.command} #{args.join(' ')}", backtrace, package
+ )
- return
+ yield expand_at_signs_in_path(argument, package, backtrace).split(' ')
end
- def prepend_variable(base_package, name, value)
- value = expand_and_retrieve_variable_value(base_package, name, value)
- @variables.prepend_variable(name, value)
+ return
+ end
- return
- end
+ def expand_variable_as_path_and_process_retrieves(
+ variable_name, variable_value, base_package, backtrace
+ )
+ return variable_value unless base_package && base_package.name
- def lookup_package(name, version, backtrace)
- package = get_package(name)
- if package.nil?
- if not version
- Logging.fatal "No version specified for #{name}."
- raise RepositoryError.new
- end
+ variable_value =
+ expand_at_signs_in_path(variable_value, base_package, backtrace)
- package = @repository.get_package(
- PackageDescriptor.new(name, version, nil)
- )
- package.backtrace = backtrace
- @packages[name] = package
- elsif version && version != package.version
- string_handle = StringIO.new
- backtrace.dump(string_handle) if backtrace
- package.backtrace.dump(string_handle) if package.backtrace
- stacktrace = string_handle.string
- Logging.fatal \
- "Version mismatch: #{name}" \
- + ( stacktrace.empty? ? '' : "\n#{stacktrace}" )
- raise RepositoryError.new
- end
+ return variable_value if not @retrieves.member?(variable_name)
- return package
- end
+ return retrieve_files(
+ variable_name, variable_value, base_package, backtrace
+ )
+ end
- # Replace @ symbol with the package's directory, "[package]" with the
- # package name.
- def expand_and_retrieve_variable_value(base_package, name, value)
- return value unless base_package && base_package.name
+ def retrieve_files(variable_name, variable_value, base_package, backtrace)
+ check_source_existence(
+ variable_name, variable_value, base_package, backtrace
+ )
- file = expand_path(value, base_package)
+ destination_path =
+ derive_retrieve_destination(variable_name, variable_value, base_package)
- if @retrieve_vars.member?(name)
- # A '//' in the source file's path tells us to preserve path
- # information after the '//' when doing a retrieve.
- if file.split('//').size > 1
- preserved_path = file.split('//').last
- target = File.join(
- translate_retrieve_variables(base_package, name),
- preserved_path
- )
- else
- target = File.join(
- translate_retrieve_variables(base_package, name)
- )
- if not File.directory?(file)
- target = File.join(target, File.basename(file))
- end
- end
- @retriever.with_package_version(
- base_package.name, base_package.version
- ) do
- @retriever.retrieve(file, target)
- end
- file = target
- end
+ @working_directory_maintainer.switch_to_package_version(
+ base_package.name, base_package.version
+ )
+ @working_directory_maintainer.retrieve(variable_value, destination_path)
- return file
- end
+ return destination_path
+ end
- def expand_path(path, base_package)
- expanded_path = expand_at_sign_package_references(path, base_package)
- check_for_bad_escape(expanded_path, path)
+ def check_source_existence(
+ variable_name, variable_value, base_package, backtrace
+ )
+ return if File.exists?(variable_value) || File.symlink?(variable_value)
- return expanded_path.gsub(%r< \\ ([\\@]) >x, '\1')
- end
+ raise_repository_error(
+ %Q<In #{base_package}, the #{variable_name} variable points to a path that does not exist ("#{variable_value}", after expansion).>,
+ backtrace,
+ base_package
+ )
+ end
- def expand_at_sign_package_references(arg, base_package)
- return arg.gsub(
- %r<
- (?: ^ | \G) # Zero-width anchor.
- ( [^\\@]* (?:\\{2})*) # An even number of leading backslashes
- \@ # The package indicator
- >x
- ) do |match|
- backslashes = $1 || ''
- backslashes + base_package.directory
- end
+ def derive_retrieve_destination(variable_name, variable_value, base_package)
+ retrieve_path =
+ get_retrieve_path_with_substitution(variable_name, base_package)
- return
+ # A '//' in the variable value tells us to preserve path
+ # information after the '//' when doing a retrieve.
+ if variable_value.include? '//'
+ preserved_path = variable_value.split('//').last
+
+ return File.join(retrieve_path, preserved_path)
end
- def expand_command_line_argument(arg)
- package_substituted = expand_named_package_references(arg)
- check_for_bad_escape(package_substituted, arg)
-
- return package_substituted.gsub(%r< \\ ([\\@]) >x, '\1')
+ if File.directory?(variable_value)
+ return retrieve_path
end
- def expand_named_package_references(arg)
- return arg.gsub(
- # TODO: Refactor package name regex into PackageDescriptor constant.
- %r<
- (?: ^ | \G) # Zero-width anchor.
- ( [^\\@]* (?:\\{2})*) # An even number of leading backslashes
- \@ # The package indicator
- ( [a-zA-Z0-9_.-]+ ) # Package name
- >x
- ) do |match|
- backslashes = $1 || ''
- package = get_package($2)
- if package.nil?
- raise RepositoryError.new("Package not found: #{$1}")
- end
- backslashes + package.directory
- end
+ return File.join(retrieve_path, File.basename(variable_value))
+ end
+
+ def expand_at_signs_in_path(path, base_package, backtrace)
+ expanded_path =
+ replace_at_signs_with_package_references(path, base_package)
+ check_for_bad_escape(expanded_path, path, base_package, backtrace)
+
+ return collapse_backslashes_for_escaped_at_signs(expanded_path)
+ end
+
+ def replace_at_signs_with_package_references(arg, base_package)
+ return arg.gsub(
+ %r<
+ (?: ^ | \G) # Zero-width anchor.
+ ( [^\\@]* (?:\\{2})*) # An even number of leading backslashes
+ \@ # The package indicator
+ >x
+ ) do |match|
+ backslashes = $1 || ''
+ backslashes + base_package.directory
end
+ end
- # The value is expected to have had any @ substitution already done, but
- # collapsing of escapes not done yet.
- def check_for_bad_escape(substituted, original)
- if substituted =~ %r<
- (?: ^ | [^\\]) # Start of line or non backslash
- (?: \\{2})* # Even number of backslashes (including zero)
- ( \\ [^\\@] ) # A bad escape
+ def expand_command_line_argument(arg, backtrace, package)
+ package_substituted = expand_named_package_references(arg, backtrace)
+ check_for_bad_escape(package_substituted, arg, package, backtrace)
+
+ return collapse_backslashes_for_escaped_at_signs(package_substituted)
+ end
+
+ def expand_named_package_references(arg, backtrace)
+ return arg.gsub(
+ # TODO: Refactor package name regex into PackageDescriptor constant.
+ %r<
+ (?: ^ | \G) # Zero-width anchor.
+ ( [^\\@]* (?:\\{2})*) # An even number of leading backslashes
+ \@ # The package indicator
+ ( [a-zA-Z0-9_.-]+ ) # Package name
>x
- raise RepositoryError.new(
- %Q<Unknown escape "#{$1}" in "#{original}">
+ ) do |match|
+ backslashes = $1 || ''
+ package_name = $2
+ package = get_package(package_name)
+ if package.nil?
+ raise_repository_error(
+ %Q<Command-line referenced the "#{package_name}" package, which has not been referenced by any other package, so there's nothing to substitute with.>,
+ backtrace,
+ nil
)
end
-
- return
+ backslashes + package.directory
end
+ end
- def translate_retrieve_variables(base_package, name)
- return \
- @retrieve_vars[name].gsub(/ \[package\] /x, base_package.name)
+ # The value is expected to have had any @ substitution already done, but
+ # collapsing of escapes not done yet.
+ def check_for_bad_escape(substituted, original, package, backtrace)
+ if substituted =~ %r<
+ (?: ^ | [^\\]) # Start of line or non backslash
+ (?: \\{2})* # Even number of backslashes (including zero)
+ ( \\ [^\\@] ) # A bad escape
+ >x
+ raise_repository_error(
+ %Q<Unknown escape "#{$1}" in "#{original}">, backtrace, package
+ )
end
+
+ return
+ end
+
+ # After @ substitution, we need to get rid of the backslashes in front of
+ # any escaped @ signs.
+ def collapse_backslashes_for_escaped_at_signs(string)
+ return string.gsub(%r< \\ ([\\@]) >x, '\1')
+ end
+
+ def get_retrieve_path_with_substitution(name, base_package)
+ retrieve_statement = @retrieves[name]
+ retrieve_statement.referenced(true)
+
+ return retrieve_statement.path.gsub(/ \[package\] /x, base_package.name)
+ end
+
+ def raise_repository_error(message, backtrace, package)
+ string_handle = StringIO.new
+ backtrace.dump(string_handle) if backtrace
+ package.backtrace.dump(string_handle) if package && package.backtrace
+ stacktrace = string_handle.string
+
+ raise Fig::RepositoryError.new(
+ message + ( stacktrace.empty? ? '' : "\n#{stacktrace}" )
+ )
end
end