configtoolkit 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/FAQ.txt +113 -25
  2. data/Hash.txt +128 -0
  3. data/History.txt +45 -2
  4. data/KeyValue.txt +235 -0
  5. data/Manifest.txt +66 -4
  6. data/README.txt +666 -202
  7. data/Rakefile +3 -5
  8. data/Ruby.txt +172 -0
  9. data/YAML.txt +188 -0
  10. data/examples/hash_example.rb +71 -0
  11. data/examples/key_value_example.dump.cfg +5 -0
  12. data/examples/key_value_example.normal1.cfg +20 -0
  13. data/examples/key_value_example.normal2.cfg +15 -0
  14. data/examples/key_value_example.rb +72 -0
  15. data/examples/key_value_example.wacky.cfg +13 -0
  16. data/examples/load_example.rb +32 -0
  17. data/examples/load_example.yaml +34 -0
  18. data/examples/load_group_example.rb +28 -0
  19. data/examples/load_group_example.yaml +33 -0
  20. data/examples/machineconfig.rb +77 -0
  21. data/examples/ruby_example.rb +32 -0
  22. data/examples/ruby_example.rcfg +52 -0
  23. data/examples/usage_example.rb +93 -0
  24. data/examples/yaml_example.dump.yaml +12 -0
  25. data/examples/yaml_example.rb +48 -0
  26. data/examples/yaml_example.yaml +59 -0
  27. data/lib/configtoolkit.rb +1 -1
  28. data/lib/configtoolkit/baseconfig.rb +522 -418
  29. data/lib/configtoolkit/hasharrayvisitor.rb +242 -0
  30. data/lib/configtoolkit/hashreader.rb +41 -0
  31. data/lib/configtoolkit/hashwriter.rb +45 -0
  32. data/lib/configtoolkit/keyvalueconfig.rb +105 -0
  33. data/lib/configtoolkit/keyvaluereader.rb +597 -0
  34. data/lib/configtoolkit/keyvaluewriter.rb +157 -0
  35. data/lib/configtoolkit/prettyprintwriter.rb +167 -0
  36. data/lib/configtoolkit/reader.rb +62 -0
  37. data/lib/configtoolkit/rubyreader.rb +270 -0
  38. data/lib/configtoolkit/types.rb +42 -26
  39. data/lib/configtoolkit/writer.rb +116 -0
  40. data/lib/configtoolkit/yamlreader.rb +10 -6
  41. data/lib/configtoolkit/yamlwriter.rb +113 -71
  42. data/test/bad_array_index.rcfg +1 -0
  43. data/test/bad_containing_object_assignment.rcfg +2 -0
  44. data/test/bad_directive.cfg +1 -0
  45. data/test/bad_include1.cfg +2 -0
  46. data/test/bad_include2.cfg +3 -0
  47. data/test/bad_parameter_reference.rcfg +1 -0
  48. data/test/contained_sample.cfg +10 -0
  49. data/test/contained_sample.pretty_print +30 -0
  50. data/test/contained_sample.rcfg +22 -0
  51. data/test/contained_sample.yaml +22 -21
  52. data/test/containers.cfg +6 -0
  53. data/test/exta_string_after_container.cfg +2 -0
  54. data/test/extra_container_closing.cfg +2 -0
  55. data/test/extra_string_after_container.cfg +2 -0
  56. data/test/extra_string_after_nested_container.cfg +1 -0
  57. data/test/missing_array_closing.cfg +2 -0
  58. data/test/missing_array_element.cfg +2 -0
  59. data/test/missing_directive.cfg +1 -0
  60. data/test/missing_hash_closing.cfg +2 -0
  61. data/test/missing_hash_element.cfg +2 -0
  62. data/test/missing_hash_value.cfg +1 -0
  63. data/test/missing_include_argument.cfg +1 -0
  64. data/test/missing_key_value_delimiter.cfg +1 -0
  65. data/test/readerwritertest.rb +28 -7
  66. data/test/sample.cfg +10 -0
  67. data/test/sample.pretty_print +30 -0
  68. data/test/sample.rcfg +26 -0
  69. data/test/test_baseconfig.rb +152 -38
  70. data/test/test_hash.rb +82 -0
  71. data/test/test_keyvalue.rb +185 -0
  72. data/test/test_prettyprint.rb +28 -0
  73. data/test/test_ruby.rb +50 -0
  74. data/test/test_yaml.rb +33 -26
  75. data/test/wacky_sample1.cfg +16 -0
  76. data/test/wacky_sample2.cfg +5 -0
  77. data/test/wacky_sample3.cfg +4 -0
  78. data/test/wacky_sample4.cfg +1 -0
  79. metadata +101 -10
  80. data/lib/configtoolkit/toolkit.rb +0 -20
  81. data/test/common.rb +0 -5
