1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Handle ini config files.
18 # A configuration tree that can read and store data in ini format
22 # var config = new ConfigTree("config.ini")
23 # config["goo"] = "goo"
24 # config["foo.bar"] = "foobar"
25 # config["foo.baz"] = "foobaz"
27 # assert config.to_map.length == 3
31 # config = new ConfigTree("config.ini")
32 # assert config.has_key("foo.bar")
33 # assert config["foo.bar"] == "foobar"
37 # The ini file used to read/store data
40 init do if ini_file
.file_exists
then load
42 # Get the config value for `key`
44 # REQUIRE: `has_key(key)`
46 # var config = new ConfigTree("config.ini")
47 # assert config["goo"] == "goo"
48 # assert config["foo.bar"] == "foobar"
49 # assert config["foo.baz"] == "foobaz"
50 fun [](key
: String): String do
51 if not has_key
(key
) then
52 print
"error: config key `{key}` not found"
55 var node
= get_node
(key
).as(not null)
56 if node
.value
== null then
57 print
"error: config key `{key}` has no value"
60 return node
.value
.as(not null)
63 # Get the config values under `key`
65 # REQUIRE: `has_key(key)`
67 # var config = new ConfigTree("config.ini")
68 # var values = config.at("foo")
69 # assert values.has_key("bar")
70 # assert values.has_key("baz")
71 # assert not values.has_key("goo")
72 fun at
(key
: String): Map[String, String] do
73 if not has_key
(key
) then
74 print
"error: config key `{key}` not found"
77 var map
= new HashMap[String, String]
78 var node
= get_node
(key
).as(not null)
79 for k
, child
in node
.children
do
80 if child
.value
== null then continue
81 map
[k
] = child
.value
.to_s
86 # Set `value` at `key`
88 # var config = new ConfigTree("config.ini")
89 # assert config["foo.bar"] == "foobar"
90 # config["foo.bar"] = "baz"
91 # assert config["foo.bar"] == "baz"
92 fun []=(key
: String, value
: nullable String) do
96 # Is `key` in the config?
98 # var config = new ConfigTree("config.ini")
99 # assert config.has_key("goo")
100 # assert config.has_key("foo.bar")
101 # assert not config.has_key("zoo")
102 fun has_key
(key
: String): Bool do
103 var parts
= key
.split
(".").reversed
104 var node
= get_root
(parts
.pop
)
105 if node
== null then return false
106 while not parts
.is_empty
do
107 node
= node
.get_child
(parts
.pop
)
108 if node
== null then return false
113 # Get `self` as a Map of `key`, `value`
115 # var config = new ConfigTree("config.ini")
116 # var map = config.to_map
117 # assert map.has_key("goo")
118 # assert map.has_key("foo.bar")
119 # assert map.has_key("foo.baz")
120 # assert map.length == 3
121 fun to_map
: Map[String, String] do
122 var map
= new HashMap[String, String]
123 for node
in leaves
do
124 if node
.value
== null then continue
125 map
[node
.key
] = node
.value
.to_s
130 redef fun to_s
do return to_map
.join
(", ", ":")
132 redef fun write_to
(stream
) do
133 for node
in leaves
do
134 if node
.value
== null then continue
135 stream
.write
("{node.key}={node.value.to_s}\n")
139 # Reload config from file
140 # Done automatically at init
142 # Example with hierarchical ini file:
149 # str.write_to_file("config1.ini")
151 # var config = new ConfigTree("config1.ini")
152 # assert config["foo.bar"] == "foobar"
154 # Example with sections:
164 # str.write_to_file("config2.ini")
166 # config = new ConfigTree("config2.ini")
167 # assert config["foo.bar"] == "foobar"
168 # assert config["boo.bar"] == "boobar"
170 # Example with both hierarchy and section:
179 # baz.bar=gooboobazbar"""
180 # str.write_to_file("config3.ini")
182 # config = new ConfigTree("config3.ini")
183 # assert config["goo"] == "goo"
184 # assert config["foo.bar.baz"] == "foobarbaz"
185 # assert config["goo.boo.bar"] == "gooboobar"
186 # assert config["goo.boo.baz.bar"] == "gooboobazbar"
188 # Using the array notation
194 # str.write_to_file("config4.ini")
196 # config = new ConfigTree("config4.ini")
197 # print config.to_map.join(":", ",")
198 # assert config["foo.0"] == "a"
199 # assert config["foo.1"] == "b"
200 # assert config["foo.2"] == "c"
201 # assert config.at("foo").values.join(",") == "a,b,c"
204 var stream
= new FileReader.open
(ini_file
)
205 var path
: nullable String = null
207 while not stream
.eof
do
208 var line
= stream
.read_line
210 if line
.is_empty
then
212 else if line
.has_prefix
(";") then
214 else if line
.has_prefix
("[") then
216 var key
= line
.substring
(1, line
.length
- 2)
220 var parts
= line
.split
("=")
221 assert parts
.length
> 1 else
222 print
"Error: malformed ini at line {line_number}"
224 var key
= parts
[0].trim
225 var val
= parts
[1].trim
226 if path
!= null then key
= "{path}.{key}"
227 if key
.has_suffix
("[]") then
237 # Save config to file
238 fun save
do write_to_file
(ini_file
)
240 private var roots
= new Array[ConfigNode]
242 # Append `value` to array at `key`
243 private fun set_array
(key
: String, value
: nullable String) do
244 key
= key
.substring
(0, key
.length
- 2)
247 len
= get_node
(key
).children
.length
249 set_node
("{key}.{len.to_s}", value
)
252 private fun set_node
(key
: String, value
: nullable String) do
253 var parts
= key
.split
(".").reversed
255 var root
= get_root
(k
)
257 root
= new ConfigNode(k
)
258 if parts
.is_empty
then
263 while not parts
.is_empty
do
265 var node
= root
.get_child
(k
)
267 node
= new ConfigNode(k
)
269 root
.children
[node
.name
] = node
271 if parts
.is_empty
then
278 private fun get_node
(key
: String): nullable ConfigNode do
279 var parts
= key
.split
(".").reversed
280 var node
= get_root
(parts
.pop
)
281 while not parts
.is_empty
do
282 node
= node
.get_child
(parts
.pop
)
287 private fun get_root
(name
: String): nullable ConfigNode do
289 if root
.name
== name
then return root
294 private fun leaves
: Array[ConfigNode] do
295 var res
= new Array[ConfigNode]
296 var todo
= new Array[ConfigNode]
298 while not todo
.is_empty
do
300 if node
.children
.is_empty
then
303 todo
.add_all node
.children
.values
310 private class ConfigNode
312 var parent
: nullable ConfigNode = null
313 var children
= new HashMap[String, ConfigNode]
314 var name
: String is writable
315 var value
: nullable String = null
318 if parent
== null then
321 return "{parent.key}.{name}"
324 fun get_child
(name
: String): nullable ConfigNode do
325 if children
.has_key
(name
) then
326 return children
[name
]