author | Olivier Brunel
<jjk@jjacky.com> 2023-04-19 18:25:11 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2023-05-20 18:06:37 UTC |
parent | f86ed1863f489e678598cb2cd7b605a9f8c2572d |
src/doc/copa.h.0.md | +48 | -0 |
src/doc/copa.h/copa_init.3.md | +117 | -0 |
src/liblimb/copa.h/copa_init.c | +12 | -0 |
src/liblimb/copa.h/copa_next.c | +79 | -0 |
src/liblimb/include/limb/copa.h | +28 | -0 |
diff --git a/src/doc/copa.h.0.md b/src/doc/copa.h.0.md new file mode 100644 index 0000000..fbfd745 --- /dev/null +++ b/src/doc/copa.h.0.md @@ -0,0 +1,48 @@ +% limb manual +% copa.h(0) + +# NAME + +copa.h - helper for configuration parsing + + +# SYNOPSIS + + #include <limb/copa.h> + + +# DESCRIPTION + +The header defines helper functions to parse configuration file. + +## Structures + +The following structures are defined : + +: *struct copa* +:: An opaque structure to be passed to the functions below. + +## Functions + +The following functions/macros are defined : + +: [copa_init](3) +:: To initialize a configuration parser. + +: [copa_is_section](3) +:: To known whether we're at a section or not (i.e. an option). + +: [copa_name](3) +:: To get a pointer to the current name. + +: [copa_next](3) +:: To advance parsing to the next element. + +: [copa_nlen](3) +:: To get the length of the current name. + +: [copa_value](3) +:: To get a pointer to the current value. + +: [copa_vlen](3) +:: To get the length of the current value. diff --git a/src/doc/copa.h/copa_init.3.md b/src/doc/copa.h/copa_init.3.md new file mode 100644 index 0000000..44a74d9 --- /dev/null +++ b/src/doc/copa.h/copa_init.3.md @@ -0,0 +1,117 @@ +% limb manual +% copa_init(3) + +# NAME + +copa\_init, copa\_is\_section, copa\_name, copa\_next, copa\_nlen, copa\_value, +copa\_vlen - configuration parsing + +# SYNOPSIS + + #include <limb/copa.h> + +```pre hl +void copa_init(const char *<em>data</em>, size_t <em>dlen</em>, struct copa *<em>copa</em>) +int copa_next(struct copa *<em>copa</em>) + +int copa_is_section(struct copa *<em>copa</em>) +const char *copa_name(struct copa *<em>copa</em>) +size_t copa_nlen(struct copa *<em>copa</em>) +const char *copa_value(struct copa *<em>copa</em>) +size_t copa_vlen(struct copa *<em>copa</em>) +``` + +# DESCRIPTION + +The `copa_init`() function will initialize the opaque structure pointed to by +`copa` for parsing configuration pointed by `data` of length `dlen` bytes. + +Once initialized, repeated calls can be made to the `copa_next`() function to +progress through parsing of the configuration. It is required that the data +pointed by `data` not change during the entire process. + +Configuration data here is expected to be an INI-like text, processed line by +line using LF (`\n`) as delimiter, that can be be organized into sections. A +section is begins with a name having the section's name enclosed within square +brackets, e.g: + [section name] + +Options can be specified with or without value. If an equal sign (`=`) is found +on a line, the option name is what comes before, its value what comes after. +Else the option has no value. +If an equal sign if found, there must be a value, i.e. using `option=` is +invalid. + +Characters considered space (refer to [isspace](3)) on the beginning of a line +are skipped, as are those of the end of a line. + +Any line beginning (past spaces) with a semi-colon (`;`) or a dash sign (`#`) is +considered a comment and skipped entirely. + +! HINT: +! To allow setting an option to nothing, or have a value beginning/ending with +! spaces, it is possible to use quotes, e.g. use `option = ""` +! +! Note that copa itself doesn't deal with quoting, and would in such a case +! return a pointer to the string `""` (of length 2) as value. It is up to the +! caller to then process that as being an empty string/value. + +The `copa_next`() function must be called on a previously initialized opaque +structure, pointed to be `copa`, and will advance parsing up to the next element +(section or option), if any. + +If it returns 1, parsing was successful and the macros below can then be used to +process things further. Otherwise, parsing stops there. It is possible to then +re-use the same opaque structure with a new call to `copa_init`() in order to +parse another configuration. + +The `copa_is_section`() macro will return non-zero is the current element, as +defined by the state of the opaque structure pointed to by `copa`, is of a +section, and zero otherwise. + +The `copa_name`() macro will return a pointer to the name of the current +element, as defined by the state of the opaque structure pointed to by `copa`. +This is *not* a NUL-terminated string, and instead shall only be read for a +length whose value will be returned by the `copa_nlen`() macro. + +The `copa_value`() macro is similar to the `copa_name`() macro, only for the +value of the option. It should not be used for sections, nor if there are no +value given for the current option, which should be determined by checking if +the length of the value is non-zero, as returned by the `copa_vlen`() macro. + +# RETURN VALUE + +The `copa_next`() function returns 1 when parsing successfully progressed into +the next element. It returns 0 if parsing was successful and the end of data +was reached, i.e. parsing successfully ended. +Otherwise it returns -1 and sets `errno` to indicate the error. + +The `copa_name`() macro returns a pointer to the name of the current element, +which is /not/ a NUL-terminated string. + +The `cop_nlen`() macro returns the length of the name of the current element. + +The `copa_value`() returns a pointer to the value of the current option, which +is /not/ a NUL-terminated string, *only if there is one*. It should /not/ be +used otherwise, nor to determine whether or not an value was specified. + +The `copa_vlen`() macro returns the length of the value of the current option, +or 0 when there are none. /This/ is what should be used to determine if the +current option had a value specified or not. + +# ERRORS + +The `copa_next`() function may fail if : + +: *ENODATA* +:: Empty section, i.e. a line `[]` without a name. + +: *EINVAL* +:: Invalid assignment, i.e. an equal sign without option name (e.g: a line `=bar`) + +: *ENOMSG* +:: Option without value, i.e. an equal sign with nothing after (e.g: a line `foo=`) + +# SEE ALSO + +[esc_scan](3) diff --git a/src/liblimb/copa.h/copa_init.c b/src/liblimb/copa.h/copa_init.c new file mode 100644 index 0000000..b2923b9 --- /dev/null +++ b/src/liblimb/copa.h/copa_init.c @@ -0,0 +1,12 @@ +/* This file is part of limb https://lila.oss/limb + * Copyright (C) 2023 Olivier Brunel jjk@jjacky.com */ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include <limb/copa.h> + +void +copa_init(const char *data, size_t dlen, struct copa *ctx) +{ + ctx->data = data; + ctx->dlen = dlen; + ctx->noff = ctx->nlen = ctx->voff = ctx->vlen = 0; +} diff --git a/src/liblimb/copa.h/copa_next.c b/src/liblimb/copa.h/copa_next.c new file mode 100644 index 0000000..ca62885 --- /dev/null +++ b/src/liblimb/copa.h/copa_next.c @@ -0,0 +1,79 @@ +/* This file is part of limb https://lila.oss/limb + * Copyright (C) 2023 Olivier Brunel jjk@jjacky.com */ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include <ctype.h> +#include <errno.h> +#include <limb/bytestr.h> +#include <limb/copa.h> + +int +copa_next(struct copa *ctx) +{ + /* not the first line? */ + if (ctx->nlen) + /* move to the next line */ + ctx->noff += byte_chr(copa_name(ctx), ctx->dlen - ctx->noff, '\n'); + + /* move past spaces */ + while (ctx->noff < ctx->dlen && isspace(ctx->data[ctx->noff])) + ++ctx->noff; + /* EOF */ + if (ctx->noff == ctx->dlen) + return 0; + + /* comments */ + if (ctx->data[ctx->noff] == ';' || ctx->data[ctx->noff] == '#') + return (++ctx->nlen, copa_next(ctx)); + + /* search for end of line */ + ctx->nlen = byte_chr(copa_name(ctx), ctx->dlen - ctx->noff, '\n'); + + /* search for an '=' sign, i.e. a value */ + ctx->voff = byte_chr(copa_name(ctx), ctx->nlen, '='); + if (ctx->voff == ctx->nlen) { + /* no value */ + ctx->vlen = 0; + + /* remove trailing spaces on name */ + while (ctx->nlen && isspace(ctx->data[ctx->noff + ctx->nlen - 1])) + --ctx->nlen; + + /* section? */ + if (ctx->data[ctx->noff] == '[' && ctx->data[ctx->noff + ctx->nlen - 1] == ']') { + ++ctx->noff; + ctx->nlen -= 2; + } + + /* no section name ("[]") */ + if (!ctx->nlen) return (errno = ENODATA, -1); + + return 1; + } + + /* adjust offsets/lengths */ + ctx->vlen = ctx->nlen - ctx->voff - 1; + ctx->nlen = ctx->voff; + ctx->voff += ctx->noff + 1; + + /* remove trailing spaces on name */ + while (ctx->nlen && isspace(ctx->data[ctx->noff + ctx->nlen - 1])) + --ctx->nlen; + + /* nothing before '=' */ + if (!ctx->nlen) return (errno = EINVAL, -1); + + /* move value past spaces */ + while (ctx->vlen && isspace(ctx->data[ctx->voff])) { + ++ctx->voff; + --ctx->vlen; + } + + /* nothing after '=' */ + if (!ctx->vlen) return (errno = ENOMSG, -1); + + /* remova trailing spaces on value */ + while (ctx->vlen && isspace(ctx->data[ctx->voff + ctx->vlen - 1])) + --ctx->vlen; + + return 1; +} diff --git a/src/liblimb/include/limb/copa.h b/src/liblimb/include/limb/copa.h new file mode 100644 index 0000000..91e7c66 --- /dev/null +++ b/src/liblimb/include/limb/copa.h @@ -0,0 +1,28 @@ +/* This file is part of limb https://lila.oss/limb + * Copyright (C) 2023 Olivier Brunel jjk@jjacky.com */ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LIMB_COPA_H +#define LIMB_COPA_H + +#include <sys/types.h> /* size_t */ + +/* opaque structure */ +struct copa { + const char *data; + size_t dlen; + size_t noff; + size_t nlen; + size_t voff; + size_t vlen; +}; + +extern void copa_init(const char *data, size_t dlen, struct copa *ctx); +extern int copa_next(struct copa *ctx); + +#define copa_is_section(ctx) (copa_name(ctx)[(ctx)->nlen] == ']') +#define copa_name(ctx) ((ctx)->data + (ctx)->noff) +#define copa_nlen(ctx) (ctx)->nlen +#define copa_value(ctx) ((ctx)->data + (ctx)->voff) +#define copa_vlen(ctx) (ctx)->vlen + +#endif /* LIMB_COPA_H */