# Copyright (C) 2011-2012 RightScale, Inc, All Rights Reserved Worldwide. # # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use, # reproduction, modification, or disclosure of this program is # strictly prohibited. Any use of this program by an authorized # licensee is strictly subject to the terms and conditions, # including confidentiality obligations, set forth in the applicable # License Agreement between RightScale.com, Inc. and # the licensee module RightConf # Defines ~/.rconf_overrides file DSL class OverridesLanguage # Path to overrides file OVERRIDES_FILE = File.join(ENV['HOME'], '.rconf_overrides') # Lexical token used to indicate that the value of an overridden configurator attribute should literally # be nil, not the string "nil" or "none". NIL_LITERAL = /^(nil|none)$/ # Return override for given configurator setting if any # # === Parameters # key(String):: Configurator key # # === Return # value(String||Nil):: Override values if any, nil otherwise def self.overrides_for(key) self.load unless @overrides @overrides && @overrides[key] || {} end # Load overrides from file # # === Return # overrides(Hash):: Loaded overrides def self.load return @overrides if @overrides return {} unless File.readable?(OVERRIDES_FILE) @overrides = self.parse(IO.read(OVERRIDES_FILE)) || {} end # Persist overrides to overrides file # # === Return # true:: Always return true def self.save File.open(OVERRIDES_FILE, 'w') do |f| @overrides.each do |key, settings| settings.each do |name, value| f.puts "#{key}.#{name}=#{value}" end end end true end # Return parse errors from last call to 'parse' if any def self.parse_error @parse_error end protected # Load overrides from config file and produce hash of # overrides keyed by configurator and setting name # # === Parameters # source(String):: Overrides text # # === Return # overrides(Hash):: Overrides hash def self.parse(source) @parse_error = nil @overrides = nil overrides = Object.new ConfiguratorRegistry.each do |key, configurator| klass, code = override_class_code(key, configurator) overrides.class.class_eval(code) overrides.instance_eval("@#{key} = #{klass}.new") end begin source.split("\n").each do |o| operands = o.gsub(/\s+/, '').split('=') if operands.size != 2 raise "Invalid syntax '#{o}', must be of the form 'key.setting=value'" end if operands[1].start_with?('"') || operands[1].start_with?("'") # Quoted string value = operands[1] elsif operands[1] =~ NIL_LITERAL # Literal nil value = 'nil' else # Anything else: assume unquoted string value = "'#{operands[1]}'" end overrides.instance_eval("@#{operands[0]}=#{value}") end rescue Exception => e @overrides = nil @parse_error = e end @overrides end # Add override read from overrides file # # === Paramaters # key(String):: Configurator key # setting(String):: Setting name # value(String):: Override value # # === Return # true:: Always return true def self.add_override(key, setting, value) @overrides ||= {} @overrides[key] ||= {} @overrides[key][setting] = value true end # Build code for override class associated with given configurator # That class defines one setter method per setting which sets # the value in the @overrides class variable of *this* # (OverridesLanguage) class # # === Parameters # key(String):: Configurator key # configurator(Class):: Configurator class # # === Return # res(Array):: Pair of class name and class ruby code def self.override_class_code(key, configurator) klass = "#{key.capitalize}Rconf" code = "class #{klass}\n" configurator.all_settings.each do |s| code += <<-EOS def #{s[:name]}=(val) OverridesLanguage.add_override('#{key}', '#{s[:name]}', val) end EOS end code += 'end' [klass, code] end end end