+static void
+process_opt_attr (const char *p, const bool is_opt)
+{
+ /* If attributes are added to this "list", also increase MAX_ATTRIBUTE_CHARS! */
+ const struct attr attrs[] = {
+ { "bold", 1, ATTR_BOLD },
+ { "underscore", 4, ATTR_UNDERSCORE },
+ { "blink", 5, ATTR_BLINK },
+ { "reverse", 7, ATTR_REVERSE },
+ { "concealed", 8, ATTR_CONCEALED },
+ };
+ unsigned int attr_types = 0;
+ const char *desc_type[2] = { "--attr switch", "attr conf option" };
+ const unsigned int DESC_TYPE = is_opt ? DESC_OPTION : DESC_CONF;
+
+ while (*p)
+ {
+ const char *s;
+ if (!isalnum (*p))
+ vfprintf_fail ("%s must be provided a string", desc_type[DESC_TYPE]);
+ s = p;
+ while (isalnum (*p))
+ p++;
+ if (*p != '\0' && *p != ',')
+ vfprintf_fail ("%s must have strings separated by ,", desc_type[DESC_TYPE]);
+ else
+ {
+ bool valid_attr = false;
+ unsigned int i;
+ for (i = 0; i < COUNT_OF (attrs, struct attr); i++)
+ {
+ const size_t name_len = strlen (attrs[i].name);
+ if ((size_t)(p - s) == name_len && strneq (s, attrs[i].name, name_len))
+ {
+ write_attr (&attrs[i], &attr_types, is_opt);
+ valid_attr = true;
+ break;
+ }
+ }
+ if (!valid_attr)
+ {
+ char *attr_invalid = xmalloc ((p - s) + 1);
+ STACK_VAR (attr_invalid);
+ strncpy (attr_invalid, s, p - s);
+ attr_invalid[p - s] = '\0';
+ vfprintf_fail ("%s attribute '%s' is not valid", desc_type[DESC_TYPE], attr_invalid);
+ RELEASE_VAR (attr_invalid); /* never reached */
+ }
+ }
+ if (*p)
+ p++;
+ }
+}
+
+static void
+write_attr (const struct attr *attr_i, unsigned int *attr_types, const bool is_opt)
+{
+ const unsigned int val = attr_i->val;
+ const enum attr_type attr_type = attr_i->type;
+ const char *attr_name = attr_i->name;
+
+ if (*attr_types & attr_type)
+ vfprintf_fail ("%s has attribute '%s' twice or more",
+ is_opt ? "--attr switch" : "attr conf option", attr_name);
+ snprintf (attr + strlen (attr), 3, "%u;", val);
+ *attr_types |= attr_type;
+}
+
+static void
+process_opt_exclude_random (const char *s, const bool is_opt)
+{
+ bool valid = false;
+ unsigned int i;
+ if (exclude)
+ RELEASE_VAR (exclude);
+ exclude = xstrdup (s);
+ STACK_VAR (exclude);
+ for (i = 1; i < tables[GENERIC].count - 1; i++) /* skip color none and default */
+ {
+ const struct color *entry = &tables[GENERIC].entries[i];
+ if (streq (exclude, entry->name))
+ {
+ valid = true;
+ break;
+ }
+ }
+ if (!valid)
+ vfprintf_fail ("%s must be provided a plain color",
+ is_opt ? "--exlude-random switch" : "exclude-random conf option");
+}
+
+#define IS_SPACE(c) ((c) == ' ' || (c) == '\t')
+
+static void
+parse_conf (const char *conf_file, struct conf *config)
+{
+ char line[256 + 1];
+ FILE *conf;
+
+ conf = open_file (conf_file, "r");
+
+ while (fgets (line, sizeof (line), conf))
+ {
+ char *cfg, *val;
+ char *assign, *comment, *opt, *value;
+ char *p;
+
+ if (strlen (line) > (sizeof (line) - 2))
+ vfprintf_fail ("%s: line exceeds maximum of %u characters", conf_file, (unsigned int)(sizeof (line) - 2));
+ if ((p = strrchr (line, '\n')))
+ *p = '\0';
+/* NAME PARSING (start) */
+ p = line;
+ /* skip leading spaces and tabs for name */
+ while (IS_SPACE (*p))
+ p++;
+ /* skip line if a) string end, b) comment, [cd]) newline */
+ if (*p == '\0' || *p == '#' || *p == '\n' || *p == '\r')
+ continue;
+ opt = p;
+ if (!(assign = strchr (opt, '='))) /* check for = */
+ {
+ char *space;
+ if ((space = strchr (opt, ' ')))
+ *space = '\0';
+ vfprintf_fail (formats[FMT_CONF], conf_file, opt, "not followed by =");
+ }
+ p = assign;
+ /* skip trailing spaces and tabs for name */
+ while (IS_SPACE (*(p - 1)))
+ p--;
+ *p = '\0';
+/* NAME PARSING (end) */
+/* NAME VALIDATION (start) */
+ for (p = opt; *p; p++)
+ if (!isalnum (*p) && *p != '-')
+ vfprintf_fail (formats[FMT_CONF], conf_file, opt, "cannot be made of non-option characters");
+/* NAME VALIDATION (end) */
+/* VALUE PARSING (start) */
+ p = assign + 1;
+ /* skip leading spaces and tabs for value */
+ while (IS_SPACE (*p))
+ p++;
+ /* skip line if comment */
+ if (*p == '#')
+ continue;
+ value = p;
+ if ((comment = strchr (p, '#')))
+ p = comment;
+ else
+ p += strlen (p);
+ /* skip trailing spaces and tabs for value */
+ while (IS_SPACE (*(p - 1)))
+ p--;
+ *p = '\0';
+/* VALUE PARSING (end) */
+
+ /* save option name */
+ cfg = xstrdup (opt);
+ /* save option value (allow empty ones) */
+ val = strlen (value) ? xstrdup (value) : NULL;
+
+ assign_conf (conf_file, config, cfg, val);
+ free (cfg);
+ }
+
+ fclose (conf);
+}
+
+static void
+assign_conf (const char *conf_file, struct conf *config, const char *cfg, char *val)
+{
+ if (streq (cfg, "attr"))
+ {
+ free (config->attr);
+ config->attr = val;
+ }
+ else if (streq (cfg, "color"))
+ {
+ free (config->color);
+ config->color = val;
+ }
+ else if (streq (cfg, "exclude-random"))
+ {
+ free (config->exclude_random);
+ config->exclude_random = val;
+ }
+ else if (streq (cfg, "omit-color-empty"))
+ {
+ free (config->omit_color_empty);
+ config->omit_color_empty = val;
+ }
+ else
+ vfprintf_fail (formats[FMT_CONF], conf_file, cfg, "not recognized");
+}
+
+static void
+init_conf_vars (const struct conf *config)
+{
+ if (config->attr)
+ process_opt_attr (config->attr, false);
+ if (config->exclude_random)
+ process_opt_exclude_random (config->exclude_random, false);
+ if (config->omit_color_empty)
+ {
+ if (streq (config->omit_color_empty, "yes"))
+ omit_color_empty = true;
+ else if (streq (config->omit_color_empty, "no"))
+ omit_color_empty = false;
+ else
+ vfprintf_fail ("omit-color-empty conf option is not valid");
+ }
+}
+