Welcome to little lamb

Code » limb » commit d9349dd

Add copa.h & related functions: configuration parser

author Olivier Brunel
2023-04-19 18:25:11 UTC
committer Olivier Brunel
2023-05-20 18:06:37 UTC
parent f86ed1863f489e678598cb2cd7b605a9f8c2572d

Add copa.h & related functions: configuration parser

To parse a simple configuration (INI-like) file, first use copa_init()
setting the data/dlen parameters, then simply repeat calls to
copa_next() for as long as there are sections/options found.

Data is processed line by line (using '\n' as delimiter), leading/trailing
spaces are ignored and lines begining with ';' or '#' are skipped
(comments).

Macros are available to get the name/value as needed. Obviously the
actual processing of said sections/options is up to the caller.

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 */