Files
2025-09-29 00:52:08 +02:00

232 lines
6.2 KiB
Ruby
Executable File

#
# environment.rb
# Project Environment Table Class
#
# Author:: David Muir <david.muir@rockstarnorth.com>
# Date:: 8 February 2008
#
module Pipeline
#
# == Description
#
# Exception raised for any errors within the Environment class occurs.
#
class EnvironmentException < Exception; end
#
# == Description
#
# Environment symbol table implementation. Essentially just a nice wrapper
# around Ruby's Hash.
#
# Internally the symbol_table does not use the ENVIRONMENT_PREFIX.
#
# Symbols added to the environment automatically declare publically
# readable member variables with the same name as the variable. This
# allows speedy lookup if you know the name of the environment variable.
# A NameError exception will be raised by Ruby however if the variable is
# not available.
#
# == Example Usage
#
# env = Pipeline::Environment.new()
# env.add( "testvar1", "**testvalue1**" )
# env.add( "testvar2", "&&testvalue2&&" )
# env.list
# env.testvar1 => "**testvalue1**"
# env.testvar2 => "&&testvalue2&&"
#
class Environment
protected
attr_reader :symbol_table
# If any additional member variables are added to this class, ensure you
# add their name to this list, otherwise bad things will happen in the
# unlikely event a user attempts to get the environment to store such a
# variable.
DISALLOWED_VARS = [ 'symbol_table', 'symbol_stack' ]
public
#---------------------------------------------------------------------
# Constants
#---------------------------------------------------------------------
ENVIRONMENT_PREFIX = '$'
#---------------------------------------------------------------------
# Methods
#---------------------------------------------------------------------
#
# == Description
#
# Class constructor, initialising a new Environment object with an
# empty symbol table.
#
def initialize( )
@symbol_table = Hash.new()
@symbol_stack = []
@symbol_stack << @symbol_table
end
#
# == Description
#
# Returns the value of the specified environment variable name.
#
def lookup( varname )
# Return variable value if we have it.
return @symbol_table[varname] unless !@symbol_table[varname]
# Otherwise throw an exception.
raise EnvironmentException.new( "Environment has no definition for #{varname}." )
end
#
# == Description
#
# Returns a new string with any environment variables having their
# actual value substituted in place.
#
# Since the subst string may contain new symbols requiring further substitution
# set exhaustive to true to make sure they are replaced. But DONT make such things
# iterate forever :) - I should probably find a way to prevent this. - DW
#
def subst( string, exhaustive = false )
throw ArgumentError.new( "Invalid String to substitute (#{string}, #{string.class})." ) \
unless ( string.is_a?( String ) )
return nil if ( nil == string )
newstring = string.dup
if exhaustive then
loop do
replaced = false
@symbol_table.each do |varname, value|
realVarname = "$(#{varname})"
result = newstring.gsub!( realVarname, value )
replaced = true if (not result.nil?)
end
break if not replaced
end
else
@symbol_table.each do |varname, value|
realVarname = "$(#{varname})"
newstring.gsub!( realVarname, value )
end
end
newstring
end
#
# == Description
#
# Add a new or replace an existing, environment variable definition in
# our symbol table.
#
# == References
#
# http://www.pullmonkey.com/2008/1/6/convert-a-ruby-hash-into-a-class-object
#
def add( varname, value )
throw EnvironmentException.new( "Environment \"#{varname}\" cannot contain variables resolving to nil." ) \
unless ( nil != value )
throw EnvironmentException.new( "Environment cannot contain a variable called \"#{varname}\"." ) \
if ( DISALLOWED_VARS.include?( varname ) )
@symbol_table[varname] = value
syminst = :"@#{varname}"
sym = :"#{varname}"
self.instance_variable_set( syminst, value )
self.class.send( :define_method, sym, proc { self.instance_variable_get( syminst ) } )
end
#
# Clear all environment variables.
#
def clear( )
@symbol_table.clear()
# Clear any created instance variables
self.instance_variables.each do |var|
next if ( DISALLOWED_VARS.include?( var.tr( '@', '' ) ) )
self.instance_variable_set( var, nil )
end
end
#
# Return the number of variables we have currently defined.
#
def count
@symbol_table.size()
end
#
# Push a new symbol table onto the stack.
#
def push
@symbol_stack.push( @symbol_table.clone() )
@symbol_table = @symbol_stack[@symbol_stack.size - 1]
end
#
# Pop a symbol table from the stack and correct the @symbol_table
# reference to ensure we point to the top of the stack.
#
def pop
@symbol_stack.pop()
# Update to point to top of symbol_stack
@symbol_table = @symbol_stack[@symbol_stack.size - 1]
end
#
# Pretty print listing of symbol table to stdout for debugging.
#
def list( )
indent = @symbol_stack.size - 1
@symbol_stack.reverse_each do |sym|
print_symbol_table( sym, indent, "\t" )
indent -= 1
end
end
#---------------------------------------------------------------------
# Private
#---------------------------------------------------------------------
private
def print_symbol_table( table, indent = 0, indent_char = "\t" )
puts ""
indent.times do print indent_char; end
print "Environment Listing (depth: #{indent}):\n"
indent.times do print indent_char; end
print "---------------------------------------\n"
table.each do |varname, value|
indent.times do print indent_char; end
print " #{varname} => #{value}\n"
end
indent.times do print indent_char; end
print "---------------------------------------\n"
end
end
end # Pipeline module
# End of environment.rb