lab419-config 0.1
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/Manifest +27 -0
- data/Rakefile +20 -0
- data/config.rb +7 -0
- data/lab419-config.gemspec +30 -0
- data/lib/doc/Lab419.html +156 -0
- data/lib/doc/Lab419/Config.html +184 -0
- data/lib/doc/Lab419/Config/Data.html +372 -0
- data/lib/doc/Lab419/Config/Parser.html +469 -0
- data/lib/doc/Lab419/Config/Reader.html +604 -0
- data/lib/doc/Lab419/Config/Source.html +429 -0
- data/lib/doc/created.rid +6 -0
- data/lib/doc/index.html +115 -0
- data/lib/doc/lab419/config/data_rb.html +54 -0
- data/lib/doc/lab419/config/parser_rb.html +52 -0
- data/lib/doc/lab419/config/reader_rb.html +58 -0
- data/lib/doc/lab419/config/source_rb.html +52 -0
- data/lib/doc/lab419/config_rb.html +54 -0
- data/lib/doc/rdoc.css +701 -0
- data/lib/lab419/config.rb +2 -0
- data/lib/lab419/config/data.rb +58 -0
- data/lib/lab419/config/parser.rb +81 -0
- data/lib/lab419/config/reader.rb +80 -0
- data/lib/lab419/config/source.rb +56 -0
- data/specs/config_data_spec.rb +68 -0
- data/specs/config_reader_spec.rb +133 -0
- data/specs/config_source_spec.rb +74 -0
- data/specs/custom_handler_spec.rb +40 -0
- metadata +109 -0
@@ -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
|
+
|