Line data Source code
1 : // Copyright (c) 2006-2024 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/advgetopt
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : // advgetopt
21 : //
22 : #include <advgetopt/utils.h>
23 :
24 : #include <advgetopt/conf_file.h>
25 : #include <advgetopt/exception.h>
26 :
27 :
28 : // self
29 : //
30 : #include "catch_main.h"
31 :
32 :
33 : // snapdev
34 : //
35 : #include <snapdev/safe_setenv.h>
36 :
37 :
38 : // C++
39 : //
40 : #include <fstream>
41 : #include <random>
42 :
43 :
44 : // last include
45 : //
46 : #include <snapdev/poison.h>
47 :
48 :
49 :
50 2 : CATCH_TEST_CASE("utils_unquote", "[utils][valid]")
51 : {
52 2 : CATCH_START_SECTION("Unquote, default pairs")
53 : {
54 1 : CATCH_REQUIRE(advgetopt::unquote("") == "");
55 1 : CATCH_REQUIRE(advgetopt::unquote("a") == "a");
56 1 : CATCH_REQUIRE(advgetopt::unquote("ab") == "ab");
57 1 : CATCH_REQUIRE(advgetopt::unquote("abc") == "abc");
58 :
59 1 : CATCH_REQUIRE(advgetopt::unquote("''") == "");
60 1 : CATCH_REQUIRE(advgetopt::unquote("'a'") == "a");
61 1 : CATCH_REQUIRE(advgetopt::unquote("'ab'") == "ab");
62 1 : CATCH_REQUIRE(advgetopt::unquote("'abc'") == "abc");
63 1 : CATCH_REQUIRE(advgetopt::unquote("'abcd'") == "abcd");
64 :
65 1 : CATCH_REQUIRE(advgetopt::unquote("\"\"") == "");
66 1 : CATCH_REQUIRE(advgetopt::unquote("\"a\"") == "a");
67 1 : CATCH_REQUIRE(advgetopt::unquote("\"ab\"") == "ab");
68 1 : CATCH_REQUIRE(advgetopt::unquote("\"abc\"") == "abc");
69 1 : CATCH_REQUIRE(advgetopt::unquote("\"abcd\"") == "abcd");
70 :
71 1 : CATCH_REQUIRE(advgetopt::unquote("\"'") == "\"'");
72 1 : CATCH_REQUIRE(advgetopt::unquote("\"a'") == "\"a'");
73 1 : CATCH_REQUIRE(advgetopt::unquote("\"ab'") == "\"ab'");
74 1 : CATCH_REQUIRE(advgetopt::unquote("\"abc'") == "\"abc'");
75 1 : CATCH_REQUIRE(advgetopt::unquote("\"abcd'") == "\"abcd'");
76 :
77 1 : CATCH_REQUIRE(advgetopt::unquote("'\"") == "'\"");
78 1 : CATCH_REQUIRE(advgetopt::unquote("'a\"") == "'a\"");
79 1 : CATCH_REQUIRE(advgetopt::unquote("'ab\"") == "'ab\"");
80 1 : CATCH_REQUIRE(advgetopt::unquote("'abc\"") == "'abc\"");
81 1 : CATCH_REQUIRE(advgetopt::unquote("'abcd\"") == "'abcd\"");
82 :
83 1 : CATCH_REQUIRE(advgetopt::unquote("\"") == "\"");
84 1 : CATCH_REQUIRE(advgetopt::unquote("\"a") == "\"a");
85 1 : CATCH_REQUIRE(advgetopt::unquote("\"ab") == "\"ab");
86 1 : CATCH_REQUIRE(advgetopt::unquote("\"abc") == "\"abc");
87 1 : CATCH_REQUIRE(advgetopt::unquote("\"abcd") == "\"abcd");
88 :
89 1 : CATCH_REQUIRE(advgetopt::unquote("'") == "'");
90 1 : CATCH_REQUIRE(advgetopt::unquote("'a") == "'a");
91 1 : CATCH_REQUIRE(advgetopt::unquote("'ab") == "'ab");
92 1 : CATCH_REQUIRE(advgetopt::unquote("'abc") == "'abc");
93 1 : CATCH_REQUIRE(advgetopt::unquote("'abcd") == "'abcd");
94 :
95 1 : CATCH_REQUIRE(advgetopt::unquote("'") == "'");
96 1 : CATCH_REQUIRE(advgetopt::unquote("a'") == "a'");
97 1 : CATCH_REQUIRE(advgetopt::unquote("ab'") == "ab'");
98 1 : CATCH_REQUIRE(advgetopt::unquote("abc'") == "abc'");
99 1 : CATCH_REQUIRE(advgetopt::unquote("abcd'") == "abcd'");
100 :
101 1 : CATCH_REQUIRE(advgetopt::unquote("\"") == "\"");
102 1 : CATCH_REQUIRE(advgetopt::unquote("a\"") == "a\"");
103 1 : CATCH_REQUIRE(advgetopt::unquote("ab\"") == "ab\"");
104 1 : CATCH_REQUIRE(advgetopt::unquote("abc\"") == "abc\"");
105 1 : CATCH_REQUIRE(advgetopt::unquote("abcd\"") == "abcd\"");
106 : }
107 2 : CATCH_END_SECTION()
108 :
109 2 : CATCH_START_SECTION("Unquote, brackets")
110 : {
111 1 : CATCH_REQUIRE(advgetopt::unquote("", "[]<>{}") == "");
112 1 : CATCH_REQUIRE(advgetopt::unquote("a", "[]<>{}") == "a");
113 1 : CATCH_REQUIRE(advgetopt::unquote("ab", "[]<>{}") == "ab");
114 1 : CATCH_REQUIRE(advgetopt::unquote("abc", "[]<>{}") == "abc");
115 :
116 1 : CATCH_REQUIRE(advgetopt::unquote("{}", "[]<>{}") == "");
117 1 : CATCH_REQUIRE(advgetopt::unquote("{a}", "[]<>{}") == "a");
118 1 : CATCH_REQUIRE(advgetopt::unquote("{ab}", "[]<>{}") == "ab");
119 1 : CATCH_REQUIRE(advgetopt::unquote("{abc}", "[]<>{}") == "abc");
120 1 : CATCH_REQUIRE(advgetopt::unquote("{abcd}", "[]<>{}") == "abcd");
121 :
122 1 : CATCH_REQUIRE(advgetopt::unquote("[]", "[]<>{}") == "");
123 1 : CATCH_REQUIRE(advgetopt::unquote("[a]", "[]<>{}") == "a");
124 1 : CATCH_REQUIRE(advgetopt::unquote("[ab]", "[]<>{}") == "ab");
125 1 : CATCH_REQUIRE(advgetopt::unquote("[abc]", "[]<>{}") == "abc");
126 1 : CATCH_REQUIRE(advgetopt::unquote("[abcd]", "[]<>{}") == "abcd");
127 :
128 1 : CATCH_REQUIRE(advgetopt::unquote("<>", "[]<>{}") == "");
129 1 : CATCH_REQUIRE(advgetopt::unquote("<a>", "[]<>{}") == "a");
130 1 : CATCH_REQUIRE(advgetopt::unquote("<ab>", "[]<>{}") == "ab");
131 1 : CATCH_REQUIRE(advgetopt::unquote("<abc>", "[]<>{}") == "abc");
132 1 : CATCH_REQUIRE(advgetopt::unquote("<abcd>", "[]<>{}") == "abcd");
133 :
134 1 : CATCH_REQUIRE(advgetopt::unquote("[}", "[]<>{}") == "[}");
135 1 : CATCH_REQUIRE(advgetopt::unquote("[a}", "[]<>{}") == "[a}");
136 1 : CATCH_REQUIRE(advgetopt::unquote("[ab}", "[]<>{}") == "[ab}");
137 1 : CATCH_REQUIRE(advgetopt::unquote("[abc}", "[]<>{}") == "[abc}");
138 1 : CATCH_REQUIRE(advgetopt::unquote("[abcd}", "[]<>{}") == "[abcd}");
139 :
140 1 : CATCH_REQUIRE(advgetopt::unquote("[>", "[]<>{}") == "[>");
141 1 : CATCH_REQUIRE(advgetopt::unquote("[a>", "[]<>{}") == "[a>");
142 1 : CATCH_REQUIRE(advgetopt::unquote("[ab>", "[]<>{}") == "[ab>");
143 1 : CATCH_REQUIRE(advgetopt::unquote("[abc>", "[]<>{}") == "[abc>");
144 1 : CATCH_REQUIRE(advgetopt::unquote("[abcd>", "[]<>{}") == "[abcd>");
145 :
146 1 : CATCH_REQUIRE(advgetopt::unquote("'\"", "[]<>{}") == "'\"");
147 1 : CATCH_REQUIRE(advgetopt::unquote("'a\"", "[]<>{}") == "'a\"");
148 1 : CATCH_REQUIRE(advgetopt::unquote("'ab\"", "[]<>{}") == "'ab\"");
149 1 : CATCH_REQUIRE(advgetopt::unquote("'abc\"", "[]<>{}") == "'abc\"");
150 1 : CATCH_REQUIRE(advgetopt::unquote("'abcd\"", "[]<>{}") == "'abcd\"");
151 :
152 1 : CATCH_REQUIRE(advgetopt::unquote("[", "[]<>{}") == "[");
153 1 : CATCH_REQUIRE(advgetopt::unquote("[a", "[]<>{}") == "[a");
154 1 : CATCH_REQUIRE(advgetopt::unquote("[ab", "[]<>{}") == "[ab");
155 1 : CATCH_REQUIRE(advgetopt::unquote("[abc", "[]<>{}") == "[abc");
156 1 : CATCH_REQUIRE(advgetopt::unquote("[abcd", "[]<>{}") == "[abcd");
157 :
158 1 : CATCH_REQUIRE(advgetopt::unquote("{", "[]<>{}") == "{");
159 1 : CATCH_REQUIRE(advgetopt::unquote("{a", "[]<>{}") == "{a");
160 1 : CATCH_REQUIRE(advgetopt::unquote("{ab", "[]<>{}") == "{ab");
161 1 : CATCH_REQUIRE(advgetopt::unquote("{abc", "[]<>{}") == "{abc");
162 1 : CATCH_REQUIRE(advgetopt::unquote("{abcd", "[]<>{}") == "{abcd");
163 :
164 1 : CATCH_REQUIRE(advgetopt::unquote("<", "[]<>{}") == "<");
165 1 : CATCH_REQUIRE(advgetopt::unquote("<a", "[]<>{}") == "<a");
166 1 : CATCH_REQUIRE(advgetopt::unquote("<ab", "[]<>{}") == "<ab");
167 1 : CATCH_REQUIRE(advgetopt::unquote("<abc", "[]<>{}") == "<abc");
168 1 : CATCH_REQUIRE(advgetopt::unquote("<abcd", "[]<>{}") == "<abcd");
169 :
170 1 : CATCH_REQUIRE(advgetopt::unquote("}", "[]<>{}") == "}");
171 1 : CATCH_REQUIRE(advgetopt::unquote("a}", "[]<>{}") == "a}");
172 1 : CATCH_REQUIRE(advgetopt::unquote("ab}", "[]<>{}") == "ab}");
173 1 : CATCH_REQUIRE(advgetopt::unquote("abc}", "[]<>{}") == "abc}");
174 1 : CATCH_REQUIRE(advgetopt::unquote("abcd}", "[]<>{}") == "abcd}");
175 :
176 1 : CATCH_REQUIRE(advgetopt::unquote("]", "[]<>{}") == "]");
177 1 : CATCH_REQUIRE(advgetopt::unquote("a]", "[]<>{}") == "a]");
178 1 : CATCH_REQUIRE(advgetopt::unquote("ab]", "[]<>{}") == "ab]");
179 1 : CATCH_REQUIRE(advgetopt::unquote("abc]", "[]<>{}") == "abc]");
180 1 : CATCH_REQUIRE(advgetopt::unquote("abcd]", "[]<>{}") == "abcd]");
181 :
182 1 : CATCH_REQUIRE(advgetopt::unquote(">", "[]<>{}") == ">");
183 1 : CATCH_REQUIRE(advgetopt::unquote("a>", "[]<>{}") == "a>");
184 1 : CATCH_REQUIRE(advgetopt::unquote("ab>", "[]<>{}") == "ab>");
185 1 : CATCH_REQUIRE(advgetopt::unquote("abc>", "[]<>{}") == "abc>");
186 1 : CATCH_REQUIRE(advgetopt::unquote("abcd>", "[]<>{}") == "abcd>");
187 : }
188 2 : CATCH_END_SECTION()
189 2 : }
190 :
191 :
192 :
193 :
194 1 : CATCH_TEST_CASE("utils_quote", "[utils][valid]")
195 : {
196 1 : CATCH_START_SECTION("Quote, default pairs")
197 : {
198 1 : CATCH_REQUIRE(advgetopt::quote("") == "\"\"");
199 1 : CATCH_REQUIRE(advgetopt::quote("a") == "\"a\"");
200 1 : CATCH_REQUIRE(advgetopt::quote("ab") == "\"ab\"");
201 1 : CATCH_REQUIRE(advgetopt::quote("abc") == "\"abc\"");
202 :
203 1 : CATCH_REQUIRE(advgetopt::quote("", '"') == "\"\"");
204 1 : CATCH_REQUIRE(advgetopt::quote("a", '"') == "\"a\"");
205 1 : CATCH_REQUIRE(advgetopt::quote("ab", '"') == "\"ab\"");
206 1 : CATCH_REQUIRE(advgetopt::quote("abc", '"') == "\"abc\"");
207 1 : CATCH_REQUIRE(advgetopt::quote("abcd", '"') == "\"abcd\"");
208 :
209 1 : CATCH_REQUIRE(advgetopt::quote("", '\'') == "''");
210 1 : CATCH_REQUIRE(advgetopt::quote("a", '\'') == "'a'");
211 1 : CATCH_REQUIRE(advgetopt::quote("ab", '\'') == "'ab'");
212 1 : CATCH_REQUIRE(advgetopt::quote("abc", '\'') == "'abc'");
213 1 : CATCH_REQUIRE(advgetopt::quote("abcd", '\'') == "'abcd'");
214 :
215 1 : CATCH_REQUIRE(advgetopt::quote("", '[', ']') == "[]");
216 1 : CATCH_REQUIRE(advgetopt::quote("a", '[', ']') == "[a]");
217 1 : CATCH_REQUIRE(advgetopt::quote("ab", '[', ']') == "[ab]");
218 1 : CATCH_REQUIRE(advgetopt::quote("abc", '[', ']') == "[abc]");
219 1 : CATCH_REQUIRE(advgetopt::quote("abcd", '[', ']') == "[abcd]");
220 :
221 1 : CATCH_REQUIRE(advgetopt::quote("[]", '[', ']') == "[\\[\\]]");
222 1 : CATCH_REQUIRE(advgetopt::quote("[a]", '[', ']') == "[\\[a\\]]");
223 1 : CATCH_REQUIRE(advgetopt::quote("[ab]", '[', ']') == "[\\[ab\\]]");
224 1 : CATCH_REQUIRE(advgetopt::quote("[abc]", '[', ']') == "[\\[abc\\]]");
225 1 : CATCH_REQUIRE(advgetopt::quote("[abcd]", '[', ']') == "[\\[abcd\\]]");
226 : }
227 1 : CATCH_END_SECTION()
228 1 : }
229 :
230 :
231 :
232 :
233 11 : CATCH_TEST_CASE("utils_split", "[utils][valid]")
234 : {
235 11 : CATCH_START_SECTION("Split three words")
236 1 : advgetopt::string_list_t result;
237 2 : advgetopt::split_string("test with spaces"
238 : , result
239 : , {" "});
240 1 : CATCH_REQUIRE(result.size() == 3);
241 1 : CATCH_REQUIRE(result[0] == "test");
242 1 : CATCH_REQUIRE(result[1] == "with");
243 1 : CATCH_REQUIRE(result[2] == "spaces");
244 12 : CATCH_END_SECTION()
245 :
246 11 : CATCH_START_SECTION("Split three words, one with single quotes")
247 1 : advgetopt::string_list_t result;
248 2 : advgetopt::split_string("test 'with quotes and' spaces"
249 : , result
250 : , {" "});
251 1 : CATCH_REQUIRE(result.size() == 3);
252 1 : CATCH_REQUIRE(result[0] == "test");
253 1 : CATCH_REQUIRE(result[1] == "with quotes and");
254 1 : CATCH_REQUIRE(result[2] == "spaces");
255 12 : CATCH_END_SECTION()
256 :
257 11 : CATCH_START_SECTION("Split three words, one with double quotes")
258 1 : advgetopt::string_list_t result;
259 2 : advgetopt::split_string("test \"with quotes and\" spaces"
260 : , result
261 : , {" "});
262 1 : CATCH_REQUIRE(result.size() == 3);
263 1 : CATCH_REQUIRE(result[0] == "test");
264 1 : CATCH_REQUIRE(result[1] == "with quotes and");
265 1 : CATCH_REQUIRE(result[2] == "spaces");
266 12 : CATCH_END_SECTION()
267 :
268 11 : CATCH_START_SECTION("Split three words, one with single quotes but no spaces")
269 1 : advgetopt::string_list_t result;
270 2 : advgetopt::split_string("test'with quotes and'nospaces"
271 : , result
272 : , {" "});
273 1 : CATCH_REQUIRE(result.size() == 3);
274 1 : CATCH_REQUIRE(result[0] == "test");
275 1 : CATCH_REQUIRE(result[1] == "with quotes and");
276 1 : CATCH_REQUIRE(result[2] == "nospaces");
277 12 : CATCH_END_SECTION()
278 :
279 11 : CATCH_START_SECTION("Split three words, one with double quotes but no spaces")
280 1 : advgetopt::string_list_t result;
281 2 : advgetopt::split_string("test\"with quotes and\"nospaces"
282 : , result
283 : , {" "});
284 1 : CATCH_REQUIRE(result.size() == 3);
285 1 : CATCH_REQUIRE(result[0] == "test");
286 1 : CATCH_REQUIRE(result[1] == "with quotes and");
287 1 : CATCH_REQUIRE(result[2] == "nospaces");
288 12 : CATCH_END_SECTION()
289 :
290 11 : CATCH_START_SECTION("Split five words, four separators")
291 1 : advgetopt::string_list_t result;
292 5 : advgetopt::split_string("test,with quite|many;separators"
293 : , result
294 : , {" ",",","|",";"});
295 1 : CATCH_REQUIRE(result.size() == 5);
296 1 : CATCH_REQUIRE(result[0] == "test");
297 1 : CATCH_REQUIRE(result[1] == "with");
298 1 : CATCH_REQUIRE(result[2] == "quite");
299 1 : CATCH_REQUIRE(result[3] == "many");
300 1 : CATCH_REQUIRE(result[4] == "separators");
301 12 : CATCH_END_SECTION()
302 :
303 11 : CATCH_START_SECTION("Split five words, multiple/repeated separators")
304 1 : advgetopt::string_list_t result;
305 5 : advgetopt::split_string("test, with quite|||many ; separators"
306 : , result
307 : , {" ",",","|",";"});
308 1 : CATCH_REQUIRE(result.size() == 5);
309 1 : CATCH_REQUIRE(result[0] == "test");
310 1 : CATCH_REQUIRE(result[1] == "with");
311 1 : CATCH_REQUIRE(result[2] == "quite");
312 1 : CATCH_REQUIRE(result[3] == "many");
313 1 : CATCH_REQUIRE(result[4] == "separators");
314 12 : CATCH_END_SECTION()
315 :
316 11 : CATCH_START_SECTION("Split five words, and empty entries")
317 1 : advgetopt::string_list_t result;
318 5 : advgetopt::split_string("|||test, with quite\"\"many ; ''separators''"
319 : , result
320 : , {" ",",","|",";"});
321 1 : CATCH_REQUIRE(result.size() == 5);
322 1 : CATCH_REQUIRE(result[0] == "test");
323 1 : CATCH_REQUIRE(result[1] == "with");
324 1 : CATCH_REQUIRE(result[2] == "quite");
325 1 : CATCH_REQUIRE(result[3] == "many");
326 1 : CATCH_REQUIRE(result[4] == "separators");
327 12 : CATCH_END_SECTION()
328 :
329 11 : CATCH_START_SECTION("Split five words, start/end with separator")
330 1 : advgetopt::string_list_t result;
331 2 : advgetopt::split_string("|start|and|end|with|separator|"
332 : , result
333 : , {"|"});
334 1 : CATCH_REQUIRE(result.size() == 5);
335 1 : CATCH_REQUIRE(result[0] == "start");
336 1 : CATCH_REQUIRE(result[1] == "and");
337 1 : CATCH_REQUIRE(result[2] == "end");
338 1 : CATCH_REQUIRE(result[3] == "with");
339 1 : CATCH_REQUIRE(result[4] == "separator");
340 12 : CATCH_END_SECTION()
341 :
342 11 : CATCH_START_SECTION("Split five words, unclosed double quote")
343 1 : advgetopt::string_list_t result;
344 2 : advgetopt::split_string("\"unclosed quote|mark"
345 : , result
346 : , {"|"});
347 1 : CATCH_REQUIRE(result.size() == 1);
348 1 : CATCH_REQUIRE(result[0] == "unclosed quote|mark");
349 12 : CATCH_END_SECTION()
350 :
351 11 : CATCH_START_SECTION("Split five words, unclosed single quote")
352 1 : advgetopt::string_list_t result;
353 3 : advgetopt::split_string("here is an \"unclosed quote|mark"
354 : , result
355 : , {"|"," "});
356 : //CATCH_REQUIRE(result.size() == 4);
357 1 : CATCH_REQUIRE(result[0] == "here");
358 1 : CATCH_REQUIRE(result[1] == "is");
359 1 : CATCH_REQUIRE(result[2] == "an");
360 1 : CATCH_REQUIRE(result[3] == "unclosed quote|mark");
361 12 : CATCH_END_SECTION()
362 11 : }
363 :
364 :
365 :
366 :
367 5 : CATCH_TEST_CASE("utils_insert_group_name", "[utils][valid]")
368 : {
369 5 : CATCH_START_SECTION("utils_insert_group_name: Full insert")
370 : {
371 : // CONFIG FILE HAS NO EXTENSION
372 : {
373 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
374 : "/this/is/a/path"
375 : , "group-name"
376 4 : , "project-name"));
377 1 : CATCH_REQUIRE(fullname.size() == 1);
378 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/group-name.d/50-path");
379 1 : }
380 :
381 : {
382 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
383 : "/this/is/a/path"
384 : , "group-name"
385 4 : , ""));
386 1 : CATCH_REQUIRE(fullname.size() == 1);
387 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/group-name.d/50-path");
388 1 : }
389 :
390 : {
391 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
392 : "/this/is/a/path"
393 : , "group-name"
394 4 : , nullptr));
395 1 : CATCH_REQUIRE(fullname.size() == 1);
396 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/group-name.d/50-path");
397 1 : }
398 :
399 : {
400 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
401 : "/this/is/a/path"
402 : , ""
403 4 : , "project-name"));
404 1 : CATCH_REQUIRE(fullname.size() == 1);
405 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/project-name.d/50-path");
406 1 : }
407 :
408 : {
409 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
410 : "/this/is/a/path"
411 : , nullptr
412 4 : , "project-name"));
413 1 : CATCH_REQUIRE(fullname.size() == 1);
414 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/project-name.d/50-path");
415 1 : }
416 :
417 : // CONFIG FILE HAS EXTENSION
418 : {
419 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
420 : "/this/is/a/basename.ext"
421 : , "group-name"
422 4 : , "project-name"));
423 1 : CATCH_REQUIRE(fullname.size() == 1);
424 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/group-name.d/50-basename.ext");
425 1 : }
426 :
427 : {
428 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
429 : "/this/is/a/basename.ext"
430 : , "group-name"
431 4 : , ""));
432 1 : CATCH_REQUIRE(fullname.size() == 1);
433 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/group-name.d/50-basename.ext");
434 1 : }
435 :
436 : {
437 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
438 : "/this/is/a/basename.ext"
439 : , "group-name"
440 4 : , nullptr));
441 1 : CATCH_REQUIRE(fullname.size() == 1);
442 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/group-name.d/50-basename.ext");
443 1 : }
444 :
445 : {
446 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
447 : "/this/is/a/basename.ext"
448 : , ""
449 4 : , "project-name"));
450 1 : CATCH_REQUIRE(fullname.size() == 1);
451 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/project-name.d/50-basename.ext");
452 1 : }
453 :
454 : {
455 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
456 : "/this/is/a/basename.ext"
457 : , nullptr
458 4 : , "project-name"));
459 1 : CATCH_REQUIRE(fullname.size() == 1);
460 1 : CATCH_REQUIRE(fullname[0] == "/this/is/a/project-name.d/50-basename.ext");
461 1 : }
462 : }
463 5 : CATCH_END_SECTION()
464 :
465 5 : CATCH_START_SECTION("utils_insert_group_name: Empty cases")
466 : {
467 : {
468 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
469 : "/this/is/a/path"
470 : , nullptr
471 4 : , nullptr));
472 1 : CATCH_REQUIRE(fullname.empty());
473 1 : }
474 :
475 : {
476 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
477 : "/this/is/a/path"
478 : , nullptr
479 4 : , ""));
480 1 : CATCH_REQUIRE(fullname.empty());
481 1 : }
482 :
483 : {
484 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
485 : "/this/is/a/path"
486 : , ""
487 4 : , nullptr));
488 1 : CATCH_REQUIRE(fullname.empty());
489 1 : }
490 :
491 : {
492 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
493 : "/this/is/a/path"
494 : , ""
495 4 : , ""));
496 1 : CATCH_REQUIRE(fullname.empty());
497 1 : }
498 :
499 : {
500 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
501 : ""
502 : , "group-name"
503 4 : , "project-name"));
504 1 : CATCH_REQUIRE(fullname.empty());
505 1 : }
506 :
507 : {
508 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
509 : ""
510 : , ""
511 4 : , "project-name"));
512 1 : CATCH_REQUIRE(fullname.empty());
513 1 : }
514 :
515 : {
516 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
517 : ""
518 : , nullptr
519 4 : , "project-name"));
520 1 : CATCH_REQUIRE(fullname.empty());
521 1 : }
522 :
523 : {
524 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
525 : ""
526 : , nullptr
527 4 : , ""));
528 1 : CATCH_REQUIRE(fullname.empty());
529 1 : }
530 :
531 : {
532 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
533 : ""
534 : , nullptr
535 4 : , nullptr));
536 1 : CATCH_REQUIRE(fullname.empty());
537 1 : }
538 : }
539 5 : CATCH_END_SECTION()
540 :
541 5 : CATCH_START_SECTION("utils_insert_group_name: cases")
542 : {
543 5 : CATCH_REQUIRE_THROWS_MATCHES(advgetopt::insert_group_name(
544 : "/this-is-a-path"
545 : , "group"
546 : , "project")
547 : , advgetopt::getopt_root_filename
548 : , Catch::Matchers::ExceptionMessage(
549 : "getopt_exception: filename \"/this-is-a-path\" last slash (/) is at the start, which is not allowed."));
550 : }
551 5 : CATCH_END_SECTION()
552 :
553 5 : CATCH_START_SECTION("utils_insert_group_name: Basename Only")
554 : {
555 : {
556 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
557 : "basename"
558 : , nullptr
559 4 : , "advgetopt"));
560 1 : CATCH_REQUIRE(fullname.size() == 1);
561 1 : CATCH_REQUIRE(fullname[0] == "advgetopt.d/50-basename");
562 1 : }
563 :
564 : {
565 2 : advgetopt::string_list_t fullname(advgetopt::insert_group_name(
566 : "basename.ext"
567 : , "advgetopt"
568 4 : , nullptr));
569 1 : CATCH_REQUIRE(fullname.size() == 1);
570 1 : CATCH_REQUIRE(fullname[0] == "advgetopt.d/50-basename.ext");
571 1 : }
572 : }
573 5 : CATCH_END_SECTION()
574 :
575 5 : CATCH_START_SECTION("utils_insert_group_name: Actual List of Files on Disk")
576 : {
577 1 : SNAP_CATCH2_NAMESPACE::init_tmp_dir("advgetopt-multi", "sorted-user-conf", false);
578 :
579 : // generate an array of numbers from 00 to 99
580 : //
581 1 : std::vector<int> numbers;
582 101 : for(int i = 0; i < 100; ++i)
583 : {
584 100 : numbers.push_back(i);
585 : }
586 1 : std::random_device rd;
587 1 : std::mt19937 g(rd());
588 1 : std::shuffle(numbers.begin(), numbers.end(), g);
589 1 : int const max(rand() % 50 + 10);
590 1 : numbers.resize(max);
591 1 : std::string path(SNAP_CATCH2_NAMESPACE::g_config_project_filename);
592 1 : std::string::size_type const pos(path.rfind('/'));
593 1 : path = path.substr(0, pos);
594 1 : advgetopt::string_list_t filenames;
595 21 : for(int i = 0; i < max; ++i)
596 : {
597 20 : std::stringstream ss;
598 20 : ss << path;
599 20 : ss << '/';
600 20 : int const n(numbers[i]);
601 20 : if(n < 10)
602 : {
603 4 : ss << '0';
604 : }
605 20 : ss << n;
606 20 : ss << "-sorted-user-conf.config";
607 20 : filenames.push_back(ss.str());
608 20 : std::ofstream conf;
609 20 : conf.open(ss.str(), std::ios_base::out);
610 20 : CATCH_REQUIRE(conf.is_open());
611 20 : conf << "# Config with a number" << std::endl;
612 20 : conf << "var=\"value: " << numbers[i] << "\"" << std::endl;
613 20 : }
614 1 : std::sort(filenames.begin(), filenames.end());
615 1 : std::string const last_filename(*filenames.rbegin());
616 1 : std::string::size_type const slash_pos(last_filename.rfind('/'));
617 2 : std::string const expected_var("value: " + last_filename.substr(slash_pos + 1, 2));
618 :
619 1 : advgetopt::string_list_t fullnames(advgetopt::insert_group_name(
620 : SNAP_CATCH2_NAMESPACE::g_config_filename
621 : , "advgetopt-multi"
622 1 : , "multi-channels"));
623 1 : CATCH_REQUIRE(fullnames.size() == filenames.size());
624 21 : for(size_t idx(0); idx < filenames.size(); ++idx)
625 : {
626 20 : CATCH_REQUIRE(fullnames[idx] == filenames[idx]);
627 : }
628 :
629 : {
630 1 : std::ofstream conf;
631 1 : conf.open(SNAP_CATCH2_NAMESPACE::g_config_filename, std::ios_base::out);
632 1 : CATCH_REQUIRE(conf.is_open());
633 1 : conf << "# Original Config with a number" << std::endl;
634 1 : conf << "var=master value" << std::endl;
635 :
636 : // verify the master config file
637 : //
638 1 : advgetopt::conf_file_setup setup(SNAP_CATCH2_NAMESPACE::g_config_filename);
639 1 : advgetopt::conf_file::pointer_t config_file(advgetopt::conf_file::get_conf_file(setup));
640 1 : CATCH_REQUIRE(config_file->get_parameter("var") == "master value");
641 1 : }
642 :
643 : {
644 : // run a load to verify that we indeed get the last var=...
645 : // value and not some random entry
646 : //
647 1 : std::string temp_dir = SNAP_CATCH2_NAMESPACE::g_tmp_dir() + "/.config";
648 1 : char const * const dirs[] = {
649 1 : temp_dir.c_str(),
650 : nullptr
651 1 : };
652 1 : advgetopt::option opts[] = {
653 : advgetopt::define_option(
654 : advgetopt::Name("var")
655 : , advgetopt::Flags(advgetopt::all_flags<advgetopt::GETOPT_FLAG_SHOW_USAGE_ON_ERROR>())
656 : , advgetopt::Help("verify loading configuration files in a serie.")
657 : ),
658 : advgetopt::end_options()
659 : };
660 : #pragma GCC diagnostic push
661 : #pragma GCC diagnostic ignored "-Wpedantic"
662 1 : advgetopt::options_environment env = {
663 : .f_project_name = "sorted-configs",
664 : .f_group_name = "advgetopt-multi",
665 : .f_options = opts,
666 : .f_options_files_directory = nullptr,
667 : .f_environment_variable_name = nullptr,
668 : .f_environment_variable_intro = nullptr,
669 : .f_section_variables_name = nullptr,
670 : .f_configuration_files = nullptr,
671 : .f_configuration_filename = "sorted-user-conf.config",
672 : .f_configuration_directories = dirs,
673 : .f_environment_flags = advgetopt::GETOPT_ENVIRONMENT_FLAG_PROCESS_SYSTEM_PARAMETERS,
674 : .f_help_header = nullptr,
675 : .f_help_footer = nullptr,
676 : .f_version = nullptr,
677 : .f_license = nullptr,
678 : .f_copyright = nullptr,
679 : .f_build_date = UTC_BUILD_DATE,
680 : .f_build_time = UTC_BUILD_TIME,
681 : .f_groups = nullptr
682 1 : };
683 : #pragma GCC diagnostic pop
684 1 : char const * const argv[] = {
685 : "test",
686 : nullptr
687 : };
688 1 : advgetopt::getopt opt(env, 1, const_cast<char **>(argv));
689 1 : CATCH_REQUIRE(memcmp(&opt.get_options_environment(), &env, sizeof(env)) == 0);
690 1 : CATCH_REQUIRE(opt.get_string("var") == expected_var);
691 1 : }
692 1 : }
693 5 : CATCH_END_SECTION()
694 5 : }
695 :
696 :
697 5 : CATCH_TEST_CASE("utils_default_group_name", "[utils][valid]")
698 : {
699 5 : CATCH_START_SECTION("utils_default_group_name: Full insert")
700 : {
701 : // CONFIG FILE HAS NO EXTENSION
702 : {
703 2 : std::string const fullname(advgetopt::default_group_name(
704 : "/this/is/a/config"
705 : , "group-name"
706 4 : , "project-name"));
707 1 : CATCH_REQUIRE(fullname == "/this/is/a/group-name.d/50-config");
708 1 : }
709 :
710 : {
711 2 : std::string const fullname(advgetopt::default_group_name(
712 : "/this/is/a/advgetopt"
713 : , "group-name"
714 4 : , ""));
715 1 : CATCH_REQUIRE(fullname == "/this/is/a/group-name.d/50-advgetopt");
716 1 : }
717 :
718 : {
719 2 : std::string const fullname(advgetopt::default_group_name(
720 : "/this/is/a/complete"
721 : , "group-name"
722 4 : , nullptr));
723 1 : CATCH_REQUIRE(fullname == "/this/is/a/group-name.d/50-complete");
724 1 : }
725 :
726 : {
727 2 : std::string const fullname(advgetopt::default_group_name(
728 : "/this/is/a/swapped"
729 : , ""
730 4 : , "project-name"));
731 1 : CATCH_REQUIRE(fullname == "/this/is/a/project-name.d/50-swapped");
732 1 : }
733 :
734 : {
735 2 : std::string const fullname(advgetopt::default_group_name(
736 : "/this/is/a/null"
737 : , nullptr
738 4 : , "project-name"));
739 1 : CATCH_REQUIRE(fullname == "/this/is/a/project-name.d/50-null");
740 1 : }
741 :
742 : // CONFIG FILE HAS EXTENSION
743 : {
744 2 : std::string const fullname(advgetopt::default_group_name(
745 : "/this/is/a/basename.ext"
746 : , "group-name"
747 4 : , "project-name"));
748 1 : CATCH_REQUIRE(fullname == "/this/is/a/group-name.d/50-basename.ext");
749 1 : }
750 :
751 : {
752 2 : std::string const fullname(advgetopt::default_group_name(
753 : "/this/is/a/basename.ext"
754 : , "group-name"
755 4 : , ""));
756 1 : CATCH_REQUIRE(fullname == "/this/is/a/group-name.d/50-basename.ext");
757 1 : }
758 :
759 : {
760 2 : std::string const fullname(advgetopt::default_group_name(
761 : "/this/is/a/basename.ext"
762 : , "group-name"
763 4 : , nullptr));
764 1 : CATCH_REQUIRE(fullname == "/this/is/a/group-name.d/50-basename.ext");
765 1 : }
766 :
767 : {
768 2 : std::string const fullname(advgetopt::default_group_name(
769 : "/this/is/a/basename.ext"
770 : , ""
771 4 : , "project-name"));
772 1 : CATCH_REQUIRE(fullname == "/this/is/a/project-name.d/50-basename.ext");
773 1 : }
774 :
775 : {
776 2 : std::string const fullname(advgetopt::default_group_name(
777 : "/this/is/a/basename.ext"
778 : , nullptr
779 4 : , "project-name"));
780 1 : CATCH_REQUIRE(fullname == "/this/is/a/project-name.d/50-basename.ext");
781 1 : }
782 :
783 : // verify all valid priorities
784 : //
785 11 : for(int priority(0); priority < 10; ++priority)
786 : {
787 20 : std::string const fullname(advgetopt::default_group_name(
788 : "/this/is/a/basename.ext"
789 : , "group-name"
790 : , "project-name"
791 40 : , priority));
792 10 : CATCH_REQUIRE(fullname == "/this/is/a/group-name.d/0"
793 : + std::to_string(priority)
794 : + "-basename.ext");
795 10 : }
796 91 : for(int priority(10); priority < 100; ++priority)
797 : {
798 180 : std::string const fullname(advgetopt::default_group_name(
799 : "/this/is/a/basename.ext"
800 : , "group-name"
801 : , "project-name"
802 360 : , priority));
803 90 : CATCH_REQUIRE(fullname == "/this/is/a/group-name.d/"
804 : + std::to_string(priority)
805 : + "-basename.ext");
806 90 : }
807 : }
808 5 : CATCH_END_SECTION()
809 :
810 5 : CATCH_START_SECTION("utils_default_group_name: Empty cases")
811 : {
812 : {
813 2 : std::string const fullname(advgetopt::default_group_name(
814 : "/this/is/a/path"
815 : , nullptr
816 4 : , nullptr));
817 1 : CATCH_REQUIRE(fullname.empty());
818 1 : }
819 :
820 : {
821 2 : std::string const fullname(advgetopt::default_group_name(
822 : "/this/is/a/path"
823 : , nullptr
824 4 : , ""));
825 1 : CATCH_REQUIRE(fullname.empty());
826 1 : }
827 :
828 : {
829 2 : std::string const fullname(advgetopt::default_group_name(
830 : "/this/is/a/path"
831 : , ""
832 4 : , nullptr));
833 1 : CATCH_REQUIRE(fullname.empty());
834 1 : }
835 :
836 : {
837 2 : std::string const fullname(advgetopt::default_group_name(
838 : "/this/is/a/path"
839 : , ""
840 4 : , ""));
841 1 : CATCH_REQUIRE(fullname.empty());
842 1 : }
843 :
844 : {
845 2 : std::string const fullname(advgetopt::default_group_name(
846 : ""
847 : , "group-name"
848 4 : , "project-name"));
849 1 : CATCH_REQUIRE(fullname.empty());
850 1 : }
851 :
852 : {
853 2 : std::string const fullname(advgetopt::default_group_name(
854 : ""
855 : , ""
856 4 : , "project-name"));
857 1 : CATCH_REQUIRE(fullname.empty());
858 1 : }
859 :
860 : {
861 2 : std::string const fullname(advgetopt::default_group_name(
862 : ""
863 : , nullptr
864 4 : , "project-name"));
865 1 : CATCH_REQUIRE(fullname.empty());
866 1 : }
867 :
868 : {
869 2 : std::string const fullname(advgetopt::default_group_name(
870 : ""
871 : , nullptr
872 4 : , ""));
873 1 : CATCH_REQUIRE(fullname.empty());
874 1 : }
875 :
876 : {
877 2 : std::string const fullname(advgetopt::default_group_name(
878 : ""
879 : , nullptr
880 4 : , nullptr));
881 1 : CATCH_REQUIRE(fullname.empty());
882 1 : }
883 : }
884 5 : CATCH_END_SECTION()
885 :
886 5 : CATCH_START_SECTION("utils_default_group_name: single '/' at the start")
887 : {
888 5 : CATCH_REQUIRE_THROWS_MATCHES(advgetopt::default_group_name(
889 : "/this-is-a-path"
890 : , "group"
891 : , "project")
892 : , advgetopt::getopt_root_filename
893 : , Catch::Matchers::ExceptionMessage(
894 : "getopt_exception: filename \"/this-is-a-path\" starts with a slash (/), which is not allowed."));
895 : }
896 5 : CATCH_END_SECTION()
897 :
898 5 : CATCH_START_SECTION("utils_default_group_name: invalid priority")
899 : {
900 : // verify that negative priorities are prevented
901 : //
902 21 : for(int priority(-20); priority < 0; ++priority)
903 : {
904 100 : CATCH_REQUIRE_THROWS_MATCHES(advgetopt::default_group_name(
905 : "/this/is/a/basename.ext"
906 : , ((rand() & 1) == 0 ? "group-name" : nullptr)
907 : , "project-name"
908 : , priority)
909 : , advgetopt::getopt_invalid_parameter
910 : , Catch::Matchers::ExceptionMessage(
911 : "getopt_exception: priority must be a number between 0 and 99 inclusive; "
912 : + std::to_string(priority)
913 : + " is invalid."));
914 : }
915 :
916 : // verify that large priorities are prevented
917 : //
918 21 : for(int priority(100); priority < 120; ++priority)
919 : {
920 100 : CATCH_REQUIRE_THROWS_MATCHES(advgetopt::default_group_name(
921 : "/this/is/a/basename.ext"
922 : , ((rand() & 1) == 0 ? "group-name" : nullptr)
923 : , "project-name"
924 : , priority)
925 : , advgetopt::getopt_invalid_parameter
926 : , Catch::Matchers::ExceptionMessage(
927 : "getopt_exception: priority must be a number between 0 and 99 inclusive; "
928 : + std::to_string(priority)
929 : + " is invalid."));
930 : }
931 : }
932 5 : CATCH_END_SECTION()
933 :
934 5 : CATCH_START_SECTION("utils_default_group_name: Basename Only")
935 : {
936 : {
937 2 : std::string const fullname(advgetopt::default_group_name(
938 : "basename"
939 : , nullptr
940 4 : , "advgetopt"));
941 1 : CATCH_REQUIRE(fullname == "advgetopt.d/50-basename");
942 1 : }
943 :
944 : {
945 2 : std::string const fullname(advgetopt::default_group_name(
946 : "basename.ext"
947 : , "advgetopt"
948 4 : , nullptr));
949 1 : CATCH_REQUIRE(fullname == "advgetopt.d/50-basename.ext");
950 1 : }
951 : }
952 5 : CATCH_END_SECTION()
953 5 : }
954 :
955 :
956 :
957 :
958 :
959 :
960 :
961 3 : CATCH_TEST_CASE("utils_handle_user_directory", "[utils][valid]")
962 : {
963 3 : CATCH_START_SECTION("Valid cases")
964 5 : snapdev::safe_setenv env("HOME", "/home/advgetopt");
965 :
966 : {
967 3 : std::string result(advgetopt::handle_user_directory("~"));
968 1 : CATCH_REQUIRE(result == "/home/advgetopt");
969 1 : }
970 :
971 : {
972 3 : std::string result(advgetopt::handle_user_directory("~/"));
973 1 : CATCH_REQUIRE(result == "/home/advgetopt/");
974 1 : }
975 :
976 : {
977 3 : std::string result(advgetopt::handle_user_directory("~/.config/advgetopt.conf"));
978 1 : CATCH_REQUIRE(result == "/home/advgetopt/.config/advgetopt.conf");
979 1 : }
980 4 : CATCH_END_SECTION()
981 :
982 3 : CATCH_START_SECTION("$HOME is empty")
983 5 : snapdev::safe_setenv env("HOME", "");
984 :
985 : {
986 3 : std::string result(advgetopt::handle_user_directory("~"));
987 1 : CATCH_REQUIRE(result == "~");
988 1 : }
989 :
990 : {
991 3 : std::string result(advgetopt::handle_user_directory("~/.config/advgetopt.conf"));
992 1 : CATCH_REQUIRE(result == "~/.config/advgetopt.conf");
993 1 : }
994 4 : CATCH_END_SECTION()
995 :
996 3 : CATCH_START_SECTION("Paths do not start with ~")
997 5 : snapdev::safe_setenv env("HOME", "/home/advgetopt");
998 :
999 : {
1000 3 : std::string result(advgetopt::handle_user_directory("/~"));
1001 1 : CATCH_REQUIRE(result == "/~");
1002 1 : }
1003 :
1004 : {
1005 3 : std::string result(advgetopt::handle_user_directory("/~/.config/advgetopt.conf"));
1006 1 : CATCH_REQUIRE(result == "/~/.config/advgetopt.conf");
1007 1 : }
1008 4 : CATCH_END_SECTION()
1009 3 : }
1010 :
1011 :
1012 :
1013 :
1014 :
1015 2 : CATCH_TEST_CASE("utils_true_false", "[utils][boolean]")
1016 : {
1017 2 : CATCH_START_SECTION("True Values")
1018 : {
1019 1 : CATCH_REQUIRE(advgetopt::is_true("true"));
1020 1 : CATCH_REQUIRE(advgetopt::is_true("on"));
1021 1 : CATCH_REQUIRE(advgetopt::is_true("1"));
1022 :
1023 1 : CATCH_REQUIRE_FALSE(advgetopt::is_true("false"));
1024 1 : CATCH_REQUIRE_FALSE(advgetopt::is_true("off"));
1025 1 : CATCH_REQUIRE_FALSE(advgetopt::is_true("0"));
1026 :
1027 1 : CATCH_REQUIRE_FALSE(advgetopt::is_true("random"));
1028 : }
1029 2 : CATCH_END_SECTION()
1030 :
1031 2 : CATCH_START_SECTION("False Values")
1032 : {
1033 1 : CATCH_REQUIRE(advgetopt::is_false("false"));
1034 1 : CATCH_REQUIRE(advgetopt::is_false("off"));
1035 1 : CATCH_REQUIRE(advgetopt::is_false("0"));
1036 :
1037 1 : CATCH_REQUIRE_FALSE(advgetopt::is_false("true"));
1038 1 : CATCH_REQUIRE_FALSE(advgetopt::is_false("on"));
1039 1 : CATCH_REQUIRE_FALSE(advgetopt::is_false("1"));
1040 :
1041 1 : CATCH_REQUIRE_FALSE(advgetopt::is_false("random"));
1042 : }
1043 2 : CATCH_END_SECTION()
1044 2 : }
1045 :
1046 :
1047 :
1048 :
1049 :
1050 :
1051 :
1052 : // vim: ts=4 sw=4 et
|