Add utility functions for handling comma separated lists

A lot of profile options deal with manipulating strings containing
comma separated list of things, using several strains of similar but
not exactly the same code, duplicated for the purposes of processing
command line arguments and parsing configuration files.

Having utility functions available for handling such list strings can
make higher level logic shorter, cleaner and function in more uniform
manner.

Signed-off-by: Simo Piiroinen <simo.piiroinen@jolla.com>
Signed-off-by: Tomi Leppänen <tomi.leppanen@jolla.com>
This commit is contained in:
Simo Piiroinen 2020-11-05 18:52:29 +02:00 committed by Tomi Leppänen
parent 8a7b96974f
commit cddc483222
2 changed files with 143 additions and 0 deletions

View file

@ -451,6 +451,9 @@ int profile_check_line(char *ptr, int lineno, const char *fname);
// add a profile entry in cfg.profile list; use str to populate the list
void profile_add(char *str);
void profile_add_ignore(const char *str);
char *profile_list_normalize(char *list);
char *profile_list_compress(char *list);
void profile_list_augment(char **list, const char *items);
// list.c
void list(void);

View file

@ -1773,3 +1773,143 @@ void profile_read(const char *fname) {
}
fclose(fp);
}
char *profile_list_normalize(char *list)
{
/* Remove redundant commas.
*
* As result is always shorter than original,
* in-place copying can be used.
*/
size_t i = 0;
size_t j = 0;
int c;
while (list[i] == ',')
++i;
while ((c = list[i++])) {
if (c == ',') {
while (list[i] == ',')
++i;
if (list[i] == 0)
break;
}
list[j++] = c;
}
list[j] = 0;
return list;
}
char *profile_list_compress(char *list)
{
size_t i;
/* Comma separated list is processed so that:
* "item" -> adds item to list
* "-item" -> removes item from list
* "+item" -> adds item to list
* "=item" -> clear list, add item
*
* For example:
* ,a,,,b,,,c, -> a,b,c
* a,,b,,,c,a -> a,b,c
* a,b,c,-a -> b,c
* a,b,c,-a,a -> b,c,a
* a,+b,c -> a,b,c
* a,b,=c,d -> c,d
* a,b,c,= ->
*/
profile_list_normalize(list);
/* Count items: comma count + 1 */
size_t count = 1;
for (i = 0; list[i]; ++i) {
if (list[i] == ',')
++count;
}
/* Collect items in an array */
char *in[count];
count = 0;
in[count++] = list;
for (i = 0; list[i]; ++i) {
if (list[i] != ',')
continue;
list[i] = 0;
in[count++] = list + i + 1;
}
/* Filter array: add, remove, reset, filter out duplicates */
for (i = 0; i < count; ++i) {
char *item = in[i];
assert(item);
size_t k;
switch (*item) {
case '-':
++item;
/* Do not include this item */
in[i] = 0;
/* Remove if already included */
for (k = 0; k < i; ++k) {
if (in[k] && !strcmp(in[k], item)) {
in[k] = 0;
break;
}
}
break;
case '+':
/* Allow +/- symmetry */
in[i] = ++item;
/* FALLTHRU */
default:
/* Adding empty item is a NOP */
if (!*item) {
in[i] = 0;
break;
}
/* Include item unless it is already included */
for (k = 0; k < i; ++k) {
if (in[k] && !strcmp(in[k], item)) {
in[i] = 0;
break;
}
}
break;
case '=':
in[i] = ++item;
/* Include non-empty item */
if (!*item)
in[i] = 0;
/* Remove all allready included items */
for (k = 0; k < i; ++k)
in[k] = 0;
break;
}
}
/* Copying back using in-place data works because the
* original order is retained and no item gets longer
* than what it used to be.
*/
char *pos = list;
for (i = 0; i < count; ++i) {
char *item = in[i];
if (!item)
continue;
if (pos > list)
*pos++ = ',';
while (*item)
*pos++ = *item++;
}
*pos = 0;
return list;
}
void profile_list_augment(char **list, const char *items)
{
char *tmp = 0;
if (asprintf(&tmp, "%s,%s", *list ?: "", items ?: "") < 0)
errExit("asprintf");
free(*list);
*list = profile_list_compress(tmp);
}