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
@@ -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"