ant_hill 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/ant_hill.gemspec +29 -0
- data/bin/spawn_queen +14 -0
- data/lib/ant_hill.rb +36 -0
- data/lib/ant_hill/ant.rb +126 -0
- data/lib/ant_hill/ant_colony.rb +212 -0
- data/lib/ant_hill/configuration.rb +154 -0
- data/lib/ant_hill/connection_pool.rb +98 -0
- data/lib/ant_hill/connections/ssh_connection.rb +67 -0
- data/lib/ant_hill/creep.rb +301 -0
- data/lib/ant_hill/creep_modifier.rb +150 -0
- data/lib/ant_hill/log.rb +35 -0
- data/lib/ant_hill/queen.rb +334 -0
- data/lib/ant_hill/version.rb +4 -0
- data/lib/tasks/ant_hill.rake +48 -0
- data/spec/ant_hill/ant_colony_spec.rb +118 -0
- data/spec/ant_hill/ant_spec.rb +101 -0
- data/spec/ant_hill/configuration_spec.rb +201 -0
- data/spec/ant_hill/creep_modifier_spec.rb +30 -0
- data/spec/ant_hill/creep_spec.rb +165 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/config.yml +43 -0
- metadata +117 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
load 'lib/tasks/ant_hill.rake'
|
data/ant_hill.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "ant_hill/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "ant_hill"
|
7
|
+
s.version = AntHill::VERSION
|
8
|
+
s.authors = ["Ivan Neverov"]
|
9
|
+
s.email = ["ineverov@sphereconsultinginc.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Run tests in grid}
|
12
|
+
s.description = %q{Application for running stuff with same purpose on several nodes
|
13
|
+
It find "best matching job" (based on setup time) for particular node and setup node
|
14
|
+
Then it run job on this node
|
15
|
+
|
16
|
+
Originally it was desined for CI.}
|
17
|
+
|
18
|
+
s.rubyforge_project = "ant_hill"
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
|
25
|
+
# specify any dependencies here; for example:
|
26
|
+
#s.add_development_dependency "ruby-debug19"
|
27
|
+
s.add_runtime_dependency "net-ssh"
|
28
|
+
s.add_development_dependency "rspec"
|
29
|
+
end
|
data/bin/spawn_queen
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ant_hill'
|
4
|
+
#AntHill::Configuration.config
|
5
|
+
queen = nil
|
6
|
+
if ARGV.size == 1
|
7
|
+
queen = AntHill::Queen.queen
|
8
|
+
elsif ARGV.size == 2
|
9
|
+
queen = AntHill::Queen.queen
|
10
|
+
filename = ARGV[1]
|
11
|
+
queen.restore_queen(filename)
|
12
|
+
end
|
13
|
+
queen.service
|
14
|
+
|
data/lib/ant_hill.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'logger'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'drb'
|
5
|
+
|
6
|
+
# Require file from ant_hill directory
|
7
|
+
def require_ant_hill(file)
|
8
|
+
require "ant_hill/#{file}"
|
9
|
+
end
|
10
|
+
private :require_ant_hill
|
11
|
+
|
12
|
+
# Main module
|
13
|
+
module AntHill
|
14
|
+
end
|
15
|
+
|
16
|
+
# Instance of job
|
17
|
+
require_ant_hill 'ant'
|
18
|
+
# Configuration
|
19
|
+
require_ant_hill 'configuration'
|
20
|
+
# Node
|
21
|
+
require_ant_hill 'creep'
|
22
|
+
# Main object
|
23
|
+
require_ant_hill 'queen'
|
24
|
+
# Set of jobs with same logic
|
25
|
+
require_ant_hill 'ant_colony'
|
26
|
+
# "Colony" specific logic for setting up and running job
|
27
|
+
require_ant_hill 'creep_modifier'
|
28
|
+
# Base connection class
|
29
|
+
require_ant_hill 'connection_pool'
|
30
|
+
# SSH connection
|
31
|
+
require_ant_hill 'connections/ssh_connection'
|
32
|
+
# Gem version
|
33
|
+
require_ant_hill 'version'
|
34
|
+
# logger
|
35
|
+
require_ant_hill 'log'
|
36
|
+
|
data/lib/ant_hill/ant.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
module AntHill
|
2
|
+
# Instance of job to process
|
3
|
+
class Ant
|
4
|
+
# Attribute readers
|
5
|
+
# +type+:: +AntColony+ type
|
6
|
+
# +colony+:: +AntColony+
|
7
|
+
# +status+:: ant status
|
8
|
+
# +config+:: configuration
|
9
|
+
# +params+:: +AntColony+ params_for_ant and ant specific params
|
10
|
+
attr_reader :type, :colony, :status, :config, :params
|
11
|
+
# Attribute accessors
|
12
|
+
# +execution_status+:: status of run in +CreepModifier+
|
13
|
+
# +runner+:: +Creep+ there ant is processing or was processed
|
14
|
+
# +prior+:: base priority of ant
|
15
|
+
# +output+:: return value of run method of +CreepModifier+
|
16
|
+
attr_accessor :execution_status, :runner, :prior, :output
|
17
|
+
include DRbUndumped
|
18
|
+
|
19
|
+
# Initialize method
|
20
|
+
# +params+:: +Ant+ specific params
|
21
|
+
# +colony+:: +AntColony+ which +Ant+ belongs to
|
22
|
+
# [+config+]:: configuration
|
23
|
+
def initialize(params, colony, config = Configuration.config)
|
24
|
+
@colony = colony
|
25
|
+
@config = config
|
26
|
+
@output = ''
|
27
|
+
# Ant params are colony params_for_ant + specific ant params
|
28
|
+
@params = @colony.params_for_ant.merge(params)
|
29
|
+
|
30
|
+
@status = :not_started
|
31
|
+
@execution_status = :queued
|
32
|
+
# Cache priorities for each creep for faster access
|
33
|
+
@cached_priorities = {}
|
34
|
+
# Ant type is colony type
|
35
|
+
@type = colony.type
|
36
|
+
# Set initial priority to queen start time - Time.now so ants created later will have lower priority
|
37
|
+
@prior = config.init_time - Time.now
|
38
|
+
# Add colony priority
|
39
|
+
@prior += colony.get_priority
|
40
|
+
end
|
41
|
+
|
42
|
+
# Cache of creeps priorities
|
43
|
+
def priority_cache(creep)
|
44
|
+
@cached_priorities[creep] ||= creep.priority(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Delete priority cache for specified creep
|
48
|
+
# +creep+:: +Creep+ for which delete cache
|
49
|
+
def delete_cache_for_creep(creep)
|
50
|
+
@cached_priorities.delete(creep)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Deelte all priprities cahce
|
54
|
+
def delete_cache
|
55
|
+
@cached_priorities = {}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create string representation of Ant
|
59
|
+
def to_s
|
60
|
+
@colony.ant_to_s(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Show diff between ant params and ant colony params
|
64
|
+
def diff_with_colony
|
65
|
+
colony_params = colony.params
|
66
|
+
params.inject({}){ |res, kv|
|
67
|
+
res[kv[0]] = kv[1] if colony_params[kv[0]] != kv[1]
|
68
|
+
res
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Create Ant from hash
|
73
|
+
def from_hash(data)
|
74
|
+
@type = data[:type]
|
75
|
+
@status = data[:status]
|
76
|
+
@executeion_status = data[:executeion_status]
|
77
|
+
@prior = data[:prior]
|
78
|
+
@output = data[:output]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Convert Ant to hash
|
82
|
+
def to_hash
|
83
|
+
{
|
84
|
+
:type => @type,
|
85
|
+
:params => diff_with_colony,
|
86
|
+
:status => @status,
|
87
|
+
:execution_status => @execution_status,
|
88
|
+
:prior => @prior,
|
89
|
+
:output => @output
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
# Re-process current ant
|
94
|
+
def return_to_queue(queen = Queen.queen)
|
95
|
+
queen.add_ants([self])
|
96
|
+
end
|
97
|
+
|
98
|
+
# Update status for ant
|
99
|
+
# +status+:: new status
|
100
|
+
def change_status(status)
|
101
|
+
@status = status
|
102
|
+
end
|
103
|
+
|
104
|
+
# return logger for ant_colony
|
105
|
+
def logger
|
106
|
+
colony.logger
|
107
|
+
end
|
108
|
+
|
109
|
+
# Start ant processing
|
110
|
+
def start
|
111
|
+
change_status(:started)
|
112
|
+
colony.colony_ant_started
|
113
|
+
end
|
114
|
+
|
115
|
+
# Finish ant processing
|
116
|
+
def finish
|
117
|
+
change_status(:finished)
|
118
|
+
colony.colony_ant_finished
|
119
|
+
end
|
120
|
+
|
121
|
+
# Check if ant had been finished
|
122
|
+
def finished?
|
123
|
+
status == :finished
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module AntHill
|
2
|
+
|
3
|
+
# Object that find Ants and store them
|
4
|
+
class AntColony
|
5
|
+
# Attribute accessors
|
6
|
+
# +params+:: +AntColony+ params
|
7
|
+
# +ants+:: array of +Ant+'s for this colony
|
8
|
+
attr_accessor :params, :ants
|
9
|
+
|
10
|
+
# Attribute reader
|
11
|
+
# +logger+:: logger for AntColony
|
12
|
+
attr_reader :logger
|
13
|
+
|
14
|
+
include DRbUndumped
|
15
|
+
|
16
|
+
# Initailize of +AntColony+
|
17
|
+
# +params+:: params for colony
|
18
|
+
# [+config+]:: configuration
|
19
|
+
def initialize(params={}, config = Configuration.config )
|
20
|
+
@params = params
|
21
|
+
@config = config
|
22
|
+
@logger = Log.logger_for(:ant_colony, config)
|
23
|
+
@created_at = Time.now
|
24
|
+
@ants = []
|
25
|
+
@started = false
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create +AntColony+ from hash
|
29
|
+
def from_hash(hash = nil)
|
30
|
+
if hash
|
31
|
+
@started = hash[:started]
|
32
|
+
@params = hash[:params]
|
33
|
+
@ants = hash[:ants].collect{|ant_data|
|
34
|
+
ant = Ant.new(ant_data[:params], self)
|
35
|
+
ant.from_hash(ant_data)
|
36
|
+
ant
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Convert +AntColony+ into hash
|
42
|
+
# +include_finished+:: include in hash finished +Ant+'s (default: false)
|
43
|
+
def to_hash(include_finished = false)
|
44
|
+
_ants = @ants
|
45
|
+
_ants = @ants.select{|a| !a.finished?} unless include_finished
|
46
|
+
{
|
47
|
+
:id => object_id,
|
48
|
+
:started => @started,
|
49
|
+
:params => @params,
|
50
|
+
:ants => _ants.collect{|a| a.to_hash}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Ger +CreepModifier+ class for +AntColony+ type
|
55
|
+
def creep_modifier_class
|
56
|
+
@creep_modifier_class ||= @config.creep_modifier_class(type)
|
57
|
+
return @creep_modifier_class if @creep_modifier_class
|
58
|
+
logger.error "Colony will die without creep modifier ;("
|
59
|
+
end
|
60
|
+
|
61
|
+
# Params will be inherited by +Ant+s
|
62
|
+
def params_for_ant
|
63
|
+
params.inject({}) do |hash,kv|
|
64
|
+
if !inherited_params || inherited_params.include?(kv[0])
|
65
|
+
hash[kv[0]]=kv[1]
|
66
|
+
end
|
67
|
+
hash
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# return true if no +CreepModifier+ found for colony
|
72
|
+
def spoiled?
|
73
|
+
!@creep_modifier
|
74
|
+
end
|
75
|
+
|
76
|
+
# Find ants for colony params
|
77
|
+
def get_ants
|
78
|
+
@ants = []
|
79
|
+
ant_larvas = search_ants(params)
|
80
|
+
@ants = ant_larvas.collect{|larva|
|
81
|
+
Ant.new(larva, self)
|
82
|
+
}
|
83
|
+
after_search
|
84
|
+
@ants
|
85
|
+
rescue => e
|
86
|
+
logger.error "Error while processing search ants for colony\n#{e}\n#{e.backtrace}"
|
87
|
+
# Retry 3 times, esle return []
|
88
|
+
retries ||= 0
|
89
|
+
retries += 1
|
90
|
+
retry if retries < 3
|
91
|
+
[]
|
92
|
+
ensure
|
93
|
+
# FIXME: Trigger colony finished if no ants were found
|
94
|
+
colony_ant_finished
|
95
|
+
end
|
96
|
+
|
97
|
+
# Check if colony matches params
|
98
|
+
# +params+:: params to match
|
99
|
+
def is_it_me?(params)
|
100
|
+
params.all? do |key, value|
|
101
|
+
@params[key] == value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Return list of not finished ants
|
106
|
+
def not_finished
|
107
|
+
ants.select{|ant| ant.finished? }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Check if colony had been finished
|
111
|
+
def finished?
|
112
|
+
ants.all?{ |a| a.finished? } || ants.empty?
|
113
|
+
end
|
114
|
+
|
115
|
+
# Return logger
|
116
|
+
def logger
|
117
|
+
Log.logger_for :ant_colony
|
118
|
+
end
|
119
|
+
|
120
|
+
# Trigger colony_started if not already started
|
121
|
+
def colony_ant_started
|
122
|
+
# FIXME: Dont require any arguments
|
123
|
+
unless @started
|
124
|
+
@started = true
|
125
|
+
begin
|
126
|
+
colony_started
|
127
|
+
rescue => e
|
128
|
+
logger.error "There was an error processing colony_started method for #{self.class}: #{e}\n#{e.backtrace}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
@started ||= true
|
132
|
+
end
|
133
|
+
|
134
|
+
# Trigger colony_finished if all ants are finished
|
135
|
+
def colony_ant_finished
|
136
|
+
# FIXME: Dont require any arguments
|
137
|
+
if finished?
|
138
|
+
begin
|
139
|
+
colony_finished
|
140
|
+
rescue => e
|
141
|
+
logger.error "There was an error processing colony_finished method for #{self.class}: #{e}\n#{e.backtrace}"
|
142
|
+
ensure
|
143
|
+
Queen.queen.kill_colony(self)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Colony type
|
149
|
+
def type
|
150
|
+
@params['type']
|
151
|
+
end
|
152
|
+
|
153
|
+
# Calculate priority for colony
|
154
|
+
def get_priority
|
155
|
+
pr = 0
|
156
|
+
begin
|
157
|
+
pr = priority
|
158
|
+
rescue => e
|
159
|
+
logger.error "There was an error processing priority method for #{self.class}: #{e}\n#{e.backtrace}"
|
160
|
+
ensure
|
161
|
+
return pr
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Mark all unprocessed ants as finished
|
166
|
+
def kill
|
167
|
+
ants.each do |ant|
|
168
|
+
ant.change_status(:finished) if ant.status == :not_started
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Return array of params hashes for ants
|
173
|
+
# default: empty array
|
174
|
+
# Should be redefined in child class
|
175
|
+
def search_ants(params)
|
176
|
+
[]
|
177
|
+
end
|
178
|
+
|
179
|
+
# Return AntColony priority
|
180
|
+
# default: -created_at.to_int
|
181
|
+
# Can be redefined in child class
|
182
|
+
def priority
|
183
|
+
-created_at.to_i
|
184
|
+
end
|
185
|
+
|
186
|
+
# Convert ant to string
|
187
|
+
# Can be redefined in child class
|
188
|
+
def ant_to_s(ant)
|
189
|
+
ant.params.inspect
|
190
|
+
end
|
191
|
+
|
192
|
+
# Actions to perform if colony started
|
193
|
+
# Can be redefined in child class
|
194
|
+
def colony_started
|
195
|
+
end
|
196
|
+
|
197
|
+
# Actions to perform if colony finished
|
198
|
+
# Can be redefined in child class
|
199
|
+
def colony_finished
|
200
|
+
end
|
201
|
+
|
202
|
+
# Actions to perform after ants were found
|
203
|
+
# Can be redefined in child class
|
204
|
+
def after_search
|
205
|
+
end
|
206
|
+
|
207
|
+
# List of params Ants will inherit
|
208
|
+
# Can be redefined in child class
|
209
|
+
def inherited_params
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module AntHill
|
2
|
+
# Configuration of AntHill
|
3
|
+
class Configuration
|
4
|
+
# Attribute readers
|
5
|
+
# +init_time+:: initialize time
|
6
|
+
attr_reader :init_time
|
7
|
+
include DRbUndumped
|
8
|
+
# Default output string lengths for console monitor
|
9
|
+
DEFAULT_MONITOR = { 'hostname_lenght' => 15, 'processed_lenght' => 7}
|
10
|
+
|
11
|
+
# Initialize
|
12
|
+
def initialize
|
13
|
+
@init_time = Time.now
|
14
|
+
@config_file = ''
|
15
|
+
@configuration = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Convert config to hash
|
19
|
+
def to_hash
|
20
|
+
{:init_time => @init_time}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Convert hash to config
|
24
|
+
def from_hash(hash)
|
25
|
+
if hash
|
26
|
+
@init_time = hash[:init_time]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Parse configuration file
|
31
|
+
# +filename+:: configuration file path
|
32
|
+
def parse_yaml(filename)
|
33
|
+
@config_file = filename
|
34
|
+
begin
|
35
|
+
@configuration = YAML::load_file(filename)
|
36
|
+
rescue => ex
|
37
|
+
STDERR.puts "Couldn't find config file #{filename}"
|
38
|
+
STDERR.puts ex
|
39
|
+
STDERR.puts ex.backtrace
|
40
|
+
exit(1)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Require ant_hill implementations
|
45
|
+
def require_libs
|
46
|
+
basedir = @configuration['basedir']
|
47
|
+
lib_path = @configuration['lib_path']
|
48
|
+
$LOAD_PATH << basedir
|
49
|
+
require File.join(basedir,lib_path)
|
50
|
+
rescue LoadError => e
|
51
|
+
STDERR.puts "Configuration file is invalid! No such file exists #{File.join(basedir, lib_path)}\n#{e}\n#{e.backtrace}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Validate minimum configuration is set
|
55
|
+
def validate
|
56
|
+
strict_attrs = ['basedir', 'lib_path', 'types', 'creeps', 'log_dir', 'log_level']
|
57
|
+
error = false
|
58
|
+
unless strict_attrs.all?{|a| @configuration[a]}
|
59
|
+
STDERR.puts "Configuration file is invalid! Pls. define #{strict_attrs.find{|a| !@configuration[a]}.inspect} keys in it"
|
60
|
+
exit(1)
|
61
|
+
end
|
62
|
+
if @configuration['types'].length == 0
|
63
|
+
STDERR.puts "Configuration file is invalid! Pls. define at least one colony type in types section"
|
64
|
+
exit(1)
|
65
|
+
end
|
66
|
+
if @configuration['creeps'].length == 0
|
67
|
+
STDERR.puts "Configuration file is invalid! Pls. define at least one creep type in creeps section"
|
68
|
+
exit(1)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# return class for colony type
|
73
|
+
# +type+:: AntColony type, if nil defult_type key will be used
|
74
|
+
def ant_colony_class(type=nil)
|
75
|
+
get_class_by_type_and_object(type || default_type, 'ant_colony_class')
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return +CreepModifier+ class for colony type
|
79
|
+
# +type+:: AntColony type, if nil defult_type key will be used
|
80
|
+
def creep_modifier_class(type=nil)
|
81
|
+
get_class_by_type_and_object(type || default_type, 'creep_modifier_class')
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get class for given parameters
|
85
|
+
# +type+:: colony type
|
86
|
+
# +object+:: ant_colony_class or creep_modifier_class
|
87
|
+
def get_class_by_type_and_object(type, object)
|
88
|
+
if @configuration['types'][type] && klass = @configuration['types'][type][object]
|
89
|
+
return get_const_by_name(klass)
|
90
|
+
else
|
91
|
+
Log.logger_for(:configuration).error("No class configuration defined for #{object} and type #{type}")
|
92
|
+
end
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get constant by name
|
97
|
+
# +name+:: constant name
|
98
|
+
def get_const_by_name(name)
|
99
|
+
consts = name.split("::")
|
100
|
+
obj = Object
|
101
|
+
begin
|
102
|
+
consts.each{|const|
|
103
|
+
obj = obj.const_get(const)
|
104
|
+
}
|
105
|
+
rescue
|
106
|
+
Log.logger_for(:configuration).error("No such class defined: #{name}")
|
107
|
+
end
|
108
|
+
return obj
|
109
|
+
end
|
110
|
+
|
111
|
+
# Class used for accesing creep node
|
112
|
+
def get_connection_class
|
113
|
+
get_const_by_name(connection_class)
|
114
|
+
end
|
115
|
+
|
116
|
+
# monitor configuration
|
117
|
+
def monitor
|
118
|
+
return @monitor if @monitor
|
119
|
+
@monitor = DEFAULT_MONITOR
|
120
|
+
config = @configuration['monitor'] || {}
|
121
|
+
@monitor = @monitor.merge(config)
|
122
|
+
end
|
123
|
+
|
124
|
+
# get configuration value by name
|
125
|
+
def [](key)
|
126
|
+
@configuration[key.to_s]
|
127
|
+
end
|
128
|
+
|
129
|
+
# allow to use config.<key> syntax
|
130
|
+
# +key+:: return value for specified key
|
131
|
+
def method_missing(key, *args)
|
132
|
+
meth = key.to_s.gsub(/^get_/, "")
|
133
|
+
if @configuration.has_key?(meth)
|
134
|
+
@configuration[meth]
|
135
|
+
else
|
136
|
+
STDERR.puts "No key #{meth} defined in #{@config_file}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Singleton object
|
141
|
+
class << self
|
142
|
+
# Return configuration object
|
143
|
+
# [+filename+]:: config filename or first argument
|
144
|
+
def config(filename = ARGV[0])
|
145
|
+
return @@config if defined?(@@config)
|
146
|
+
@@config = self.new
|
147
|
+
@@config.parse_yaml(filename)
|
148
|
+
@@config.validate
|
149
|
+
@@config.require_libs
|
150
|
+
@@config
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|