Class: Util::ConfigYaml

Inherits:
Hash
  • Object
show all
Defined in:
../lib/config.rb

Overview

The main configuration component of the GERET library. The ConfigYaml instance loads the file with the YAML syntax and provides the configuration values and the generic factory for the creation of Ruby classes. This facility allows the separation of the generic GE algorithm from its internal classes using the “Injection of control” design pattern.

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (ConfigYaml) initialize(file = nil)

Load the YAML file and prepare the hash of configuration values. For example, if the file.yaml contains this text:


  selector: 
    class: MySelector
    require: myselector_class.rb
    attribute1: 3
    attr2: something 
  option1: 42
  composite_option:
    level1:
      level2: foo
   

then, after the calling:


  cfg = ConfigYaml.new('file.yaml') 
  

the cfg[‘selector’] will be the hash of factory configuration values:


  {'class'=>'MySelector', 'require'=>'myselector_class.rb', 'attribute1'=>3, 'attr2'=>'something'}
  

the cfg[‘option1’] will contain the value 42 and, the cfg[‘composite_option’] will be:


  { 'level1' => {'level2'=>'foo'} }


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File '../lib/config.rb', line 38

def initialize file=nil

  super()
  @class_methods = Set.new
 
  return if file.nil?

  obj = YAML::load( File.open( file ) )   
  raise "ConfigYaml: top level yaml object is not a hash" unless obj.kind_of? Hash
  update obj 

  each_value do |details|
    requirement = details.fetch( 'require', nil )
    require requirement unless requirement.nil?
  end

end

Class Method Details

+ (Object) parse_options(args, options = {})

This helper function takes the options hash and overloads its values according the args. Note the ’-’ character separates the level of the hashing.

For example:


  args = ['file.txt', '--arg=12', '--no', '--opt-sub=xyz', 'file2.out', '--opt-sub2-sub3=4.4']
  opts = ConfigYaml.parse_options( args, {'orig'=>42, 'arg'=>'22'} )
  

opts will be:


  { 'orig'=>42, 'arg'=>12, 'no'=>nil, 'opt'=>{'sub'=>'xyz', 'sub2'=>{'sub3'=>4.4} }  }


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File '../lib/config.rb', line 147

def ConfigYaml.parse_options( args, options = {} )
  args.each do |arg|
    next unless /^--/ =~ arg
    key, value = arg.sub( /^--/, '' ).split('=')
    value = value.to_i if value.to_i.to_s == value
    value = value.to_f if value.to_f.to_s == value     
    hsh = options 
    keys = key.split(/-/)
    while keys.size > 1
      k = keys.shift
      hsh[k] = {} unless hsh.has_key? k
      hsh = hsh[k]
    end
    hsh[ keys.last ] = value
  end
  options
end

+ (Object) remove_options!(args)

This helper function removes all items beginning with ’--’ from the argument array. Eg.:


  ARGV = ['file.txt', '--option=42', 'file2.txt', '--quiet', '-x', 'output.txt']
  ConfigYaml.remove_options! ARGV
  

ARGV is now:


  ['file.txt', 'file2.txt', '-x', 'output.txt']


173
174
175
# File '../lib/config.rb', line 173

def ConfigYaml.remove_options! args
  args.delete_if { |arg| /^--/ =~ arg }
end

Instance Method Details

- (Object) factory(key, *args)

Create the instance of the class which is dynamically specified by the YAML file. The key (the first argument) has to be present as the section in the configuration, the class subsection contains the name of the class. All remaining args of the factory method are then passed as the constructor arguments. The optional require subsection the file with the class implementation (all GERET’s own classes are automatically present via require ‘lib/geret’). Subsections with leading underscores are interpreted as names of class methods. The underscores are removed from the methods’ names and methods are called once, just before the creation of the first instance. Remaining keys are considered as attribute names and their values are always assigned to the newly created instance.

For example, if the file.yaml contains this text:


  selector: 
    class: MySelector
    require: myselector_class.rb
    attribute1: 3
    attr2: something 
    _prepare_context: 42

then the code:


  cfg = ConfigYaml.new('file.yaml') 
  sel = cfg.factory( 'selector', 'my_1st_arg', '2nd_one' )
  sel = cfg.factory( 'selector', 'another', 'instance' )
  

is equivalent to:


  require 'myselector_class.rb'
  MySelector.prepare_context( 42 )
  sel = MySelector.new( 'my_1st_arg', '2nd_one' )
  sel.attribute1 = 3
  sel.attr2 = 'something'
  sel = MySelector.new( 'another', 'instance' )
  sel.attribute1 = 3
  sel.attr2 = 'something'


94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File '../lib/config.rb', line 94

def factory( key, *args )
  details = fetch( key, nil )
  raise "ConfigYaml: missing key when calling factory('#{key}')" if details.nil?
  klass = details.fetch( 'class', nil )
  raise "ConfigYaml: missing class when calling factory('#{key}')" if klass.nil?

  requirement = details.fetch( 'require', nil )
  require requirement unless requirement.nil?

  unless @class_methods.include? klass
    @class_methods.add klass
    static_keys = details.keys.find_all { |k| k[0].chr == '_' } 
    static_keys.each do |k|
      method = k.sub( /^_/, '' )
      text = "#{klass}.#{method}( #{ details[k].inspect } )"
      eval text 
    end
  end
 
  if args.empty?
    text = "#{klass}.new( #{ details.fetch( 'initialize', '' ) } )" 
  else
    text = "#{klass}.new( "
    args.each_index { |index| text += "args[#{index}]," }
    text = text[ 0...text.size-1 ] + ' )'
  end

  begin
    instance = eval text
  rescue => details
    raise "ConfigYaml: cannot eval '#{text}' (missing require?)\n" + details.inspect
  end

  details.each_pair do |k,value|
    next if ['class','initialize', 'require'].include? k
    next if k[0].chr == '_' # no class methods, please
    eval "instance.#{k} = #{value.inspect}"
  end

  instance
end