# # environment.rb # Project Environment Table Class # # Author:: David Muir # 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