misc/vim: inform the user when no results are found
[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 Writable
36
37 # The ini file used to read/store data
38 var ini_file: String
39
40 init do if ini_file.file_exists then load
41
42 # Get the config value for `key`
43 #
44 # REQUIRE: `has_key(key)`
45 #
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"
53 abort
54 end
55 var node = get_node(key).as(not null)
56 if node.value == null then
57 print "error: config key `{key}` has no value"
58 abort
59 end
60 return node.value.as(not null)
61 end
62
63 # Get the config values under `key`
64 #
65 # REQUIRE: `has_key(key)`
66 #
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"
75 abort
76 end
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
82 end
83 return map
84 end
85
86 # Set `value` at `key`
87 #
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
93 set_node(key, value)
94 end
95
96 # Is `key` in the config?
97 #
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
109 end
110 return true
111 end
112
113 # Get `self` as a Map of `key`, `value`
114 #
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
126 end
127 return map
128 end
129
130 redef fun to_s do return to_map.join(", ", ":")
131
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")
136 end
137 end
138
139 # Reload config from file
140 # Done automatically at init
141 #
142 # Example with hierarchical ini file:
143 #
144 # # init file
145 # var str = """
146 # foo.bar=foobar
147 # foo.baz=foobaz
148 # goo=goo"""
149 # str.write_to_file("config1.ini")
150 # # load file
151 # var config = new ConfigTree("config1.ini")
152 # assert config["foo.bar"] == "foobar"
153 #
154 # Example with sections:
155 #
156 # # init file
157 # str = """
158 # goo=goo
159 # [foo]
160 # bar=foobar
161 # baz=foobaz
162 # [boo]
163 # bar=boobar"""
164 # str.write_to_file("config2.ini")
165 # # load file
166 # config = new ConfigTree("config2.ini")
167 # assert config["foo.bar"] == "foobar"
168 # assert config["boo.bar"] == "boobar"
169 #
170 # Example with both hierarchy and section:
171 #
172 # # init file
173 # str = """
174 # goo=goo
175 # [foo]
176 # bar.baz=foobarbaz
177 # [goo.boo]
178 # bar=gooboobar
179 # baz.bar=gooboobazbar"""
180 # str.write_to_file("config3.ini")
181 # # load file
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"
187 fun load do
188 roots.clear
189 var stream = new FileReader.open(ini_file)
190 var path: nullable String = null
191 while not stream.eof do
192 var line = stream.read_line
193 if line.is_empty then
194 continue
195 else if line.has_prefix(";") then
196 continue
197 else if line.has_prefix("[") then
198 line = line.trim
199 var key = line.substring(1, line.length - 2)
200 path = key
201 set_node(path, null)
202 else
203 var parts = line.split("=")
204 var key = parts[0].trim
205 var val = parts[1].trim
206 if path == null then
207 set_node(key, val)
208 else
209 set_node("{path}.{key}", val)
210 end
211 end
212 end
213 stream.close
214 end
215
216 # Save config to file
217 fun save do write_to_file(ini_file)
218
219 private var roots = new Array[ConfigNode]
220
221 private fun set_node(key: String, value: nullable String) do
222 var parts = key.split(".").reversed
223 var k = parts.pop
224 var root = get_root(k)
225 if root == null then
226 root = new ConfigNode(k)
227 if parts.is_empty then
228 root.value = value
229 end
230 roots.add root
231 end
232 while not parts.is_empty do
233 k = parts.pop
234 var node = root.get_child(k)
235 if node == null then
236 node = new ConfigNode(k)
237 node.parent = root
238 root.children[node.name] = node
239 end
240 if parts.is_empty then
241 node.value = value
242 end
243 root = node
244 end
245 end
246
247 private fun get_node(key: String): nullable ConfigNode do
248 var parts = key.split(".").reversed
249 var node = get_root(parts.pop)
250 while not parts.is_empty do
251 node = node.get_child(parts.pop)
252 end
253 return node
254 end
255
256 private fun get_root(name: String): nullable ConfigNode do
257 for root in roots do
258 if root.name == name then return root
259 end
260 return null
261 end
262
263 private fun leaves: Array[ConfigNode] do
264 var res = new Array[ConfigNode]
265 var todo = new Array[ConfigNode]
266 todo.add_all roots
267 while not todo.is_empty do
268 var node = todo.pop
269 if node.children.is_empty then
270 res.add node
271 else
272 todo.add_all node.children.values
273 end
274 end
275 return res
276 end
277 end
278
279 private class ConfigNode
280
281 var parent: nullable ConfigNode = null
282 var children = new HashMap[String, ConfigNode]
283 var name: String is writable
284 var value: nullable String = null
285
286 fun key: String do
287 if parent == null then
288 return name
289 end
290 return "{parent.key}.{name}"
291 end
292
293 fun get_child(name: String): nullable ConfigNode do
294 if children.has_key(name) then
295 return children[name]
296 end
297 return null
298 end
299 end
300