Merge: nitrpg: Move `nitrpg` to its own repository
[nit.git] / lib / ini / README.md
1 # `ini` - Read and write INI configuration files
2
3 [INI files](https://en.wikipedia.org/wiki/INI_file) are simple text files with
4 a basic structure composed of sections, properties and values used to store
5 configuration parameters.
6
7 Here's an example from the `package.ini` of this package:
8
9 ~~~
10 import ini
11
12 var package_ini = """
13 [package]
14 name=ini
15 desc=Read and write INI configuration files.
16 [upstream]
17 git=https://github.com/nitlang/nit.git
18 git.directory=lib/ini/
19 """
20 ~~~
21
22 ## Basic usage
23
24 `IniFile` is used to parse INI strings and access their content:
25
26 ~~~
27 var ini = new IniFile.from_string(package_ini)
28 assert ini["package.name"] == "ini"
29 assert ini["upstream.git.directory"] == "lib/ini/"
30 assert ini["unknown.unknown"] == null
31 ~~~
32
33 `IniFile` can also load INI configuration from a file:
34
35 ~~~
36 package_ini.write_to_file("my_package.ini")
37
38 ini = new IniFile.from_file("my_package.ini")
39 assert ini["package.name"] == "ini"
40 assert ini["upstream.git.directory"] == "lib/ini/"
41
42 "my_package.ini".to_path.delete
43 ~~~
44
45 INI content can be added or edited through the `IniFile` API then written to
46 a stream or a file.
47
48 ~~~
49 ini["package.name"] = "new name"
50 ini["upstream.git.directory"] = "/dev/null"
51 ini["section.key"] = "value"
52
53 var stream = new StringWriter
54 ini.write_to(stream)
55
56 assert stream.to_s == """
57 [package]
58 name=new name
59 desc=Read and write INI configuration files.
60 [upstream]
61 git=https://github.com/nitlang/nit.git
62 git.directory=/dev/null
63 [section]
64 key=value
65 """
66 ~~~
67
68 ## INI content
69
70 ### Properties
71
72 Properties are the basic element of the INI format.
73 Every property correspond to a *key* associated to a *value* thanks to the equal (`=`) sign.
74
75 ~~~
76 ini = new IniFile.from_string("""
77 key1=value1
78 key2=value2
79 """)
80 assert ini["key1"] == "value1"
81 assert ini["key2"] == "value2"
82 assert ini.length == 2
83 ~~~
84
85 Accessing an unknown property returns `null`:
86
87 ~~~
88 assert ini["unknown"] == null
89 ~~~
90
91 Properties can be iterated over:
92
93 ~~~
94 var i = 1
95 for key, value in ini do
96         assert key == "key{i}"
97         assert value == "value{i}"
98         i += 1
99 end
100 ~~~
101
102 Property keys cannot contain the character `=`.
103 Values can contain any character.
104 Spaces are trimmed.
105
106 ~~~
107 ini = new IniFile.from_string("""
108 prop=erty1=value1
109  property2 =  value2
110 property3=value3 ; with semicolon
111 """)
112 assert ini[";property1"] == null
113 assert ini["prop=erty1"] == null
114 assert ini["prop"] == "erty1=value1"
115 assert ini["property2"] == "value2"
116 assert ini[" property2 "] == "value2"
117 assert ini["property3"] == "value3 ; with semicolon"
118 ~~~
119
120 Both keys and values are case sensitive.
121
122 ~~~
123 ini = new IniFile.from_string("""
124 Property1=value1
125 property2=Value2
126 """)
127 assert ini["property1"] == null
128 assert ini["Property1"] == "value1"
129 assert ini["property2"] != "value2"
130 assert ini["property2"] == "Value2"
131 ~~~
132
133 ### Sections
134
135 Properties may be grouped into arbitrary sections.
136 The section name appears on a line by itself between square brackets (`[` and `]`).
137
138 All keys after the section declaration are associated with that section.
139 The is no explicit "end of section" delimiter; sections end at the next section
140 declaration or the end of the file.
141 Sections cannot be nested.
142
143 ~~~
144 var content = """
145 key1=value1
146 key2=value2
147 [section1]
148 key1=value3
149 key2=value4
150 [section2]
151 key1=value5
152 """
153
154 ini = new IniFile.from_string(content)
155 assert ini["key1"] == "value1"
156 assert ini["unknown"] == null
157 assert ini["section1.key1"] == "value3"
158 assert ini["section1.unknown"] == null
159 assert ini["section2.key1"] == "value5"
160 ~~~
161
162 Sections can be iterated over:
163
164 ~~~
165 i = 1
166 for section in ini.sections do
167         assert section.name == "section{i}"
168         assert section["key1"].has_prefix("value")
169         i += 1
170 end
171 ~~~
172
173 When iterating over a file properties, only properties at root are returned.
174 `flatten` can be used to iterate over all properties including the one from
175 sections.
176
177 ~~~
178 assert ini.join(", ", ": ") == "key1: value1, key2: value2"
179 assert ini.flatten.join(", ", ": ") ==
180         "key1: value1, key2: value2, section1.key1: value3, section1.key2: value4, section2.key1: value5"
181
182 i = 0
183 for key, value in ini do
184         i += 1
185         assert key == "key{i}" and value == "value{i}"
186 end
187 assert i == 2
188
189 ~~~
190
191 Sections name may contain any character including brackets (`[` and `]`).
192 Spaces are trimmed.
193
194 ~~~
195 ini = new IniFile.from_string("""
196 [[section1]]
197 key=value1
198 [ section 2 ]
199 key=value2
200 [section1.section3]
201 key=value3
202 """)
203 assert ini.sections.length == 3
204 assert ini["[section1].key"] == "value1"
205 assert ini["section 2.key"] == "value2"
206 assert ini["section1.section3.key"] == "value3"
207 assert ini.sections.last.name == "section1.section3"
208 ~~~
209
210 The dot `.` notation is used to create new sections with `[]=`.
211 Unknown sections will be created on the fly.
212
213 ~~~
214 ini = new IniFile
215 ini["key"] = "value1"
216 ini["section1.key"] = "value2"
217 ini["section2.key"] = "value3"
218
219 stream = new StringWriter
220 ini.write_to(stream)
221 assert stream.to_s == """
222 key=value1
223 [section1]
224 key=value2
225 [section2]
226 key=value3
227 """
228 ~~~
229
230 Sections can also be created manually:
231
232 ~~~
233 ini = new IniFile
234 ini["key"] = "value1"
235
236 var section = new IniSection("section1")
237 section["key"] = "value2"
238 ini.sections.add section
239
240 stream = new StringWriter
241 ini.write_to(stream)
242 assert stream.to_s == """
243 key=value1
244 [section1]
245 key=value2
246 """
247 ~~~
248
249 ### Comments
250
251 Comments are indicated by semicolon (`;`) or a number sign (`#`) at the begining
252 of the line. Commented lines are ignored as well as empty lines.
253
254 ~~~
255 ini = new IniFile.from_string("""
256 ; This is a comment.
257 ; property1=value1
258
259 # This is another comment.
260 # property2=value2
261 """)
262 assert ini.is_empty
263 ~~~
264
265 ### Unicode support
266
267 INI files support Unicode:
268
269 ~~~
270 ini = new IniFile.from_string("""
271 property❤=héhé
272 """)
273 assert ini["property❤"] == "héhé"
274 ~~~
275
276 ## Error handling
277
278 By default `IniFile` does not stop when it cannot parse a line in a string (loaded
279 by `from_string` or `load_string`) or a file (loaded by `from_file` or `load_file`).
280
281 ~~~
282 ini = new IniFile.from_string("""
283 key1=value1
284 key2
285 key3=value3
286 """)
287
288 assert ini.length == 2
289 assert ini["key1"] == "value1"
290 assert ini["key2"] == null
291 assert ini["key3"] == "value3"
292 ~~~
293
294
295 This behaviour can be modified by setting `stop_on_first_error` to `true`.
296
297 ~~~
298 ini = new IniFile.from_string("""
299 key1=value1
300 key2
301 key3=value3
302 """, stop_on_first_error = true)
303
304 assert ini.length == 1
305 assert ini["key1"] == "value1"
306 assert ini["key2"] == null
307 assert ini["key3"] == null
308 ~~~
309
310 Wathever the value set for `stop_on_first_error`, errors can be checked thanks
311 to the `errors` array:
312
313 ~~~
314 assert ini.errors.length == 1
315 assert ini.errors.first.message == "Unexpected string `key2` at line 2."
316 ~~~