configgy 0.6

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.
@@ -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