src: transform all old writable in annotations
[nit.git] / lib / ini.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Handle ini config files.
16 module ini
17
18 # A configuration tree that can read and store data in ini format
19 #
20 # Write example:
21 #
22 # var config = new ConfigTree("config.ini")
23 # config["goo"] = "goo"
24 # config["foo.bar"] = "foobar"
25 # config["foo.baz"] = "foobaz"
26 # config.save
27 # assert config.to_map.length == 3
28 #
29 # Read example:
30 #
31 # config = new ConfigTree("config.ini")
32 # assert config.has_key("foo.bar")
33 # assert config["foo.bar"] == "foobar"
34 class ConfigTree
35 super Streamable
36
37 # The ini file used to read/store data
38 var ini_file: String
39
40 init(file: String) do
41 self.ini_file = file
42 if file.file_exists then load
43 end
44
45 # Get the config value for `key`
46 #
47 # REQUIRE: `has_key(key)`
48 #
49 # var config = new ConfigTree("config.ini")
50 # assert config["goo"] == "goo"
51 # assert config["foo.bar"] == "foobar"
52 # assert config["foo.baz"] == "foobaz"
53 fun [](key: String): String do
54 if not has_key(key) then
55 print "error: config key `{key}` not found"
56 abort
57 end
58 var node = get_node(key).as(not null)
59 if node.value == null then
60 print "error: config key `{key}` has no value"
61 abort
62 end
63 return node.value.as(not null)
64 end
65
66 # Get the config values under `key`
67 #
68 # REQUIRE: `has_key(key)`
69 #
70 # var config = new ConfigTree("config.ini")
71 # var values = config.at("foo")
72 # assert values.has_key("bar")
73 # assert values.has_key("baz")
74 # assert not values.has_key("goo")
75 fun at(key: String): Map[String, String] do
76 if not has_key(key) then
77 print "error: config key `{key}` not found"
78 abort
79 end
80 var map = new HashMap[String, String]
81 var node = get_node(key).as(not null)
82 for k, child in node.children do
83 if child.value == null then continue
84 map[k] = child.value.to_s
85 end
86 return map
87 end
88
89 # Set `value` at `key`
90 #
91 # var config = new ConfigTree("config.ini")
92 # assert config["foo.bar"] == "foobar"
93 # config["foo.bar"] = "baz"
94 # assert config["foo.bar"] == "baz"
95 fun []=(key: String, value: nullable String) do
96 set_node(key, value)
97 end
98
99 # Is `key` in the config?
100 #
101 # var config = new ConfigTree("config.ini")
102 # assert config.has_key("goo")
103 # assert config.has_key("foo.bar")
104 # assert not config.has_key("zoo")
105 fun has_key(key: String): Bool do
106 var children = roots
107 var parts = key.split(".").reversed
108 var node = get_root(parts.pop)
109 if node == null then return false
110 while not parts.is_empty do
111 node = node.get_child(parts.pop)
112 if node == null then return false
113 end
114 return true
115 end
116
117 # Get `self` as a Map of `key`, `value`
118 #
119 # var config = new ConfigTree("config.ini")
120 # var map = config.to_map
121 # assert map.has_key("goo")
122 # assert map.has_key("foo.bar")
123 # assert map.has_key("foo.baz")
124 # assert map.length == 3
125 fun to_map: Map[String, String] do
126 var map = new HashMap[String, String]
127 for node in leaves do
128 if node.value == null then continue
129 map[node.key] = node.value.to_s
130 end
131 return map
132 end
133
134 redef fun to_s do return to_map.join(", ", ":")
135
136 redef fun write_to(stream) do
137 for node in leaves do
138 if node.value == null then continue
139 stream.write("{node.key}={node.value.to_s}\n")
140 end
141 end
142
143 # Reload config from file
144 # Done automatically at init
145 #
146 # Example with hierarchical ini file:
147 #
148 # # init file
149 # var str = """
150 # foo.bar=foobar
151 # foo.baz=foobaz
152 # goo=goo"""
153 # str.write_to_file("config1.ini")
154 # # load file
155 # var config = new ConfigTree("config1.ini")
156 # assert config["foo.bar"] == "foobar"
157 #
158 # Example with sections:
159 #
160 # # init file
161 # str = """
162 # goo=goo
163 # [foo]
164 # bar=foobar
165 # baz=foobaz
166 # [boo]
167 # bar=boobar"""
168 # str.write_to_file("config2.ini")
169 # # load file
170 # config = new ConfigTree("config2.ini")
171 # assert config["foo.bar"] == "foobar"
172 # assert config["boo.bar"] == "boobar"
173 #
174 # Example with both hierarchy and section:
175 #
176 # # init file
177 # str = """
178 # goo=goo
179 # [foo]
180 # bar.baz=foobarbaz
181 # [goo.boo]
182 # bar=gooboobar
183 # baz.bar=gooboobazbar"""
184 # str.write_to_file("config3.ini")
185 # # load file
186 # config = new ConfigTree("config3.ini")
187 # assert config["goo"] == "goo"
188 # assert config["foo.bar.baz"] == "foobarbaz"
189 # assert config["goo.boo.bar"] == "gooboobar"
190 # assert config["goo.boo.baz.bar"] == "gooboobazbar"
191 fun load do
192 roots.clear
193 var stream = new IFStream.open(ini_file)
194 var path: nullable String = null
195 while not stream.eof do
196 var line = stream.read_line
197 if line.is_empty then
198 continue
199 else if line.has_prefix(";") then
200 continue
201 else if line.has_prefix("[") then
202 var key = line.trim.substring(1, line.length - 2)
203 path = key
204 set_node(path, null)
205 else
206 var parts = line.split("=")
207 var key = parts[0].trim
208 var val = parts[1].trim
209 if path == null then
210 set_node(key, val)
211 else
212 set_node("{path}.{key}", val)
213 end
214 end
215 end
216 stream.close
217 end
218
219 # Save config to file
220 fun save do write_to_file(ini_file)
221
222 private var roots = new Array[ConfigNode]
223
224 private fun set_node(key: String, value: nullable String) do
225 var children = roots
226 var parts = key.split(".").reversed
227 var k = parts.pop
228 var root = get_root(k)
229 if root == null then
230 root = new ConfigNode(k)
231 if parts.is_empty then
232 root.value = value
233 end
234 roots.add root
235 end
236 while not parts.is_empty do
237 k = parts.pop
238 var node = root.get_child(k)
239 if node == null then
240 node = new ConfigNode(k)
241 node.parent = root
242 root.children[node.name] = node
243 end
244 if parts.is_empty then
245 node.value = value
246 end
247 root = node
248 end
249 end
250
251 private fun get_node(key: String): nullable ConfigNode do
252 var children = roots
253 var parts = key.split(".").reversed
254 var node = get_root(parts.pop)
255 while not parts.is_empty do
256 node = node.get_child(parts.pop)
257 end
258 return node
259 end
260
261 private fun get_root(name: String): nullable ConfigNode do
262 for root in roots do
263 if root.name == name then return root
264 end
265 return null
266 end
267
268 private fun leaves: Array[ConfigNode] do
269 var res = new Array[ConfigNode]
270 var todo = new Array[ConfigNode]
271 todo.add_all roots
272 while not todo.is_empty do
273 var node = todo.pop
274 if node.children.is_empty then
275 res.add node
276 else
277 todo.add_all node.children.values
278 end
279 end
280 return res
281 end
282 end
283
284 private class ConfigNode
285 var parent: nullable ConfigNode
286 var children = new HashMap[String, ConfigNode]
287 var name: String is writable
288 var value: nullable String
289
290 init(name: String) do
291 self.name = name
292 end
293
294 fun key: String do
295 if parent == null then
296 return name
297 end
298 return "{parent.key}.{name}"
299 end
300
301 fun get_child(name: String): nullable ConfigNode do
302 if children.has_key(name) then
303 return children[name]
304 end
305 return null
306 end
307 end
308