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 if child
.value
== null then continue
72 map
[k
] = child
.value
.to_s
77 # Set `value` at `key`
79 # var config = new ConfigTree("config.ini")
80 # assert config["foo.bar"] == "foobar"
81 # config["foo.bar"] = "baz"
82 # assert config["foo.bar"] == "baz"
83 fun []=(key
: String, value
: nullable String) do
87 # Is `key` in the config?
89 # var config = new ConfigTree("config.ini")
90 # assert config.has_key("goo")
91 # assert config.has_key("foo.bar")
92 # assert not config.has_key("zoo")
93 fun has_key
(key
: String): Bool do
94 var parts
= key
.split
(".").reversed
95 var node
= get_root
(parts
.pop
)
96 if node
== null then return false
97 while not parts
.is_empty
do
98 node
= node
.get_child
(parts
.pop
)
99 if node
== null then return false
104 # Get `self` as a Map of `key`, `value`
106 # var config = new ConfigTree("config.ini")
107 # var map = config.to_map
108 # assert map.has_key("goo")
109 # assert map.has_key("foo.bar")
110 # assert map.has_key("foo.baz")
111 # assert map.length == 3
112 fun to_map
: Map[String, String] do
113 var map
= new HashMap[String, String]
114 for node
in leaves
do
115 if node
.value
== null then continue
116 map
[node
.key
] = node
.value
.to_s
121 redef fun to_s
do return to_map
.join
(", ", ":")
123 redef fun write_to
(stream
) do
124 for node
in leaves
do
125 if node
.value
== null then continue
126 stream
.write
("{node.key}={node.value.to_s}\n")
130 # Reload config from file
131 # Done automatically at init
133 # Example with hierarchical ini file:
140 # str.write_to_file("config1.ini")
142 # var config = new ConfigTree("config1.ini")
143 # assert config["foo.bar"] == "foobar"
145 # Example with sections:
155 # str.write_to_file("config2.ini")
157 # config = new ConfigTree("config2.ini")
158 # assert config["foo.bar"] == "foobar"
159 # assert config["boo.bar"] == "boobar"
161 # Example with both hierarchy and section:
170 # baz.bar=gooboobazbar"""
171 # str.write_to_file("config3.ini")
173 # config = new ConfigTree("config3.ini")
174 # assert config["goo"] == "goo"
175 # assert config["foo.bar.baz"] == "foobarbaz"
176 # assert config["goo.boo.bar"] == "gooboobar"
177 # assert config["goo.boo.baz.bar"] == "gooboobazbar"
179 # Using the array notation
185 # str.write_to_file("config4.ini")
187 # config = new ConfigTree("config4.ini")
188 # print config.to_map.join(":", ",")
189 # assert config["foo.0"] == "a"
190 # assert config["foo.1"] == "b"
191 # assert config["foo.2"] == "c"
192 # assert config.at("foo").values.join(",") == "a,b,c"
195 var stream
= new FileReader.open
(ini_file
)
196 var path
: nullable String = null
198 while not stream
.eof
do
199 var line
= stream
.read_line
201 if line
.is_empty
then
203 else if line
.has_prefix
(";") then
205 else if line
.has_prefix
("[") then
207 var key
= line
.substring
(1, line
.length
- 2)
211 var parts
= line
.split
("=")
212 assert parts
.length
> 1 else
213 print
"Error: malformed ini at line {line_number}"
215 var key
= parts
[0].trim
216 var val
= parts
[1].trim
217 if path
!= null then key
= "{path}.{key}"
218 if key
.has_suffix
("[]") then
228 # Save config to file
229 fun save
do write_to_file
(ini_file
)
231 private var roots
= new Array[ConfigNode]
233 # Append `value` to array at `key`
234 private fun set_array
(key
: String, value
: nullable String) do
235 key
= key
.substring
(0, key
.length
- 2)
238 len
= get_node
(key
).children
.length
240 set_node
("{key}.{len.to_s}", value
)
243 private fun set_node
(key
: String, value
: nullable String) do
244 var parts
= key
.split
(".").reversed
246 var root
= get_root
(k
)
248 root
= new ConfigNode(k
)
249 if parts
.is_empty
then
254 while not parts
.is_empty
do
256 var node
= root
.get_child
(k
)
258 node
= new ConfigNode(k
)
260 root
.children
[node
.name
] = node
262 if parts
.is_empty
then
269 private fun get_node
(key
: String): nullable ConfigNode do
270 var parts
= key
.split
(".").reversed
271 var node
= get_root
(parts
.pop
)
272 while node
!= null and not parts
.is_empty
do
273 node
= node
.get_child
(parts
.pop
)
278 private fun get_root
(name
: String): nullable ConfigNode do
280 if root
.name
== name
then return root
285 private fun leaves
: Array[ConfigNode] do
286 var res
= new Array[ConfigNode]
287 var todo
= new Array[ConfigNode]
289 while not todo
.is_empty
do
291 if node
.children
.is_empty
then
294 todo
.add_all node
.children
.values
301 private class ConfigNode
303 var parent
: nullable ConfigNode = null
304 var children
= new HashMap[String, ConfigNode]
305 var name
: String is writable
306 var value
: nullable String = null
309 if parent
== null then
312 return "{parent.key}.{name}"
315 fun get_child
(name
: String): nullable ConfigNode do
316 if children
.has_key
(name
) then
317 return children
[name
]