configgy 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,177 @@
1
+ grammar Treetop_Configgy
2
+ rule root
3
+ d:declaration* whitespace* {
4
+ def apply(config_map, parser)
5
+ d.elements.each { |node| node.apply(config_map, parser) }
6
+ end
7
+ }
8
+ end
9
+
10
+ rule declaration
11
+ whitespace* d:(include_file / assignment / toggle / section) {
12
+ def apply(config_map, parser)
13
+ d.apply(config_map, parser)
14
+ end
15
+ }
16
+ end
17
+
18
+ rule assignment
19
+ identifier whitespace* op:("=" / "?=") whitespace* value {
20
+ def apply(config_map, parser)
21
+ key = identifier.text_value
22
+ if (op.text_value == "=") or !config_map.has_key?(key)
23
+ config_map[key] = value.to_value(config_map)
24
+ end
25
+ end
26
+ }
27
+ end
28
+
29
+ rule toggle
30
+ identifier whitespace* true_false {
31
+ def apply(config_map, parser)
32
+ key = identifier.text_value
33
+ config_map[key] = true_false.to_value
34
+ end
35
+ }
36
+ end
37
+
38
+ rule value
39
+ number / string / true_false / string_list
40
+ end
41
+
42
+ rule whitespace
43
+ (" " / "\t" / "\n" / "#" [^\n]* "\n")
44
+ end
45
+
46
+ rule number
47
+ "-"? [0-9]+ decimal:("." [0-9]+)? {
48
+ def to_value(config_map=nil)
49
+ decimal.text_value.empty? ? text_value.to_i : text_value.to_f
50
+ end
51
+ }
52
+ end
53
+
54
+ rule string
55
+ "\"" raw:string_innards* "\"" {
56
+ def to_value(config_map=nil)
57
+ rv = raw.elements.inject("") { |total, segment| total + segment.unquoted }
58
+ config_map ? config_map.interpolate(rv) : rv
59
+ end
60
+ }
61
+ end
62
+
63
+ rule string_innards
64
+ [^\\\"]+ {
65
+ def unquoted
66
+ text_value
67
+ end
68
+ } / "\\" quoted_entity {
69
+ def unquoted
70
+ quoted_entity.unquoted
71
+ end
72
+ }
73
+ end
74
+
75
+ rule quoted_entity
76
+ [^ux] {
77
+ def unquoted
78
+ case text_value
79
+ when "r" then "\r"
80
+ when "n" then "\n"
81
+ when "t" then "\t"
82
+ when "$" then "\\$"
83
+ else text_value
84
+ end
85
+ end
86
+ } / "u" digits:(hex_digit hex_digit hex_digit hex_digit) {
87
+ def unquoted
88
+ [ digits.text_value.to_i(16) ].pack("U")
89
+ end
90
+ } / "x" digits:(hex_digit hex_digit) {
91
+ def unquoted
92
+ digits.text_value.to_i(16).chr
93
+ end
94
+ }
95
+ end
96
+
97
+ rule hex_digit
98
+ [0-9a-fA-F]
99
+ end
100
+
101
+ rule true_false
102
+ "true" {
103
+ def to_value(config_map=nil)
104
+ true
105
+ end
106
+ } / "on" {
107
+ def to_value(config_map=nil)
108
+ true
109
+ end
110
+ } / "false" {
111
+ def to_value(config_map=nil)
112
+ false
113
+ end
114
+ } / "off" {
115
+ def to_value(config_map=nil)
116
+ false
117
+ end
118
+ }
119
+ end
120
+
121
+ rule string_list
122
+ "[" whitespace* list:(item:(string / number) (whitespace* ",")? whitespace*)* "]" {
123
+ def to_value(config_map=nil)
124
+ list.elements.map { |e| e.item.to_value(config_map) }
125
+ end
126
+ }
127
+ end
128
+
129
+ rule identifier
130
+ identifier_token ("\." identifier_token)*
131
+ end
132
+
133
+ rule identifier_token
134
+ ([\da-zA-Z] [-\da-zA-Z_]*)
135
+ end
136
+
137
+ rule section
138
+ identifier_token whitespace* attribute_list:("(" whitespace* attributes:(tag_attribute whitespace*)* ")")? whitespace* "{" root "}" {
139
+ def apply(config_map, parser)
140
+ new_name = identifier_token.text_value
141
+ nested_config_map = Configgy::ConfigMap.new(config_map.root, config_map.name == "" ? new_name : config_map.name + "." + new_name)
142
+ if attribute_list.elements
143
+ attribute_list.attributes.elements.map { |e| e.tag_attribute }.each do |attr|
144
+ case attr.name.text_value
145
+ when "inherit"
146
+ v = attr.value.to_value
147
+ if config_map[v].instance_of?(Configgy::ConfigMap)
148
+ nested_config_map.inherit_from = config_map[v]
149
+ elsif config_map.root
150
+ config_map.root[v] = Configgy::ConfigMap.new(config_map.root, v) unless config_map.root.has_key?(v)
151
+ if config_map.root[v].instance_of?(Configgy::ConfigMap)
152
+ nested_config_map.inherit_from = config_map.root[v]
153
+ else
154
+ raise ConfigException("can only inherit from blocks")
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ config_map[new_name] = nested_config_map
161
+ root.apply(nested_config_map, parser)
162
+ end
163
+ }
164
+ end
165
+
166
+ rule tag_attribute
167
+ name:"inherit" "=" value:string
168
+ end
169
+
170
+ rule include_file
171
+ "include" whitespace* string {
172
+ def apply(config_map, parser)
173
+ parser.load_file(string.to_value, config_map)
174
+ end
175
+ }
176
+ end
177
+ end
@@ -0,0 +1,161 @@
1
+ $LOAD_PATH << "lib"
2
+
3
+ require 'rubygems'
4
+ require 'configgy'
5
+
6
+ describe "ConfigMap" do
7
+ before do
8
+ @map = Configgy::ConfigMap.new(nil, "root")
9
+ end
10
+
11
+ it "sets values" do
12
+ @map.inspect.should == '{root: }'
13
+ @map[:name] = "Communist"
14
+ @map.inspect.should == '{root: name="Communist"}'
15
+ @map[:age] = 8
16
+ @map.inspect.should == '{root: age=8 name="Communist"}'
17
+ @map[:age] = 19
18
+ @map.inspect.should == '{root: age=19 name="Communist"}'
19
+ @map[:sleepy] = true
20
+ @map.inspect.should == '{root: age=19 name="Communist" sleepy=true}'
21
+ end
22
+
23
+ it "gets values" do
24
+ @map[:name] = "Communist"
25
+ @map[:age] = 8
26
+ @map[:sleepy] = true
27
+ @map[:money] = 1900500400300
28
+ @map[:name].should == "Communist"
29
+ @map[:age].should == 8
30
+ @map[:unknown].should == nil
31
+ @map[:money].should == 1900500400300
32
+ @map[:sleepy].should == true
33
+ end
34
+
35
+ it "sets compound values" do
36
+ @map[:name] = "Communist"
37
+ @map[:age] = 8
38
+ @map[:disposition] = "fighter"
39
+ @map["diet.food"] = "Meow Mix"
40
+ @map["diet.liquid"] = "water"
41
+ @map[:data] = "\r\r"
42
+ @map.inspect.should == '{root: age=8 data="\\r\\r" diet={root.diet: food="Meow Mix" liquid="water"} disposition="fighter" name="Communist"}'
43
+ end
44
+
45
+ it "knows what it contains" do
46
+ @map[:name] = "Communist"
47
+ @map[:age] = 8
48
+ @map["diet.food"] = "Meow Mix"
49
+ @map["diet.liquid"] = "water"
50
+ @map["age"].should_not == nil
51
+ @map["unknown"].should == nil
52
+ @map["diet.food"].should_not == nil
53
+ @map["diet.gas"].should == nil
54
+ end
55
+
56
+ it "auto-vivifies" do
57
+ @map["a.b.c"] = 8
58
+ @map.inspect.should == "{root: a={root.a: b={root.a.b: c=8}}}"
59
+ @map["a.d.x"].should == nil
60
+ @map.inspect.should == "{root: a={root.a: b={root.a.b: c=8}}}"
61
+ end
62
+
63
+ it "compares with ==" do
64
+ @map[:name] = "Communist"
65
+ @map[:age] = 8
66
+ @map["diet.food.dry"] = "Meow Mix"
67
+ map2 = Configgy::ConfigMap.new(nil, "root")
68
+ map2[:name] = "Communist"
69
+ map2[:age] = 8
70
+ map2["diet.food.dry"] = "Meow Mix"
71
+ @map.should == map2
72
+ end
73
+
74
+ it "removes values" do
75
+ @map[:name] = "Communist"
76
+ @map[:age] = 8
77
+ @map["diet.food"] = "Meow Mix"
78
+ @map["diet.liquid"] = "water"
79
+ @map.inspect.should == '{root: age=8 diet={root.diet: food="Meow Mix" liquid="water"} name="Communist"}'
80
+ @map.delete("diet.food").should == "Meow Mix"
81
+ @map.delete("diet.food").should == nil
82
+ @map.inspect.should == '{root: age=8 diet={root.diet: liquid="water"} name="Communist"}'
83
+ @map.delete("age").should == 8
84
+ @map.delete("age").should == nil
85
+ @map.inspect.should == '{root: diet={root.diet: liquid="water"} name="Communist"}'
86
+ end
87
+
88
+ it "converts to a map" do
89
+ @map[:name] = "Communist"
90
+ @map[:age] = 8
91
+ @map["diet.food"] = "Meow Mix"
92
+ @map["diet.liquid"] = "water"
93
+ @map.to_map.should == { "name" => "Communist", "age" => 8, "diet" => { "food" => "Meow Mix", "liquid" => "water" } }
94
+ end
95
+
96
+ it "dupes" do
97
+ @map[:name] = "Communist"
98
+ @map[:age] = 8
99
+ @map["diet.food"] = "Meow Mix"
100
+ @map["diet.liquid"] = "water"
101
+ t = @map.dup
102
+ @map.inspect.should == '{root: age=8 diet={root.diet: food="Meow Mix" liquid="water"} name="Communist"}'
103
+ t.inspect.should == '{root: age=8 diet={root.diet: food="Meow Mix" liquid="water"} name="Communist"}'
104
+
105
+ @map["diet.food"] = "fish"
106
+ @map[:age] = 9
107
+ @map.inspect.should == '{root: age=9 diet={root.diet: food="fish" liquid="water"} name="Communist"}'
108
+ t.inspect.should == '{root: age=8 diet={root.diet: food="Meow Mix" liquid="water"} name="Communist"}'
109
+ end
110
+
111
+ it "dupes with inheritance" do
112
+ @map[:name] = "Communist"
113
+ @map[:age] = 8
114
+ ancestor = Configgy::ConfigMap.new(nil, "ancestor")
115
+ ancestor[:age] = 12
116
+ ancestor[:disposition] = "hungry"
117
+ @map.inherit_from = ancestor
118
+
119
+ t = @map.dup
120
+ @map.inspect.should == '{root (inherit=ancestor): age=8 name="Communist"}'
121
+ t.inspect.should == '{root: age=8 disposition="hungry" name="Communist"}'
122
+ end
123
+
124
+ it "merges a map" do
125
+ @map[:name] = "Communist"
126
+ @map[:age] = 8
127
+ new_map = @map.merge(:age => 20, :weight => 10)
128
+ @map[:age].should == 8
129
+ new_map[:age].should == 20
130
+ new_map[:name].should == "Communist"
131
+ new_map[:weight].should == 10
132
+ end
133
+
134
+ it "creates a config string" do
135
+ @map[:name] = "Sparky"
136
+ @map[:age] = 10
137
+ @map[:diet] = "poor"
138
+ @map["muffy.name"] = "Muffy"
139
+ @map["muffy.age"] = 11
140
+ @map["fido.name"] = "Fido"
141
+ @map["fido.age"] = 5
142
+ @map["fido.roger.name"] = "Roger"
143
+ @map["fido.roger"].inherit_from = @map[:muffy]
144
+ @map.to_config_string.should == <<-END
145
+ age = 10
146
+ diet = "poor"
147
+ fido {
148
+ age = 5
149
+ name = "Fido"
150
+ roger (inherit="root.muffy") {
151
+ name = "Roger"
152
+ }
153
+ }
154
+ muffy {
155
+ age = 11
156
+ name = "Muffy"
157
+ }
158
+ name = "Sparky"
159
+ END
160
+ end
161
+ end
@@ -0,0 +1,283 @@
1
+ $LOAD_PATH << "lib"
2
+
3
+ require 'rubygems'
4
+ require 'configgy'
5
+
6
+ describe "ConfigParser" do
7
+ before do
8
+ @parser = Configgy::ConfigParser.new
9
+ end
10
+
11
+ def parse(str)
12
+ @parser.root = :root
13
+ @parser.read(str)
14
+ end
15
+
16
+ def unquoted(str)
17
+ @parser.root = :string
18
+ @parser.parse(str).to_value
19
+ end
20
+
21
+ it "unquotes strings" do
22
+ unquoted('"nothing"').should == "nothing"
23
+ unquoted('"name\\tvalue\\t\\x44b\\xfcllet?"').should == "name\tvalue\tDb\xfcllet?"
24
+ unquoted('"name\\tvalue\\t\\u20acb\\u00fcllet?\\u20ac"').should == "name\tvalue\t\342\202\254b\303\274llet?\342\202\254"
25
+ unquoted('"she said \\"hello\\""').should == "she said \"hello\""
26
+ unquoted('"\\\\backslash"').should == "\\backslash"
27
+ unquoted('"real\\$dollar"').should == "real\\$dollar"
28
+ unquoted('"silly\\/quote"').should == "silly/quote"
29
+ end
30
+
31
+ it "parses assignment" do
32
+ parse("weight = 48").inspect.should == "{: weight=48}"
33
+ end
34
+
35
+ it "parses conditional assignment" do
36
+ parse("weight = 48\n weight ?= 16").inspect.should == "{: weight=48}"
37
+ end
38
+
39
+ it "ignores comments" do
40
+ parse("# doing stuff\n weight = 48\n # more comments\n").inspect.should == "{: weight=48}"
41
+ end
42
+
43
+ it "parses booleans" do
44
+ parse("wine off\nwhiskey on\n").inspect.should == '{: whiskey=true wine=false}'
45
+ parse("wine = false\nwhiskey = on\n").inspect.should == '{: whiskey=true wine=false}'
46
+ end
47
+
48
+ it "handles string lists" do
49
+ b = parse('cats = ["Commie", "Buttons", "Sockington"]')
50
+ b[:cats].should == [ 'Commie', 'Buttons', 'Sockington' ]
51
+ end
52
+
53
+ it "handles number lists" do
54
+ b = parse('widths = [ 90, 100 ]')
55
+ b[:widths].should == [ 90, 100 ]
56
+ end
57
+
58
+ it "handles whole numbers as identifiers" do
59
+ parse("1 = 2").inspect.should == "{: 1=2}"
60
+ parse("1 = 2\n 3 = 4").inspect.should == "{: 1=2 3=4}"
61
+ parse("20 = 1").inspect.should == "{: 20=1}"
62
+ parse("2 = \"skeletor\"").inspect.should == "{: 2=\"skeletor\"}"
63
+ parse("4 = \"hostname:1234\"").inspect.should == "{: 4=\"hostname:1234\"}"
64
+ parse("4 = [\"a\", \"b\"]").inspect.should == "{: 4=[\"a\", \"b\"]}"
65
+ end
66
+
67
+ it "handles lists without comma separators" do
68
+ b = parse('cats = ["Commie" "Buttons" "Sockington"]')
69
+ b[:cats].should == [ 'Commie', 'Buttons', 'Sockington' ]
70
+ end
71
+
72
+ it "handles lists with a trailing comma" do
73
+ b = parse('cats = ["Commie", "Buttons", "Sockington",]')
74
+ b[:cats].should == [ 'Commie', 'Buttons', 'Sockington' ]
75
+ end
76
+
77
+ it "handles nested blocks" do
78
+ parse('''alpha="hello"
79
+ beta {
80
+ gamma=23
81
+ }
82
+ ''').inspect.should == '{: alpha="hello" beta={beta: gamma=23}}'
83
+ parse('''alpha="hello"
84
+ beta {
85
+ gamma=23
86
+ toaster on
87
+ }
88
+ ''').inspect.should == '{: alpha="hello" beta={beta: gamma=23 toaster=true}}'
89
+ parse('''home {
90
+ states = ["California", "Tennessee", "Idaho"]
91
+ regions = ["pacific", "southeast", "northwest"]
92
+ }
93
+ ''').inspect.should == '{: home={home: regions=["pacific", "southeast", "northwest"] states=["California", "Tennessee", "Idaho"]}}'
94
+ end
95
+
96
+ it "handles items after closing a block" do
97
+ parse('''alpha = 17
98
+ inner {
99
+ name = "foo"
100
+ further {
101
+ age = 500
102
+ }
103
+ zipcode = 99999
104
+ }
105
+ beta = 19
106
+ ''').inspect.should == '{: alpha=17 beta=19 inner={inner: further={inner.further: age=500} name="foo" zipcode=99999}}'
107
+ end
108
+
109
+ it "imports files" do
110
+ def @parser.load_file(filename, map=nil)
111
+ if filename == "test1"
112
+ read("staff=\"weird skull\"\n", map)
113
+ end
114
+ end
115
+
116
+ parse('''toplevel = "skeletor"
117
+ inner {
118
+ include "test1"
119
+ home = "greyskull"
120
+ }
121
+ ''').inspect.should == '{: inner={inner: home="greyskull" staff="weird skull"} toplevel="skeletor"}'
122
+ end
123
+
124
+ it "imports multiple, nested files" do
125
+ def @parser.load_file(filename, map=nil)
126
+ if filename == "test2"
127
+ data = '''
128
+ inner {
129
+ cat="meow"
130
+ include "test3"
131
+ dog ?= "blah"
132
+ }
133
+ '''
134
+ end
135
+ if filename == "test3"
136
+ data = '''
137
+ dog="bark"
138
+ cat ?= "blah"
139
+ '''
140
+ end
141
+ if filename == "test4"
142
+ data = '''
143
+ cow = "moo"
144
+ '''
145
+ end
146
+ read(data, map)
147
+ end
148
+
149
+ parse('''toplevel = "hat"
150
+ include "test2"
151
+ include "test4"
152
+ ''').inspect.should == '{: cow="moo" inner={inner: cat="meow" dog="bark"} toplevel="hat"}'
153
+ end
154
+
155
+ it "interpolate strings" do
156
+ parse('horse="ed" word="sch$(horse)ule"').inspect.should == '{: horse="ed" word="schedule"}'
157
+ parse('lastname="Columbo" firstname="Bob" fullname="$(firstname) $(lastname)"').inspect.should ==
158
+ '{: firstname="Bob" fullname="Bob Columbo" lastname="Columbo"}'
159
+ end
160
+
161
+ it "doesn't interpolate unassigned strings" do
162
+ parse('horse="ed" word="sch\\$(horse)ule"').inspect.should == '{: horse="ed" word="sch$(horse)ule"}'
163
+ end
164
+
165
+ it "interpolates nested references" do
166
+ parse('''horse="ed"
167
+ alpha {
168
+ horse="frank"
169
+ drink="$(horse)ly"
170
+ beta {
171
+ word="sch$(horse)ule"
172
+ greeting="$(alpha.drink) yours"
173
+ }
174
+ }
175
+ ''').inspect.should == '{: alpha={alpha: beta={alpha.beta: greeting="frankly yours" word="schedule"} drink="frankly" horse="frank"} horse="ed"}'
176
+ end
177
+
178
+ it "interpolates environment vars" do
179
+ ENV["GOOBER"] = "sparky"
180
+ parse('user="$(GOOBER)"').inspect.should != '{: user="sparky"}'
181
+ end
182
+
183
+ it "inherits" do
184
+ p = parse('''daemon {
185
+ ulimit_fd = 32768
186
+ uid = 16
187
+ }
188
+
189
+ upp (inherit="daemon") {
190
+ uid = 23
191
+ }
192
+ ''')
193
+ p.inspect.should == '{: daemon={daemon: uid=16 ulimit_fd=32768} upp={upp (inherit=daemon): uid=23}}'
194
+ p["upp.ulimit_fd"].should == 32768
195
+ p["upp.uid"].should == 23
196
+ end
197
+
198
+ it "uses parent scope for inherit lookups" do
199
+ p = parse('''daemon {
200
+ inner {
201
+ common {
202
+ ulimit_fd = 32768
203
+ uid = 16
204
+ }
205
+ upp (inherit="common") {
206
+ uid = 23
207
+ }
208
+ slac (inherit="daemon.inner.common") {
209
+ }
210
+ }
211
+ }
212
+ ''')
213
+ p["daemon.inner.upp.ulimit_fd"].should == 32768
214
+ p["daemon.inner.upp.uid"].should == 23
215
+ p["daemon.inner.slac.uid"].should == 16
216
+ end
217
+
218
+ it "handles block names with dashes" do
219
+ parse('''horse="ed"
220
+ daemon {
221
+ base-dat {
222
+ ulimit_fd = 32768
223
+ }
224
+ }
225
+ ''').inspect.should == '{: daemon={daemon: base-dat={daemon.base-dat: ulimit_fd=32768}} horse="ed"}'
226
+ end
227
+
228
+ it "handles an assignment after a block" do
229
+ parse('''daemon {
230
+ base {
231
+ ulimit_fd = 32768
232
+ }
233
+ useless = 3
234
+ }
235
+ ''').inspect.should == '{: daemon={daemon: base={daemon.base: ulimit_fd=32768} useless=3}}'
236
+ end
237
+
238
+ it "handles two consecutive groups" do
239
+ parse('''daemon {
240
+ useless = 3
241
+ }
242
+
243
+ upp (inherit="daemon") {
244
+ uid = 16
245
+ }
246
+ ''').inspect.should == '{: daemon={daemon: useless=3} upp={upp (inherit=daemon): uid=16}}'
247
+ end
248
+
249
+ it "handles a complex case" do
250
+ p = parse('''daemon {
251
+ useless = 3
252
+ base {
253
+ ulimit_fd = 32768
254
+ }
255
+ }
256
+
257
+ upp (inherit="daemon.base") {
258
+ uid = 16
259
+ alpha (inherit="upp") {
260
+ name="alpha"
261
+ }
262
+ beta (inherit="daemon") {
263
+ name="beta"
264
+ }
265
+ some_int = 1
266
+ }
267
+ ''')
268
+ p.inspect.should == ('{: daemon={daemon: base={daemon.base: ulimit_fd=32768} useless=3} upp={upp (inherit=daemon.base): ' +
269
+ 'alpha={upp.alpha (inherit=upp): name="alpha"} beta={upp.beta (inherit=daemon): name="beta"} some_int=1 uid=16}}')
270
+ p["daemon.useless"].should == 3
271
+ p["upp.uid"].should == 16
272
+ p["upp.ulimit_fd"].should == 32768
273
+ p["upp.name"].should == nil
274
+ p["upp.alpha.name"].should == "alpha"
275
+ p["upp.beta.name"].should == "beta"
276
+ p["upp.alpha.ulimit_fd"].should == 32768
277
+ p["upp.beta.ulimit_fd"].should == nil
278
+ p["upp.alpha.useless"].should == nil
279
+ p["upp.beta.useless"].should == 3
280
+ p["upp.some_int"].should == 1
281
+ end
282
+
283
+ end