@@ -1,23 +1,37 @@
1
1
  #!/usr/bin/env ruby
2
+
3
+ require 'singleton'
4
+
2
5
  module ConfigToolkit
3
6
 
7
+ #
8
+ # All errors raised by ConfigToolkit code descend from
9
+ # this one; while right now it is the only error raised
10
+ # from the ConfigToolkit, at some point an error class hierarchy
11
+ # rooted in this class might be developed.
12
+ #
13
+ class Error < RuntimeError; end
14
+
4
15
  #
5
16
  # Since Ruby does not have a boolean type (TrueClass and FalseClass classes
6
17
  # for boolean values exist, but they descend from Object),
7
- # define an empty marker class that can be passed into the BaseConfig methods
8
- # to add new parameters in order to indicate that the parameter is a boolean
9
- # (the value of the parameter will be FalseClass or TrueClass, however).
18
+ # this is an empty marker class that can be passed into the BaseConfig methods
19
+ # to add new parameters (BaseConfig.add_required_param and
20
+ # BaseConfig.add_optional_param) in order to indicate that the
21
+ # parameter is a boolean (the value of the parameter will be +true+ or
22
+ # +false+, however). Boolean values can be written as "true"/"false" or as
23
+ # "yes"/"no" in configuration files.
10
24
  #
11
25
  class Boolean; end
12
26
 
13
27
  #
14
- # The ConstrainedArray models an Array with a specified size
15
- # and with all elements being a specified class (basically, it models
16
- # the arrays generally offerred by statically typed languages).
28
+ # ConstrainedArray classes model Arrays with specified sizes
29
+ # and with all elements being instances of specified classes (basically,
30
+ # they model the arrays generally offerred by statically typed languages).
17
31
  # A ConstrainedArray has:
18
- # 1.) A minimum number of elements (could be zero)
19
- # 2.) A maximum number of elements (could be infinity)
20
- # 3.) An element class
32
+ # * A minimum number of elements (could be zero)
33
+ # * A maximum number of elements (could be infinity)
34
+ # * An element class (could be Object, which allows any class)
21
35
  #
22
36
  # A future enhancement might be to allow users to specify a different
23
37
  # class for each element. Note that the ConstrainedArray also allows
@@ -25,13 +39,14 @@ class Boolean; end
25
39
  # just instances of its element class. Thus, a ConstrainedArray containing
26
40
  # class Object essentially would have no class constraints.
27
41
  #
28
- # class ConstrainedArray actually is a class generator, similar to Struct.
29
- # Its new() method does *not* return a ConstrainedArray instance, but
30
- # instead returns a new class with the specified constraints that
31
- # descends from ConstrainedArray. The class returned by new() is meant
32
- # to be used in the BaseConfig methods to add new parameters. The
42
+ # ConstrainedArray actually is a class generator, similar to Struct.
43
+ # Its new method does *not* return a ConstrainedArray instance
44
+ # but instead returns a new class with the specified constraints that
45
+ # descends from ConstrainedArray. The class returned by new is meant
46
+ # to be used in the BaseConfig methods to add new parameters
47
+ # (BaseConfig.add_required_param and BaseConfig.add_optional_param). The
33
48
  # value of one of these parameters actually will be a native Ruby
34
- # Array, but one that satisifes the constraints contained in
49
+ # Array, but one that is guaranteed to satisify the constraints contained in
35
50
  # the ConstrainedArray class.
36
51
  #
37
52
  class ConstrainedArray
@@ -46,13 +61,13 @@ class ConstrainedArray
46
61
  attr_reader :element_class
47
62
 
48
63
  #
