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
@@ -0,0 +1,5 @@
1
+ production.www.num_cpus : 64
2
+ production.www.behind_firewall : true
3
+ production.www.os : {version <<-->> 10.0, name <<-->> Solaris}
4
+ production.www.addresses : [http://www.designingpatterns.com, http://tokyo.designingpatterns.com]
5
+ production.www.contains_sensitive_data : true
@@ -0,0 +1,20 @@
1
+ #
2
+ # This file contains MachineConfig configurations
3
+ # (see examples/machineconfig.rb for the configuration's specification).
4
+ #
5
+
6
+ ############################################################
7
+ # First configuration
8
+ num_cpus = 32
9
+ os = {name => AIX, version => 5.3}
10
+ behind_firewall = no
11
+ contains_sensitive_data = no
12
+ addresses = [http://default.designingpatterns.com, http://apple.designingpatterns.com]
13
+ ############################################################
14
+
15
+ #
16
+ # Include a second configuration from a different file.
17
+ # Note that the path to the second file is relative to
18
+ # *this* file (they are in the same directory).
19
+ #
20
+ *include key_value_example.normal2.cfg
@@ -0,0 +1,15 @@
1
+ #
2
+ # This file contains MachineConfig configurations
3
+ # (see examples/machineconfig.rb for the configuration's specification).
4
+ #
5
+
6
+ ############################################################
7
+ # Second configuration (nested in the production.www
8
+ # containing object). Note how all of the keys are prefixed
9
+ # with production.www in order to indicate that they are
10
+ # within the production.www containing object.
11
+ production.www.num_cpus = 64
12
+ production.www.os = {name => Solaris, version => 10.0}
13
+ production.www.contains_sensitive_data = yes
14
+ production.www.addresses = [http://www.designingpatterns.com, http://tokyo.designingpatterns.com]
15
+ ############################################################
@@ -0,0 +1,72 @@
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/keyvaluereader'
16
+ require 'configtoolkit/keyvaluewriter'
17
+
18
+ #
19
+ # The key-value format used by this file is the default format.
20
+ #
21
+ NORMAL_CONFIGURATION_FILE =
22
+ File.expand_path_relative_to_caller("key_value_example.normal1.cfg")
23
+
24
+ #
25
+ # The first configuration has no containing object name.
26
+ #
27
+ reader = ConfigToolkit::KeyValueReader.new(NORMAL_CONFIGURATION_FILE)
28
+ first_config = MachineConfig.load(reader)
29
+ print("The first config:\n#{first_config}\n")
30
+
31
+ #
32
+ # The second configuration has "production.www" as a containing
33
+ # object name. Note that the second configuration is contained in a different
34
+ # file that the first file includes.
35
+ #
36
+ reader = ConfigToolkit::KeyValueReader.new(NORMAL_CONFIGURATION_FILE)
37
+ second_config = MachineConfig.load(reader, "production.www")
38
+ print("The second config:\n#{second_config}\n")
39
+
40
+ #
41
+ # Configure a new, admittedly wacky, key-value format.
42
+ # Note how the block passed to the constructor initializes
43
+ # only a few of the KeyValueConfig parameters; the other
44
+ # parameters will be assigned their default values.
45
+ #
46
+ key_value_config = ConfigToolkit::KeyValueConfig.new() do |config|
47
+ config.key_value_delimiter = ":"
48
+ config.comment_line_prefix = ";"
49
+ config.hash_key_value_delimiter = "<<-->>"
50
+ end
51
+
52
+ #
53
+ # Read the first configuration from a file containing it in the new
54
+ # format.
55
+ #
56
+ WACKY_CONFIGURATION_FILE =
57
+ File.expand_path_relative_to_caller("key_value_example.wacky.cfg")
58
+ reader = ConfigToolkit::KeyValueReader.new(WACKY_CONFIGURATION_FILE,
59
+ key_value_config)
60
+ first_config = MachineConfig.load(reader)
61
+ print("The first config sourced from a different key-value format:\n")
62
+ print("#{first_config}\n")
63
+
64
+ #
65
+ # Write second_config in the new format to a string stream, the contents
66
+ # of which will be printed.
67
+ #
68
+ string_stream = StringIO.new()
69
+ writer = ConfigToolkit::KeyValueWriter.new(string_stream, key_value_config)
70
+ second_config.dump(writer)
71
+ print("The second configuration dumped to a new key-value file format:\n")
72
+ print("#{string_stream.string}")
@@ -0,0 +1,13 @@
1
+ ;
2
+ ; This file contains MachineConfig configurations
3
+ ; (see examples/machineconfig.rb for the configuration's specification).
4
+ ;
5
+
6
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
7
+ ; First configuration, but with a wacky format
8
+ num_cpus: 32
9
+ os: {name <<-->> AIX, version <<-->> 5.3}
10
+ behind_firewall: no
11
+ contains_sensitive_data: no
12
+ addresses: [http://default.designingpatterns.com, http://apple.designingpatterns.com]
13
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -0,0 +1,32 @@
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/yamlreader'
16
+
17
+ CONFIGURATION_FILE = File.expand_path_relative_to_caller("load_example.yaml")
18
+
19
+ #
20
+ # The first configuration has no containing object name.
21
+ #
22
+ reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
23
+ first_config = MachineConfig.load(reader)
24
+ print("The first config:\n#{first_config}\n")
25
+
26
+ #
27
+ # The second configuration has "production.www" as a containing
28
+ # object name.
29
+ #
30
+ reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
31
+ second_config = MachineConfig.load(reader, "production.www")
32
+ print("The second config:\n#{second_config}\n")
@@ -0,0 +1,34 @@
1
+ #
2
+ # This file contains MachineConfig configurations
3
+ # (see examples/machineconfig.rb for the configuration's specification).
4
+ #
5
+
6
+ ############################################################
7
+ # First configuration
8
+ num_cpus: 32
9
+ os:
10
+ name: AIX
11
+ version: 5.3
12
+ behind_firewall: no
13
+ contains_sensitive_data: no
14
+ addresses:
15
+ - http://default.designingpatterns.com
16
+ - http://apple.designingpatterns.com
17
+ ############################################################
18
+
19
+ # production is a containing object
20
+ production:
21
+ # production.www is a containing object
22
+ www:
23
+ ############################################################
24
+ # Second configuration (nested in the production.www
25
+ # containing object)
26
+ num_cpus: 64
27
+ os:
28
+ name: Solaris
29
+ version: 10.0
30
+ contains_sensitive_data: yes
31
+ addresses:
32
+ - http://www.designingpatterns.com
33
+ - http://tokyo.designingpatterns.com
34
+ ############################################################
@@ -0,0 +1,28 @@
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/yamlreader'
16
+
17
+ CONFIGURATION_FILE =
18
+ File.expand_path_relative_to_caller("load_group_example.yaml")
19
+
20
+ #
21
+ # The group of configurations is under the db_cluster containing
22
+ # object name.
23
+ #
24
+ reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
25
+ configs = MachineConfig.load_group(reader, "db_cluster")
26
+ configs.each do |name, config|
27
+ print "The #{name} configuration:\n#{config}\n"
28
+ end
@@ -0,0 +1,33 @@
1
+ #
2
+ # This file contains MachineConfig configurations
3
+ # (see examples/machineconfig.rb for the configuration's specification).
4
+ #
5
+
6
+ db_cluster:
7
+ db1:
8
+ num_cpus: 16
9
+ os:
10
+ name: Solaris
11
+ version: 10.0
12
+ contains_sensitive_data: yes
13
+ behind_firewall: yes
14
+ addresses:
15
+ - http://db1.designingpatterns.com
16
+ db2:
17
+ num_cpus: 12
18
+ os:
19
+ name: AIX
20
+ version: 5.3
21
+ contains_sensitive_data: yes
22
+ behind_firewall: yes
23
+ addresses:
24
+ - http://db2.designingpatterns.com
25
+ db3:
26
+ num_cpus: 24
27
+ os:
28
+ name: Solaris
29
+ version: 10.0
30
+ contains_sensitive_data: yes
31
+ behind_firewall: yes
32
+ addresses:
33
+ - http://db3.designingpatterns.com
@@ -0,0 +1,77 @@
1
+ require 'rubygems'
2
+ require 'configtoolkit'
3
+
4
+ require 'uri'
5
+
6
+ #
7
+ # This configuration class is nested within MachineConfig (below).
8
+ # All configuration classes must descend from ConfigToolkit::BaseConfig.
9
+ #
10
+ class OSConfig < ConfigToolkit::BaseConfig
11
+ add_required_param(:name, String)
12
+ add_required_param(:version, Float)
13
+ end
14
+
15
+ #
16
+ # This configuration class is used in all of the example programs.
17
+ # Since it is a configuration class, it descends from
18
+ # ConfigToolkit::BaseConfig.
19
+ #
20
+ class MachineConfig < ConfigToolkit::BaseConfig
21
+ #
22
+ # This is a required parameter with extra user-specified validation
23
+ # (that :num_cpus > 0 must be true)
24
+ #
25
+ add_required_param(:num_cpus, Integer) do |value|
26
+ if(value <= 0)
27
+ raise_error("num_cpus must be greater than zero")
28
+ end
29
+ end
30
+
31
+ #
32
+ # This required parameter is itself a BaseConfig instance; BaseConfig
33
+ # instances can be nested within each other.
34
+ #
35
+ add_required_param(:os, OSConfig)
36
+
37
+ #
38
+ # This is a required boolean parameter. Note that Ruby does not
39
+ # have a boolean type (it has a TrueClass and a FalseClass), so use
40
+ # a marker class (ConfigToolkit::Boolean) to indicate that
41
+ # the parameter will have boolean values (true and false).
42
+ # Boolean values can be written as "true"/"false" or as
43
+ # "yes"/"no" in configuration files.
44
+ #
45
+ add_required_param(:contains_sensitive_data, ConfigToolkit::Boolean)
46
+
47
+ #
48
+ # The behind_firewall parameter is optional and has a default value of
49
+ # true, which means that it will be set to the true if not explicitly set
50
+ # by a configuration file.
51
+ #
52
+ add_optional_param(:behind_firewall, ConfigToolkit::Boolean, true)
53
+
54
+ #
55
+ # The primary_contact parameter is optional and has no default value, which
56
+ # means that it will be absent if not explicitly set by a configuration file.
57
+ #
58
+ add_optional_param(:primary_contact, String)
59
+
60
+ #
61
+ # This parameter's values are guaranteed to be Arrays with URI elements.
62
+ #
63
+ add_required_param(:addresses, ConfigToolkit::ConstrainedArray.new(URI))
64
+
65
+ #
66
+ # This method is called by load() after loading values for
67
+ # all parameters from a specified configuration and can enforce
68
+ # constraints between different parameters. In this case, this method
69
+ # ensures that all machines containing sensitive data are behind
70
+ # the firewall.
71
+ #
72
+ def validate_all_values
73
+ if(contains_sensitive_data && !behind_firewall)
74
+ raise_error("only machines behind firewalls can contain sensitive data")
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,32 @@
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/rubyreader'
16
+
17
+ CONFIGURATION_FILE = File.expand_path_relative_to_caller("ruby_example.rcfg")
18
+
19
+ #
20
+ # The first configuration has no containing object name.
21
+ #
22
+ reader = ConfigToolkit::RubyReader.new(CONFIGURATION_FILE)
23
+ first_config = MachineConfig.load(reader)
24
+ print("The first config:\n#{first_config}\n")
25
+
26
+ #
27
+ # The second configuration has "production.www" as a containing
28
+ # object name.
29
+ #
30
+ reader = ConfigToolkit::RubyReader.new(CONFIGURATION_FILE)
31
+ second_config = MachineConfig.load(reader, "production.www")
32
+ print("The second config:\n#{second_config}\n")
@@ -0,0 +1,52 @@
1
+ #
2
+ # This file contains MachineConfig configurations
3
+ # (see examples/machineconfig.rb for the configuration's specification).
4
+ #
5
+
6
+ require 'uri'
7
+
8
+ config.num_cpus = 32
9
+
10
+ #
11
+ # Note how the members of the nested OSConfig
12
+ # can be set by first referring to "os" and then to the
13
+ # desired member (i.e., config.os.name). config.os
14
+ # also could have been set with a Hash (see below
15
+ # for an example).
16
+ #
17
+ config.os.name = "AIX"
18
+ config.os.version = 5.3
19
+
20
+ config.behind_firewall = false
21
+
22
+ #
23
+ # Notice how the behind_firewall value can be referenced
24
+ # in the contains_sensitive_data value assignment, since
25
+ # it was set above.
26
+ #
27
+ config.contains_sensitive_data = config.behind_firewall
28
+ config.addresses = [
29
+ URI("http://default.designingpatterns.com"),
30
+ URI("http://apple.designingpatterns.com")
31
+ ]
32
+
33
+ #
34
+ # A trivial example of using a Ruby expression to calculate
35
+ # a parameter value.
36
+ #
37
+ config.production.www.num_cpus = 32 + 16 + 8 + 4 + 2 + 1 + 1
38
+
39
+ #
40
+ # Note that here the nested OSConfig is being set with a Hash (the
41
+ # configuration above sets the OSConfig parameters individually).
42
+ #
43
+ config.production.www.os = {
44
+ :name => "Solaris",
45
+ :version => 10.0
46
+ }
47
+
48
+ config.production.www.contains_sensitive_data = true
49
+ config.production.www.addresses = [
50
+ URI("http://www.designingpatterns.com"),
51
+ URI("http://tokyo.designingpatterns.com")
52
+ ]
@@ -0,0 +1,93 @@
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
+ #
16
+ # If new() is passed a block, the block *must* initialize all
17
+ # required parameters (otherwise a ConfigToolkit::Error will
18
+ # be thrown). Optional parameters to not need to be
19
+ # explicitly initialized, however. If a default value was
20
+ # specified for an uninitialized optional parameter, the
21
+ # optional parameter will be set to the default value after
22
+ # the block (the behind_firewall parameter is an example of this below);
23
+ # otherwise, the default value will have no value
24
+ # (the primary_contact parameter is an example of this below).
25
+ #
26
+ config = MachineConfig.new() do |config|
27
+ config.num_cpus = 8
28
+ config.os = OSConfig.new() do |os_config|
29
+ os_config.name = "Solaris"
30
+ os_config.version = 10.0
31
+ end
32
+ config.contains_sensitive_data = true
33
+ config.addresses = [URI("http://www.designingpatterns.com"),
34
+ URI("http://jackfruit.designingpatterns.com")]
35
+ end
36
+
37
+ #
38
+ # Note in the output that the behind_firewall parameter is true, its
39
+ # default value, because it was not initialized in the block above.
40
+ # Also note that the primary_contact parameter is not present.
41
+ #
42
+ print "Config after initialization:\n#{config}\n"
43
+
44
+ #
45
+ # Each parameter has a predicate method (automatically generated by
46
+ # add_required_param or add_optional_param) that returns whether or not
47
+ # the parameter has a value.
48
+ #
49
+ if(!config.primary_contact?)
50
+ #
51
+ # This will be printed...
52
+ #
53
+ print "Optional primary_contact parameter is NOT set!\n"
54
+ end
55
+
56
+ #
57
+ # Each parameter has getter and setter accessor methods (automatically
58
+ # generated by add_required_param or add_optional_param), just like
59
+ # "normal" attributes.
60
+ #
61
+ config.primary_contact = "Tony"
62
+ print("primary_contact parameter set to: #{config.primary_contact}\n")
63
+
64
+ if(config.primary_contact?)
65
+ #
66
+ # This will be printed...
67
+ #
68
+ print "Optional primary_contact parameter is now IS set!\n\n"
69
+ end
70
+
71
+ #
72
+ # Note in the output that the primary_contact parameter *now* is present.
73
+ #
74
+ print "Config after setting optional primary_contact parameter:\n#{config}\n"
75
+
76
+ #
77
+ # Each optional parameter has a clear method (automatically generated by
78
+ # add_optional_param) that deletes the parameter's value.
79
+ #
80
+ config.clear_primary_contact()
81
+
82
+ if(!config.primary_contact?)
83
+ #
84
+ # This will be printed...
85
+ #
86
+ print "Optional primary_contact parameter has been deleted!\n\n"
87
+ end
88
+
89
+ #
90
+ # Note in the output that the primary_contact parameter once again
91
+ # is absent.
92
+ #
93
+ print "Config after deleting the primary_contact parameter:\n#{config}\n"