Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
option_parser.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
8
9#include "option_parser.h"
10
11#include <glib.h>
12#include <stdbool.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <errno.h>
17
18#include "dunst.h"
19#include "log.h"
20#include "utils.h"
21#include "settings.h"
22#include "rules.h"
23#include "settings_data.h"
24
25static int cmdline_argc;
26static char **cmdline_argv;
27static char *usage_str = NULL;
28
29#define STRING_PARSE_RET(string, value) if (STR_EQ(s, string)) { *ret = value; return true; }
30
31int string_parse_enum(const void *data, const char *s, void * ret) {
32 struct string_to_enum_def *string_to_enum = (struct string_to_enum_def*)data;
33 for (int i = 0; string_to_enum[i].string != NULL; i++) {
34 if (strcmp(s, string_to_enum[i].string) == 0) {
35 *(int*) ret = string_to_enum[i].enum_value;
36 LOG_D("Setting enum to %i (%s)", *(int*) ret, string_to_enum[i].string);
37 return true;
38 }
39 }
40 return false;
41}
42
43// Only changes the return when succesful
44//
45// @return True for success, false otherwise.
46int string_parse_enum_list(const void *data, char **s, void *ret_void)
47{
48 int **ret = (int **) ret_void;
49 int *tmp;
50 ASSERT_OR_RET(s, false);
51 ASSERT_OR_RET(ret, false);
52
53 int len = string_array_length(s);
54
55 tmp = g_malloc_n((len + 1), sizeof(int));
56 for (int i = 0; i < len; i++) {
57 if (!string_parse_enum(data, s[i], tmp + i)) {
58 LOG_W("Unknown mouse action value: '%s'", s[i]);
59 g_free(tmp);
60 return false;
61 }
62 }
63 tmp[len] = MOUSE_ACTION_END; // sentinel end value
64 g_free(*ret);
65 *ret = tmp;
66 return true;
67}
68
78int string_parse_enum_list_to_single(const void *data, char **s, int *ret)
79{
80 int tmp = 0, tmp_ret = 0;
81 ASSERT_OR_RET(s, false);
82 ASSERT_OR_RET(ret, false);
83
84 int len = string_array_length(s);
85 for (int i = 0; i < len; i++) {
86 if (!string_parse_enum(data, s[i], &tmp)) {
87 LOG_W("Unknown value: '%s'", s[i]);
88 return false;
89 }
90 tmp_ret |= tmp;
91 }
92 *ret = tmp_ret;
93 return true;
94}
95
96int string_parse_corners(const void *data, const char *s, void *ret)
97{
98 char **s_arr = string_to_array(s, ",");
99 int success = string_parse_enum_list_to_single(data, s_arr, ret);
100 g_strfreev(s_arr);
101 return success;
102}
103
109bool string_parse_int_list(char **s, int **ret, bool allow_empty) {
110 int len = string_array_length(s);
111 ASSERT_OR_RET(s, false);
112
113 int *tmp = g_malloc_n((len + 1), sizeof(int));
114 for (int i = 0; i < len; i++) {
115 if (allow_empty && STR_EMPTY(s[i])) {
116 tmp[i] = -1;
117 continue;
118 }
119 bool success = safe_string_to_int(&tmp[i], s[i]);
120 if (!success) {
121 LOG_W("Invalid int value: '%s'", s[i]);
122 g_free(tmp);
123 return false;
124 }
125
126 }
127
128 tmp[len] = LIST_END;
129 g_free(*ret);
130 *ret = tmp;
131 return true;
132}
133
137int string_parse_list(const void *data, const char *s, void *ret) {
138 const enum list_type type = GPOINTER_TO_INT(data);
139 char **arr = NULL;
140 int success = false;
141 switch (type) {
142 case MOUSE_LIST:
143 arr = string_to_array(s, ",");
144 success = string_parse_enum_list(&mouse_action_enum_data, arr, ret);
145 break;
146 case OFFSET_LIST:
147 arr = string_to_array(s, "x");
148 int len = string_array_length(arr);
149 if (len != 2) {
150 success = false;
151 break;
152 }
153 int *int_arr = NULL;
154 success = string_parse_int_list(arr, &int_arr, false);
155 if (!success)
156 break;
157
158 struct position* offset = (struct position*) ret;
159 offset->x = int_arr[0];
160 offset->y = int_arr[1];
161 g_free(int_arr);
162 break;
163 case STRING_LIST: ;
164 g_strfreev(*(char ***) ret);
165 *(char ***) ret = string_to_array(s, ",");
166 success = true;
167 break;
168
169 default:
170 LOG_W("Don't know this list type: %i", type);
171 break;
172 }
173 g_strfreev(arr);
174 return success;
175}
176
177int string_parse_sepcolor(const void *data, const char *s, void *ret)
178{
179 LOG_D("parsing sep_color");
180 struct separator_color_data *sep_color = (struct separator_color_data*) ret;
181 struct color invalid = COLOR_UNINIT;
182
183 enum separator_color type;
184 bool is_enum = string_parse_enum(data, s, &type);
185 if (is_enum) {
186 sep_color->type = type;
187 sep_color->color = invalid;
188 return true;
189 } else {
190 if (STR_EMPTY(s)) {
191 LOG_W("Sep color is empty, make sure to quote the value if it's a color.");
192 return false;
193 }
194
195 if (string_parse_color(s, &sep_color->color)) {
196 sep_color->type = SEP_CUSTOM;
197 return true;
198 }
199 }
200 return false;
201}
202
203#define UINT_MAX_N(bits) ((1 << bits) - 1)
204
209int string_parse_color(const char *s, struct color *ret)
210{
211 if (STR_EMPTY(s) || *s != '#') {
212 LOG_W("A color string should start with '#' and contain at least 3 hex characters");
213 return false;
214 }
215
216 char *end = NULL;
217 unsigned long val = strtoul(s + 1, &end, 16);
218
219 if (end[0] != '\0' && end[1] != '\0') {
220 LOG_W("Invalid color string: '%s'", s);
221 return false;
222 }
223
224 int bpc = 0;
225 switch (end - (s + 1)) {
226 case 3:
227 bpc = 4;
228 val = (val << 4) | 0xF;
229 break;
230 case 6:
231 bpc = 8;
232 val = (val << 8) | 0xFF;
233 break;
234 case 4:
235 bpc = 4;
236 break;
237 case 8:
238 bpc = 8;
239 break;
240 default:
241 LOG_W("Invalid color string: '%s'", s);
242 return false;
243 }
244
245 const unsigned single_max = UINT_MAX_N(bpc);
246
247 ret->r = ((val >> 3 * bpc) & single_max) / (double)single_max;
248 ret->g = ((val >> 2 * bpc) & single_max) / (double)single_max;
249 ret->b = ((val >> 1 * bpc) & single_max) / (double)single_max;
250 ret->a = ((val) & single_max) / (double)single_max;
251
252 return true;
253}
254
255int string_parse_gradient(const char *s, struct gradient **ret)
256{
257 struct color colors[16];
258 size_t length = 0;
259
260 gchar **strs = g_strsplit(s, ",", -1);
261 for (int i = 0; strs[i] != NULL; i++) {
262 if (i > 16) {
263 LOG_W("Do you really need so many colors? ;)");
264 break;
265 }
266
267 if (!string_parse_color(g_strstrip(strs[i]), &colors[length++])) {
268 g_strfreev(strs);
269 return false;
270 }
271 }
272
273 g_strfreev(strs);
274 if (length == 0) {
275 LOG_W("Provide at least one color");
276 return false;
277 }
278
279 *ret = gradient_alloc(length);
280 memcpy((*ret)->colors, colors, length * sizeof(struct color));
281 gradient_pattern(*ret);
282
283 return true;
284}
285
286int string_parse_bool(const void *data, const char *s, void *ret)
287{
288 // This is needed, since string_parse_enum assumses a
289 // variable of size int is passed
290 int tmp_int = -1;
291 bool success = string_parse_enum(data, s, &tmp_int);
292
293 *(bool*) ret = (bool) tmp_int;
294 return success;
295}
296
298int string_parse_maybe_int(const void *data, const char *s, void *ret)
299{
300 int *intval = (int *)data;
301 if (!safe_string_to_int(intval, s)) {
302 *intval = INT_MIN;
303 }
304
305 g_free(*(char**) ret);
306 *(char**) ret = g_strdup(s);
307 return true;
308}
309
310int get_setting_id(const char *key, const char *section) {
311 int error_code = 0;
312 int partial_match_id = -1;
313 bool match_section = section && is_special_section(section);
314 if (!match_section) {
315 LOG_D("not matching section %s", section);
316 }
317 for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) {
318 if (strcmp(allowed_settings[i].name, key) == 0) {
319 bool is_rule = allowed_settings[i].rule_offset > 0;
320
321 // a rule matches every section
322 if (is_rule || strcmp(section, allowed_settings[i].section) == 0) {
323 return i;
324 } else {
325 // name matches, but in wrong section. Continueing to see
326 // if we find the same setting name with another section
327 error_code = -2;
328 partial_match_id = i;
329 continue;
330 }
331 }
332 }
333
334 if (error_code == -2) {
335 LOG_W("Setting %s is in the wrong section (%s, should be %s)",
336 key, section,
337 allowed_settings[partial_match_id].section);
338 // found, but in wrong section
339 return -2;
340 }
341
342 // not found
343 return -1;
344}
345
349int string_parse_length(void *ret_in, const char *s) {
350 struct length *ret = (struct length*) ret_in;
351 char *s_stripped = string_strip_brackets(s);
352
353 // single int without brackets
354 if (!s_stripped) {
355 int val = 0;
356 bool success = safe_string_to_int(&val, s);
357 if (!success) {
358 LOG_W("Specify either a single value or two comma-separated values between parentheses");
359 return false;
360 }
361
362 ret->min = val;
363 ret->max = val;
364 return true;
365 }
366
367 char **s_arr = string_to_array(s_stripped, ",");
368 g_free(s_stripped);
369
370 int len = string_array_length(s_arr);
371 if (len != 2) {
372 g_strfreev(s_arr);
373 LOG_W("Specify either a single value or two comma-separated values between parentheses");
374 return false;
375 }
376
377 int *int_arr = NULL;
378 bool success = string_parse_int_list(s_arr, &int_arr, true);
379 g_strfreev(s_arr);
380
381 if (!success)
382 return false;
383
384 ret->min = int_arr[0] == -1 ? INT_MIN : int_arr[0];
385 ret->max = int_arr[1] == -1 ? INT_MAX : int_arr[1];
386
387 g_free(int_arr);
388 return success;
389}
390
391bool set_from_string(void *target, struct setting setting, const char *value) {
392 GError *error = NULL;
393
394 if (!strlen(value) && setting.type != TYPE_STRING) {
395 LOG_W("Cannot set empty value for setting %s", setting.name);
396 return false;
397 }
398
399 bool success = false;
400 // Do not use setting.value, since we might want to set a rule.
401 // Use target instead!
402 switch (setting.type) {
403 case TYPE_INT:
404 return safe_string_to_int(target, value);
405 case TYPE_DOUBLE:
406 return safe_string_to_double(target, value);
407 case TYPE_STRING:
408 g_free(*(char**) target);
409 *(char**) target = g_strdup(value);
410 return true;
411 case TYPE_CUSTOM:
412 if (setting.parser == NULL) {
413 LOG_W("Setting %s doesn't have parser", setting.name);
414 return false;
415 }
416 success = setting.parser(setting.parser_data, value, target);
417
418 if (!success) LOG_W("Invalid %s value: '%s'", setting.name, value);
419 return success;
420 case TYPE_PATH: ;
421 g_free(*(char**) target);
422 *(char**) target = string_to_path(g_strdup(value));
423
424 // TODO make scripts take arguments in the config and
425 // deprecate the arguments that are now passed to the
426 // scripts
427 if (!setting.parser_data)
428 return true;
429 g_strfreev(*(char***)setting.parser_data);
430 if (!g_shell_parse_argv(*(char**) target, NULL, (char***)setting.parser_data, &error)) {
431 LOG_W("Unable to parse %s command: '%s'. "
432 "It's functionality will be disabled.",
433 setting.name, error->message);
434 g_error_free(error);
435 return false;
436 }
437 return true;
438 case TYPE_TIME: ;
439 gint64 tmp_time = string_to_time(value);
440 if (errno != 0) {
441 return false;
442 }
443 *(gint64*) target = tmp_time;
444 return true;
445 case TYPE_LIST: ;
446 LOG_D("list type %i", GPOINTER_TO_INT(setting.parser_data));
447 return string_parse_list(setting.parser_data, value, target);
448 case TYPE_LENGTH:
449 // Keep compatibility with old offset syntax
450 if (STR_EQ(setting.name, "offset") && string_parse_list(GINT_TO_POINTER(OFFSET_LIST), value, target)) {
451 LOG_M("Using legacy offset syntax NxN, you should switch to the new syntax (N, N)");
452 return true;
453 }
454
455 // Keep compatibility with old height semantics
456 if (STR_EQ(setting.name, "height") && string_is_int(value)) {
457 LOG_M("Setting 'height' has changed behaviour after dunst 1.12.0, see https://dunst-project.org/release/#v1.12.0.");
458 LOG_M("Legacy height support may be dropped in the future. If you want to hide this message transition to");
459 LOG_M("'height = (0, X)' for dynamic height (old behaviour equivalent) or to 'height = (X, X)' for a fixed height.");
460
461 int height;
462 if (!safe_string_to_int(&height, value))
463 return false;
464
465 ((struct length *)target)->min = 0;
466 ((struct length *)target)->max = height;
467 return true;
468 }
469 return string_parse_length(target, value);
470 case TYPE_COLOR:
471 return string_parse_color(value, target);
472 case TYPE_GRADIENT:
473 return string_parse_gradient(value, target);
474 default:
475 LOG_W("Setting type of '%s' is not known (type %i)", setting.name, setting.type);
476 return false;
477 }
478}
479
480bool set_setting(struct setting setting, char* value) {
481 LOG_D("[%s] Trying to set %s to %s", setting.section, setting.name, value);
482 if (setting.value == NULL) {
483 // setting.value is NULL, so it must be only a rule
484 return true;
485 }
486
487 return set_from_string(setting.value, setting, value);
488}
489
490int set_rule_value(struct rule* r, struct setting setting, char* value) {
491 // Apply rule member offset. Converting to char* because it's
492 // guaranteed to be 1 byte
493 void *target = (char*)r + setting.rule_offset;
494
495 return set_from_string(target, setting, value);
496}
497
498bool set_rule(struct setting setting, char* value, char* section) {
499 struct rule *r = get_rule(section);
500 if (!r) {
501 r = rule_new(section);
502 LOG_D("Creating new rule '%s'", section);
503 }
504 return set_rule_value(r, setting, value);
505}
506
507void set_defaults(void) {
508 LOG_D("Initializing settings");
509 settings = (struct settings) {0};
510
511 for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) {
512 // FIXME Rule settings can only have a default if they have an
513 // working entry in the settings struct as well. Make an
514 // alternative way of setting defaults for rules.
515
516 if (!allowed_settings[i].value) // don't set default if it's only a rule
517 continue;
518
519 if(!set_setting(allowed_settings[i], allowed_settings[i].default_value)) {
520 LOG_E("Could not set default of setting %s", allowed_settings[i].name);
521 }
522 }
523}
524
525void save_settings(struct ini *ini) {
526 for (int i = 0; i < ini->section_count; i++) {
527 const struct section curr_section = ini->sections[i];
528
529 if (is_deprecated_section(curr_section.name)) {
530 LOG_W("Section %s is deprecated.\n%s\nIgnoring this section.",
531 curr_section.name,
532 get_section_deprecation_message(curr_section.name));
533 continue;
534 }
535
536 LOG_D("Entering section [%s]", curr_section.name);
537 for (int j = 0; j < curr_section.entry_count; j++) {
538 const struct entry curr_entry = curr_section.entries[j];
539 int setting_id = get_setting_id(curr_entry.key, curr_section.name);
540 struct setting curr_setting = allowed_settings[setting_id];
541 if (setting_id < 0) {
542 if (setting_id == -1) {
543 LOG_W("Setting %s in section %s doesn't exist", curr_entry.key, curr_section.name);
544 }
545 continue;
546 }
547
548 bool is_rule = curr_setting.rule_offset > 0;
549 if (is_special_section(curr_section.name)) {
550 if (is_rule) {
551 // set as a rule, but only if it's not a filter
552 if (rule_offset_is_modifying(curr_setting.rule_offset)) {
553 LOG_D("Adding rule '%s = %s' to special section %s",
554 curr_entry.key,
555 curr_entry.value,
556 curr_section.name);
557 set_rule(curr_setting, curr_entry.value, curr_section.name);
558 } else {
559 LOG_W("Cannot use filtering rules in special section. Ignoring %s in section %s.",
560 curr_entry.key,
561 curr_section.name);
562 }
563 } else {
564 // set as a regular setting
565 char *value = g_strstrip(curr_entry.value);
566 set_setting(curr_setting, value);
567 }
568 } else {
569 // interpret this section as a rule
570 LOG_D("Adding rule '%s = %s' to section %s",
571 curr_entry.key,
572 curr_entry.value,
573 curr_section.name);
574 set_rule(curr_setting, curr_entry.value, curr_section.name);
575 }
576 }
577 }
578}
579
580void cmdline_load(int argc, char *argv[])
581{
582 cmdline_argc = argc;
583 cmdline_argv = argv;
584}
585
586static int cmdline_find_option(const char *key, int start)
587{
588 ASSERT_OR_RET(key, -1);
589
590 gchar **keys = g_strsplit(key, "/", -1);
591
592 for (int i = 0; keys[i] != NULL; i++) {
593 for (int j = start; j < cmdline_argc; j++) {
594 if (STR_EQ(keys[i], cmdline_argv[j])) {
595 g_strfreev(keys);
596 return j;
597 }
598 }
599 }
600
601 g_strfreev(keys);
602 return -1;
603}
604
605static const char *cmdline_get_value(const char *key, int start, int *found)
606{
607 int idx = cmdline_find_option(key, start);
608 if (idx < 0) {
609 return NULL;
610 }
611
612 if (found)
613 *found = idx + 1;
614
615 if (idx + 1 >= cmdline_argc) {
616 /* the argument is missing */
617 LOG_W("%s: Missing argument. Ignoring.", key);
618 return NULL;
619 }
620 return cmdline_argv[idx + 1];
621}
622
623char *cmdline_get_string_offset(const char *key, const char *def, int start, int *found)
624{
625 const char *str = cmdline_get_value(key, start, found);
626
627 if (str)
628 return g_strdup(str);
629 if (def)
630 return g_strdup(def);
631 else
632 return NULL;
633}
634
635char *cmdline_get_string(const char *key, const char *def, const char *description)
636{
637 cmdline_usage_append(key, "string", description);
638 return cmdline_get_string_offset(key, def, 1, NULL);
639}
640
641char *cmdline_get_path(const char *key, const char *def, const char *description)
642{
643 cmdline_usage_append(key, "string", description);
644 const char *str = cmdline_get_value(key, 1, NULL);
645
646 if (str)
647 return string_to_path(g_strdup(str));
648 else
649 return string_to_path(g_strdup(def));
650}
651
652char **cmdline_get_list(const char *key, const char *def, const char *description)
653{
654 cmdline_usage_append(key, "list", description);
655 const char *str = cmdline_get_value(key, 1, NULL);
656
657 if (str)
658 return string_to_array(str, ",");
659 else
660 return string_to_array(def, ",");
661}
662
663gint64 cmdline_get_time(const char *key, gint64 def, const char *description)
664{
665 cmdline_usage_append(key, "time", description);
666 const char *timestring = cmdline_get_value(key, 1, NULL);
667 gint64 val = def;
668
669 if (timestring) {
670 val = string_to_time(timestring);
671 }
672
673 return val;
674}
675
676int cmdline_get_int(const char *key, int def, const char *description)
677{
678 cmdline_usage_append(key, "int", description);
679 const char *str = cmdline_get_value(key, 1, NULL);
680
681 if (str)
682 return atoi(str);
683 else
684 return def;
685}
686
687double cmdline_get_double(const char *key, double def, const char *description)
688{
689 cmdline_usage_append(key, "double", description);
690 const char *str = cmdline_get_value(key, 1, NULL);
691
692 if (str)
693 return atof(str);
694 else
695 return def;
696}
697
698int cmdline_get_bool(const char *key, int def, const char *description)
699{
700 cmdline_usage_append(key, "", description);
701 int idx = cmdline_find_option(key, 1);
702
703 if (idx > 0)
704 return true;
705 else
706 return def;
707}
708
709bool cmdline_is_set(const char *key)
710{
711 return cmdline_get_value(key, 1, NULL) != NULL;
712}
713
714void cmdline_usage_append(const char *key, const char *type, const char *description)
715{
716 char *key_type;
717 if (STR_FULL(type))
718 key_type = g_strdup_printf("%s (%s)", key, type);
719 else
720 key_type = g_strdup(key);
721
722 if (!usage_str) {
723 usage_str =
724 g_strdup_printf("%-50s - %s\n", key_type, description);
725 g_free(key_type);
726 return;
727 }
728
729 char *tmp;
730 tmp = g_strdup_printf("%s%-50s - %s\n", usage_str, key_type, description);
731 g_free(key_type);
732
733 g_free(usage_str);
734 usage_str = tmp;
735
736}
737
738const char *cmdline_create_usage(void)
739{
740 return usage_str;
741}
Main event loop logic.
Logging subsystem and helpers.
#define LOG_E
Prefix message with "[<source path>:<function name>:<line number>] ".
Definition log.h:42
bool string_parse_int_list(char **s, int **ret, bool allow_empty)
Parse a list of integers.
int string_parse_length(void *ret_in, const char *s)
int string_parse_color(const char *s, struct color *ret)
Parse a color string.
int string_parse_enum_list_to_single(const void *data, char **s, int *ret)
Parse a string list of enum values and return a single integer with the values bit-flipped into it.
int string_parse_list(const void *data, const char *s, void *ret)
int string_parse_maybe_int(const void *data, const char *s, void *ret)
Parse a string that may represent an integer value.
Parser for settings and cmdline arguments.
struct rule * get_rule(const char *name)
Check if a rule exists with that name.
Definition rules.c:336
struct rule * rule_new(const char *name)
Allocate a new rule with given name.
Definition rules.c:237
bool rule_offset_is_modifying(const size_t offset)
see rules.h
Definition rules.c:348
Rules managment and helpers.
Type definitions for settings.
List of all the valid settings and values.
Definition draw.h:48
Definition ini.h:17
Definition ini.h:28
Definition rules.h:20
Definition ini.h:22
void * value
(nullable) A pointer to the corresponding setting in the setting struct.
char * section
A string with the ini section where the variable is allowed.
char * name
A string with the setting key as found in the config file.
enum setting_type type
Enum of the setting type.
const void * parser_data
(nullable) A pointer to the data required for the parser to parse this setting.
size_t rule_offset
The offset of this setting in the rule struct, if it exists.
char * description
A string with a short description of the config variable.
int(* parser)(const void *data, const char *cfg_value, void *ret)
(nullable) Function pointer for the parser - to be used in case of enums or other special settings.
bool string_is_int(const char *str)
Check if string contains digits.
Definition utils.c:485
int string_array_length(char **s)
Returns the length of a string array, -1 if the input is NULL.
Definition utils.c:166
char * string_strip_brackets(const char *s)
Strips a string of it's brackets if the first and last character are a bracket.
Definition utils.c:425
bool safe_string_to_double(double *in, const char *str)
Same as safe_string_to_int, but then for a double.
Definition utils.c:245
bool safe_string_to_int(int *in, const char *str)
Convert string to int in a safe way.
Definition utils.c:229
char * string_to_path(char *string)
Replace tilde and path-specific values with it's equivalents.
Definition utils.c:178
bool is_deprecated_section(const char *s)
This function tells if a section is deprecated.
Definition utils.c:407
bool is_special_section(const char *s)
Some sections are handled differently in dunst.
Definition utils.c:398
char ** string_to_array(const char *string, const char *delimiter)
Parse a string into a dynamic array of tokens, using the delimiter string.
Definition utils.c:154
gint64 string_to_time(const char *string)
Convert time units (ms, s, m) to the internal gint64 microseconds format.
Definition utils.c:263
String, time and other various helpers.
#define STR_EMPTY(s)
Test if a string is NULL or empty.
Definition utils.h:20
#define STR_EQ(a, b)
Test if string a and b contain the same chars.
Definition utils.h:26
#define STR_FULL(s)
Test if a string is non-NULL and not empty.
Definition utils.h:23
#define ASSERT_OR_RET(expr, val)
Assert that expr evaluates to true, if not return val.
Definition utils.h:42