49
- # The minimum number of elements, or nil if there is no
64
+ # The minimum number of elements, or +nil+ if there is no
50
65
  # minimum.
51
66
  #
52
67
  attr_reader :min_num_elements
53
68
 
54
69
  #
55
- # The maximum number of elements, or nil if there is no
70
+ # The maximum number of elements, or +nil+ if there is no
56
71
  # maximum.
57
72
  #
58
73
  attr_reader :max_num_elements
@@ -62,23 +77,24 @@ class ConstrainedArray
62
77
  # ====Description:
63
78
  # This method does *not* return a ConstrainedArray instance. Instead,
64
79
  # it returns a new ConstrainedArray child class that represents
65
- # an array with the constraints specified in the method arguments.
80
+ # an Array with the constraints specified in the method arguments.
66
81
  # This class can be passed as a parameter class into the BaseConfig methods
67
- # to add parameters.
82
+ # to add parameters (BaseConfig.add_required_param and
83
+ # BaseConfig.add_optional_param).
68
84
  #
69
85
  # ====Parameters:
70
86
  # [element_class]
71
87
  # This constrains all elements of the generated ConstrainedArray
72
- # class to be of this class or one of its child classes.
73
- # If this argument is object, then this constraint effectively
88
+ # class to be of this class or of one of its child classes.
89
+ # If this argument is Object, then this constraint effectively
74
90
  # disappears.
75
- # [min_num_elements = nil]
91
+ # [min_num_elements]
76
92
  # This constrains the generated ConstrainedArray class to have
77
- # at least min_num_elements elements; if this argument is nil or zero,
78
- # the constraint effectively disappears.
79
- # [max_num_elements = nil]
93
+ # at least +min_num_elements+ elements; if this argument is +nil+ or
94
+ # zero, the constraint effectively disappears.
95
+ # [max_num_elements]
80
96
  # This constrains the generated ConstrainedArray class to have
81
- # at most max_num_elements elements; if this argument is nil,
97
+ # at most +max_num_elements+ elements; if this argument is +nil+,
82
98
  # the constraint effectively disappears.
83
99
  #
