configtoolkit 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/FAQ.txt +113 -25
- data/Hash.txt +128 -0
- data/History.txt +45 -2
- data/KeyValue.txt +235 -0
- data/Manifest.txt +66 -4
- data/README.txt +666 -202
- data/Rakefile +3 -5
- data/Ruby.txt +172 -0
- data/YAML.txt +188 -0
- data/examples/hash_example.rb +71 -0
- data/examples/key_value_example.dump.cfg +5 -0
- data/examples/key_value_example.normal1.cfg +20 -0
- data/examples/key_value_example.normal2.cfg +15 -0
- data/examples/key_value_example.rb +72 -0
- data/examples/key_value_example.wacky.cfg +13 -0
- data/examples/load_example.rb +32 -0
- data/examples/load_example.yaml +34 -0
- data/examples/load_group_example.rb +28 -0
- data/examples/load_group_example.yaml +33 -0
- data/examples/machineconfig.rb +77 -0
- data/examples/ruby_example.rb +32 -0
- data/examples/ruby_example.rcfg +52 -0
- data/examples/usage_example.rb +93 -0
- data/examples/yaml_example.dump.yaml +12 -0
- data/examples/yaml_example.rb +48 -0
- data/examples/yaml_example.yaml +59 -0
- data/lib/configtoolkit.rb +1 -1
- data/lib/configtoolkit/baseconfig.rb +522 -418
- data/lib/configtoolkit/hasharrayvisitor.rb +242 -0
- data/lib/configtoolkit/hashreader.rb +41 -0
- data/lib/configtoolkit/hashwriter.rb +45 -0
- data/lib/configtoolkit/keyvalueconfig.rb +105 -0
- data/lib/configtoolkit/keyvaluereader.rb +597 -0
- data/lib/configtoolkit/keyvaluewriter.rb +157 -0
- data/lib/configtoolkit/prettyprintwriter.rb +167 -0
- data/lib/configtoolkit/reader.rb +62 -0
- data/lib/configtoolkit/rubyreader.rb +270 -0
- data/lib/configtoolkit/types.rb +42 -26
- data/lib/configtoolkit/writer.rb +116 -0
- data/lib/configtoolkit/yamlreader.rb +10 -6
- data/lib/configtoolkit/yamlwriter.rb +113 -71
- data/test/bad_array_index.rcfg +1 -0
- data/test/bad_containing_object_assignment.rcfg +2 -0
- data/test/bad_directive.cfg +1 -0
- data/test/bad_include1.cfg +2 -0
- data/test/bad_include2.cfg +3 -0
- data/test/bad_parameter_reference.rcfg +1 -0
- data/test/contained_sample.cfg +10 -0
- data/test/contained_sample.pretty_print +30 -0
- data/test/contained_sample.rcfg +22 -0
- data/test/contained_sample.yaml +22 -21
- data/test/containers.cfg +6 -0
- data/test/exta_string_after_container.cfg +2 -0
- data/test/extra_container_closing.cfg +2 -0
- data/test/extra_string_after_container.cfg +2 -0
- data/test/extra_string_after_nested_container.cfg +1 -0
- data/test/missing_array_closing.cfg +2 -0
- data/test/missing_array_element.cfg +2 -0
- data/test/missing_directive.cfg +1 -0
- data/test/missing_hash_closing.cfg +2 -0
- data/test/missing_hash_element.cfg +2 -0
- data/test/missing_hash_value.cfg +1 -0
- data/test/missing_include_argument.cfg +1 -0
- data/test/missing_key_value_delimiter.cfg +1 -0
- data/test/readerwritertest.rb +28 -7
- data/test/sample.cfg +10 -0
- data/test/sample.pretty_print +30 -0
- data/test/sample.rcfg +26 -0
- data/test/test_baseconfig.rb +152 -38
- data/test/test_hash.rb +82 -0
- data/test/test_keyvalue.rb +185 -0
- data/test/test_prettyprint.rb +28 -0
- data/test/test_ruby.rb +50 -0
- data/test/test_yaml.rb +33 -26
- data/test/wacky_sample1.cfg +16 -0
- data/test/wacky_sample2.cfg +5 -0
- data/test/wacky_sample3.cfg +4 -0
- data/test/wacky_sample4.cfg +1 -0
- metadata +101 -10
- data/lib/configtoolkit/toolkit.rb +0 -20
- data/test/common.rb +0 -5
@@ -0,0 +1,157 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'configtoolkit/keyvalueconfig'
|
4
|
+
require 'configtoolkit/writer'
|
5
|
+
|
6
|
+
module ConfigToolkit
|
7
|
+
|
8
|
+
#
|
9
|
+
# This class implements the Writer interface for key-value configuration
|
10
|
+
# files.
|
11
|
+
#
|
12
|
+
# See KeyValue.txt for more details about the key-value configuration format.
|
13
|
+
#
|
14
|
+
class KeyValueWriter < Writer
|
15
|
+
#
|
16
|
+
# ====Description:
|
17
|
+
# This constructs a KeyValueWriter instance for a key-value
|
18
|
+
# format described by +config+ for +stream+,
|
19
|
+
# where stream is either a file name (a String)
|
20
|
+
# or an IO object.
|
21
|
+
#
|
22
|
+
# ====Parameters:
|
23
|
+
# [stream]
|
24
|
+
# A file name (String) or an IO object. If
|
25
|
+
# +stream+ is a file name, then the KeyValueWriter
|
26
|
+
# will open the associated file.
|
27
|
+
# [config]
|
28
|
+
# A KeyValueConfig that defines the format of the key-value
|
29
|
+
# output that this instance can write
|
30
|
+
#
|
31
|
+
def initialize(stream, config = KeyValueConfig::DEFAULT_CONFIG)
|
32
|
+
if(stream.class == String)
|
33
|
+
@stream = File.open(stream, "w")
|
34
|
+
else
|
35
|
+
@stream = stream
|
36
|
+
end
|
37
|
+
|
38
|
+
@config = config
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# ====Returns:
|
43
|
+
# Returns +true+, since the KeyValueWriter requires Symbol parameter names
|
44
|
+
# in the Hash passed to write.
|
45
|
+
#
|
46
|
+
def require_symbol_parameter_names?
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# This class is used for printing nested elements of the structure
|
52
|
+
# passed to write. See the comments for HashArrayVisitor for
|
53
|
+
# details on how this visitor works.
|
54
|
+
#
|
55
|
+
class Visitor < HashArrayVisitor # :nodoc:
|
56
|
+
#
|
57
|
+
# ====Description:
|
58
|
+
# This constructs a Visitor to write structures in key-value format to
|
59
|
+
# stream stream.
|
60
|
+
#
|
61
|
+
# ====Parameters:
|
62
|
+
# [stream]
|
63
|
+
# The stream to which to write
|
64
|
+
# [config]
|
65
|
+
# The KeyValueConfig that defines the format of the key-value
|
66
|
+
# output that this instance can write
|
67
|
+
#
|
68
|
+
def initialize(stream, config)
|
69
|
+
@stream = stream
|
70
|
+
@config = config
|
71
|
+
|
72
|
+
super()
|
73
|
+
end
|
74
|
+
|
75
|
+
def enter_hash(hash)
|
76
|
+
@stream << @config.hash_opening
|
77
|
+
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def leave_hash(popped_stack_frame)
|
82
|
+
@stream << @config.hash_closing
|
83
|
+
end
|
84
|
+
|
85
|
+
def visit_hash_element(key, value, index, is_container)
|
86
|
+
if(index != 0)
|
87
|
+
@stream << "#{@config.container_element_delimiter} "
|
88
|
+
end
|
89
|
+
|
90
|
+
@stream << "#{key} #{@config.hash_key_value_delimiter} "
|
91
|
+
|
92
|
+
if(!is_container)
|
93
|
+
@stream << "#{value}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def enter_array(array)
|
98
|
+
@stream << @config.array_opening
|
99
|
+
end
|
100
|
+
|
101
|
+
def visit_array_element(value, index, is_container)
|
102
|
+
if(index != 0)
|
103
|
+
@stream << "#{@config.container_element_delimiter} "
|
104
|
+
end
|
105
|
+
|
106
|
+
if(!is_container)
|
107
|
+
@stream << "#{value}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def leave_array(popped_stack_frame)
|
112
|
+
@stream << @config.array_closing
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# ====Description:
|
118
|
+
# This method writes +config_hash+ to the underlying stream.
|
119
|
+
#
|
120
|
+
# ====Parameters:
|
121
|
+
# [config_hash]
|
122
|
+
# The configuration hash to write
|
123
|
+
# [containing_object_name]
|
124
|
+
# The configuration's containing object name
|
125
|
+
#
|
126
|
+
def write(config_hash, containing_object_name)
|
127
|
+
#
|
128
|
+
# Unfortunately, the Visitor cannot be used to write out the top level
|
129
|
+
# of configuration parameters, because the top-level format is different
|
130
|
+
# than the nested levels (the top-level *is* a hash, but is written
|
131
|
+
# out without braces and with one element on each line; the top-level
|
132
|
+
# also has a containing object name).
|
133
|
+
#
|
134
|
+
if(!containing_object_name.empty?())
|
135
|
+
key_prefix = "#{containing_object_name.gsub(/\./, @config.object_delimiter)}#{@config.object_delimiter}"
|
136
|
+
else
|
137
|
+
key_prefix = ""
|
138
|
+
end
|
139
|
+
|
140
|
+
visitor = Visitor.new(@stream, @config)
|
141
|
+
config_hash.each do |key, value|
|
142
|
+
@stream << "#{key_prefix}#{key} #{@config.key_value_delimiter} "
|
143
|
+
|
144
|
+
if(value.is_a?(Hash) || value.is_a?(Array))
|
145
|
+
visitor.visit(value)
|
146
|
+
else
|
147
|
+
@stream << "#{value}"
|
148
|
+
end
|
149
|
+
|
150
|
+
@stream << "\n"
|
151
|
+
end
|
152
|
+
|
153
|
+
@stream.flush()
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'configtoolkit/hasharrayvisitor'
|
4
|
+
require 'configtoolkit/writer'
|
5
|
+
|
6
|
+
module ConfigToolkit
|
7
|
+
|
8
|
+
#
|
9
|
+
# This class implements the Writer interface for pretty printing
|
10
|
+
# configuration classes to specified streams. This is used by
|
11
|
+
# BaseConfig#to_s.
|
12
|
+
#
|
13
|
+
class PrettyPrintWriter < Writer
|
14
|
+
#
|
15
|
+
# ====Description:
|
16
|
+
# This constructs a PrettyPrintWriter instance for +stream+,
|
17
|
+
# where +stream+ either is a file name (a String)
|
18
|
+
# or an IO object.
|
19
|
+
#
|
20
|
+
# ====Parameters:
|
21
|
+
# [stream]
|
22
|
+
# A file name (String) or an IO object. If
|
23
|
+
# +stream+ is a file name, then the PrettyPrintWriter
|
24
|
+
# will open the associated file.
|
25
|
+
#
|
26
|
+
def initialize(stream)
|
27
|
+
if(stream.class == String)
|
28
|
+
@stream = File.open(stream, "w")
|
29
|
+
else
|
30
|
+
@stream = stream
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# This class is used for printing the elements of the structure
|
36
|
+
# passed to write. See the comments for HashArrayVisitor for
|
37
|
+
# details on how this visitor works.
|
38
|
+
#
|
39
|
+
class Visitor < HashArrayVisitor # :nodoc:
|
40
|
+
#
|
41
|
+
# The amount by which to indent for each nesting level when
|
42
|
+
# pretty printing.
|
43
|
+
#
|
44
|
+
NESTING_INDENT = " " * 4
|
45
|
+
|
46
|
+
StackFrame = Struct.new(:indent, :longest_param_name_length)
|
47
|
+
|
48
|
+
#
|
49
|
+
# ====Description:
|
50
|
+
# This constructs a Visitor to pretty pretty structures to
|
51
|
+
# stream stream.
|
52
|
+
#
|
53
|
+
# ====Parameters:
|
54
|
+
# [stream]
|
55
|
+
# The stream to which to write
|
56
|
+
#
|
57
|
+
def initialize(stream)
|
58
|
+
@stream = stream
|
59
|
+
|
60
|
+
super(StackFrame.new("", 0))
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# ====Description:
|
65
|
+
# This method returns a new stack frame with
|
66
|
+
# longest parameter name length longest_param_name_length.
|
67
|
+
# The indentation for the new frame automatically is
|
68
|
+
# constructed from the current indentation.
|
69
|
+
#
|
70
|
+
# ====Parameters:
|
71
|
+
# [longest_param_name_length]
|
72
|
+
# The length of the longest parameter name in the
|
73
|
+
# container associated with the frame
|
74
|
+
#
|
75
|
+
# ====Returns:
|
76
|
+
# A new stack frame
|
77
|
+
#
|
78
|
+
def construct_new_stack_frame(longest_param_name_length)
|
79
|
+
indent = stack_top().indent + NESTING_INDENT
|
80
|
+
|
81
|
+
return StackFrame.new(indent, longest_param_name_length)
|
82
|
+
end
|
83
|
+
|
84
|
+
def enter_hash(hash)
|
85
|
+
@stream << "{"
|
86
|
+
|
87
|
+
longest_param_name_length = 0
|
88
|
+
hash.each_key do |key|
|
89
|
+
key_size = key.to_s().size()
|
90
|
+
if(key_size > longest_param_name_length)
|
91
|
+
longest_param_name_length = key_size
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
return construct_new_stack_frame(longest_param_name_length)
|
96
|
+
end
|
97
|
+
|
98
|
+
def leave_hash(popped_stack_frame)
|
99
|
+
@stream << "\n#{stack_top.indent}" << "}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def visit_hash_element(key, value, index, is_container)
|
103
|
+
@stream << "\n#{stack_top.indent}"
|
104
|
+
@stream << key.to_s.ljust(stack_top.longest_param_name_length)
|
105
|
+
@stream << ": "
|
106
|
+
|
107
|
+
if(!is_container)
|
108
|
+
@stream << "#{value}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def enter_array(array)
|
113
|
+
@stream << "["
|
114
|
+
|
115
|
+
return construct_new_stack_frame(stack_top().longest_param_name_length)
|
116
|
+
end
|
117
|
+
|
118
|
+
def visit_array_element(value, index, is_container)
|
119
|
+
if(index != 0)
|
120
|
+
@stream << ","
|
121
|
+
end
|
122
|
+
|
123
|
+
@stream << "\n#{stack_top.indent}"
|
124
|
+
|
125
|
+
if(!is_container)
|
126
|
+
@stream << "#{value}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def leave_array(popped_stack_frame)
|
131
|
+
@stream << "\n#{stack_top.indent}" << "]"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# ====Returns:
|
137
|
+
# Returns +true+, since the PrettyPrintWriter requires Symbol parameter names
|
138
|
+
# in the hash passed to write.
|
139
|
+
#
|
140
|
+
def require_symbol_parameter_names?
|
141
|
+
return true
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# ====Description:
|
146
|
+
# This method pretty prints +config_hash+ to the underlying stream.
|
147
|
+
#
|
148
|
+
# ====Parameters:
|
149
|
+
# [config_hash]
|
150
|
+
# The configuration hash to write
|
151
|
+
# [containing_object_name]
|
152
|
+
# The configuration's containing object name
|
153
|
+
#
|
154
|
+
def write(config_hash, containing_object_name)
|
155
|
+
if(!containing_object_name.empty?())
|
156
|
+
@stream << "#{containing_object_name}: "
|
157
|
+
end
|
158
|
+
|
159
|
+
Visitor.new(@stream).visit(config_hash)
|
160
|
+
|
161
|
+
@stream << "\n"
|
162
|
+
|
163
|
+
@stream.flush()
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module ConfigToolkit
|
6
|
+
|
7
|
+
#
|
8
|
+
# This is an "abstract class" that documents the Reader interface.
|
9
|
+
# I put "abstract class" in quotes because Ruby of course neither supports
|
10
|
+
# nor requires abstract classes (that is, one can create a new reader class
|
11
|
+
# without extending Reader). It solely serves to document the interface.
|
12
|
+
#
|
13
|
+
# Readers read data (the read method) from some underlying source and
|
14
|
+
# return configuration represented as a Hash of parameter names
|
15
|
+
# to parameter values. The parameter names can be either Strings
|
16
|
+
# or Symbols; the parameter values can be scalars, Hashes, or Arrays.
|
17
|
+
# Array or Hash parameter values can in turn have Arrays or Hash elements,
|
18
|
+
# arbitrarily deeply nested.
|
19
|
+
#
|
20
|
+
class Reader
|
21
|
+
public
|
22
|
+
|
23
|
+
#
|
24
|
+
# ====Description:
|
25
|
+
# This class method attempts to find a file system path for +stream+, where
|
26
|
+
# stream is an argument passed to a reader constructor (if a String, then
|
27
|
+
# stream names a file; otherwise, stream should be an IO or IO-like object)
|
28
|
+
# This is used by several of the the reader classes.
|
29
|
+
#
|
30
|
+
# ====Parameters:
|
31
|
+
# [stream]
|
32
|
+
# The underlying data source passed to a reader's constructor
|
33
|
+
#
|
34
|
+
# ====Returns:
|
35
|
+
# The file system path for +stream+, or +nil+ if unable to find this
|
36
|
+
#
|
37
|
+
def self.stream_path(stream)
|
38
|
+
if(stream.is_a?(String))
|
39
|
+
return Pathname.new(stream) # stream must be a file name
|
40
|
+
elsif(stream.is_a?(File))
|
41
|
+
return Pathname.new(stream.path())
|
42
|
+
else
|
43
|
+
return nil # Who knows?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# This reads and returns configuration represented as a Hash, where
|
49
|
+
# the elements are parameter names (Strings or Symbols) mapping to
|
50
|
+
# parameter values.
|
51
|
+
#
|
52
|
+
# This *must* be implemented by Readers.
|
53
|
+
#
|
54
|
+
# ====Returns:
|
55
|
+
# The contents of a configuration as a Hash
|
56
|
+
#
|
57
|
+
def read
|
58
|
+
raise NotImplementedError, "abstract method called"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'configtoolkit/reader'
|
4
|
+
require 'configtoolkit/types'
|
5
|
+
|
6
|
+
require 'singleton'
|
7
|
+
|
8
|
+
module ConfigToolkit
|
9
|
+
|
10
|
+
#
|
11
|
+
# This class implements the Reader interface for Ruby configuration files.
|
12
|
+
# Ruby configuration files essentially are key-value configuration files
|
13
|
+
# parsed by the Ruby interpreter; they allow the full Ruby language to
|
14
|
+
# be used while building up a configuration. Parameters are specified
|
15
|
+
# by config.param_name or config.containing_object.param_name. They can
|
16
|
+
# be assigned to and, after assignment, used in other expressions. If a
|
17
|
+
# name first is referred to with a setter (i.e., "age" in
|
18
|
+
# "config.first.second.age = 5"), then the name is assumed to be a
|
19
|
+
# parameter name. If a name first is referred to with a
|
20
|
+
# getter (i.e., "first" and "second" in "config.first.second.age = 5"),
|
21
|
+
# then the name is assumed to be an object name (either a containing or
|
22
|
+
# a nested configuration object). Objects cannot be set
|
23
|
+
# (i.e., "config.first.second = 2" is illegal).
|
24
|
+
# Parameter values, once set, can be referenced in later
|
25
|
+
# expressions.
|
26
|
+
#
|
27
|
+
# See Ruby.txt for more details.
|
28
|
+
#
|
29
|
+
class RubyReader < Reader
|
30
|
+
#
|
31
|
+
# ====Description:
|
32
|
+
# This constructs a RubyReader instance for +stream+,
|
33
|
+
# where +stream+ either is a file name (a String)
|
34
|
+
# or an IO object.
|
35
|
+
#
|
36
|
+
# ====Parameters:
|
37
|
+
# [stream]
|
38
|
+
# A file name (String) or an IO object. If
|
39
|
+
# +stream+ is a file name, then the RubyReader
|
40
|
+
# will open the associated file.
|
41
|
+
#
|
42
|
+
def initialize(stream)
|
43
|
+
if(stream.class == String)
|
44
|
+
@stream = File.open(stream, "r")
|
45
|
+
else
|
46
|
+
@stream = stream
|
47
|
+
end
|
48
|
+
|
49
|
+
@stream_path = Reader.stream_path(stream)
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# This is a helper class (not meant to be used externally).
|
54
|
+
# The Ruby code in Ruby configuration files is executed in the
|
55
|
+
# context of the instances of this class. It provides a config
|
56
|
+
# instance method that allows references to "config.param_name"
|
57
|
+
# in the configuration file to work. This method returns a
|
58
|
+
# ObjectProxy, which in turn re-implements method_missing
|
59
|
+
# so as to allow references to arbitrary parameters.
|
60
|
+
#
|
61
|
+
class Interpreter # :nodoc:
|
62
|
+
#
|
63
|
+
# This serves as a marker that allows the ObjectProxy
|
64
|
+
# to distinguish between "simple" (non-object) parameter values and
|
65
|
+
# objects. It is a singleton because there is no need for more than
|
66
|
+
# one simple parameter marker object.
|
67
|
+
#
|
68
|
+
class SimpleParam # :nodoc:
|
69
|
+
include Singleton
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# ObjectProxy instances use method_missing to build up
|
74
|
+
# configuration Hashes (returned by the Reader's read method)
|
75
|
+
# from arbitrary parameter references in the Ruby configuration file.
|
76
|
+
# An instance maintains two Hashes. The first is the configuration hash
|
77
|
+
# that maps parameter names to parameter values and containing object names
|
78
|
+
# to Hashes. The second is the proxy hash that maps containing object
|
79
|
+
# names to ObjectProxy instances (and parameter names to
|
80
|
+
# the SimpleParam singleton marker).
|
81
|
+
#
|
82
|
+
# For instance, if the configuration file has "config.age = 5",
|
83
|
+
# age= will trigger a method_missing call that in turn will
|
84
|
+
# map :age to 5 in its configuration hash table (and also :age to
|
85
|
+
# SimpleParam in its proxy hash table if :age was encountered for the first
|
86
|
+
# time). A line like "config.first.age = 5", will trigger
|
87
|
+
# a method_missing call to first. Assuming first does not exist already,
|
88
|
+
# a new ObjectProxy will be created for it (:first => new
|
89
|
+
# ObjectProxy in the proxy hash table and :first => {} in the
|
90
|
+
# configuration hash table) and returned; age then will work with the
|
91
|
+
# new ObjectProxy as described.
|
92
|
+
#
|
93
|
+
# method_missing classifies references according to the following:
|
94
|
+
# 1.) Setter methods must be setting parameters.
|
95
|
+
# 2.) Getter methods must refer to a containing object if
|
96
|
+
# the name never has been encountered before and, if it has,
|
97
|
+
# to whatever it originally was resolved.
|
98
|
+
#
|
99
|
+
# Thus, containing objects are referred to via getters and only can be
|
100
|
+
# referred to via getters. Parameters first must be
|
101
|
+
# referred to via a setter and then can be referred to via a
|
102
|
+
# getter or a setter (you can't get the value of a parameter that hasn't
|
103
|
+
# been set yet, after all). Getters for containing objects return
|
104
|
+
# ObjectProxy instances, so that they in turn can service member
|
105
|
+
# references (i.e, "config.first.second.age = 2" has config, first, and
|
106
|
+
# second returning ObjectProxy instances).
|
107
|
+
#
|
108
|
+
# The configuration and proxy hashes have parallel structures, with
|
109
|
+
# the configuration hash mapping names to values and the proxy hash
|
110
|
+
# mapping names to containing object proxies.
|
111
|
+
#
|
112
|
+
class ObjectProxy # :nodoc:
|
113
|
+
#
|
114
|
+
# ====Description:
|
115
|
+
# This contructs a ObjectProxy instance to have
|
116
|
+
# empty configuration and proxy hashes.
|
117
|
+
#
|
118
|
+
def initialize(name)
|
119
|
+
@name = name
|
120
|
+
@config_hash = {}
|
121
|
+
@proxy_hash = {}
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# ====Description:
|
126
|
+
# This is an accessor for the @config_hash member variable. It is
|
127
|
+
# surrounded by ___ characters on each side in order to reduce the
|
128
|
+
# chance of any collision occurring with a name referenced in the
|
129
|
+
# configuration file (___config_hash___ cannot be used as a
|
130
|
+
# parameter or containing object name).
|
131
|
+
#
|
132
|
+
# ====Returns:
|
133
|
+
# @config_hash
|
134
|
+
#
|
135
|
+
def ___config_hash___
|
136
|
+
return @config_hash
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# ====Description:
|
141
|
+
# This method simulates methods in order to allow configuration
|
142
|
+
# files to reference arbitrary parameter and containing object names.
|
143
|
+
# When a configuration file contains "config.first.second.age = 2",
|
144
|
+
# the references to first, second, and age are handled by this
|
145
|
+
# method.
|
146
|
+
#
|
147
|
+
# ====Parameters:
|
148
|
+
# [method_name]
|
149
|
+
# The missing method
|
150
|
+
# [*args]
|
151
|
+
# The arguments to the missing method
|
152
|
+
#
|
153
|
+
# ====Returns:
|
154
|
+
# If a getter, then the ObjectProxy associated with the
|
155
|
+
# containing object or the parameter's value. If a setter (which
|
156
|
+
# must act on a parameter), then the new value.
|
157
|
+
#
|
158
|
+
def method_missing(method_name, *args)
|
159
|
+
method_name_str = method_name.to_s()
|
160
|
+
|
161
|
+
if((method_name_str.size() >= 2) && (method_name_str[0, 2] == "[]"))
|
162
|
+
raise Error, "cannot index with array notation into #{@name}"
|
163
|
+
elsif(method_name_str[-1,1] == "=")
|
164
|
+
new_value = args[0]
|
165
|
+
|
166
|
+
#
|
167
|
+
# The interpreter more or less guarantees that args.size() == 1
|
168
|
+
# for setter methods.
|
169
|
+
#
|
170
|
+
|
171
|
+
#
|
172
|
+
# Get rid of the trailing '='
|
173
|
+
#
|
174
|
+
object_name = method_name_str[0, method_name_str.size() - 1].to_sym()
|
175
|
+
proxy = @proxy_hash.fetch(object_name, nil)
|
176
|
+
if(proxy == nil)
|
177
|
+
#
|
178
|
+
# A parameter value is being set for the first time, so
|
179
|
+
# record the parameter name in the proxy hash
|
180
|
+
#
|
181
|
+
@proxy_hash[object_name] = SimpleParam.instance()
|
182
|
+
return (@config_hash[object_name] = new_value)
|
183
|
+
elsif(proxy.is_a?(SimpleParam))
|
184
|
+
return (@config_hash[object_name] = new_value)
|
185
|
+
else
|
186
|
+
raise Error, "cannot assign to object #{@name}.#{object_name}"
|
187
|
+
end
|
188
|
+
elsif(args.size != 0)
|
189
|
+
super.missing_method(method_name, *args)
|
190
|
+
else
|
191
|
+
object_name = method_name
|
192
|
+
proxy = @proxy_hash.fetch(object_name, nil)
|
193
|
+
|
194
|
+
if(proxy == nil)
|
195
|
+
#
|
196
|
+
# Since this is a getter and there is no record of the
|
197
|
+
# name, the name must refer to a containing object.
|
198
|
+
#
|
199
|
+
proxy = @proxy_hash[object_name] = ObjectProxy.new("#{@name}.#{object_name}")
|
200
|
+
@config_hash[object_name] = proxy.___config_hash___
|
201
|
+
return proxy
|
202
|
+
elsif(proxy.is_a?(ObjectProxy))
|
203
|
+
return proxy
|
204
|
+
else
|
205
|
+
return @config_hash[object_name]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
#
|
212
|
+
# ====Description:
|
213
|
+
# This initializes the Interpreter with an empty
|
214
|
+
# configuration hash.
|
215
|
+
#
|
216
|
+
def initialize
|
217
|
+
@root_proxy = ObjectProxy.new("config")
|
218
|
+
end
|
219
|
+
|
220
|
+
#
|
221
|
+
# ====Description:
|
222
|
+
# This method returns a reference to the root ObjectProxy.
|
223
|
+
# It exists so that configuration parameters can be set in Ruby
|
224
|
+
# configuration files via lines like "config.age = 5".
|
225
|
+
#
|
226
|
+
# ====Returns:
|
227
|
+
# The root ObjectProxy
|
228
|
+
#
|
229
|
+
def config
|
230
|
+
return @root_proxy
|
231
|
+
end
|
232
|
+
private :config
|
233
|
+
|
234
|
+
#
|
235
|
+
# ====Description:
|
236
|
+
# This method interprets (evals) code (which should have been
|
237
|
+
# sourced from a file with path file_path) and returns a Hash
|
238
|
+
# representing the configuration built by code
|
239
|
+
#
|
240
|
+
# ====Parameters:
|
241
|
+
# [code]
|
242
|
+
# The code containing the configuration to be interpreted (String)
|
243
|
+
# [file_path]
|
244
|
+
# The path of the file containing the code (Pathname or nil)
|
245
|
+
#
|
246
|
+
# ====Returns:
|
247
|
+
# A Hash representing the configuration built by code
|
248
|
+
#
|
249
|
+
def interpret(code, file_path)
|
250
|
+
if(file_path == nil)
|
251
|
+
instance_eval(code)
|
252
|
+
else
|
253
|
+
instance_eval(code, file_path.to_s())
|
254
|
+
end
|
255
|
+
|
256
|
+
return @root_proxy.___config_hash___
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
#
|
261
|
+
# ====Returns:
|
262
|
+
# The contents of the ruby configuration file
|
263
|
+
#
|
264
|
+
def read
|
265
|
+
interpreter = Interpreter.new()
|
266
|
+
return interpreter.interpret(@stream.read(), @stream_path)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|