config_logic 0.1.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.
data/README.rdoc ADDED
@@ -0,0 +1,159 @@
1
+ = Config Logic
2
+
3
+ Config Logic is a configuration management tool for Ruby/Rails applications. It
4
+ wraps any set of config files in a managed access layer which supports
5
+
6
+ * caching
7
+ * multi argument, hash style, or dot style access
8
+ * ordered overlays
9
+ * dynamic or static multiplexing
10
+
11
+
12
+ == Requirements
13
+
14
+ The following gems will be installed along with config_logic
15
+
16
+ * activesupport (2.2.2+)
17
+ * buffered_logger (0.1.2+)
18
+
19
+
20
+ == Installation
21
+
22
+ gem install config_logic
23
+
24
+
25
+ == Usage
26
+
27
+ Imagine that your application has a config directory with the following layout,
28
+
29
+ config/config_prod.yml -->
30
+
31
+ :key1: 1
32
+ :key2: 2
33
+ :key3: 3
34
+
35
+ config/config_dev.yml -->
36
+
37
+ :key1: 11
38
+ :key2: 12
39
+ :key3: 13
40
+
41
+ config/dir1/config_prod.yml -->
42
+
43
+ :key1: 11
44
+ :key2: 12
45
+ :key3: 13
46
+
47
+ config/dir2/config_dev.yml -->
48
+
49
+ :key4: 1
50
+ :key5: 2
51
+ :key6: 3
52
+
53
+
54
+ === Initialization
55
+
56
+ c = ConfigLogic.new('path/to/config/dir')
57
+ c = ConfigLogic.new(['path1', 'path2'])
58
+
59
+
60
+ === Data Access
61
+
62
+ c.config.key1 => 1
63
+ c[:config][:key1] => 1
64
+ c['config']['key1'] => 1
65
+ c(:config, :key1) => 1
66
+ c('config', 'key1') => 1
67
+ c.unknown => nil
68
+ c.config.unknown => nil
69
+
70
+
71
+ === Rebuilding the Config Cache
72
+
73
+ c.reload!
74
+
75
+
76
+ === Applying Overlays
77
+
78
+ An Overlay allows multiple config values to be merged in a specified order. The
79
+ merge order is determined by the order of the key names in the :inputs parameter.
80
+
81
+ ==== Global
82
+
83
+ c = ConfigLogic.new('path/to/config/dir', :overlays => [ { :name => 'config',
84
+ :inputs => [:config_prod, :config_dev] } ] )
85
+ c.config.key1 => 11
86
+ c.config.key2 => 12
87
+ c.config.key3 => 13
88
+ c.config.dir1.key1 => 11
89
+ c.config.dir1.key2 => 12
90
+ c.config.dir1.key3 => 13
91
+ c.config.dir1.key4 => 1
92
+ c.config.dir1.key5 => 2
93
+ c.config.dir1.key6 => 3
94
+
95
+ ==== Local
96
+
97
+ c.config.insert_overlay( { :name => 'config',
98
+ :inputs => [:config_prod, :config_dev] } )
99
+ c.config.key1 => 11
100
+ c.config.key2 => 12
101
+ c.config.key3 => 13
102
+ c.config.dir1.key1 => 1
103
+ c.config.dir1.key2 => 2
104
+ c.config.dir1.key3 => 3
105
+ c.config.dir1.key4 => nil
106
+
107
+
108
+ === Applying Multiplexers
109
+
110
+ A multiplexer groups multiple config values and uses the result of a supplied code
111
+ block to choose the appropriate one. Multiplexers can be static or dynamic. A
112
+ static multiplexer is evaluated when it is created and its result replaces the
113
+ key group specified. A dynamic multiplexer is evaluated every time it's called.
114
+ Multiplexers are static by default.
115
+
116
+
117
+ ==== Global
118
+
119
+ c = ConfigLogic.new('path/to/config/dir', :multiplexers => [ { :name => 'key',
120
+ :selector => Proc.new {1 + 1 },
121
+ :inputs => {1 => :key1, 2 => :key2, 3 => :key3} } ] )
122
+ c.config.key => 2
123
+ c.config.key1 => nil
124
+ c.config.key2 => nil
125
+ c.config.key3 => nil
126
+ c.config.dir1.key => 2
127
+ c.config.dir1.key1 => nil
128
+ c.config.dir1.key2 => nil
129
+ c.config.dir1.key3 => nil
130
+
131
+ ==== Local
132
+
133
+ c.config.insert_multiplexer({ :name => 'key',
134
+ :selector => Proc.new {1 + 1 },
135
+ :inputs => {1 => :key1, 2 => :key2, 3 => :key3} })
136
+ c.config.key => 2
137
+ c.config.key1 => nil
138
+ c.config.key2 => nil
139
+ c.config.key3 => nil
140
+ c.config.dir1.key => nil
141
+ c.config.dir1.key1 => 1
142
+ c.config.dir1.key2 => 2
143
+ c.config.dir1.key3 => 3
144
+
145
+ ==== Static vs Dynamic
146
+
147
+ A dynamic multiplexer will re-evaluate the selecor proc every time it's called
148
+
149
+ c.config.insert_multiplexer({ :name => 'key',
150
+ :static => false,
151
+ :selector => Proc.new { rand(3) + 1 },
152
+ :inputs => {1 => :key1, 2 => :key2, 3 => :key3} })
153
+ c.config.key => 2 # time = 0
154
+ c.config.key => 1 # time = 0 + n
155
+
156
+
157
+ == Credits
158
+
159
+ Inspired by RConfig (Rahmal Conda) / activeconfig (Enova Financial)
data/TODO ADDED
File without changes
@@ -0,0 +1,22 @@
1
+ class ConfigLogic::Cache < SimpleDelegator
2
+ include ConfigLogic::Logger
3
+ include Enumerable
4
+
5
+ def initialize(load_paths, params = {})
6
+ @load_paths = [load_paths].flatten
7
+ reload!(params)
8
+ super(@cache)
9
+ end
10
+
11
+ def reload!(params = {})
12
+ __setobj__(@cache)
13
+ self
14
+ end
15
+
16
+ def each
17
+ @cache.each do |node|
18
+ yield node
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,54 @@
1
+ class ConfigLogic < SimpleDelegator
2
+ extend Forwardable
3
+
4
+ def_delegators :@cache, :reload!
5
+ def_delegators :@data, :inspect
6
+
7
+ def initialize(load_paths, params = {})
8
+ @cache = TreeCache.new(load_paths, params)
9
+ @path = params[:path] || []
10
+ @data = @cache[*@path]
11
+ super(@data)
12
+ end
13
+
14
+ def [](*keys)
15
+ new_path = @path + keys
16
+ val = @cache[*new_path]
17
+
18
+ case val
19
+ when Hash
20
+ self.clone_with_new_path(new_path)
21
+ when ConfigLogic::LogicElement
22
+ val.output
23
+ else val
24
+ end
25
+ end
26
+
27
+ protected
28
+
29
+ def clone_with_new_path(path)
30
+ clone = self.dup
31
+ clone.instance_variable_set('@path', path)
32
+ new_delegate = \
33
+ clone.instance_variable_set('@data', @cache[*path])
34
+ clone.__setobj__(new_delegate)
35
+ clone
36
+ end
37
+
38
+ private
39
+
40
+ def method_missing(method_symbol, *args)
41
+ method_name = method_symbol.to_s
42
+ element_matcher = ConfigLogic::LogicElement.available_elements.join('|')
43
+
44
+ if (val = self[method_symbol])
45
+ val
46
+ elsif /^insert_(#{element_matcher})$/ === method_name
47
+ type = ConfigLogic::LogicElement.name_to_type($1)
48
+ @cache.insert_logic_element(type.new(args.first), @data) if type
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,15 @@
1
+ class Array
2
+
3
+ def stringify_symbols
4
+ self.map { |val| val.is_a?(Symbol) ? val.to_s : val }
5
+ end
6
+
7
+ def symbolize_strings
8
+ self.map { |val| val.is_a?(Symbol) ? val.to_s : val }
9
+ end
10
+
11
+ def to_hash
12
+ self.inject({}) { |h, (key, val)| h[key] = val; h }
13
+ end
14
+
15
+ end
@@ -0,0 +1,7 @@
1
+ class Class
2
+
3
+ def simple_name
4
+ name.split('::').last
5
+ end
6
+
7
+ end
@@ -0,0 +1,78 @@
1
+ require 'yaml'
2
+ require 'find'
3
+
4
+ class ConfigLogic::FileCache < ConfigLogic::Cache
5
+
6
+ PARSER_MAP = {
7
+ ['.yaml', '.yml'] => lambda do |path| YAML::load_file(path) end,
8
+ ['.txt'] => lambda do |path| File.read(path) end,
9
+ ['.json'] => lambda do |path| end }.freeze
10
+
11
+ VALID_EXTENSIONS = PARSER_MAP.map { |(extensions, parser_proc)| extensions }.flatten!.freeze
12
+
13
+ def reload!(params = {})
14
+ @cache = rebuild_primary_cache(params)
15
+ super
16
+ end
17
+
18
+ private
19
+
20
+ def rebuild_primary_cache(params)
21
+ file_cache = []
22
+ load_file = Proc.new do |path|
23
+ end
24
+
25
+ valid_files = @load_paths.inject([]) do |valid, path|
26
+ valid << find_valid_files(path)
27
+ end.flatten
28
+
29
+ valid_files.each do |path|
30
+ content = parse(path)
31
+ file_cache << [path, content] if content
32
+ log.debug "found file #{BLUE} #{path}"
33
+ end
34
+
35
+ file_cache.inject({}) do
36
+ |h, (path, content)| h[path] = content; h
37
+ end
38
+ end
39
+
40
+ def find_valid_files(path)
41
+ valid_files = []
42
+ Find.find(path) do |file|
43
+ valid_files << file if valid_file_ext?(file)
44
+ end
45
+ valid_files
46
+ end
47
+
48
+ # parsing helpers
49
+
50
+ def parse(path)
51
+ parser = find_parser(path)
52
+ return unless parser
53
+
54
+ begin
55
+ parser.call(path)
56
+ rescue => e
57
+ log.warn "#{RED} #{path} #{RESET} is not a valid config file"
58
+ end
59
+ end
60
+
61
+ def find_parser(path)
62
+ parser = PARSER_MAP.detect { |(extensions, parse_proc)| extensions.include?(File.extname(path)) }
63
+ parser ? parser.last : nil
64
+ end
65
+
66
+ # path helpers
67
+
68
+ def load_path_exists?(path)
69
+ unless File.exists?(path)
70
+ log.warn "#{RED} #{path} #{RESET} does not exist"; false
71
+ else true end
72
+ end
73
+
74
+ def valid_file_ext?(path)
75
+ VALID_EXTENSIONS.include?(File.extname(path))
76
+ end
77
+
78
+ end
@@ -0,0 +1,40 @@
1
+ require 'buffered_logger'
2
+
3
+ class ConfigLogic::Log
4
+ private_class_method :new
5
+ LOG_LEVEL = 6
6
+
7
+ def self.init_log(log_level, color)
8
+ @log = BufferedLogger.new(STDOUT, log_level || LOG_LEVEL, default_format)
9
+ log.disable_color unless color
10
+ end
11
+
12
+ def self.log
13
+ @log ||= BufferedLogger.new(STDOUT, LOG_LEVEL, default_format)
14
+ end
15
+
16
+ private
17
+
18
+ def self.default_format
19
+ { :debug => "$negative DEBUG: $reset %s",
20
+ :warn => "$yellow WARNING: $reset %s",
21
+ :error => "$red ERROR: $reset %s" }
22
+ end
23
+
24
+ end
25
+
26
+
27
+ module ConfigLogic::Logger
28
+ module Colors
29
+ RED = '$red'
30
+ BLUE = '$blue'
31
+ GREEN = '$green'
32
+ YELLOW = '$yellow'
33
+ RESET = '$reset'
34
+ end
35
+ include Colors
36
+
37
+ def log
38
+ ConfigLogic::Log.log
39
+ end
40
+ end
@@ -0,0 +1,62 @@
1
+ require 'set'
2
+
3
+ class ConfigLogic::LogicElement
4
+ include ConfigLogic::Logger
5
+
6
+ MIN_INPUTS = 2
7
+ NAME = 'logic_element'
8
+ STATIC = true
9
+
10
+ attr_reader :name, :inputs
11
+ cattr_reader :min_inputs, :types
12
+ @@types = Set.new
13
+ @@min_inputs = MIN_INPUTS
14
+
15
+ def initialize(params)
16
+ @name = params[:name] || NAME
17
+ @static = params[:static].nil? ? STATIC : params[:static]
18
+ @inputs = HashWithIndifferentAccess.new
19
+ end
20
+
21
+ def set_input(input_name, value)
22
+ @inputs[input_name] = value
23
+ end
24
+
25
+ def input_names
26
+ @inputs.keys
27
+ end
28
+
29
+ def static?
30
+ @static
31
+ end
32
+
33
+ class << self
34
+
35
+ def inherited(child)
36
+ @@types << child
37
+ end
38
+
39
+ def available_elements
40
+ @@types.map { |t| t.simple_name.downcase }
41
+ end
42
+
43
+ def name_to_type(name)
44
+ if available_elements.include?(name)
45
+ ('ConfigLogic::' + name.classify).constantize
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ private
52
+
53
+ def inputs_valid?
54
+ input_count = @inputs.values.compact.size
55
+ if input_count < min_inputs
56
+ log.error("number of inputs (#{input_count}) is less than minimum (#{@@min_inputs})")
57
+ false
58
+ else true
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,22 @@
1
+ class ConfigLogic::Multiplexer < ConfigLogic::LogicElement
2
+
3
+ attr_reader :selector
4
+
5
+ def initialize(params)
6
+ super
7
+ @multiplexer = params[:inputs]
8
+ @inputs = HashWithIndifferentAccess.new(@multiplexer.values.to_hash)
9
+ @selector = params[:selector]
10
+ end
11
+
12
+ def output
13
+ return unless inputs_valid?
14
+ case @selector
15
+ when Proc
16
+ @inputs[@multiplexer[@selector.call]]
17
+ else
18
+ @inputs[@multiplexer[@selector]]
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,24 @@
1
+ class ConfigLogic::Overlay < ConfigLogic::LogicElement
2
+
3
+ attr_reader :order
4
+
5
+ def initialize(params)
6
+ super
7
+ @order = params[:inputs]
8
+ @inputs = HashWithIndifferentAccess.new(@order.to_hash)
9
+ end
10
+
11
+ # merge if possible, return highest priority data if not
12
+ #
13
+ def output
14
+ return unless inputs_valid?
15
+ if @inputs.values.all? { |d| d.is_a? Hash }
16
+ @order.inject({}) do |output, input_name|
17
+ output.deep_merge!(@inputs[input_name])
18
+ end
19
+ else
20
+ @inputs[@order.last]
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,132 @@
1
+ class ConfigLogic::TreeCache < ConfigLogic::Cache
2
+
3
+ def initialize(load_paths, params = {})
4
+ @global_logic = params.inject([]) do |a, (type_name, elements)|
5
+ type_name = type_name.to_s.singularize
6
+ type = ConfigLogic::LogicElement.name_to_type(type_name)
7
+ type ? a << elements.map { |settings| type.new(settings) } : a
8
+ end.flatten
9
+ super
10
+ end
11
+
12
+ def [](*key_path)
13
+ return @cache if key_path.blank?
14
+ key_path.inject(@cache) do |val, key|
15
+ val[key] if val
16
+ end
17
+ end
18
+
19
+ def reload!(params = {})
20
+ @cache = rebuild_primary_cache(params)
21
+ unless @cache.empty?
22
+ trim_cache! unless params[:no_trim]
23
+ @flat_cache = flatten_tree_cache
24
+ apply_global_logic
25
+ end
26
+ super
27
+ end
28
+
29
+ def insert_logic_element(element, node)
30
+ input_names = element.input_names
31
+ input_names.each do |input_name|
32
+ element.set_input(input_name, node[input_name])
33
+ end
34
+ log.debug "inserted logic element:\n#{element.pretty_inspect}"
35
+
36
+ node[element.name] = element.static? ? element.output : element
37
+ prune!(node, input_names)
38
+ end
39
+
40
+ private
41
+
42
+ def rebuild_primary_cache(params)
43
+ file_cache = ConfigLogic::FileCache.new(@load_paths)
44
+ return {} unless valid_file_cache?(file_cache)
45
+
46
+ tree_cache = HashWithIndifferentAccess.new
47
+ file_cache.each do |path, content|
48
+ tree_cache = tree_cache.deep_merge(hashify_path(path, content))
49
+ end
50
+ tree_cache
51
+ end
52
+
53
+ def hashify_path(path, content)
54
+ path_keys = split_file_path(path)
55
+ content = HashWithIndifferentAccess.new({ path_keys.pop => content })
56
+ path_keys.reverse.inject(content) do |content, key|
57
+ HashWithIndifferentAccess.new({ key => content })
58
+ end
59
+ end
60
+
61
+ def trim_cache!
62
+ return if @cache.size > 1
63
+
64
+ until @cache.size > 1 do
65
+ @cache = @cache[@cache.keys.first]
66
+ end
67
+ end
68
+
69
+ # node cache helpers
70
+
71
+ def flatten_tree_cache
72
+ flatten = Proc.new do |tree_cache|
73
+ tree_cache.inject([]) do |hashes, (k,v)|
74
+ if v.is_a? Hash
75
+ hashes << v << flatten.call(v)
76
+ else hashes end
77
+ end.flatten
78
+ end
79
+ flatten.call(@cache) << @cache
80
+ end
81
+
82
+ def nodes_with_keys(keys, min_matches = 1)
83
+ self.select do |node|
84
+ node_keys = node.keys
85
+ node_keys.size - (node_keys - keys).size >= min_matches
86
+ end
87
+ end
88
+
89
+ def prune!(node, keys)
90
+ node.reject! { |k, v| keys.include?(k) }
91
+ end
92
+
93
+ # logic helpers
94
+
95
+ def apply_global_logic
96
+ @global_logic.each do |element|
97
+ log.debug "applying #{element.class}: #{[element.name, element.input_names].inspect}"
98
+ insert_global_logic_element(element)
99
+ end
100
+ end
101
+
102
+ def insert_global_logic_element(element)
103
+ matching_nodes = nodes_with_keys(element.input_names, element.min_inputs)
104
+ log.debug "matching nodes:\n#{matching_nodes.pretty_inspect}"
105
+ matching_nodes.each do |node|
106
+ insert_logic_element(element, node)
107
+ end
108
+ end
109
+
110
+ # path helpers
111
+
112
+ def split_file_path(path)
113
+ basename = File.basename(path, '.*')
114
+ dirname = File.dirname(path)
115
+ dirname.split('/')[1..-1] << basename
116
+ end
117
+
118
+ def valid_file_cache?(file_cache)
119
+ if file_cache.empty?
120
+ log.error "file cache is empty"; false
121
+ else true end
122
+ end
123
+
124
+ # enumerable
125
+
126
+ def each
127
+ @flat_cache.each do |node|
128
+ yield node
129
+ end
130
+ end
131
+
132
+ end
@@ -0,0 +1,27 @@
1
+ $: << File.dirname(__FILE__)
2
+
3
+ require 'rubygems'
4
+ require 'active_support/core_ext/class/attribute_accessors'
5
+ require 'active_support/inflector'
6
+ require 'active_support/core_ext/string/inflections'
7
+ require 'active_support/core_ext/hash/indifferent_access'
8
+ require 'active_support/core_ext/hash/deep_merge'
9
+ require 'buffered_logger'
10
+ require 'forwardable'
11
+ require 'delegate'
12
+
13
+ class ConfigLogic < SimpleDelegator
14
+ module Logger; end
15
+ include ConfigLogic::Logger
16
+ end
17
+
18
+ require 'config_logic/core_ext/class'
19
+ require 'config_logic/core_ext/array'
20
+ require 'config_logic/logger'
21
+ require 'config_logic/cache'
22
+ require 'config_logic/file_cache'
23
+ require 'config_logic/logic_element'
24
+ require 'config_logic/multiplexer'
25
+ require 'config_logic/overlay'
26
+ require 'config_logic/tree_cache'
27
+ require 'config_logic/config_logic'
@@ -0,0 +1,24 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper.rb")
2
+
3
+ describe ConfigLogic::Cache do
4
+
5
+ before :each do
6
+ @c = ConfigLogic::Cache.new(CONFIG_DIR)
7
+ end
8
+
9
+ it 'should initialize' do
10
+ @c.should be_an_instance_of(ConfigLogic::Cache)
11
+ end
12
+
13
+ it 'should delegate unknown calls to the cache container' do
14
+ cache = @c.instance_variable_get('@cache')
15
+ cache.public_methods(false).each do |m|
16
+ @c.should respond_to(m)
17
+ end
18
+ end
19
+
20
+ it 'should be enumerable' do
21
+ @c.should respond_to(:each)
22
+ end
23
+
24
+ end
@@ -0,0 +1,59 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper.rb")
2
+
3
+ describe ConfigLogic do
4
+
5
+ before :each do
6
+ @c = ConfigLogic.new(CONFIG_DIR)
7
+ end
8
+
9
+ it 'should initialize' do
10
+ @c.should be_an_instance_of(ConfigLogic)
11
+ end
12
+
13
+ it 'should delegate unknown calls to cache node pointed to by current path' do
14
+ node = @c.instance_variable_get('@data')
15
+ node.public_methods(false).each do |m|
16
+ @c.should respond_to(m)
17
+ end
18
+ end
19
+
20
+ it 'should delegate :reload! and :inspect calls to cache' do
21
+ @c.should respond_to(:reload!)
22
+ @c.should respond_to(:inspect)
23
+ end
24
+
25
+ it 'should be accessible via different access methods' do
26
+ @c[:dir1, :config1, :key1, :nestedkey1].should == 1
27
+ @c[:dir1][:config1][:key1][:nestedkey1].should == 1
28
+ @c['dir1', 'config1', 'key1', 'nestedkey1'].should == 1
29
+ @c['dir1']['config1']['key1']['nestedkey1'].should == 1
30
+ @c.dir1.config1.key1.nestedkey1.should == 1
31
+ end
32
+
33
+ it 'should return ConfigLogic hashes for any path that doesnt point to a simple value' do
34
+ @c.dir1.should be_a_kind_of(ConfigLogic)
35
+ @c.dir1.config1.should be_a_kind_of(ConfigLogic)
36
+ @c.dir1.config1.key1.should be_a_kind_of(ConfigLogic)
37
+ end
38
+
39
+ it 'should apply local overlay elements' do
40
+ @c.dir1.config1.insert_overlay({:name => :over, :inputs => [:key1, :key2, :key3]})
41
+ overlayed_result = {"nestedkey1" => 1, "nestedkey2" => 2, "nestedkey3" => 3,
42
+ "nestedkey4" => 4, "nestedkey5" => 5, "nestedkey6" => 6,
43
+ "nestedkey7" => 7, "nestedkey8" => 8, "nestedkey9" => 9}
44
+ @c[:dir1][:config1][:over].to_hash.should == overlayed_result
45
+ @c[:dir1][:config1].keys.size.should == 1
46
+ @c[:dir1][:config2].keys.size.should == 3
47
+ end
48
+
49
+ it 'should apply local multiplexer elements' do
50
+ @c.dir1.insert_multiplexer({:name => :multi, :selector => 1, :inputs => {1 => :config1, 2 => :config2, 3 => :config3}})
51
+ multiplexed_result = {"key1"=>{"nestedkey1"=>1, "nestedkey2"=>2, "nestedkey3"=>3},
52
+ "key2"=>{"nestedkey4"=>4, "nestedkey5"=>5, "nestedkey6"=>6},
53
+ "key3"=>{"nestedkey7"=>7, "nestedkey8"=>8, "nestedkey9"=>9}}
54
+ @c[:dir1][:multi].to_hash.should == multiplexed_result
55
+ @c[:dir1].keys.size.should == 1
56
+ @c.keys.size.should == 5
57
+ end
58
+
59
+ end
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper.rb")
2
+
3
+ describe ConfigLogic::FileCache do
4
+
5
+ def find_valid_files(path)
6
+ valid_files = ConfigLogic::FileCache::VALID_EXTENSIONS.inject([]) do |valid, ext|
7
+ valid << Dir["#{path}/**/*#{ext}"]
8
+ end.flatten
9
+ valid_files.reject { |f| f =~ /syntax_error/ }
10
+ end
11
+
12
+ def cache_valid?(path, cache)
13
+ cache = cache.keys
14
+ valid_files = find_valid_files(path)
15
+ cache.empty?.should be_false
16
+ cache.each do |cached_file|
17
+ valid_files.include?(cached_file).should be_true
18
+ end
19
+ end
20
+
21
+ before :each do
22
+ @c = ConfigLogic::FileCache.new(CONFIG_DIR)
23
+ end
24
+
25
+ it 'should initialize' do
26
+ @c.should be_an_instance_of(ConfigLogic::FileCache)
27
+ end
28
+
29
+ it 'should build a valid file cache during initialization' do
30
+ cache_valid?(CONFIG_DIR, @c.instance_variable_get('@cache'))
31
+ end
32
+
33
+ it 'should rebuild cache during a reload!' do
34
+ config_dir = "#{CONFIG_DIR}/dir1"
35
+ @c.instance_variable_set('@load_paths', [config_dir])
36
+ @c.reload!
37
+ cache_valid?(config_dir, @c.instance_variable_get('@cache'))
38
+ end
39
+
40
+ end
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper.rb")
2
+
3
+ describe ConfigLogic::LogicElement do
4
+
5
+ before :each do
6
+ settings = {:name => :a_logic_element}
7
+ @e = ConfigLogic::LogicElement.new(settings)
8
+ end
9
+
10
+ it 'should initialize' do
11
+ @e.should be_an_instance_of(ConfigLogic::LogicElement)
12
+ @e.name.should == :a_logic_element
13
+ @e.static?.should == true
14
+ end
15
+
16
+ it 'should specify the minimum number of inputs' do
17
+ @e.class.min_inputs.should == 2
18
+ @e.min_inputs.should == 2
19
+ end
20
+
21
+ it 'should respond with all registered component types' do
22
+ (@e.class.available_elements - ['multiplexer', 'overlay']).should == []
23
+ end
24
+
25
+ it 'should set input values' do
26
+ @e.set_input(:a, 1)
27
+ @e.set_input(:b, 2)
28
+ @e.set_input(:c, 3)
29
+ @e.inputs.should == {'a' => 1, 'b' => 2, 'c' => 3}
30
+ end
31
+
32
+ it 'should determine if the input state is valid' do
33
+ @e.send(:inputs_valid?).should == false
34
+ @e.instance_variable_set('@inputs', {'a' => 1, 'b' => 2})
35
+ @e.send(:inputs_valid?).should == true
36
+ end
37
+
38
+ it 'should convert element name to type' do
39
+ @e.class.name_to_type('multiplexer').should == ConfigLogic::Multiplexer
40
+ @e.class.name_to_type('overlay').should == ConfigLogic::Overlay
41
+ end
42
+
43
+ end
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper.rb")
2
+
3
+ describe ConfigLogic::Multiplexer do
4
+
5
+ before :each do
6
+ settings = { :name => :a_multiplexer,
7
+ :selector => Proc.new { 1 + 1 },
8
+ :inputs => {1 => :a, 2 => :b, 3 => :c} }
9
+ @m = ConfigLogic::Multiplexer.new(settings)
10
+ @m.set_input(:a, 10)
11
+ @m.set_input(:b, 11)
12
+ @m.set_input(:c, 12)
13
+ end
14
+
15
+ it 'should initialize' do
16
+ @m.should be_an_instance_of(ConfigLogic::Multiplexer)
17
+ @m.should be_a_kind_of(ConfigLogic::LogicElement)
18
+ @m.name.should == :a_multiplexer
19
+ @m.inputs.should == {'a' => 10, 'b' => 11, 'c' => 12}
20
+ end
21
+
22
+ it 'should return selector proc' do
23
+ @m.should respond_to(:selector)
24
+ end
25
+
26
+ it 'should correctly multiplex outputs based on selector' do
27
+ @m.output.should == 11
28
+ end
29
+
30
+ end
@@ -0,0 +1,35 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper.rb")
2
+
3
+ describe ConfigLogic::Overlay do
4
+
5
+ before :each do
6
+ settings = { :name => :an_overlay, :inputs => [:input1, :input2, :input3] }
7
+ @e = ConfigLogic::Overlay.new(settings)
8
+ @e.set_input(:input1, {:a => 1})
9
+ @e.set_input(:input2, {:b => 2})
10
+ @e.set_input(:input3, {:a => 2, :c => 3})
11
+ end
12
+
13
+ it 'should initialize' do
14
+ @e.should be_an_instance_of(ConfigLogic::Overlay)
15
+ @e.should be_a_kind_of(ConfigLogic::LogicElement)
16
+ @e.name.should == :an_overlay
17
+ @e.inputs.should == {'input1' => {'a' => 1},
18
+ 'input2' => {'b' => 2},
19
+ 'input3' => {'a' => 2, 'c' => 3}}
20
+ end
21
+
22
+ it 'should output a propper overlay when all inputs are hashes' do
23
+ @e.output.should == {'a' => 2, 'b' => 2, 'c' => 3}
24
+ end
25
+
26
+ it 'should output a propper overlay when not all inputs are hashes' do
27
+ settings = { :name => :an_overlay, :inputs => [:input1, :input2, :input3] }
28
+ e = ConfigLogic::Overlay.new(settings)
29
+ e.set_input(:input1, 1)
30
+ e.set_input(:input2, {:a => 1})
31
+ e.set_input(:input3, 3)
32
+ e.output.should == 3
33
+ end
34
+
35
+ end
@@ -0,0 +1,5 @@
1
+ $: << File.join(File.dirname(__FILE__), "../lib")
2
+ CONFIG_DIR = File.join(File.dirname(__FILE__), 'config')
3
+
4
+ require 'rspec'
5
+ require 'config_logic'
@@ -0,0 +1,56 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper.rb")
2
+
3
+ describe ConfigLogic::TreeCache do
4
+
5
+ before :each do
6
+ @c = ConfigLogic::TreeCache.new(CONFIG_DIR)
7
+ end
8
+
9
+ it 'should initialize' do
10
+ @c.should be_an_instance_of(ConfigLogic::TreeCache)
11
+ end
12
+
13
+ it 'should build a valid tree cache' do
14
+ @c.to_hash.keys.sort.should == ['config1', 'config2', 'config3', 'dir1', 'dir4']
15
+ end
16
+
17
+ it 'should rebuild cache during a reload!' do
18
+ config_dir = "#{CONFIG_DIR}/dir1"
19
+ @c.instance_variable_set('@load_paths', [config_dir])
20
+ @c.reload!
21
+ @c.to_hash.keys.sort.should == ['config1', 'config2', 'config3']
22
+ end
23
+
24
+ it 'should apply global overlay elements' do
25
+ @c = ConfigLogic::TreeCache.new(CONFIG_DIR, :overlays => [{:name => :over, :inputs => [:key1, :key2, :key3]}] )
26
+ overlayed_result = {"nestedkey1" => 1, "nestedkey2" => 2, "nestedkey3" => 3,
27
+ "nestedkey4" => 4, "nestedkey5" => 5, "nestedkey6" => 6,
28
+ "nestedkey7" => 7, "nestedkey8" => 8, "nestedkey9" => 9}
29
+ @c[:dir1][:config1][:over].to_hash.should == overlayed_result
30
+ @c[:dir1][:config2][:over].to_hash.should == overlayed_result
31
+ @c[:dir1][:config3][:over].to_hash.should == overlayed_result
32
+ @c[:dir1][:config3].keys.size.should == 1
33
+ @c[:config1][:over].to_hash.should == overlayed_result
34
+ @c[:config2][:over].to_hash.should == overlayed_result
35
+ @c[:config3][:over].to_hash.should == overlayed_result
36
+ @c[:config3].keys.size.should == 1
37
+ end
38
+
39
+ it 'should apply global multiplexer elements' do
40
+ @c = ConfigLogic::TreeCache.new(CONFIG_DIR, :multiplexers => [{:name => :multi, :selector => 1, :inputs => {1 => :config1, 2 => :config2, 3 => :config3}}])
41
+ multiplexed_result = {"key1"=>{"nestedkey1"=>1, "nestedkey2"=>2, "nestedkey3"=>3},
42
+ "key2"=>{"nestedkey4"=>4, "nestedkey5"=>5, "nestedkey6"=>6},
43
+ "key3"=>{"nestedkey7"=>7, "nestedkey8"=>8, "nestedkey9"=>9}}
44
+ @c[:dir1][:multi].to_hash.should == multiplexed_result
45
+ @c[:dir1].keys.size.should == 1
46
+ @c[:multi].to_hash.should == multiplexed_result
47
+ @c.keys.size.should == 3
48
+ end
49
+
50
+ it 'should return node at any path' do
51
+ @c[:dir1, :config1, :key1, :nestedkey1].should == 1
52
+ @c[:dir1].should be_a_kind_of(Hash)
53
+ @c[:dir1, :config1, :key1].should be_a_kind_of(Hash)
54
+ end
55
+
56
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: config_logic
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Alex Skryl
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-08-17 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 2.2.2
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: buffered_logger
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 0.1.2
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ description: An intuitive configuration access layer
39
+ email: rut216@gmail.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ files:
47
+ - lib/config_logic.rb
48
+ - lib/config_logic/logger.rb
49
+ - lib/config_logic/config_logic.rb
50
+ - lib/config_logic/logic_element.rb
51
+ - lib/config_logic/cache.rb
52
+ - lib/config_logic/tree_cache.rb
53
+ - lib/config_logic/file_cache.rb
54
+ - lib/config_logic/multiplexer.rb
55
+ - lib/config_logic/core_ext/array.rb
56
+ - lib/config_logic/core_ext/class.rb
57
+ - lib/config_logic/overlay.rb
58
+ - spec/cache_spec.rb
59
+ - spec/file_cache_spec.rb
60
+ - spec/logic_element_spec.rb
61
+ - spec/multiplexer_spec.rb
62
+ - spec/overlay_spec.rb
63
+ - spec/config_logic_spec.rb
64
+ - spec/spec_helper.rb
65
+ - spec/tree_cache_spec.rb
66
+ - README.rdoc
67
+ - TODO
68
+ has_rdoc: true
69
+ homepage: http://github.com/skryl
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options: []
74
+
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.6.2
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: A configuration access layer for Ruby/Rails applications
96
+ test_files: []
97
+