84
100
  # ====Returns:
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module ConfigToolkit
4
+
5
+ #
6
+ # This is an "abstract class" that documents the Writer interface.
7
+ # I put "abstract class" in quotes because Ruby of course neither supports
8
+ # nor requires abstract classes (that is, one can create a new writer class
9
+ # without extending Writer). It solely serves to document the interface.
10
+ #
11
+ # Writers write configuration (represented by Hashes of parameter names to
12
+ # parameter values with the parameter names being Strings or Symbols and
13
+ # the parameter values being scalars, Hashes, or Arrays) to some underlying
14
+ # data sink (the write method).
15
+ #
16
+ # Writers must implement require_symbol_parameter_names? in order to
17
+ # advertise whether they want parameter names as Strings or Symbols in the
18
+ # Hash passed to the write method.
19
+ #
20
+ class Writer
21
+ #
22
+ # ====Description:
23
+ # This is a concrete method that may be useful to Writers. It
24
+ # returns a Hash containing (possibly nested) entries for
25
+ # +containing_object_name+ and, in the last object name's Hash,
26
+ # +config_hash+. Basically, it returns a Hash that reflects
27
+ # +config_hash+ *and* +containing_object_name+.
28
+ #
29
+ # Some Writers may want to display containing objects in a special way,
30
+ # in which case the arguments to the +write+ method (the configuration Hash
31
+ # and the containing object name) allow them to do so. For Writers
32
+ # that do not want any special handling for containing objects,
33
+ # however, this method can convert that information into a
34
+ # single Hash representation in which the configuration parameters
35
+ # are nested within Hashes for the containing objects (this matches
36
+ # the format returned by readers, as they do not know about
37
+ # containing objects).
38
+ #
39
+ # This should *not* be re-implemented by writers.
40
+ #
41
+ # ====Parameters:
42
+ # [config_hash]
43
+ # A configuration represented as a Hash
44
+ # [containing_object_name]
45
+ # The configuration's containing object name
46
+ #
47
+ # ====Returns:
48
+ # A Hash in which +config_hash+ is nested within Hashes for the
49
+ # containing objects in +containing_object_name+
50
+ #
51
+ def create_containing_object_hash(config_hash, containing_object_name)
52
+ if(containing_object_name.empty?())
53
+ return config_hash
54
+ else
55
+ use_symbol_parameter_names = require_symbol_parameter_names?()
56
+
57
+ containing_object_hash = {}
58
+ object_hash = containing_object_hash
59
+
60
+ object_names = containing_object_name.split(".")
61
+ object_names.each_with_index do |object_name, index|
62
+ #
63
+ # The hash for the last object in containing_object_name must
64
+ # be config_hash, so it gets treated specially.
65
+ #
66
+ if(index < (object_names.size() - 1))
67
+ contained_hash = {}
68
+ else
69
+ contained_hash = config_hash
70
+ end
71
+
72
+ if(use_symbol_parameter_names)
73
+ object_hash[object_names[index].to_sym()] = contained_hash
74
+ else
75
+ object_hash[object_names[index]] = contained_hash
76
+ end
77
+
78
+ object_hash = contained_hash
79
+ end
80
+
81
+ return containing_object_hash
82
+ end
83
+ end
84
+
85
+ public
86
+ #
87
+ # This *must* be implemented by Writers.
88
+ #
89
+ # ====Returns:
90
+ # Returns +true+ if and only if the Writer requires Symbol parameter
91
+ # names (rather than String parameter names) in the Hash passed to write.
92
+ #
93
+ def require_symbol_parameter_names?
94
+ raise NotImplementedError, "abstract method called"
95
+ end
96
+
97
+ #
98
+ # ====Description:
99
+ # This method writes the configuration in +config_hash+, with
100
+ # containing object name +containing_object_name+, to the
101
+ # underlying data sink.
102
+ #
103
+ # This *must* be implemented by Writers.
104
+ #
105
+ # ====Parameters:
106
+ # [config_hash]
107
+ # A configuration represented as a Hash
108
+ # [containing_object_name]
109
+ # The configuration's containing object name
110
+ #
111
+ def write(config_hash, containing_object_name)
112
+ raise NotImplementedError, "abstract method called"
113
+ end
114
+ end
115
+
116
+ end
@@ -1,24 +1,28 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'configtoolkit/reader'
3
+
2
4
  require 'yaml'
3
5
 
4
6
  module ConfigToolkit
5
7
 
6
8
  #
7
- # This class implements the reader interface for
9
+ # This class implements the Reader interface for
8
10
  # YAML configuration files. It basically is a wrapper
9
11
  # over Ruby's YAML library.
10
12
  #
11
- class YAMLReader
13
+ # See YAML.txt for more details.
14
+ #
15
+ class YAMLReader < Reader
12
16
  #
13
17
  # ====Description:
14
- # This constructs a YAMLReader instance for stream,
15
- # where stream either is a file name (a String)
18
+ # This constructs a YAMLReader instance for +stream+,
19
+ # where +stream+ either is a file name (a String)
16
20
  # or an IO object.
17
21
  #
18
22
  # ====Parameters:
19
23
  # [stream]
20
24
  # A file name (String) or an IO object. If
21
- # stream is a file name, then the YAMLReader
25
+ # +stream+ is a file name, then the YAMLReader
22
26
  # will open the associated file.
23
27
  #
24
28
  def initialize(stream)
@@ -31,7 +35,7 @@ class YAMLReader
31
35
 
32
36
  #
33
37
  # ====Returns:
34
- # the contents of the next YAML document read from the underlying
38
+ # The contents of the next YAML document read from the underlying
35
39
  # stream.
36
40
  #
37
41
  def read
@@ -1,45 +1,50 @@
1
1
  #!/usr/bin/env ruby
2
+
3
+ require 'configtoolkit/hasharrayvisitor'
4
+ require 'configtoolkit/writer'
5
+
2
6
  require 'yaml'
3
7
 
4
8
  module ConfigToolkit
5
9
 
6
10
  #
7
- # This class implements the writer interface for
11
+ # This class implements the Writer interface for
8
12
  # YAML configuration files. It basically is a wrapper
9
13
  # over Ruby's YAML library.
10
14
  #
11
15
  # YAML natively supports Integers, Floats, Strings, and Booleans.
12
16
  # Instances of other classes are written by Ruby to YAML configuration
