ohmu
·
2026-10-15
1#include "common/properties.h"
2#include "common/textModifier.h"
3
4#include <stdio.h>
5#include <stdlib.h>
6
7static void verify(bool expression, const char* expressionStr, int lineNo) {
8 if (expression) {
9 return;
10 }
11
12 fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, expressionStr);
13 exit(1);
14}
15
16#define VERIFY(expression) verify((expression), #expression, __LINE__)
17
18static void verifyLine(const char* line, const char* start, bool expectedResult, const char* expectedValue, int lineNo) {
19 FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
20 const char* cursor = line;
21 const bool result = ffParsePropLinePointer(&cursor, start, &buffer);
22
23 if (result != expectedResult || (result && !ffStrbufEqualS(&buffer, expectedValue))) {
24 fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffParsePropLine(\"%s\", \"%s\"): expected %s \"%s\", got %s \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
25 lineNo, line, start, expectedResult ? "true" : "false", expectedResult ? expectedValue : "",
26 result ? "true" : "false", result ? buffer.chars : "");
27 exit(1);
28 }
29}
30
31#define VERIFY_LINE(...) verifyLine(__VA_ARGS__, __LINE__)
32
33static void verifyLines(const char* lines, const char* start, bool expectedResult, const char* expectedValue, int lineNo) {
34 FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
35 const bool result = ffParsePropLines(lines, start, &buffer);
36
37 if (result != expectedResult || (result && !ffStrbufEqualS(&buffer, expectedValue))) {
38 fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffParsePropLines(\"%s\", \"%s\"): expected %s \"%s\", got %s \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
39 lineNo, lines, start, expectedResult ? "true" : "false", expectedResult ? expectedValue : "",
40 result ? "true" : "false", result ? buffer.chars : "");
41 exit(1);
42 }
43}
44
45#define VERIFY_LINES(...) verifyLines(__VA_ARGS__, __LINE__)
46
47int main(void) {
48 // The plain case: `key: value`, with the value running to the end of the line.
49 {
50 VERIFY_LINE("Name: foo", "Name:", true, "foo");
51 VERIFY_LINE("Name: foo\n", "Name:", true, "foo");
52 VERIFY_LINE("Name: foo\nBar: baz", "Name:", true, "foo"); // stops at the newline
53 VERIFY_LINE("Name:foo", "Name:", true, "foo");
54 VERIFY_LINE("Name: ", "Name:", true, ""); // found, but the value is empty
55 VERIFY_LINE("Name:", "Name:", true, "");
56 VERIFY_LINE("Name: a:b", "Name:", true, "a:b"); // only the first colon is the separator
57 VERIFY_LINE("Name: foo bar", "Name:", true, "foo bar"); // spaces inside the value are kept
58 }
59
60 // Any amount of whitespace in the format matches any amount of whitespace in the line, including
61 // none -- the two sides are independent.
62 {
63 VERIFY_LINE("Name: foo", "Name:", true, "foo");
64 VERIFY_LINE("Name:foo", "Name: ", true, "foo");
65 VERIFY_LINE("Name: foo", "Name: ", true, "foo");
66 VERIFY_LINE("Name:\tfoo", "Name: ", true, "foo");
67 VERIFY_LINE("Name: foo", "Name:\t", true, "foo");
68 VERIFY_LINE(" \t Name: foo", "Name:", true, "foo"); // leading whitespace of the line is skipped
69 }
70
71 // A mismatch anywhere in the key makes the whole line fail, and the key must match in full.
72 {
73 VERIFY_LINE("Name: foo", "Value:", false, "");
74 VERIFY_LINE("name: foo", "Name:", true, "foo"); // the key is compared case insensitively
75 VERIFY_LINE("NAME: foo", "name:", true, "foo");
76 VERIFY_LINE("nAmE: foo", "NAME:", true, "foo");
77 VERIFY_LINE("NameX: foo", "Name:", false, "");
78 VERIFY_LINE("Name :foo", "Name:", false, "");
79 VERIFY_LINE("Nam", "Name:", false, ""); // the line ends before the key does
80 VERIFY_LINE("", "Name:", false, "");
81 VERIFY_LINE("\n", "Name:", false, "");
82 VERIFY_LINE("Name", "Name:", false, "");
83 }
84
85 // Trailing spaces are trimmed, but nothing else is: a trailing tab survives.
86 {
87 VERIFY_LINE("Name: foo ", "Name:", true, "foo");
88 VERIFY_LINE("Name: foo\t", "Name:", true, "foo\t");
89 VERIFY_LINE("Name: foo \n", "Name:", true, "foo");
90 }
91
92 // A quoted value ends at its closing quote, so trailing text is not part of the value.
93 {
94 VERIFY_LINE("Name: \"foo bar\"", "Name:", true, "foo bar");
95 VERIFY_LINE("Name: 'foo bar'", "Name:", true, "foo bar");
96 VERIFY_LINE("Name: \"foo\" trailing", "Name:", true, "foo");
97 VERIFY_LINE("Name: \"\"", "Name:", true, "");
98 VERIFY_LINE("Name: \"foo", "Name:", true, "foo"); // unterminated quote: the line end closes it
99
100 // The trim still applies, and it only removes the right hand side
101 VERIFY_LINE("Name: \" foo \"", "Name:", true, " foo");
102 }
103
104 // A key ending in `>` switches the parser to XML mode, where the value ends at `<` instead of at
105 // the end of the line.
106 {
107 VERIFY_LINE("<name>foo</name>", "<name>", true, "foo");
108 VERIFY_LINE("<name></name>", "<name>", true, "");
109 VERIFY_LINE("<name>foo", "<name>", true, "foo"); // no closing tag
110 VERIFY_LINE("<name>foo bar</name>", "<name>", true, "foo bar");
111 VERIFY_LINE("<name> foo</name>", "<name>", true, "foo");
112 VERIFY_LINE("<name>foo\n", "<name>", true, "foo");
113 }
114
115 // ffParsePropLines walks the lines until the key is found, and the first match wins -- including
116 // when that match has an empty value.
117 {
118 VERIFY_LINES("A: 1\nName: foo\nB: 2", "Name:", true, "foo");
119 VERIFY_LINES("A: 1\nName: foo", "Name:", true, "foo");
120 VERIFY_LINES("Name: foo", "Name:", true, "foo");
121 VERIFY_LINES("\n\nName: foo", "Name:", true, "foo");
122 VERIFY_LINES("Name: foo\nName: bar", "Name:", true, "foo");
123 VERIFY_LINES("Name:\nName: bar", "Name:", true, "");
124 VERIFY_LINES("A: 1\nB: 2", "Name:", false, "");
125 VERIFY_LINES("A: 1\nB: 2\n", "Name:", false, "");
126 VERIFY_LINES("", "Name:", false, "");
127 }
128
129 // The value is appended to the buffer, so a caller can collect several keys into one string.
130 {
131 FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateS("pre");
132 VERIFY(ffParsePropLines("Name: foo", "Name:", &buffer));
133 VERIFY(ffStrbufEqualS(&buffer, "prefoo"));
134 }
135
136 puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
137 return 0;
138}