lab419-config 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+
2
+ require 'lab419/config/reader'
@@ -0,0 +1,58 @@
1
+ require 'forwardable'
2
+
3
+ module Lab419
4
+ module Config
5
+ class Data
6
+ extend Forwardable
7
+ def_delegators :@store, :empty?
8
+
9
+ def [] key
10
+ @store[ key.to_sym ]
11
+ end
12
+ def []= key, val
13
+ case key
14
+ when String
15
+ store_value val, key.split(".")
16
+ when Symbol
17
+ @store[ key ] = val
18
+ end
19
+ end
20
+ private
21
+ def initialize params={}
22
+ @store = {}
23
+ @overrideable = params[:overrideable]
24
+ end # def initialize
25
+
26
+ def method_missing name, *args, &blk
27
+ if blk.nil? && args.size == 1 && /=\z/ === name then
28
+ return self[ name[0..-2] ] = args.first
29
+ end
30
+
31
+ return super( name, *args, &blk ) unless args.empty?
32
+ @store.fetch( name ){ @store[ name ] = self.class.new( overrideable: @overrideable ) }.
33
+ tap do | ele |
34
+ blk[ ele ] if blk
35
+ end
36
+ end
37
+ def store_value val, keys
38
+ # TODO: This needs HEAVY refactoring
39
+ last_key = keys.pop
40
+ child = parent = @store
41
+ keys.each do | key |
42
+ child = parent[ key.to_sym ]
43
+ case child
44
+ when self.class
45
+ parent = child
46
+ when nil
47
+ parent[ key.to_sym ] = self.class::new
48
+ child = parent = parent[ key.to_sym ]
49
+ else
50
+ raise ArgumentError, "cannot reassign to #{child}"
51
+ end
52
+ end
53
+ raise ArgumentError, "cannot reassign to #{child[last_key]}" if child[last_key]
54
+ child[ last_key.to_sym ] = val
55
+ end
56
+ end # class Data
57
+ end # module Config
58
+ end # module Lab419
@@ -0,0 +1,81 @@
1
+
2
+ module Lab419
3
+ module Config
4
+ IllegalMonitorState = Class::new RuntimeError
5
+
6
+ module Parser
7
+
8
+ SyntaxError = Class::new RuntimeError
9
+
10
+ def parse params={}
11
+ check_source_setup
12
+ @params = params
13
+ @parsed = true
14
+ loop do
15
+ @source.next while @source.current && @source.current =~ comment
16
+ return self if @source.eos?
17
+ key, value = @source.current.split separator_rgx, 2
18
+ raise SyntaxError, "No = found in line \"#{key}\"" unless value
19
+ @config[key.strip] = parse_value value, params
20
+ @source.next
21
+ end
22
+ end
23
+
24
+ private
25
+ def default_handler value
26
+ case value
27
+ when /\A\s*"/
28
+ # TODO: Implement a real string parser
29
+ value.sub(/.*?"/,"").reverse.sub(/\s*"/,"").reverse
30
+ when /\A\s*\[\s*(.*)/
31
+ parse_array $1
32
+ when /\A\s*\{\s*(.*)/
33
+ parse_hash $1
34
+ else
35
+ value.strip
36
+ end
37
+ end
38
+ # HMMM: This might be overkill?
39
+ def invocation handler
40
+ case handler
41
+ when Symbol
42
+ send handler
43
+ when Array
44
+ handler.next.send( @source, *handler )
45
+ when Proc
46
+ handler[ @source ]
47
+ else
48
+ raise IllegalHandlerSpec, "#{handler} is neither a sym, array or proc"
49
+ end
50
+ end
51
+ def parse_array current_element
52
+ result = current_element.strip.empty? ? [] : [ parse_value( current_element ) ]
53
+ while line = @source.next do
54
+ if line =~ /\A\s*\]/ then
55
+ return result
56
+ end
57
+ result << parse_value( line )
58
+ end
59
+ raise SyntaxError, "missing ]"
60
+ end
61
+ def parse_hash current_pair
62
+ raise SyntaxError, "not yet implemented"
63
+ end
64
+ def parse_value value, params={}
65
+ @handlers.update( params.fetch( :handlers, {} ) ).
66
+ each do | trigger_rgx, handler |
67
+ if value =~ trigger_rgx then
68
+ return invocation( handler )
69
+ end
70
+ end
71
+ return default_handler( value )
72
+ end
73
+
74
+ def check_source_setup
75
+ raise Lab419::Config::IllegalMonitorState, "source not set up, either call new with a string source or set the source up with
76
+ one of the with_* methods" unless @source
77
+
78
+ end
79
+ end # module Parser
80
+ end # module Config
81
+ end # module Lab419
@@ -0,0 +1,80 @@
1
+
2
+ require 'lab419/config/data'
3
+ require 'lab419/config/source'
4
+ require 'lab419/config/parser'
5
+
6
+ module Lab419
7
+ module Config
8
+ IllegalHandlerSpec = Class::new RuntimeError
9
+ DataError = Class::new RuntimeError
10
+ # TODO: code @handler setters, @handler is a dead variable right now
11
+ # no specs imply its usage yet
12
+ class Reader
13
+ include Parser
14
+
15
+ attr_reader :config, :name
16
+
17
+ def parsed?; @parsed end
18
+
19
+ def handle trigger_rgx, message_name = nil, &blk
20
+ if message_name && blk || blk.nil? && message_name.nil?
21
+ raise ArgumentError, "please specify a message_name *or* a block, not both or none"
22
+ end
23
+ @handlers.update( trigger_rgx => message_name || blk )
24
+ end
25
+ def with_file filename, &blk
26
+ File.open( filename ) do | f |
27
+ init_source f, filename
28
+ end
29
+ blk[self] if blk
30
+ self
31
+ rescue Errno::ENOENT
32
+ raise DataError, "hmm file: '#{filename}' does not seem to be readable!"
33
+ end
34
+
35
+ def with_string string, &blk
36
+ init_source string
37
+ blk[self] if blk
38
+ self
39
+ end
40
+
41
+ private
42
+ def comment
43
+ %r{\A\s*#|\A\s*\z}
44
+ end
45
+ def separator_rgx
46
+ @params.fetch( :separator, %r{\s*=} )
47
+ end
48
+ def initialize input=nil
49
+ @config = Data::new
50
+ @params = {}
51
+ @source = nil
52
+ @parsed = false
53
+ init_source input if input
54
+ init_handlers
55
+ end
56
+
57
+ def init_handlers
58
+ @handlers={}
59
+ end
60
+
61
+ def init_source input, name=nil
62
+ source =
63
+ case input
64
+ when String
65
+ input.split( %r{\r?\n} )
66
+ when Array
67
+ input
68
+ when IO
69
+ input.readlines.map( &:chomp )
70
+ end
71
+ @name = name
72
+ @source = Lab419::Config::Source::new( source, name )
73
+ end # def init_source
74
+
75
+ def syntax_error msg=""
76
+ raise SyntaxError, msg
77
+ end
78
+ end # class Reader
79
+ end # module Config
80
+ end # module Lab419
@@ -0,0 +1,56 @@
1
+
2
+ module Lab419
3
+ module Config
4
+ #
5
+ # Source is a wrapper arround an array of lines, adding the ability to
6
+ # store the current line_number #line_nb and the name of the source #name.
7
+ # It implements a minimal interface, allowing to advance line by line via
8
+ # the #next method, accessing the current element via #current and testing
9
+ # for the end of the array via the #eos? method
10
+ #
11
+ class Source
12
+
13
+ attr_reader :name
14
+
15
+ #
16
+ # Readers
17
+ #
18
+
19
+ #
20
+ # accesses the current element
21
+ def current
22
+ @lines[ @cursor ]
23
+ end
24
+ #
25
+ # true iff #next has advanced over the last line
26
+ def eos?
27
+ @lines[ @cursor ].nil?
28
+ end
29
+ #
30
+ # current line number, starting with 1, incremented by each
31
+ # call of #next, up to a maximum of the size of the underlying
32
+ # array.
33
+ def line_nb
34
+ [ @cursor + 1, @lines.length ].min
35
+ end
36
+
37
+ # This is the *only* modifier of Source, it
38
+ # advances to the next line of the underlying array.
39
+ # The operation is irreversible.
40
+ def next
41
+ @cursor += 1
42
+ current
43
+ end
44
+
45
+ def to_debug
46
+ "#{@lines[@cursor-1]}:>#{current}<:#{@lines[@cursor+1]}"
47
+ end
48
+ private
49
+ def initialize lines, name=nil
50
+ @lines = lines
51
+ @cursor = 0
52
+ @name = name || "<annon>"
53
+ end
54
+ end # class Source
55
+ end # module Config
56
+ end # module Lab419
@@ -0,0 +1,68 @@
1
+
2
+ $:.unshift File.join( File.dirname( __FILE__ ), %w{ .. lib } )
3
+ require 'lab419/config/data'
4
+
5
+ describe Lab419::Config::Data do
6
+ before :each do
7
+ @data = Lab419::Config::Data::new
8
+ end
9
+
10
+ it "shall not have any data [Symbol]" do
11
+ @data[:x].should be_nil
12
+ end
13
+ it "shall not have any data Method" do
14
+ @data.x.should be_empty
15
+ end
16
+ it "shall recoginze missing methods with params" do
17
+ lambda{ @data.x(42) }.should raise_error( NoMethodError )
18
+ end # it "shall recoginze all other method formats as missing methods"
19
+ it "shall recoginze missing methods with params and blocks" do
20
+ lambda{ @data.x(42){42} }.should raise_error( NoMethodError )
21
+ end # it "shall recoginze all other method formats as missing methods"
22
+ it "shall not have any data [String]" do
23
+ @data["x"].should be_nil
24
+ end
25
+
26
+ it "shall store values for simple keys" do
27
+ @data["x"] = 42
28
+ @data["x"].should == 42
29
+ end
30
+ it "shall store values for simple keys being Str/Sym agnostic" do
31
+ @data["x"] = 42
32
+ @data[:x].should == 42
33
+ end
34
+
35
+ describe "compund keys" do
36
+ before :each do
37
+ @data["a.b.c"] = 41
38
+ end # before :each
39
+
40
+ it "shall intercept the compound key" do
41
+ @data['a.b.c'].should be_nil
42
+ end # it "shall intercept the compound key"
43
+
44
+ it "shall give data objects for intermediates" do
45
+ @data['a'].should be_kind_of( @data.class )
46
+ end # it "shall give data objects for intermediates"
47
+
48
+ it "shall give scalars for final objects" do
49
+ @data['a'].b[:c].should == 41
50
+ end # it "shall give scalars for final objects"
51
+
52
+ end # describe "compund keys"
53
+
54
+ describe "autoassignment via method" do
55
+ before :each do
56
+ @data.x = 42
57
+ @data.alpha.beta do | d |
58
+ d.gamma = 41
59
+ end
60
+ end
61
+ it "should have the key" do
62
+ @data[:x].should == 42
63
+ end
64
+ it "should have the compound key" do
65
+ @data[:alpha].beta.gamma.should == 41
66
+ end
67
+ end
68
+ end # describe Lab419::Config::Data
@@ -0,0 +1,133 @@
1
+
2
+ #require 'config_spec_helper'
3
+ $:.unshift File.join( File.dirname( __FILE__ ), %w{ .. lib } )
4
+ require 'lab419/config'
5
+
6
+ describe Lab419::Config::Reader do
7
+
8
+ before :all do
9
+ @cr = Lab419::Config::Reader::new %{
10
+ a=42
11
+ b = 43
12
+ c.d = alpha
13
+ e = " "
14
+ some.list = [ e1
15
+ e2
16
+ e3
17
+ ]
18
+ empty.list = [
19
+ ]
20
+ notha = [
21
+ 1
22
+ 2
23
+ 3
24
+ ]
25
+ }
26
+ @c = @cr.parse.config
27
+ end
28
+
29
+ describe "should contain correct values" do
30
+ describe "scalars" do
31
+ it "shall contain :a == 42" do
32
+ @c[:a].should == "42"
33
+ end
34
+ it "shall contain a == 42" do
35
+ @c["a"].should == "42"
36
+ end
37
+ it "shall contain .a == 42" do
38
+ @c.a.should == "42"
39
+ end
40
+ it "shall contain b" do
41
+ @c.b.should == "43"
42
+ end
43
+ it "shall contain subobjects" do
44
+ @c.c["d"].should == "alpha"
45
+ end
46
+ it "shall contain spaces" do
47
+ @c.e.should == " "
48
+ end
49
+ end
50
+ describe "arrays" do
51
+ it "should have e1 to e3" do
52
+ @c.some.list.should == %w{ e1 e2 e3 }
53
+ end
54
+ it "should have an empty list" do
55
+ @c[:empty].list.should be_empty
56
+ end # it "should have an empty list"
57
+ it "1,2,3" do
58
+ @c.notha.should == (1..3).map(&:to_s)
59
+ end # it "1,2,3"
60
+ end
61
+ end # describe "should contain correct values"
62
+ describe "can customize the assignment syntax" do
63
+ before :all do
64
+ @cr = Lab419::Config::Reader::new %{
65
+ a: 42
66
+ b : true
67
+ }
68
+ @d = @cr.parse separator: /\s*:/
69
+ end
70
+ it "shall see the values" do
71
+ @d.config.a.should == "42"
72
+ end # it "shall see the"
73
+ end # describe "can customize the assignment syntax"
74
+ describe "can use custom handlers" do
75
+ before :all do
76
+ @cr = Lab419::Config::Reader::new %w{
77
+ a = 42
78
+ b.c = nil
79
+ d = {
80
+ a, b,
81
+ c
82
+ }
83
+ }
84
+ end
85
+ end # describe "can use custom handlers"
86
+ end # describe Lab419::ConfigReader
87
+
88
+ def should_parse_correctly
89
+ @cr.config.a.should == '42'
90
+ end
91
+ describe "how to provide data" do
92
+ before :all do
93
+ File.open( "xxx", "w" ) do |f|
94
+ f.puts "a = 42"
95
+ end
96
+ end
97
+ before :each do
98
+ @cr = Lab419::Config::Reader::new
99
+ end
100
+
101
+ after :all do
102
+ File.unlink "xxx"
103
+ end
104
+
105
+ it "shall not parse without a source" do
106
+ lambda{ @cr.parse }.should raise_error( Lab419::Config::IllegalMonitorState )
107
+ end # it "shall not parse without a source"
108
+ it "shall parse with a string source" do
109
+ @cr.with_string( "a=42" ).parse
110
+ should_parse_correctly
111
+ end
112
+ it "shall parse with a file source" do
113
+ @cr.with_file "xxx"
114
+ @cr.parse
115
+ should_parse_correctly
116
+ end
117
+ it "shall have a correct name" do
118
+ @cr.with_file "xxx" do | r |
119
+ r.parse
120
+ r.config[:a].should == "42"
121
+ end
122
+ @cr.parsed?.should == true
123
+ end
124
+ it "shall have access to the name" do
125
+ @cr.with_file "xxx" do | r |
126
+ r.name.should == "xxx"
127
+ end # with_file "xxx"
128
+ end
129
+ it "shall raise a DataError if provided file does not exist" do
130
+ lambda{ @cr.with_file "yyy" }.should raise_error( Lab419::Config::DataError )
131
+ end # it "shall raise a DataError if provided file does not exist"
132
+ end
133
+