13
17
  # files by recursively writing each of the instance's variables and
14
- # noting in the YAML file the associated Ruby classes. This may not
18
+ # noting the associated Ruby class in the YAML file. This may not
15
19
  # be desired behavior for a configuration file, especially if the file
16
20
  # is going to be shared with a non-Ruby program. On the other hand,
17
21
  # if only Ruby is using the configuration the file, this behavior may
18
22
  # well be very desirable (since arbitrarily complex objects can be
19
- # supported properly by the YAML format). Thus, the YAMLWriter
23
+ # supported directly by the YAML format). Thus, the YAMLWriter
20
24
  # offers two options:
21
25
  # * Only standard YAML classes will be written. Values of classes
22
- # not supported natively by YAML will be converted to Strings.
23
- # * Non-standard YAML classes will be written.
26
+ # not supported natively by YAML will be converted to Strings (with +to_s+).
27
+ # * Non-standard YAML classes will be written (Ruby object->YAML serialization).
28
+ #
29
+ # See YAML.txt for more details.
24
30
  #
25
- class YAMLWriter
31
+ class YAMLWriter < Writer
26
32
  #
27
33
  # ====Description:
28
- # This constructs a YAMLWriter instance for stream,
29
- # where stream either is a file name (a String)
30
- # or an IO object. If only_output_standard_yaml_classes, then
34
+ # This constructs a YAMLWriter instance for +stream+,
35
+ # where +stream+ either is a file name (a String)
36
+ # or an IO object. If +only_output_standard_yaml_classes+, then
31
37
  # the writer only will output standard YAML classes; all classes not
32
- # native to YAML will be converted into Strings before being
33
- # written. If !only_output_standard_yaml_classes, however, then
38
+ # native to YAML will be converted into Strings with +to_s+ before being
39
+ # written. If not +only_output_standard_yaml_classes+, however, then
34
40
  # Ruby serialized classes will be written to the configuration
35
41
  # file.
36
42
  #
37
43
  # ====Parameters:
38
44
  # [stream]
39
45
  # A file name (String) or an IO object. If
40
- # stream is a file name, then the YAMLReader
46
+ # +stream+ is a file name, then the YAMLWriter
41
47
  # will open the associated file.
42
- #
43
48
  # [only_output_standard_yaml_classes]
44
49
  # Whether or not the writer should output only standard
45
50
  # YAML classes.
@@ -53,86 +58,123 @@ class YAMLWriter
53
58
 
54
59
  @only_output_standard_yaml_classes = only_output_standard_yaml_classes
55
60
  end
56
-
57
- #
58
- # ====Description:
59
- # This method converts value to an equivalent representation
60
- # in a standard YAML class. In particular,
61
- # * Hash eleemnts are converted with a
62
- # convert_hash_value_to_standard_yaml_classes call
63
- # * Arrays elements are converted with recursive
64
- # convert_value_to_standard_yaml_class calls
65
- # * Non-standard YAML classes are converted to Strings (via to_s)
66
- #
67
- # ====Parameters:
68
- # [value]
69
- # The value to convert
70
- #
71
- # ====Returns:
72
- # An equivalent representation of value in a standard YAML class
73
- #
74
- def convert_value_to_standard_yaml_class(value)
75
- if(value.class == Hash)
76
- return convert_hash_values_to_standard_yaml_classes(value)
77
- elsif(value.class == Array)
78
- new_value = []
79
-
80
- value.each do |element|
81
- new_value.push(convert_value_to_standard_yaml_class(element))
61
+
62
+ #
63
+ # This class is used for converting the elements of the structure
64
+ # passed to write to standard YAML types. See the comments for
65
+ # HashArrayVisitor for details on how this visitor works.
66
+ #
67
+ # As this visitor visits the nodes of a structure, it builds up a parallel
68
+ # structure that is the same as the original except that all elements are
69
+ # of standard YAML types.
70
+ #
71
+ class Visitor < HashArrayVisitor # :nodoc:
72
+ #
73
+ # The structure currently being built.
74
+ #
75
+ attr_reader :config_hash
76
+
77
+ #
78
+ # A StackFrame contains the container currently being
79
+ # built.
80
+ #
81
+ StackFrame = Struct.new(:container)
82
+
83
+ def initialize
84
+ @config_hash = {}
85
+ @next_container = @config_hash
86
+
87
+ super()
88
+ end
89
+
90
+ #
91
+ # ====Description:
92
+ # This method returns a representation of value using
93
+ # standard YAML types.
94
+ #
95
+ # ====Parameters:
96
+ # [value]
97
+ # The value to convert
98
+ #
99
+ # ====Returns:
100
+ # A standard YAML representation of value
101
+ #
102
+ def convert_value(value)
103
+ if(value.is_a?(Hash))
104
+ return {}
105
+ elsif(value.is_a?(Array))
106
+ return []
107
+ elsif((value.class <= Integer) ||
108
+ (value.class <= Float) ||
109
+ (value.class == TrueClass) ||
110
+ (value.class == FalseClass) ||
111
+ (value.class == String))
112
+ return value
113
+ else
114
+ return value.to_s
82
115
  end
