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
data/Rakefile CHANGED
@@ -1,13 +1,11 @@
1
1
  # -*- ruby -*-
2
2
 
3
- $LOAD_PATH.unshift("lib")
4
-
5
3
  require 'rubygems'
6
4
  require 'hoe'
7
5
 
8
- $stderr = STDERR
9
-
10
- Hoe.new('configtoolkit', "1.2.0") do |p|
6
+ Hoe.new('configtoolkit', "2.0.0") do |p|
7
+ p.extra_deps << ['relative', '>= 1.0.0']
8
+ p.extra_deps << ['assertions', '>= 1.0.0']
11
9
  p.remote_rdoc_dir = ''
12
10
  p.developer('DesigningPatterns', 'technical.inquiries@designingpatterns.com')
13
11
  end
data/Ruby.txt ADDED
@@ -0,0 +1,172 @@
1
+ == RUBY CONFIGURATION FILES:
2
+ What could be better than configuring Ruby programs with Ruby
3
+ configuration files, harnessing the full power of Ruby for
4
+ configuration? The ConfigToolkit::RubyReader supports a Ruby
5
+ configuration file format that allows configurations to leverage the
6
+ expressiveness of the Ruby language in a way that's more natural than
7
+ programatically initializing the configuration (via the
8
+ ConfigToolkit::HashReader, for instance).
9
+
10
+ Ruby configuration files essentially are key-value configuration files
11
+ parsed by the Ruby interpreter; they allow the full Ruby language to
12
+ be used while building up a configuration. Parameters are specified
13
+ by +config.param_name+ or +config.containing_object.param_name+. They
14
+ can be assigned to and, after assignment, used in other expressions.
15
+ If a name first is referred to with a setter (i.e., +age+ in
16
+ <code>config.first.second.age = 5</code>), then the name is assumed to
17
+ be a parameter name. If a name first is referred to with a getter
18
+ (i.e., +first+ and +second+ in <code>config.first.second.age =
19
+ 5</code>), then the name is assumed to be an object name (either a
20
+ containing or a nested configuration object). Objects cannot be set
21
+ (i.e., <code>config.first.second = 2</code> is illegal). Parameter
22
+ values, once set, can be referenced in later expressions.
23
+
24
+ No +RubyWriter+ class is provided, since the strength of the format is
25
+ supporting dynamic expressions that calculate configuration values,
26
+ and any configuration file output by a writer only could consist of
27
+ static expressions.
28
+
29
+ == EXAMPLE:
30
+ This is a Ruby configuration file:
31
+ ======+examples/ruby_example.rcfg+:
32
+ #
33
+ # This file contains MachineConfig configurations
34
+ # (see examples/machineconfig.rb for the configuration's specification).
35
+ #
36
+
37
+ require 'uri'
38
+
39
+ config.num_cpus = 32
40
+
41
+ #
42
+ # Note how the members of the nested OSConfig
43
+ # can be set by first referring to "os" and then to the
44
+ # desired member (i.e., config.os.name). config.os
45
+ # also could have been set with a Hash (see below
46
+ # for an example).
47
+ #
48
+ config.os.name = "AIX"
49
+ config.os.version = 5.3
50
+
51
+ config.behind_firewall = false
52
+
53
+ #
54
+ # Notice how the behind_firewall value can be referenced
55
+ # in the contains_sensitive_data value assignment, since
56
+ # it was set above.
57
+ #
58
+ config.contains_sensitive_data = config.behind_firewall
59
+ config.addresses = [
60
+ URI("http://default.designingpatterns.com"),
61
+ URI("http://apple.designingpatterns.com")
62
+ ]
63
+
64
+ #
65
+ # A trivial example of using a Ruby expression to calculate
66
+ # a parameter value.
67
+ #
68
+ config.production.www.num_cpus = 32 + 16 + 8 + 4 + 2 + 1 + 1
69
+
70
+ #
71
+ # Note that here the nested OSConfig is being set with a Hash (the
72
+ # configuration above sets the OSConfig parameters individually).
73
+ #
74
+ config.production.www.os = {
75
+ :name => "Solaris",
76
+ :version => 10.0
77
+ }
78
+
79
+ config.production.www.contains_sensitive_data = true
80
+ config.production.www.addresses = [
81
+ URI("http://www.designingpatterns.com"),
82
+ URI("http://tokyo.designingpatterns.com")
83
+ ]
84
+
85
+ This program loads the two configurations in the file:
86
+ ======+examples/ruby_example.rb+:
87
+ #!/usr/bin/env ruby
88
+
89
+ #
90
+ # These example programs use the 'relative' gem in order to
91
+ # express paths relative to __FILE__ cleanly, allowing them to be run from
92
+ # any directory. In particular, they use the require_relative
93
+ # method to load examples/machineconfig.rb and
94
+ # File.expand_path_relative_to_caller in order to refer to
95
+ # configuration files within the examples directory.
96
+ #
97
+ require 'rubygems'
98
+ require 'relative'
99
+ require_relative 'machineconfig'
100
+
101
+ require 'configtoolkit/rubyreader'
102
+
103
+ CONFIGURATION_FILE = File.expand_path_relative_to_caller("ruby_example.rcfg")
104
+
105
+ #
106
+ # The first configuration has no containing object name.
107
+ #
108
+ reader = ConfigToolkit::RubyReader.new(CONFIGURATION_FILE)
109
+ first_config = MachineConfig.load(reader)
110
+ print("The first config:\n#{first_config}\n")
111
+
112
+ #
113
+ # The second configuration has "production.www" as a containing
114
+ # object name.
115
+ #
116
+ reader = ConfigToolkit::RubyReader.new(CONFIGURATION_FILE)
117
+ second_config = MachineConfig.load(reader, "production.www")
118
+ print("The second config:\n#{second_config}\n")
119
+
120
+ When run, it produces the following output:
121
+ ======The output of <code>examples/ruby_example.rb</code>:
122
+ The first config:
123
+ {
124
+ os : {
125
+ name : AIX
126
+ version: 5.3
127
+ }
128
+ behind_firewall : false
129
+ num_cpus : 32
130
+ addresses : [
131
+ http://default.designingpatterns.com,
132
+ http://apple.designingpatterns.com
133
+ ]
134
+ contains_sensitive_data: false
135
+ }
136
+
137
+ The second config:
138
+ production.www: {
139
+ os : {
140
+ name : Solaris
141
+ version: 10.0
142
+ }
143
+ behind_firewall : true
144
+ num_cpus : 64
145
+ addresses : [
146
+ http://www.designingpatterns.com,
147
+ http://tokyo.designingpatterns.com
148
+ ]
149
+ contains_sensitive_data: true
150
+ }
151
+
152
+
153
+
154
+ == FUTURE DIRECTIONS:
155
+ The Ruby configuration language could be improved in several ways. Firstly,
156
+ it could provide better support for arrays. For instance, the following
157
+ configuration file is invalid right now:
158
+ config.array_param[0] = 3
159
+ In the future, the parser automatically could detect that +array_param+ is
160
+ an array and allow array elements to be set.
161
+
162
+ In addition, the following configuration file also is invalid right now:
163
+ config.array_param = []
164
+ config.array_param[0].fixnum_param = 3
165
+ config.array_param[0].string_param = "hello"
166
+ That is, array elements could support object notation. Right now, this would
167
+ have to be written with Hashes:
168
+ config.array_param = []
169
+ config.array_param[0] = {:fixnum_param => 3, :string_param => "hello"}
170
+
171
+ We would love feedback and suggestions from the community about future
172
+ directions for this configuration language!
data/YAML.txt ADDED
@@ -0,0 +1,188 @@
1
+ == YAML CONFIGURATION FILES:
2
+ YAML is a clean, flexible data representation language that easily can
3
+ represent complicated configurations. It natively supports simple
4
+ types (integers, floats, strings, booleans), and, since Ruby supports
5
+ serialization of objects to YAML, Ruby's YAML library supports
6
+ representing objects of virtually any type in YAML. Support for YAML
7
+ is available for many programming languages, making it attractive for
8
+ configurations that must be read by several different languages.
9
+
10
+ The ConfigToolkit::YAMLReader and ConfigToolkit::YAMLWriter allow
11
+ configurations to be loaded from and dumped to YAML files.
12
+
13
+ == EXAMPLE:
14
+ The parameter values in YAML configuration files can be native YAML
15
+ types (strings, integers, floats, booleans, etc.) and also can be
16
+ serialized Ruby classes. The following configuration file
17
+ demonstrates this:
18
+ ======+examples/yaml_example.yaml+:
19
+ #
20
+ # This file contains MachineConfig configurations
21
+ # (see examples/machineconfig.rb for the configuration's specification).
22
+ #
23
+
24
+ ############################################################
25
+ # First configuration
26
+ num_cpus: 32
27
+ os:
28
+ name: AIX
29
+ version: 5.3
30
+ behind_firewall: no
31
+ contains_sensitive_data: no
32
+ addresses:
33
+ - http://default.designingpatterns.com
34
+ - http://apple.designingpatterns.com
35
+ ############################################################
36
+
37
+ production:
38
+ ############################################################
39
+ # Second configuration (nested in the production.www
40
+ # containing object). Note that the elements of the
41
+ # addresses parameter array are expressed
42
+ # not as strings, as in the first configuration, but rather
43
+ # as serialized Ruby URI instances. The YAMLReader class
44
+ # can read both, and the YAMLWriter class can
45
+ # output values of either only YAML native types or of serialized Ruby
46
+ # classes.
47
+ www:
48
+ contains_sensitive_data: true
49
+ addresses:
50
+ - !ruby/object:URI::HTTP
51
+ fragment:
52
+ host: www.designingpatterns.com
53
+ opaque:
54
+ password:
55
+ path: ""
56
+ port: 80
57
+ query:
58
+ registry:
59
+ scheme: http
60
+ user:
61
+ - !ruby/object:URI::HTTP
62
+ fragment:
63
+ host: tokyo.designingpatterns.com
64
+ opaque:
65
+ password:
66
+ path: ""
67
+ port: 80
68
+ query:
69
+ registry:
70
+ scheme: http
71
+ user:
72
+ os:
73
+ name: Solaris
74
+ version: 10.0
75
+ num_cpus: 64
76
+ behind_firewall: true
77
+ ############################################################
78
+
79
+ The first configuration is written with only native YAML types
80
+ while the second configuration is written with serialized Ruby URI
81
+ classes (the +addresses+ parameter). If the YAML file is to be read
82
+ by multiple languages, then only native YAML types should be used in
83
+ the file; FAQ.txt discusses how the ConfigToolkit converts YAML
84
+ strings to some Ruby types (Pathnames, URIs, Symbols, etc.). The following
85
+ program shows how this example YAML configuration file can be loaded,
86
+ and how a configuration can be written as a YAML file:
87
+ ======+examples/yaml_example.rb+:
88
+ #!/usr/bin/env ruby
89
+
90
+ #
91
+ # These example programs use the 'relative' gem in order to
92
+ # express paths relative to __FILE__ cleanly, allowing them to be run from
93
+ # any directory. In particular, they use the require_relative
94
+ # method to load examples/machineconfig.rb and
95
+ # File.expand_path_relative_to_caller in order to refer to
96
+ # configuration files within the examples directory.
97
+ #
98
+ require 'rubygems'
99
+ require 'relative'
100
+ require_relative 'machineconfig'
101
+
102
+ require 'configtoolkit/yamlreader'
103
+ require 'configtoolkit/yamlwriter'
104
+
105
+ CONFIGURATION_FILE = File.expand_path_relative_to_caller("yaml_example.yaml")
106
+
107
+ #
108
+ # The first configuration has no containing object name.
109
+ #
110
+ reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
111
+ first_config = MachineConfig.load(reader)
112
+ print("The first config:\n#{first_config}\n")
113
+
114
+ #
115
+ # The second configuration has "production.www" as a containing
116
+ # object name. Note that the second configuration
117
+ # reprents the URI elements of the addresses parameter array
118
+ # as Ruby URI classes, leveraging Ruby's ability to unserialize
119
+ # objects from YAML.
120
+ #
121
+ reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
122
+ second_config = MachineConfig.load(reader, "production.www")
123
+ print("The second config:\n#{second_config}\n")
124
+
125
+ #
126
+ # Write second_config to a string stream, which then is written to
127
+ # stdout. Pass true as the second argument to new in order to specify that
128
+ # the writer only should write native YAML types (Strings, integers, floats,
129
+ # booleans, etc.).
130
+ #
131
+ string_stream = StringIO.new()
132
+ writer = ConfigToolkit::YAMLWriter.new(string_stream, true)
133
+ second_config.dump(writer)
134
+ print("The second configuration written in YAML with native YAML types:\n")
135
+ print("#{string_stream.string}")
136
+
137
+ Note that the ConfigToolkit::YAMLWriter can dump parameter values with only
138
+ native YAML types or with serialized Ruby classes, depending on the second
139
+ argument passed to the ConfigToolkit::YAMLWriter.new method. If a
140
+ ConfigToolkit::YAMLWriter is instructed to use only native YAML types to
141
+ express parameter values, it will use the +to_s+ method to convert all
142
+ parameter values that are not of a native YAML type to strings. When run, the
143
+ program produces:
144
+ ======The output of <code>examples/yaml_example.rb</code>:
145
+ The first config:
146
+ {
147
+ num_cpus : 32
148
+ addresses : [
149
+ http://default.designingpatterns.com,
150
+ http://apple.designingpatterns.com
151
+ ]
152
+ contains_sensitive_data: false
153
+ os : {
154
+ version: 5.3
155
+ name : AIX
156
+ }
157
+ behind_firewall : false
158
+ }
159
+
160
+ The second config:
161
+ production.www: {
162
+ num_cpus : 64
163
+ addresses : [
164
+ http://www.designingpatterns.com,
165
+ http://tokyo.designingpatterns.com
166
+ ]
167
+ contains_sensitive_data: true
168
+ os : {
169
+ version: 10.0
170
+ name : Solaris
171
+ }
172
+ behind_firewall : true
173
+ }
174
+
175
+ The second configuration written in YAML with native YAML types:
176
+ ---
177
+ production:
178
+ www:
179
+ addresses:
180
+ - http://www.designingpatterns.com
181
+ - http://tokyo.designingpatterns.com
182
+ os:
183
+ name: Solaris
184
+ version: 10.0
185
+ behind_firewall: true
186
+ num_cpus: 64
187
+ contains_sensitive_data: true
188
+
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # These example programs use the 'relative' gem in order to
5
+ # express paths relative to __FILE__ cleanly, allowing them to be run from
6
+ # any directory. In particular, they use the require_relative
7
+ # method to load examples/machineconfig.rb and
8
+ # File.expand_path_relative_to_caller in order to refer to
9
+ # configuration files within the examples directory.
10
+ #
11
+ require 'rubygems'
12
+ require 'relative'
13
+ require_relative 'machineconfig'
14
+
15
+ require 'configtoolkit/hashreader'
16
+ require 'configtoolkit/hashwriter'
17
+
18
+ #
19
+ # The first configuration has no containing object name.
20
+ #
21
+ config_hash = {
22
+ :num_cpus => 32,
23
+ :os => {
24
+ :name => "AIX",
25
+ :version => 5.3
26
+ },
27
+ :behind_firewall => false,
28
+ :contains_sensitive_data => false,
29
+ :addresses => [
30
+ URI("http://default.designingpatterns.com"),
31
+ URI("http://apple.designingpatterns.com")
32
+ ]
33
+ }
34
+ reader = ConfigToolkit::HashReader.new(config_hash)
35
+ first_config = MachineConfig.load(reader)
36
+ print("The first config:\n#{first_config}\n")
37
+
38
+ #
39
+ # The second configuration has "production.www" as a containing
40
+ # object name and therefore the configuration is mapped to by
41
+ # :www in a Hash, which in turn is mapped to by :production
42
+ # in a containing Hash.
43
+ #
44
+ config_hash = {
45
+ :production => {
46
+ :www => {
47
+ :num_cpus => 64,
48
+ :os => {
49
+ :name => "Solaris",
50
+ :version => 10.0
51
+ },
52
+ :contains_sensitive_data => true,
53
+ :addresses => [
54
+ URI("http://www.designingpatterns.com"),
55
+ URI("http://tokyo.designingpatterns.com")
56
+ ]
57
+ }
58
+ }
59
+ }
60
+ reader = ConfigToolkit::HashReader.new(config_hash)
61
+ second_config = MachineConfig.load(reader, "production.www")
62
+ print("The second config:\n#{second_config}\n")
63
+
64
+ #
65
+ # After the call to dump, the config_hash instance member of the HashWriter
66
+ # instance contains the Hash representation of second_config.
67
+ #
68
+ writer = ConfigToolkit::HashWriter.new()
69
+ second_config.dump(writer)
70
+ print("The Hash dumped from the second config:\n")
71
+ print("#{writer.config_hash.inspect}\n")