configtoolkit 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|