config_logic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+