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 # var config = new ConfigTree("config.ini")
45 # assert config["goo"] == "goo"
46 # assert config["foo.bar"] == "foobar"
47 # assert config["foo.baz"] == "foobaz"
48 # assert config["fail.fail"] == null
49 fun [](key
: String): nullable String do
50 var node
= get_node
(key
)
51 if node
== null then return null
55 # Get the config values under `key`
57 # var config = new ConfigTree("config.ini")
58 # var values = config.at("foo")
59 # assert values.has_key("bar")
60 # assert values.has_key("baz")
61 # assert not values.has_key("goo")
63 # Return null if the key does not exists.
65 # assert config.at("fail.fail") == null
66 fun at
(key
: String): nullable Map[String, String] do
67 var node
= get_node
(key
)
68 if node
== null then return null
69 var map
= new HashMap[String, String]
70 for k
, child
in node
.children
do
71 var value
= child
.value
72 if value
== null then continue
78 # Set `value` at `key`
80 # var config = new ConfigTree("config.ini")
81 # assert config["foo.bar"] == "foobar"
82 # config["foo.bar"] = "baz"
83 # assert config["foo.bar"] == "baz"
84 fun []=(key
: String, value
: nullable String) do
88 # Is `key` in the config?
90 # var config = new ConfigTree("config.ini")
91 # assert config.has_key("goo")
92 # assert config.has_key("foo.bar")
93 # assert not config.has_key("zoo")
94 fun has_key
(key
: String): Bool do
95 var parts
= key
.split
(".").reversed
96 var node
= get_root
(parts
.pop
)
97 if node
== null then return false
98 while not parts
.is_empty
do
99 node
= node
.get_child
(parts
.pop
)
100 if node
== null then return false
105 # Get `self` as a Map of `key`, `value`
107 # var config = new ConfigTree("config.ini")
108 # var map = config.to_map
109 # assert map.has_key("goo")
110 # assert map.has_key("foo.bar")
111 # assert map.has_key("foo.baz")
112 # assert map.length == 3
113 fun to_map
: Map[String, String] do
114 var map
= new HashMap[String, String]
115 for node
in leaves
do
116 var value
= node
.value
117 if value
== null then continue
118 map
[node
.key
] = value
123 redef fun to_s
do return to_map
.join
(", ", ":")
125 # Write `self` in `stream`
127 # var config = new ConfigTree("config.ini")
128 # var out = new StringWriter
129 # config.write_to(out)
130 # assert out.to_s == """
136 redef fun write_to
(stream
) do
137 var todo
= new Array[ConfigNode].from
(roots
.reversed
)
138 while not todo
.is_empty
do
140 if node
.children
.not_empty
then
141 todo
.add_all node
.children
.values
.to_a
.reversed
143 if node
.children
.not_empty
and node
.parent
== null then
144 stream
.write
("[{node.name}]\n")
146 var value
= node
.value
147 if value
== null then continue
149 if path
.length
> 1 then path
.shift
150 stream
.write
("{path.join(".")}={value}\n")
154 # Reload config from file
155 # Done automatically at init
157 # Example with hierarchical ini file:
164 # str.write_to_file("config1.ini")
166 # var config = new ConfigTree("config1.ini")
167 # assert config["foo.bar"] == "foobar"
169 # Example with sections:
179 # str.write_to_file("config2.ini")
181 # config = new ConfigTree("config2.ini")
182 # assert config["foo.bar"] == "foobar"
183 # assert config["boo.bar"] == "boobar"
185 # Example with both hierarchy and section:
194 # baz.bar=gooboobazbar"""
195 # str.write_to_file("config3.ini")
197 # config = new ConfigTree("config3.ini")
198 # assert config["goo"] == "goo"
199 # assert config["foo.bar.baz"] == "foobarbaz"
200 # assert config["goo.boo.bar"] == "gooboobar"
201 # assert config["goo.boo.baz.bar"] == "gooboobazbar"
203 # Using the array notation
209 # str.write_to_file("config4.ini")
211 # config = new ConfigTree("config4.ini")
212 # print config.to_map.join(":", ",")
213 # assert config["foo.0"] == "a"
214 # assert config["foo.1"] == "b"
215 # assert config["foo.2"] == "c"
216 # assert config.at("foo").values.join(",") == "a,b,c"
219 var stream
= new FileReader.open
(ini_file
)
220 var path
: nullable String = null
222 while not stream
.eof
do
223 var line
= stream
.read_line
225 if line
.is_empty
then
227 else if line
.has_prefix
(";") then
229 else if line
.has_prefix
("[") then
231 var key
= line
.substring
(1, line
.length
- 2)
235 var parts
= line
.split_once_on
("=")
236 if parts
.length
== 1 then
239 var key
= parts
[0].trim
240 var val
= parts
[1].trim
241 if path
!= null then key
= "{path}.{key}"
242 if key
.has_suffix
("[]") then
252 # Save config to file
253 fun save
do write_to_file
(ini_file
)
255 private var roots
= new Array[ConfigNode]
257 # Append `value` to array at `key`
258 private fun set_array
(key
: String, value
: nullable String) do
259 key
= key
.substring
(0, key
.length
- 2)
261 var node
= get_node
(key
)
262 if node
!= null then len
= node
.children
.length
263 set_node
("{key}.{len.to_s}", value
)
266 private fun set_node
(key
: String, value
: nullable String) do
267 var parts
= key
.split
(".").reversed
269 var root
= get_root
(k
)
271 root
= new ConfigNode(k
)
272 if parts
.is_empty
then
277 while not parts
.is_empty
do
279 var node
= root
.get_child
(k
)
281 node
= new ConfigNode(k
)
283 root
.children
[node
.name
] = node
285 if parts
.is_empty
then
292 private fun get_node
(key
: String): nullable ConfigNode do
293 var parts
= key
.split
(".").reversed
294 var node
= get_root
(parts
.pop
)
295 while node
!= null and not parts
.is_empty
do
296 node
= node
.get_child
(parts
.pop
)
301 private fun get_root
(name
: String): nullable ConfigNode do
303 if root
.name
== name
then return root
308 private fun leaves
: Array[ConfigNode] do
309 var res
= new Array[ConfigNode]
310 var todo
= new Array[ConfigNode]
312 while not todo
.is_empty
do
314 if node
.children
.is_empty
then
317 todo
.add_all node
.children
.values
324 private class ConfigNode
326 var parent
: nullable ConfigNode = null
327 var children
= new HashMap[String, ConfigNode]
328 var name
: String is writable
329 var value
: nullable String = null
332 var parent
= self.parent
333 if parent
== null then
336 return "{parent.key}.{name}"
339 fun path
: Array[String] do
340 var parent
= self.parent
341 if parent
== null then
344 var res
= new Array[String].from
(parent
.path
)
349 fun get_child
(name
: String): nullable ConfigNode do
350 if children
.has_key
(name
) then
351 return children
[name
]