Class: BatchKit::Config
- Inherits:
-
Hash
- Object
- Hash
- BatchKit::Config
- Defined in:
- lib/batch-kit/config.rb
Overview
As this Config object is case and String/Symbol insensitive, different case and type keys that convert to the same lookup key are considered the same. The practical implication is that you can’t have two different values in this Config object where the keys differ only in case and/or String/Symbol class.
Defines a class for managing configuration properties; essentially, this is a hash that is case and Strng/Symbol insensitive with respect to keys; this means a value can be retrieved using any mix of case using either a String or Symbol as the lookup key.
In addition, there are some further conveniences added on:
-
Items can be accessed by either [] (using a String or Symbol key) or as methods on the Config object, i.e. the following are all equivalent:
config['Foo'] config[:foo] config.foo
-
Contents can be loaded from:
-
an existing Hash object
-
a properties file using [Section] and KEY=VALUE syntax
-
a YAML file
-
-
String values can contain placeholder variables that will be replaced when the item is added to the Config collection. Placeholder variables are denoted by $<variable> or %<variable> syntax, where <variable> is the name of another configuration variable or a key in a supplied expansion properties hash.
-
Support for encrypted values. These are decrypted on the fly, provided a decryption key has been set on the Config object.
Internally, the case and String/Symbol insensitivity is managed by maintaining a second Hash that converts the lower-cased symbol value of all keys to the actual keys used to store the object. When looking up a value, we use this lookup Hash to find the actual key used, and then lookup the value.
Class Method Summary collapse
-
.expand_placeholders(str, properties, raise_on_unknown_var = false) ⇒ String
Expand any $<variable> or %<variable> placeholders in
str
from either the suppliedprops
hash or the system environment variables. -
.load(file, props = nil, options = {}) ⇒ Config
Create a new Config object, and initialize it from the specified
file
. -
.properties_to_hash(str) ⇒ Object
Converts a
str
in the form of a properties file into a Hash.
Instance Method Summary collapse
-
#[](key) ⇒ Object
Override #[] to be agnostic as to the case of the key, and whether it is a String or a Symbol.
-
#[]=(key, val) ⇒ Object
Override #[]= to be agnostic as to the case of the key, and whether it is a String or a Symbol.
-
#clone ⇒ Object
Override #clone to also clone contents of @lookup_keys.
-
#decryption_key=(key) ⇒ Object
(also: #encryption_key=)
If set, encrypted strings (only) will be decrypted when accessed via #[] or #method_missing (for property-like access, e.g.
cfg.password
). -
#delete(key) ⇒ Object
Override #delete to be agnostic as to the case of the key, and whether it is a String or a Symbol.
-
#dup ⇒ Object
Override #dup to also clone contents of @lookup_keys.
-
#encode_with(coder) ⇒ Object
Ensure that Config objects are saved as normal hashes when writing YAML.
-
#encrypt(key_pat, master_key = @decryption_key) ⇒ Object
Recursively encrypts the values of all keys in this Config object that match
key_pat
. -
#expand_placeholders(str, raise_on_unknown_var = true) ⇒ String
Expand any $<variable> or %<variable> placeholders in
str
from this Config object or the system environment variables. -
#fetch(key, *rest) ⇒ Object
Override #fetch to be agnostic as to the case of the key, and whether it is a String or a Symbol.
-
#has_key?(key) ⇒ Boolean
(also: #include?)
Override #has_key? to be agnostic as to the case of the key, and whether it is a String or a Symbol.
-
#initialize(hsh = nil, options = {}) ⇒ Config
constructor
Create a Config object, optionally initialized from
hsh
. -
#load(path, options = {}) ⇒ Object
Read a properties or YAML file at the path specified in
path
, and load the contents to this Config object. -
#load_properties(prop_file, options = {}) ⇒ Hash
Process a property file, returning its contents as a Hash.
-
#load_yaml(yaml_file, options = {}) ⇒ Object
Load the YAML file at
yaml_file
. -
#merge(hsh, options = {}) ⇒ Object
Merge the contents of the specified
hsh
into a new Config object. -
#merge!(hsh, options = {}) ⇒ Object
Merge the contents of the specified
hsh
into this Config object. -
#method_missing(name, *args) ⇒ Object
Override method_missing to respond to method calls with the value of the property, if this Config object contains a property of the same name.
-
#read_template(template_name, raise_on_unknown_var = true) ⇒ String
Reads a template file at
template_name
, and expands any substitution variable placeholder strings from this Config object. -
#respond_to?(name) ⇒ Boolean
Override respond_to? to indicate which methods we will accept.
-
#save_yaml(yaml_file, options = {}) ⇒ Object
Save this config object as a YAML file at
yaml_file
. -
#to_cfg ⇒ Object
Override Hash core extension method #to_cfg and just return self.
Constructor Details
#initialize(hsh = nil, options = {}) ⇒ Config
Create a Config object, optionally initialized from hsh
.
129 130 131 132 133 134 |
# File 'lib/batch-kit/config.rb', line 129 def initialize(hsh = nil, = {}) super(nil) @lookup_keys = {} @decryption_key = nil merge!(hsh, ) if hsh end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args) ⇒ Object
Override method_missing to respond to method calls with the value of the property, if this Config object contains a property of the same name.
401 402 403 404 405 406 407 408 409 410 411 412 413 |
# File 'lib/batch-kit/config.rb', line 401 def method_missing(name, *args) if name =~ /^(.+)\?$/ has_key?($1) elsif has_key?(name) self[name] elsif has_key?(name.to_s.gsub('_', '')) self[name.to_s.gsub('_', '')] elsif name =~ /^(.+)=$/ self[$1]= args.first else raise ArgumentError, "No configuration entry for key '#{name}'" end end |
Class Method Details
.expand_placeholders(str, properties, raise_on_unknown_var = false) ⇒ String
Expand any $<variable> or %<variable> placeholders in str
from either the supplied props
hash or the system environment variables. The props hash is assumed to contain string or symbol keys matching the variable name between ${ and } (or %{ and }) delimiters. If no match is found in the supplied props hash or the environment, the default behaviour returns the string with the placeholder variable still in place, but this behaviour can be overridden to cause an exception to be raised if desired.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/batch-kit/config.rb', line 107 def self.(str, properties, raise_on_unknown_var = false) chain = properties.is_a?(Hash) ? [properties] : properties.reverse str.gsub(/(?:[$%])\{([a-zA-Z0-9_]+)\}/) do case when src = chain.find{ |props| props.has_key?($1) || props.has_key?($1.intern) || props.has_key?($1.downcase.intern) } src[$1] || src[$1.intern] || src[$1.downcase.intern] when ENV[$1] then ENV[$1] when raise_on_unknown_var raise KeyError, "No value supplied for placeholder variable '#{$&}'" else $& end end end |
.load(file, props = nil, options = {}) ⇒ Config
Create a new Config object, and initialize it from the specified file
.
57 58 59 60 61 |
# File 'lib/batch-kit/config.rb', line 57 def self.load(file, props = nil, = {}) cfg = self.new(props, ) cfg.load(file, ) cfg end |
.properties_to_hash(str) ⇒ Object
Converts a str
in the form of a properties file into a Hash.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/batch-kit/config.rb', line 65 def self.properties_to_hash(str) hsh = props = {} str.each_line do |line| line.chomp! if match = /^\s*\[([A-Za-z0-9_ ]+)\]\s*$/.match(line) # Section heading props = hsh[match[1]] = {} elsif match = /^\s*([A-Za-z0-9_\.]+)\s*=\s*([^#]+)/.match(line) # Property setting val = match[2] props[match[1]] = case val when /^\d+$/ then val.to_i when /^\d*\.\d+$/ then val.to_f when /^:/ then val.intern when /false/i then false when /true/i then true else val end end end hsh end |
Instance Method Details
#[](key) ⇒ Object
Override #[] to be agnostic as to the case of the key, and whether it is a String or a Symbol.
326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/batch-kit/config.rb', line 326 def [](key) key = @lookup_keys[convert_key(key)] val = super(key) if @decryption_key && val.is_a?(String) && val =~ /!AES:([a-zA-Z0-9\/+=]+)!/ begin val = Encryption.decrypt(@decryption_key, $1) rescue Exception => ex raise "An error occurred while decrypting the value for key '#{key}': #{ex.}" end end val end |
#[]=(key, val) ⇒ Object
Override #[]= to be agnostic as to the case of the key, and whether it is a String or a Symbol.
342 343 344 345 346 347 348 349 |
# File 'lib/batch-kit/config.rb', line 342 def []=(key, val) std_key = convert_key(key) if @lookup_keys[std_key] != key delete(key) @lookup_keys[std_key] = key end super key, val end |
#clone ⇒ Object
Override #clone to also clone contents of @lookup_keys.
378 379 380 381 382 |
# File 'lib/batch-kit/config.rb', line 378 def clone copy = super copy.instance_variable_set(:@lookup_keys, @lookup_keys.clone) copy end |
#decryption_key=(key) ⇒ Object Also known as: encryption_key=
If set, encrypted strings (only) will be decrypted when accessed via #[] or #method_missing (for property-like access, e.g. cfg.password
).
279 280 281 282 283 284 285 |
# File 'lib/batch-kit/config.rb', line 279 def decryption_key=(key) require_relative 'encryption' self.each do |_, val| val.decryption_key = key if val.is_a?(Config) end @decryption_key = key end |
#delete(key) ⇒ Object
Override #delete to be agnostic as to the case of the key, and whether it is a String or a Symbol.
354 355 356 357 |
# File 'lib/batch-kit/config.rb', line 354 def delete(key) key = @lookup_keys.delete(convert_key(key)) super key end |
#dup ⇒ Object
Override #dup to also clone contents of @lookup_keys.
386 387 388 389 390 |
# File 'lib/batch-kit/config.rb', line 386 def dup copy = super copy.instance_variable_set(:@lookup_keys, @lookup_keys.dup) copy end |
#encode_with(coder) ⇒ Object
Ensure that Config objects are saved as normal hashes when writing YAML.
232 233 234 |
# File 'lib/batch-kit/config.rb', line 232 def encode_with(coder) coder.represent_map nil, self end |
#encrypt(key_pat, master_key = @decryption_key) ⇒ Object
Recursively encrypts the values of all keys in this Config object that match key_pat
.
Note: key_pat
will be compared against the standardised key values of each object (i.e. lowercase, with spaces converted to _).
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/batch-kit/config.rb', line 301 def encrypt(key_pat, master_key = @decryption_key) key_pat = Regexp.new(key_pat, true) if key_pat.is_a?(String) raise ArgumentError, "key_pat must be a Regexp or String" unless key_pat.is_a?(Regexp) raise ArgumentError, "No master key has been set or passed" unless master_key require_relative 'encryption' self.each do |key, val| if Config === val val.encrypt(key_pat, master_key) else if @decryption_key && val.is_a?(String) && val =~ /!AES:([a-zA-Z0-9\/+=]+)!/ # Decrypt using old master key val = self[key] self[key] = val end if val.is_a?(String) && convert_key(key) =~ key_pat self[key] = "!AES:#{Encryption.encrypt(master_key, val).strip}!" end end end @decryption_key = master_key end |
#expand_placeholders(str, raise_on_unknown_var = true) ⇒ String
Expand any $<variable> or %<variable> placeholders in str
from this Config object or the system environment variables. This Config object is assumed to contain string or symbol keys matching the variable name between ${ and } (or %{ and }) delimiters. If no match is found in the supplied props hash or the environment, the default behaviour is to raise an exception, but this can be overriden to leave the placeholder variable still in place if desired.
447 448 449 |
# File 'lib/batch-kit/config.rb', line 447 def (str, raise_on_unknown_var = true) self.class.(str, self, raise_on_unknown_var) end |
#fetch(key, *rest) ⇒ Object
Override #fetch to be agnostic as to the case of the key, and whether it is a String or a Symbol.
371 372 373 374 |
# File 'lib/batch-kit/config.rb', line 371 def fetch(key, *rest) key = @lookup_keys[convert_key(key)] || key super end |
#has_key?(key) ⇒ Boolean Also known as: include?
Override #has_key? to be agnostic as to the case of the key, and whether it is a String or a Symbol.
362 363 364 365 |
# File 'lib/batch-kit/config.rb', line 362 def has_key?(key) key = @lookup_keys[convert_key(key)] super key end |
#load(path, options = {}) ⇒ Object
Read a properties or YAML file at the path specified in path
, and load the contents to this Config object.
146 147 148 149 150 151 |
# File 'lib/batch-kit/config.rb', line 146 def load(path, = {}) props = case File.extname(path) when /\.yaml/i then self.load_yaml(path, ) else self.load_properties(path, ) end end |
#load_properties(prop_file, options = {}) ⇒ Hash
Process a property file, returning its contents as a Hash. Only lines of the form KEY=VALUE are processed, and # indicates the start of a comment. Property files can contain sections, denoted by [SECTION].
190 191 192 193 194 |
# File 'lib/batch-kit/config.rb', line 190 def load_properties(prop_file, = {}) str = read_file(prop_file, ) hsh = self.class.properties_to_hash(str) self.merge!(hsh, ) end |
#load_yaml(yaml_file, options = {}) ⇒ Object
Load the YAML file at yaml_file
.
211 212 213 214 215 216 |
# File 'lib/batch-kit/config.rb', line 211 def load_yaml(yaml_file, = {}) require 'yaml' str = read_file(yaml_file, ) yaml = YAML.load(str) self.merge!(yaml, ) end |
#merge(hsh, options = {}) ⇒ Object
Merge the contents of the specified hsh
into a new Config object.
267 268 269 270 271 |
# File 'lib/batch-kit/config.rb', line 267 def merge(hsh, = {}) cfg = self.dup cfg.merge!(hsh, ) cfg end |
#merge!(hsh, options = {}) ⇒ Object
Merge the contents of the specified hsh
into this Config object.
244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/batch-kit/config.rb', line 244 def merge!(hsh, = {}) if hsh && !hsh.is_a?(Hash) raise ArgumentError, "Only Hash objects can be merged into Config (got #{hsh.class.name})" end hsh && hsh.each do |key, val| self[key] = convert_val(val, [:raise_on_unknown_var]) end if hsh.is_a?(Config) @decryption_key = hsh.instance_variable_get(:@decryption_key) unless @decryption_key end self end |
#read_template(template_name, raise_on_unknown_var = true) ⇒ String
Reads a template file at template_name
, and expands any substitution variable placeholder strings from this Config object.
459 460 461 462 |
# File 'lib/batch-kit/config.rb', line 459 def read_template(template_name, raise_on_unknown_var = true) template = IO.read(template_name) (template, raise_on_unknown_var) end |
#respond_to?(name) ⇒ Boolean
Override respond_to? to indicate which methods we will accept.
417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/batch-kit/config.rb', line 417 def respond_to?(name) if name =~ /^(.+)\?$/ has_key?($1) elsif has_key?(name) true elsif has_key?(name.to_s.gsub('_', '')) true elsif name =~ /^(.+)=$/ true else super end end |
#save_yaml(yaml_file, options = {}) ⇒ Object
Save this config object as a YAML file at yaml_file
.
223 224 225 226 227 |
# File 'lib/batch-kit/config.rb', line 223 def save_yaml(yaml_file, = {}) require 'yaml' str = self.to_yaml File.open(yaml_file, 'wb'){ |f| f.puts(str) } end |
#to_cfg ⇒ Object
Override Hash core extension method #to_cfg and just return self.
394 395 396 |
# File 'lib/batch-kit/config.rb', line 394 def to_cfg self end |