116
+ end
117
+ private :convert_value
83
118
 
84
- return new_value
85
- elsif((value.class <= Integer) ||
86
- (value.class <= Float) ||
87
- (value.class == TrueClass) ||
88
- (value.class == FalseClass) ||
89
- (value.class == String))
90
- return value
91
- else
92
- return value.to_s
119
+ def enter_hash(hash)
120
+ return StackFrame.new(@next_container)
121
+ end
122
+
123
+ def visit_hash_element(key, value, index, is_container)
124
+ converted_value = convert_value(value)
125
+
126
+ if(is_container)
127
+ @next_container = converted_value
128
+ end
129
+
130
+ stack_top().container[key] = converted_value
131
+ end
132
+
133
+ def enter_array(array)
134
+ return StackFrame.new(@next_container)
135
+ end
136
+
137
+ def visit_array_element(value, index, is_container)
138
+ converted_value = convert_value(value)
139
+
140
+ if(is_container)
141
+ @next_container = converted_value
142
+ end
143
+
144
+ stack_top().container.push(converted_value)
93
145
  end
94
146
  end
95
- private :convert_value_to_standard_yaml_class
96
147
 
97
- #
98
- # ====Description:
99
- # This method converts all elements of hash_value to standard
100
- # YAML classes via convert_value_to_standard_yaml_class calls.
101
- #
102
- # ====Parameters:
103
- # [hash_value]
104
- # The Hash to convert
105
148
  #
106
149
  # ====Returns:
107
- # An equivalent Hash, the values of which are of standard YAML classes.
150
+ # Returns +false+, since the YAMLWriter requires String parameter names
151
+ # in the Hash passed to write.
108
152
  #
109
- def convert_hash_values_to_standard_yaml_classes(hash_value)
110
- standard_yaml_class_hash_value = {}
111
-
112
- hash_value.each do |param_name, value|
113
- standard_yaml_class_hash_value[param_name] = convert_value_to_standard_yaml_class(value)
114
- end
115
-
116
- return standard_yaml_class_hash_value
153
+ def require_symbol_parameter_names?
154
+ return false
117
155
  end
118
- private :convert_hash_values_to_standard_yaml_classes
119
156
 
120
157
  #
121
158
  # ====Description:
122
- # This method writes config_hash to the underlying stream.
159
+ # This method writes +config_hash+ to the underlying stream.
123
160
  #
124
161
  # ====Parameters:
125
162
  # [config_hash]
126
163
  # The configuration hash to write
164
+ # [containing_object_name]
165
+ # The configuration's containing object name
127
166
  #
128
- def write(config_hash)
167
+ def write(config_hash, containing_object_name)
129
168
  if(@only_output_standard_yaml_classes)
130
- output_hash = convert_hash_values_to_standard_yaml_classes(config_hash)
169
+ visitor = Visitor.new()
170
+ visitor.visit(config_hash)
171
+ output_hash = visitor.config_hash
131
172
  else
132
173
  output_hash = config_hash
133
174
  end
134
175
 
135
- YAML::dump(output_hash, @stream)
176
+ containing_object_hash = create_containing_object_hash(output_hash, containing_object_name)
177
+ YAML::dump(containing_object_hash, @stream)
136
178
  @stream.flush()
137
179
  end
138
180
  end