
Product
Socket Now Protects the Chrome Extension Ecosystem
Socket is launching experimental protection for Chrome extensions, scanning for malware and risky permissions to prevent silent supply chain attacks.
= configtoolkit
== DESCRIPTION: This package makes sourcing information from (parsing) configuration files robust and easy! It:
== FEATURES:
== PROBLEMS: None (known).
== SYNOPSIS: Here is a sample configuration class: ======examples/machineconfig.rb: require 'rubygems' require 'configtoolkit'
require 'uri'
class OSConfig < ConfigToolkit::BaseConfig add_required_param(:name, String) add_required_param(:version, Float) end
class MachineConfig < ConfigToolkit::BaseConfig
add_required_param(:num_cpus, Integer) do |value| if(value <= 0) raise_error("num_cpus must be greater than zero") end end
add_required_param(:os, OSConfig)
add_required_param(:contains_sensitive_data, ConfigToolkit::Boolean)
add_optional_param(:behind_firewall, ConfigToolkit::Boolean, true)
add_optional_param(:primary_contact, String)
add_required_param(:addresses, ConfigToolkit::ConstrainedArray.new(URI))
def validate_all_values if(contains_sensitive_data && !behind_firewall) raise_error("only machines behind firewalls can contain sensitive data") end end end
This code creates a configuration class for a company's servers by subclassing ConfigToolkit::BaseConfig. Each of the parameters is specified (the parameter's class, whether the parameter is required, and a default value if the parameter is optional) with private class methods of ConfigToolkit::BaseConfig (ConfigToolkit::BaseConfig.add_required_param and ConfigToolkit::BaseConfig.add_optional_param).
A +validate_all_values+ method also is defined in the +MachineConfig+ class that enforces an invariant between multiple parameters. This will be called at the end of ConfigToolkit::BaseConfig#load or at the start of ConfigToolkit::BaseConfig#dump (see ConfigToolkit::BaseConfig#validate_all_values for more details).
A custom validation block is specified for the +num_cpus+ parameter; the ConfigToolkit engine does not have direct support for checking specifications like "the value must be greater than zero", but these conditions easily can be added by the programmer. Such blocks are called by the engine before the configuration parameter is set to the new value and are executed in the context of the configuration class instance. If there is a problem with the new value, the block should call ConfigToolkit::BaseConfig#raise_error.
A ConfigToolkit::Boolean class is specified for the +behind_firewall+ and +contains_sensitive_data+ parameters. Ruby does not have a standard boolean class (although it does have TrueClass and FalseClass, for boolean values), and so the ConfigToolkit provides a marker class for boolean values. The value of a parameter specified with ConfigToolkit::Boolean either is +true+ or +false+.
A ConfigToolkit::ConstrainedArray.new(URI) class is specified for the +addresses+ parameter. A ConfigToolkit::ConstrainedArray class simply specifies that the parameter value will be an Array that obeys the constraints contained in the ConfigToolkit::ConstrainedArray. In this case, the constraints ensure that all Array elements are of class URI, that the Array has at least two elements. Additional constraints on the number of elements in the Array are possible
The following program manipulates a MachineConfig instance. ======examples/usage_example.rb: #!/usr/bin/env ruby
require 'rubygems' require 'relative' require_relative 'machineconfig'
config = MachineConfig.new() do |config| config.num_cpus = 8 config.os = OSConfig.new() do |os_config| os_config.name = "Solaris" os_config.version = 10.0 end config.contains_sensitive_data = true config.addresses = [URI("http://www.designingpatterns.com"), URI("http://jackfruit.designingpatterns.com")] end
print "Config after initialization:\n#{config}\n"
if(!config.primary_contact?)
print "Optional primary_contact parameter is NOT set!\n" end
config.primary_contact = "Tony" print("primary_contact parameter set to: #{config.primary_contact}\n")
if(config.primary_contact?)
print "Optional primary_contact parameter is now IS set!\n\n" end
print "Config after setting optional primary_contact parameter:\n#{config}\n"
config.clear_primary_contact()
if(!config.primary_contact?)
print "Optional primary_contact parameter has been deleted!\n\n" end
print "Config after deleting the primary_contact parameter:\n#{config}\n"
This example uses the accessor methods (a getter, a setter, and a predicate)
that are generated automatically for each configuration parameter. These
allow configuration instances to be manipulated in the same way that
instances of a hand-written configuration class could be manipulated. Note,
however, that the same strict validation is performed for values specified
programatically through setters as is done for values sourced from
configuration files. Also note that setters copy the values of their
arguments (with the +dup+ method) not references to their arguments, unlike
the setters created by the +attr_accessor+ method.
When run, it produces:
======The output of examples/usage_example.rb
:
Config after initialization:
{
contains_sensitive_data: true
num_cpus : 8
addresses : [
http://www.designingpatterns.com,
http://jackfruit.designingpatterns.com
]
os : {
version: 10.0
name : Solaris
}
behind_firewall : true
}
Optional primary_contact parameter is NOT set! primary_contact parameter set to: Tony Optional primary_contact parameter is now IS set!
Config after setting optional primary_contact parameter: { contains_sensitive_data: true num_cpus : 8 addresses : [ http://www.designingpatterns.com, http://jackfruit.designingpatterns.com ] os : { version: 10.0 name : Solaris } behind_firewall : true primary_contact : Tony }
Optional primary_contact parameter has been deleted!
Config after deleting the primary_contact parameter: { contains_sensitive_data: true num_cpus : 8 addresses : [ http://www.designingpatterns.com, http://jackfruit.designingpatterns.com ] os : { version: 10.0 name : Solaris } behind_firewall : true }
Of course, the most common use case will be setting configuration parameters with the contents of configuration files, not programatically. The following YAML configuration file contains two MachineConfig configurations: ======examples/load_example.yaml:
############################################################
num_cpus: 32 os: name: AIX version: 5.3 behind_firewall: no contains_sensitive_data: no addresses:
production:
www: ############################################################
num_cpus: 64
os:
name: Solaris
version: 10.0
contains_sensitive_data: yes
addresses:
- http://www.designingpatterns.com
- http://tokyo.designingpatterns.com
############################################################
The following program loads and prints the two configurations from +examples/load_example.yaml+. ======examples/load_example.rb: #!/usr/bin/env ruby
require 'rubygems' require 'relative' require_relative 'machineconfig'
require 'configtoolkit/yamlreader'
CONFIGURATION_FILE = File.expand_path_relative_to_caller("load_example.yaml")
reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE) first_config = MachineConfig.load(reader) print("The first config:\n#{first_config}\n")
reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE) second_config = MachineConfig.load(reader, "production.www") print("The second config:\n#{second_config}\n")
Note how the program changes the containing object name passed to the +load+
method (first an empty string, next +production.www+, and finally
+production.mail+) in order to refer to different configurations within
the YAML file. The ConfigToolkit's support of containing objects allows it
to accomodate multiple configurations living in the same file, with each
configuration being accessible separately. When run, it produces:
======The output of examples/load_example.rb
:
The first config:
{
addresses : [
http://default.designingpatterns.com,
http://apple.designingpatterns.com
]
contains_sensitive_data: false
os : {
version: 5.3
name : AIX
}
behind_firewall : false
num_cpus : 32
}
The second config: production.www: { addresses : [ http://www.designingpatterns.com, http://tokyo.designingpatterns.com ] contains_sensitive_data: true os : { version: 10.0 name : Solaris } behind_firewall : true num_cpus : 64 }
A group of configurations also can be loaded with the ConfigToolkit::BaseConfig.load_group method. For instance, given +examples/load_group_example.yaml+: ======examples/load_group_example.yaml:
db_cluster: db1: num_cpus: 16 os: name: Solaris version: 10.0 contains_sensitive_data: yes behind_firewall: yes addresses: - http://db1.designingpatterns.com db2: num_cpus: 12 os: name: AIX version: 5.3 contains_sensitive_data: yes behind_firewall: yes addresses: - http://db2.designingpatterns.com db3: num_cpus: 24 os: name: Solaris version: 10.0 contains_sensitive_data: yes behind_firewall: yes addresses: - http://db3.designingpatterns.com
the following code will load the file's three configurations into a +Hash+ mapping containing object names to configurations: ======examples/load_group_example.rb: #!/usr/bin/env ruby
require 'rubygems' require 'relative' require_relative 'machineconfig'
require 'configtoolkit/yamlreader'
CONFIGURATION_FILE = File.expand_path_relative_to_caller("load_group_example.yaml")
reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE) configs = MachineConfig.load_group(reader, "db_cluster") configs.each do |name, config| print "The #{name} configuration:\n#{config}\n" end
When run, it produces:
======The output of examples/load_group_example.rb
:
The db3 configuration:
db_cluster.db3: {
addresses : [
http://db3.designingpatterns.com
]
contains_sensitive_data: true
os : {
name : Solaris
version: 10.0
}
behind_firewall : true
num_cpus : 24
}
The db1 configuration: db_cluster.db1: { addresses : [ http://db1.designingpatterns.com ] contains_sensitive_data: true os : { name : Solaris version: 10.0 } behind_firewall : true num_cpus : 16 }
The db2 configuration: db_cluster.db2: { addresses : [ http://db2.designingpatterns.com ] contains_sensitive_data: true os : { name : AIX version: 5.3 } behind_firewall : true num_cpus : 12 }
== CONFIGURING THE CONFIGTOOLKIT As of version 2.3.0, the ConfigToolkit itself can be configured.
Consider the following simple YAML configuration file: ======examples/config_toolkit_config_example.yaml: name: Tony
county: USA
The following code configures the +SimpleConfig+ class and specific instances of the +SimpleConfig+ class to behave differently than the ConfigToolkit default when unknown parameters are encountered when loading a configuration file. ======examples/config_toolkit_config_example.rb: #!/usr/bin/env ruby
require 'rubygems' require 'configtoolkit' require 'configtoolkit/yamlreader'
require 'relative'
class SimpleConfig < ConfigToolkit::BaseConfig
self.default_config_toolkit_config = ConfigToolkit::ConfigToolkitConfig.new() do |instance| # # This will suppress warnings when an unknown config param # is encountered when loading a configuration file. # instance.unknown_config_param_behavior = ConfigToolkit::ConfigToolkitConfig::UnknownConfigParamBehavior::IGNORE end
add_required_param(:name, String)
add_optional_param(:country, String, NO_DEFAULT_VALUE)
end
CONFIGURATION_FILE = File.expand_path_relative_to_caller("config_toolkit_config_example.yaml")
reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE) config = SimpleConfig.load(reader) puts "Unknown parameters silently were ignored when loading this: #{config}\n"
config_toolkit_config = ConfigToolkit::ConfigToolkitConfig.new() do |instance|
instance.unknown_config_param_behavior = ConfigToolkit::ConfigToolkitConfig::UnknownConfigParamBehavior::ERROR end reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
begin config = SimpleConfig.load(reader, "", config_toolkit_config) rescue ConfigToolkit::Error => e puts e end
When run, it produces:
======The output of examples/config_toolkit_config_example.rb
:
Unknown parameters silently were ignored when loading this: {
name: Tony
}
error setting county with value USA: no parameter county exists for configuration class SimpleConfig.
=== CONFIGTOOLKIT CONFIGURATION PARAMETERS +unknown_config_param_behavior+: the behavior when encountering an unknown parameter. Its possible values: +ignore+:: The unknown parameter will be ignored (+ignore+). This can be useful in order to maintain compatibility with older versions of software when adding new fields to a configuration file. +warn+:: The unknown parameter will cause a warning to be issued (+warn+). This is the default and can allow errors in configuration files to be detected more easily. It does NOT prevent the configuration file from being loaded, however. +error+:: The unknown parameter will cause an error to be raised, preventing the configuration file from being loaded.
== CONFIGURATION FILE FORMATS: === EXISTING FORMATS:
=== NEW FORMATS: The ConfigToolkit easily can be extended to support new file formats. A new reader class can be created to load from a new format, and a new writer class can be created to write out a new format.
The reader interface is documented by the ConfigToolkit::Reader class. Basically, a reader needs to have a read method that reads data from some underlying stream and returns a Hash that represents the configuration (see ConfigToolkit::Reader#read for the specifications; ConfigToolkit::YAMLReader#read is a simple example of an implementation). ConfigToolkit::HashReader is a no-op reader, so the Hash that its constructor accepts is the same that a new reader class should return from its +read+ method.
The writer interface is documented by the ConfigToolkit::Writer class. Basically, a writer needs to have a write method that accepts a Hash representing a configuration and writes it to some underlying stream (see ConfigToolkit::Writer#write for the specifications; ConfigToolkit::YAMLWriter#write is a relatively simple example of an implementation). ConfigToolkit::HashWriter is a no-op writer, so the Hash stored in its +config_hash+ attribute after a ConfigToolkit::BaseConfig#dump call is the same Hash passed to a writer's +write+ method.
While writing a new reader or writer class, it may be necessary to iterate over a Hash that contains arbitrarily deeply nested Hashes and Arrays. The ConfigToolkit::HashArrayVisitor can help with this task by allowing hook functions to be written that will be called when a particular type of node is visited. ConfigToolkit::YAMLWriter#write, ConfigToolkit::KeyValueWriter#write and ConfigToolkit::PrettyPrintWriter#write all use such visitors.
Feel free also to request that we add support for a new file format to the distribution, and we will try our best to accomodate! See below for some ideas that we have had for new readers and writers.
== CONFIGURATION CLASS DOCUMENTATION: Configuration classes can be documented properly by +rdoc+, although not by default. See FAQ.txt for full details.
== REQUIREMENTS: Hoe, assertions, and relative are required, but only for running the tests. Relative also is required for running the examples.
== INSTALL: sudo gem install configtoolkit
== POSSIBLE FUTURE TECHNICAL DIRECTIONS: Here are some possible ideas:
We would love to receive input from users about which new features are most desirable!
== AUTHORS: === Designing Patterns
== SUPPORT: Please post questions, concerns, or requests for enhancement to the forums on the project page. Alternatively, direct contact information for Designing Patterns can be found on the project page for this gem.
== ENHANCEMENTS: Please feel free to contact us with any ideas; we will try our best to enhance the software and respond to user requests. Of course, we are more likely to work on a particular enhancement if we know that there are users who want it. Designing Patterns provides contracting and consulting services, so if there is an enhancement that must get done (and in a specified time frame), please inquire about retaining our services!
== LICENSE: The license text can be found in the +LICENSE+ file at the root of the distribution.
This package is licensed with an MIT license:
Copyright (c) 2008-2009 Designing Patterns
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
== SHARE AND ENJOY!
FAQs
Unknown package
We found that configtoolkit demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket is launching experimental protection for Chrome extensions, scanning for malware and risky permissions to prevent silent supply chain attacks.
Product
Add secure dependency scanning to Claude Desktop with Socket MCP, a one-click extension that keeps your coding conversations safe from malicious packages.
Product
Socket now supports Scala and Kotlin, bringing AI-powered threat detection to JVM projects with easy manifest generation and fast, accurate scans.