clone url: git://git.m455.casa/utils.c
utils.c
1 | #include <stdlib.h> |
2 | #include <string.h> |
3 | #include <stdio.h> |
4 |
|
5 | struct kv { |
6 | char *key; |
7 | char *value; |
8 | }; |
9 |
|
10 | /* static void print_size_t(char* prefix, size_t size) { */ |
11 | /* printf("%s: %lu\n", prefix, (unsigned long)size); */ |
12 | /* } */ |
13 |
|
14 | /* thank you k&r pg. 143 lol */ |
15 | static char *str_dup(char *str) { |
16 | char *str_copy; |
17 |
|
18 | str_copy = malloc(strlen(str) + 1); |
19 | if (str_copy == NULL) { |
20 | perror("str_dup() malloc() returned NULL"); |
21 | return NULL; |
22 | } |
23 |
|
24 | strcpy(str_copy, str); |
25 |
|
26 | return str_copy; |
27 | } |
28 |
|
29 | static const char *char_html_escape(char c) { |
30 | switch(c) { |
31 | case '&': |
32 | return "&"; |
33 | case '<': |
34 | return "<"; |
35 | case '>': |
36 | return ">"; |
37 | case '"': |
38 | return """; |
39 | case '\'': |
40 | return "'"; |
41 | default: |
42 | return NULL; |
43 | } |
44 | } |
45 |
|
46 | static char *str_html_escape(char *str) { |
47 | char *buf; |
48 | const char *entity; |
49 | size_t str_i; |
50 | size_t buf_size; |
51 |
|
52 | buf_size = 0; |
53 | for(str_i = 0; str_i < strlen(str); str_i++) { |
54 | entity = char_html_escape(str[str_i]); |
55 | if (entity == NULL) { |
56 | buf_size++; |
57 | } |
58 | else { |
59 | buf_size = buf_size + strlen(entity); |
60 | } |
61 | } |
62 |
|
63 | buf = malloc(buf_size + 1); |
64 | if (buf == NULL) { |
65 | perror("str_html_escape() malloc() returned NULL"); |
66 | return NULL; |
67 | } |
68 | /* for strcat */ |
69 | buf[0] = '\0'; |
70 |
|
71 | /* we don't need to allocate space for the initial '\0' here, because it's |
72 | * overwritten by the first character of the string being appended by |
73 | * strncat. |
74 |
|
75 | * strncat also automatically appends a nul character at the end of each |
76 | * string concatination |
77 |
|
78 | * the '+ 1's after the strlen()s in each strncat call are so the nul |
79 | * character after the source string is included in the concatination. |
80 | */ |
81 | for(str_i = 0; str_i < strlen(str); str_i++) { |
82 | entity = char_html_escape(str[str_i]); |
83 | if (entity == NULL) { |
84 | /* the '&' is here because strncat requires the second argument |
85 | * to be a pointer, so providing just a str[str_i] would mean we |
86 | * are providing just a value of type 'char'. the '&' operator |
87 | * turns values into pointers |
88 | */ |
89 | strncat(buf, &str[str_i], 1); |
90 | } |
91 | else { |
92 | strcat(buf, entity); |
93 | } |
94 | } |
95 | return buf; |
96 | } |
97 |
|
98 | static char *str_slice(char *str, size_t start, size_t end) { |
99 | char *buf; |
100 |
|
101 | if (end < start) { |
102 | perror("error: str_slice() end index is less than start index"); |
103 | return NULL; |
104 | } |
105 |
|
106 | buf = malloc((end - start) + 1); |
107 | if (buf == NULL) { |
108 | perror("str_slice() malloc() returned NULL"); |
109 | return NULL; |
110 | } |
111 |
|
112 | strncpy(buf, str + start, end - start); |
113 |
|
114 | return buf; |
115 | } |
116 |
|
117 | static char *str_rep(char *str, char *from_str, char *to_str) { |
118 | char *buf; |
119 | char *found_from_str; |
120 | size_t occurences; |
121 |
|
122 | /* if any of these conditions are true, return the original string (str): |
123 | * - from_str and to_str are the same |
124 | * - no from_str was found in str |
125 | * - from_str is "" |
126 | */ |
127 | if (strcmp(from_str, to_str) == 0 |
128 | || strstr(str, from_str) == NULL |
129 | || (strlen(from_str) == 0 && strlen(to_str) > 0)) { |
130 | buf = str_dup(str); |
131 | return buf; |
132 | } |
133 |
|
134 | occurences = 0; |
135 | found_from_str = strstr(str, from_str); |
136 | while ((found_from_str = strstr(found_from_str, from_str)) != NULL) { |
137 | occurences++; |
138 | found_from_str = found_from_str + strlen(from_str); |
139 | } |
140 | /* first, subtrack all occurences of from_str from str */ |
141 | buf = malloc((strlen(str) - (strlen(from_str) * occurences)) + |
142 | /* next, add all occurences of to_str to str */ |
143 | (strlen(to_str) * occurences) + |
144 | /* +1 for nul terminator */ |
145 | 1); |
146 | if (buf == NULL) { |
147 | perror("str_rep() malloc() returned NULL"); |
148 | return NULL; |
149 | } |
150 | /* for strcat */ |
151 | buf[0] = '\0'; |
152 |
|
153 | while(occurences != 0) { |
154 | found_from_str = strstr(str, from_str); |
155 | if (found_from_str == NULL) { |
156 | free(buf); |
157 | perror("str_rep() while() strstr() returned NULL"); |
158 | return NULL; |
159 | } |
160 |
|
161 | /* add text before from_str: |
162 | * 'found_from_str - str' returns the index of first character of |
163 | * from_str. this index is used as a maximum amount of bytes to copy by |
164 | * strncat, so it won't actually include the first character. |
165 |
|
166 | * for example: |
167 |
|
168 | * if "you" is the from_str in the string "hello you", we have the |
169 | * following indices: |
170 |
|
171 | * |h|e|l|l|o| |y|o|u |
172 | * |0|1|2|3|4|5|6|7|8 |
173 |
|
174 | * the first character of "you" ('y'), starts off at index 6. |
175 |
|
176 | * we convert this index (6) from int to size_t because strncat's |
177 | * 'count' parameter requires a type of size_t. strncat then uses the |
178 | * converted value as its limit of how many bytes to concatinate, |
179 | * resulting in us copying up to, and including, 6 bytes into buf like |
180 | * this: |
181 |
|
182 | * |h|e|l|l|o| |y|o|u |
183 | * |1|2|3|4|5|6|7|8|9 |
184 | */ |
185 | strncat(buf, str, (size_t)(found_from_str - str)); |
186 |
|
187 | strcat(buf, to_str); |
188 |
|
189 | /* create a new str to operate on, which is everything after the |
190 | * from_str in str |
191 | */ |
192 | str = found_from_str + strlen(from_str); |
193 | occurences--; |
194 | } |
195 |
|
196 | /* add remaining text that didn't contain the from_str to buffer: str is |
197 | * actually mutated in the while loop above, so 'str' is actually the range |
198 | * of text after the last character of the last instance of from_str in str. |
199 | */ |
200 | strcat(buf, str); |
201 |
|
202 | return buf; |
203 | } |
204 |
|
205 | static char *str_format(char *str, char *from_str, char *to_str) { |
206 | char *mustached_from_str; |
207 | char *formatted_str; |
208 |
|
209 | mustached_from_str = malloc(strlen("{{") + strlen(from_str) + strlen("}}") + 1); |
210 | if (mustached_from_str == NULL) { |
211 | perror("str_format() malloc() returned NULL"); |
212 | return NULL; |
213 | } |
214 |
|
215 | sprintf(mustached_from_str, "{{%s}}", from_str); |
216 |
|
217 | formatted_str = str_rep(str, mustached_from_str, to_str); |
218 | if (formatted_str == NULL) { |
219 | free(mustached_from_str); |
220 | perror("str_format() str_rep() returned NULL"); |
221 | return NULL; |
222 | } |
223 |
|
224 | free(mustached_from_str); |
225 |
|
226 | return formatted_str; |
227 | } |
228 |
|
229 | static size_t pairs_in_file(char *file) { |
230 | FILE *file_stream; |
231 | char line[256]; |
232 | char *token; |
233 | int token_count; |
234 | size_t pair_count; |
235 |
|
236 | file_stream = fopen(file, "r"); |
237 | if (file_stream == NULL) { |
238 | perror("file_to_kvs() fopen() failed"); |
239 | fclose(file_stream); |
240 | return 0; |
241 | } |
242 |
|
243 | pair_count = 0; |
244 | while (fgets(line, sizeof(line), file_stream) != NULL) { |
245 | token_count = 0; |
246 | token = strtok(line, "=\n"); |
247 | if (token != NULL) { |
248 | token_count++; |
249 | token = strtok(NULL, "=\n"); |
250 | if (token != NULL) { |
251 | token_count++; |
252 | } |
253 | } |
254 | if (token_count != 2) { |
255 | perror("pairs_in_file() while loop failed: incorrect key-value formatting in config file"); |
256 | fclose(file_stream); |
257 | return 0; |
258 | } |
259 | pair_count++; |
260 | } |
261 | fclose(file_stream); |
262 |
|
263 | return pair_count; |
264 | } |
265 |
|
266 | /* this will eventuallyyyyy populate a template, but for now i just want to */ |
267 | /* properly return a struct and figure out how to free its memory lol */ |
268 | static struct kv *file_to_kvs(char *file) { |
269 | FILE *file_stream; |
270 | char line[256]; |
271 | char *token; |
272 | size_t pair_count; |
273 | size_t i; |
274 | size_t j; |
275 | struct kv *config; |
276 |
|
277 | /* do this before fopen below, so we aren't trying to read the file while it's already open */ |
278 | pair_count = pairs_in_file(file); |
279 | if (pair_count == 0) { |
280 | perror("file_to_kvs() pair_count is 0"); |
281 | return NULL; |
282 | } |
283 |
|
284 | file_stream = fopen(file, "r"); |
285 | if (file_stream == NULL) { |
286 | perror("file_to_kvs() fopen() failed"); |
287 | fclose(file_stream); |
288 | return NULL; |
289 | } |
290 |
|
291 | config = calloc(pair_count, sizeof(struct kv)); |
292 | if (config == NULL) { |
293 | perror("file_to_kvs() malloc() returned NULL"); |
294 | fclose(file_stream); |
295 | return NULL; |
296 | } |
297 |
|
298 | i = 0; |
299 | while (fgets(line, sizeof(line), file_stream) != NULL) { |
300 | token = strtok(line, "=\n"); |
301 | if (token != NULL) { |
302 | config[i].key = str_dup(token); |
303 | if (config[i].key == NULL) { |
304 | perror("file_to_kvs() config[i].key value is NULL"); |
305 | for(j = 0; j < i; j++) { |
306 | free(config[j].key); |
307 | free(config[j].value); |
308 | }; |
309 | free(config); |
310 | return NULL; |
311 | } |
312 |
|
313 | token = strtok(NULL, "=\n"); |
314 | if (token != NULL) { |
315 | config[i].value = str_dup(token); |
316 | if (config[i].value == NULL) { |
317 | perror("file_to_kvs() config[i].value value is NULL"); |
318 | for(j = 0; j < i; j++) { |
319 | free(config[j].key); |
320 | free(config[j].value); |
321 | }; |
322 | free(config); |
323 | return NULL; |
324 | } |
325 | } |
326 | } |
327 | i++; |
328 | } |
329 |
|
330 | fclose(file_stream); |
331 |
|
332 | return config; |
333 | } |
334 |
|
335 | int main(void) { |
336 | char *str_rep_result; |
337 | char *str_slice_result; |
338 | char *str_html_escape_result; |
339 | char *str_format_result; |
340 | struct kv *config; |
341 | size_t pair_count; |
342 | size_t i; |
343 |
|
344 | /* str_rep() test ========================================= */ |
345 | str_rep_result = str_rep("hey i'm a dog here. are you a dog too? okay cool, you're a dog too lol.", "dog", "person"); |
346 | if (str_rep_result == NULL) { |
347 | perror("main() str_rep() returned NULL"); |
348 | return 1; |
349 | } |
350 | /* printf("%s\n", str_rep_result); */ |
351 | free(str_rep_result); |
352 |
|
353 | /* str_slice() test ========================================= */ |
354 | str_slice_result = str_slice("hello", 0, strlen("hello") - 1); |
355 | if (str_slice_result == NULL) { |
356 | perror("main() str_slice() returned NULL"); |
357 | return 1; |
358 | } |
359 | /* printf("%s\n", str_slice_result); */ |
360 | free(str_slice_result); |
361 |
|
362 | /* str_html_escape() test ========================================= */ |
363 | str_html_escape_result = str_html_escape("hello 小李, i'm bob, & you?"); |
364 | if (str_html_escape_result == NULL) { |
365 | perror("main() str_html_escape() returned NULL"); |
366 | return 1; |
367 | } |
368 | /* printf("%s\n", str_html_escape_result); */ |
369 |
|
370 | /* double checking that the returned buffer size is correct */ |
371 | /* print_size_t("returned size", strlen(str_html_escape_result) + 1); */ |
372 | /* print_size_t("converted size", strlen("hello 小李, i'm bob, & you?") + 1); */ |
373 | free(str_html_escape_result); |
374 |
|
375 | /* str_format() test ============================================== */ |
376 | str_format_result = str_format("hello {{name}}, what's up?", "name", "sherry"); |
377 | if (str_format_result == NULL) { |
378 | perror("main() str_format() returned NULL"); |
379 | return 1; |
380 | } |
381 | /* printf("%s\n", str_format_result); */ |
382 | free(str_format_result); |
383 |
|
384 | /* file_to_kvs stuff =================================================== */ |
385 | config = file_to_kvs("config.txt"); |
386 | if (config == NULL) { |
387 | perror("main() file_to_kvs() returned NULL"); |
388 | return 1; |
389 | } |
390 |
|
391 | pair_count = pairs_in_file("config.txt"); |
392 | if (pair_count == 0) { |
393 | perror("file_to_kvs() pair_count is 0"); |
394 | return 1; |
395 | } |
396 | for(i = 0; i < pair_count; i++) { |
397 | printf("'%s': '%s'\n", config[i].key, config[i].value); |
398 | } |
399 |
|
400 | for(i = 0; i < pair_count; i++) { |
401 | free(config[i].key); |
402 | free(config[i].value); |
403 | }; |
404 |
|
405 | free(config); |
406 |
|
407 | return(0); |
408 | } |