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.
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