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.
- data/CHANGELOG +2 -0
- data/Manifest +10 -0
- data/Rakefile +21 -0
- data/configgy.gemspec +30 -0
- data/lib/configgy.rb +13 -0
- data/lib/configgy/config_map.rb +141 -0
- data/lib/configgy/config_parser.rb +20 -0
- data/lib/treetop/configgy.rb +1617 -0
- data/lib/treetop/configgy.treetop +177 -0
- data/spec/config_map_spec.rb +161 -0
- data/spec/config_parser_spec.rb +283 -0
- metadata +75 -0
@@ -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
|