Working CUtils from nsub + net.c and timing.h fix
authorNiki Roo <niki@nikiroo.be>
Thu, 20 Jun 2024 22:38:40 +0000 (00:38 +0200)
committerNiki Roo <niki@nikiroo.be>
Thu, 20 Jun 2024 22:38:40 +0000 (00:38 +0200)
17 files changed:
Makefile [new file with mode: 0644]
array.c [new file with mode: 0644]
array.h [new file with mode: 0644]
base64.c [new file with mode: 0644]
base64.h [new file with mode: 0644]
check/launcher.c [new file with mode: 0644]
check/launcher.h [new file with mode: 0644]
cstring.c [new file with mode: 0644]
cstring.h [new file with mode: 0644]
cutils.c [new file with mode: 0644]
cutils.h [new file with mode: 0644]
desktop.c [new file with mode: 0644]
desktop.h [new file with mode: 0644]
net/net.c [new file with mode: 0644]
net/net.h [new file with mode: 0644]
print.h [new file with mode: 0644]
timing.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..48d3e3d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,83 @@
+# Note: 99+ required for for-loop initial declaration (CentOS 6)
+# Note: gnu99 (instead of c99) required for libcutils-net
+
+CFLAGS   += -Wall -pedantic -I./ -std=gnu99
+CXXFLAGS += -Wall -pedantic -I./
+PREFIX   =  /usr/local
+
+ifdef DEBUG
+CFLAGS   += -ggdb -O0
+CXXFLAGS += -ggdb -O0
+endif
+
+.PHONY: all cutils check net install uninstall clean mrpropre mrpropre debug
+
+all: utils check net
+
+utils: ../../bin/libcutils.o
+
+check: ../../bin/libcutils-check.o
+
+net: ../../bin/libcutils-net.o
+
+SOURCES=$(wildcard *.c)
+HEADERS=$(wildcard *.h)
+OBJECTS=$(SOURCES:%.c=%.o)
+
+array.o:   array.[ch]
+desktop.o: desktop.[ch] array.h
+
+../../bin/libcutils.o: $(OBJECTS)
+       mkdir -p ../../bin
+       # note: -r = --relocatable, but former also works with Clang
+       $(LD) -r $(OBJECTS) -o $@
+
+../../bin/libcutils-check.o: check/launcher.o
+       mkdir -p ../../bin
+       # note: -r = --relocatable, but former also works with Clang
+       $(LD) -r check/launcher.o -o $@
+
+../../bin/libcutils-net.o: net/net.o
+       mkdir -p ../../bin
+       # note: -r = --relocatable, but former also works with Clang
+       $(LD) -r net/net.o -o $@
+
+debug: 
+       $(MAKE) -f makefile.d DEBUG=1
+
+clean:
+       rm -f *.o check/*.o net/*.o
+
+mrproper: mrpropre
+
+mrpropre: clean
+       rm -f ../../bin/libcutils.o
+       rm -f ../../bin/libcutils-check.o 
+       rm -f ../../bin/lubcutils-net.o
+       rmdir ../../bin 2>/dev/null || true
+
+install: 
+       @echo "installing cutils to $(PREFIX)..."
+       mkdir -p "$(PREFIX)/lib/cutils/"
+       cp ../../bin/libcutils.o       "$(PREFIX)/lib/cutils/"
+       cp ../../bin/libcutils-check.o "$(PREFIX)/lib/cutils/"
+       cp ../../bin/libcutils-net.o   "$(PREFIX)/lib/cutils/"
+       mkdir -p "$(PREFIX)/include/cutils/check/"
+       mkdir -p "$(PREFIX)/include/cutils/net/"
+       cp *.h       "$(PREFIX)/include/cutils/"
+       cp check/*.h "$(PREFIX)/include/cutils/check/"
+       cp net/*.h   "$(PREFIX)/include/cutils/net/"
+
+uninstall:
+       @echo "uninstalling utils from $(PREFIX)..."
+       rm -f "$(PREFIX)/lib/cutils/libcutils.o"
+       rm -f "$(PREFIX)/lib/cutils/libcutils-check.o"
+       rm -f "$(PREFIX)/lib/cutils/libcutils-net.o"
+       rmdir "$(PREFIX)/lib/cutils/"          2>/dev/null || true
+       rm -f "$(PREFIX)/include/cutils/net/"*.h
+       rm -f "$(PREFIX)/include/cutils/check/"*.h
+       rm -f "$(PREFIX)/include/cutils/"*.h
+       rmdir "$(PREFIX)/include/cutils/net"   2>/dev/null || true
+       rmdir "$(PREFIX)/include/cutils/check" 2>/dev/null || true
+       rmdir "$(PREFIX)/include/cutils"       2>/dev/null || true
+
diff --git a/array.c b/array.c
new file mode 100644 (file)
index 0000000..e5bb7ee
--- /dev/null
+++ b/array.c
@@ -0,0 +1,382 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "array.h"
+
+typedef struct {
+       size_t elem_size;
+       size_t buffer;
+       void *data;
+} priv_t;
+
+/* make sure we have at least n+1 elements in the buffer, grow if needed */
+/* +1 is so we can always end with a NULL value for array_data/convert() */
+static int array_assure(array_t *me, size_t nb_elem);
+
+/* for qsort operations */
+static int array_qsorts_func(const void *a, const void *b);
+static int array_qsorti_func(const void *a, const void *b);
+static int array_qsortl_func(const void *a, const void *b);
+static int array_qsortf_func(const void *a, const void *b);
+static int array_qsorts_rfunc(const void *a, const void *b);
+static int array_qsorti_rfunc(const void *a, const void *b);
+static int array_qsortl_rfunc(const void *a, const void *b);
+static int array_qsortf_rfunc(const void *a, const void *b);
+
+array_t *new_array(size_t elem_size, size_t initial) {
+       if (!initial)
+               return NULL;
+
+       array_t *me = malloc(sizeof(array_t));
+       if (!init_array(me, elem_size, initial)) {
+               free(me);
+               me = NULL;
+       }
+
+       return me;
+}
+
+int init_array(array_t *me, size_t elem_size, size_t initial) {
+       if (!initial)
+               return 0;
+
+       strcpy(me->CNAME, "[CArray ]");
+
+       me->priv = malloc(sizeof(priv_t));
+       if (!me->priv)
+               return 0;
+
+       priv_t *priv = (priv_t *) me->priv;
+       strcpy(me->CNAME, "[CArray ]");
+       priv->elem_size = elem_size;
+       me->count = 0;
+       priv->buffer = initial;
+       priv->data = malloc(elem_size * initial);
+       if (!priv->data) {
+               free(me->priv);
+               return 0;
+       }
+
+       return 1;
+}
+
+void free_array(array_t *me) {
+       if (me)
+               uninit_array(me);
+
+       free(me);
+}
+
+void uninit_array(array_t *me) {
+       priv_t *priv = (priv_t *) me->priv;
+       me->count = 0;
+       priv->buffer = 0;
+       free(priv->data);
+       priv->data = NULL;
+       free(priv);
+       me->priv = NULL;
+       me->CNAME[0] = '!';
+}
+
+void array_clear(array_t *me) {
+       me->count = 0;
+}
+
+// convert to void * data (free the rest)
+void *array_convert(array_t *me) {
+       priv_t *priv = (priv_t *) me->priv;
+       void *data = array_data(me);
+       free(priv);
+       free(me);
+       return data;
+}
+
+void *array_data(array_t *me) {
+       priv_t *priv = (priv_t *) me->priv;
+
+       // Note: this should be impossible
+       if (me->count >= priv->buffer)
+               array_assure(me, me->count + 1);
+
+       // cast to (char *) because we want 'byte' arithmetic
+       void *after_end = (void *) (((char *) priv->data)
+                       + (me->count * priv->elem_size));
+
+       // last item is always NULL
+       memset(after_end, '\0', priv->elem_size);
+
+       return priv->data;
+}
+
+size_t array_count(array_t *me) {
+       return me->count;
+}
+
+void *array_new(array_t *me) {
+       return array_newn(me, 1);
+}
+
+void *array_newn(array_t *me, size_t how_many) {
+       if (!array_assure(me, me->count + how_many))
+               return 0;
+
+       me->count += how_many;
+       return array_get(me, me->count - how_many);
+}
+
+void *array_first(array_t *me) {
+       if (!me->count)
+               return NULL;
+
+       priv_t *priv = (priv_t *) me->priv;
+       return priv->data;
+}
+
+void *array_last(array_t *me) {
+       if (!me->count)
+               return NULL;
+
+       priv_t *priv = (priv_t *) me->priv;
+       // cast to (char *) because we want 'byte' arithmetic
+       return (void *) (((char *) priv->data) + ((me->count - 1) * priv->elem_size));
+}
+
+void *array_prev(array_t *me, void *ptr) {
+       priv_t *priv = (priv_t *) me->priv;
+
+       // cast to (char *) because we want 'byte' arithmetic
+       char *cptr = (char *) ptr;
+       char *cdata = (char*) priv->data;
+
+       if (cptr) {
+               cptr -= priv->elem_size;
+               if (cptr >= cdata) {
+                       return cptr;
+               }
+       }
+
+       return NULL;
+}
+
+void *array_next(array_t *me, void *ptr) {
+       priv_t *priv = (priv_t *) me->priv;
+
+       // cast to (char *) because we want 'byte' arithmetic
+       char *cptr = (char *) ptr;
+       char *cdata = (char*) priv->data;
+
+       if (cptr) {
+               cptr += priv->elem_size;
+               char *last = cdata + ((me->count - 1) * priv->elem_size);
+               if (cptr <= last) {
+                       return cptr;
+               }
+       }
+
+       return NULL;
+}
+
+void *array_get(array_t *me, size_t i) {
+       priv_t *priv = (priv_t *) me->priv;
+
+       // cast to (char *) because we want 'byte' arithmetic
+       return (void *) (((char *) priv->data) + (i * priv->elem_size));
+}
+
+void *array_pop(array_t *me) {
+       return array_cut_at(me, me->count - 1);
+}
+
+void *array_cut_at(array_t *me, size_t n) {
+       if (n < me->count) {
+               void *item = array_get(me, n);
+               me->count = n;
+               return item;
+       }
+
+       return NULL;
+}
+
+void array_compact(array_t *me) {
+       priv_t *priv = (priv_t *) me->priv;
+
+       int c = me->count ? me->count : 1;
+       priv->data = realloc(priv->data, c * priv->elem_size);
+       priv->buffer = c;
+}
+
+void array_qsort(array_t *me, int (*compar)(const void *, const void *)) {
+       priv_t *priv = (priv_t *) me->priv;
+       qsort(priv->data, me->count, priv->elem_size, compar);
+}
+
+void array_qsorts(array_t *me, int rev) {
+       array_qsort(me, rev ? array_qsorts_rfunc : array_qsorts_func);
+}
+static int array_qsorts_func(const void *a, const void *b) {
+       char *stra = ((char **) a)[0];
+       char *strb = ((char **) b)[0];
+       return strcmp(stra, strb);
+}
+static int array_qsorts_rfunc(const void *a, const void *b) {
+       char *stra = ((char **) a)[0];
+       char *strb = ((char **) b)[0];
+       return strcmp(strb, stra);
+}
+
+void array_qsorti(array_t *me, int rev) {
+       array_qsort(me, rev ? array_qsorti_rfunc : array_qsorti_func);
+}
+static int array_qsorti_func(const void *a, const void *b) {
+       long ia, ib;
+       ia = ((int *) a)[0];
+       ib = ((int *) b)[0];
+       if (ia < ib)
+               return -1;
+       return !(ia == ib);
+}
+static int array_qsorti_rfunc(const void *a, const void *b) {
+       long ia, ib;
+       ia = ((int *) a)[0];
+       ib = ((int *) b)[0];
+       if (ia > ib)
+               return -1;
+       return !(ia == ib);
+}
+
+void array_qsortl(array_t *me, int rev) {
+       array_qsort(me, rev ? array_qsortl_rfunc : array_qsortl_func);
+}
+static int array_qsortl_func(const void *a, const void *b) {
+       long la, lb;
+       la = ((long *) a)[0];
+       lb = ((long *) b)[0];
+       if (la < lb)
+               return -1;
+       return !(la == lb);
+}
+static int array_qsortl_rfunc(const void *a, const void *b) {
+       long la, lb;
+       la = ((long *) a)[0];
+       lb = ((long *) b)[0];
+       if (la > lb)
+               return -1;
+       return !(la == lb);
+}
+
+void array_qsortf(array_t *me, int rev) {
+       array_qsort(me, rev ? array_qsortf_rfunc : array_qsortf_func);
+}
+static int array_qsortf_func(const void *a, const void *b) {
+       float fa, fb;
+       fa = ((float *) a)[0];
+       fb = ((float *) b)[0];
+       // Also works:
+       //memcpy(&fa, a, sizeof(float));
+       //memcpy(&fb, b, sizeof(float));
+       if (fa < fb)
+               return -1;
+       return !(fa == fb);
+}
+static int array_qsortf_rfunc(const void *a, const void *b) {
+       float fa, fb;
+       fa = ((float *) a)[0];
+       fb = ((float *) b)[0];
+       // Also works:
+       //memcpy(&fa, a, sizeof(float));
+       //memcpy(&fb, b, sizeof(float));
+       if (fa > fb)
+               return -1;
+       return !(fa == fb);
+}
+
+int array_push(array_t *me, void *data) {
+       return array_setn(me, me->count, data, 1);
+}
+
+int array_pushn(array_t *me, void *data, size_t n) {
+       return array_setn(me, me->count, data, n);
+}
+
+int array_copy(array_t *me, void *target, size_t i) {
+       return array_copyn(me, target, i, 1);
+}
+
+int array_copyn(array_t *me, void *target, size_t i, size_t n) {
+       priv_t *priv = (priv_t *) me->priv;
+
+       if (i + n < me->count) {
+               // cast to (char *) because we want 'byte' arithmetic
+               memcpy(target, ((char *) (priv->data)) + (i * priv->elem_size),
+                               n * priv->elem_size);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+int array_set(array_t *me, size_t i, void *data) {
+       return array_setn(me, i, data, 1);
+}
+
+int array_setn(array_t *me, size_t i, void *data, size_t n) {
+       // allow new items BUT NOT holes in the array
+       if (i > me->count)
+               return 0;
+
+       if (!array_assure(me, i + n))
+               return 0;
+
+       priv_t *priv = (priv_t *) me->priv;
+
+       // cast to (char *) because we want 'byte' arithmetic
+       memcpy(((char *) (priv->data)) + (i * priv->elem_size), data,
+                       n * priv->elem_size);
+       if ((i + n) > me->count)
+               me->count = i + n;
+
+       return 1;
+}
+
+/* Privates functions */
+
+static int array_assure(array_t *me, size_t nb_elem) {
+       priv_t *priv = (priv_t *) me->priv;
+
+       if (priv->buffer <= nb_elem) {
+               priv->buffer *= 2;
+               if (priv->buffer < nb_elem) {
+                       priv->buffer = nb_elem;
+               }
+
+               void *tmp = realloc(priv->data, priv->elem_size * priv->buffer);
+               if (!tmp)
+                       return 0;
+
+               priv->data = tmp;
+       }
+
+       return 1;
+}
diff --git a/array.h b/array.h
new file mode 100644 (file)
index 0000000..3638562
--- /dev/null
+++ b/array.h
@@ -0,0 +1,465 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/** 
+ * @file array.h
+ * @author Niki
+ * @date 2020 - 2022
+ *
+ * @brief A simple auto-growing array-list
+ * 
+ * A simple auto-growing array-list, with a minimum buffer of 1.
+ *
+ * It has a link to `qsort`, too, so you don't need to retype all 
+ * the parameters you already passed to the array.
+ *
+ * It is created with `new_array()` and must be freed with :
+ * - `free_array()` for normal operations
+ * - `array_convert()` if you want to free the array but keep the data
+ *   (the data is now your responsibility to free or not, it was allocated
+ *   with `malloc`/`realloc`)
+ *
+ * Example usage:
+ * ```C
+ *     array lines = new_array(sizeof(char *), 100);
+ *     
+ *     const char *l1 = "2. À l'arrière";
+ *     const char *l2 = "3. En réserve";
+ *     const char *l3 = "1. Première ligne";
+ *
+ *  // push mode (usually used for int, char, long...)
+ *     array_push(lines, &l1);
+ *     array_push(lines, &l2);
+ *
+ *  // new mode (usually used for structures)
+ *     char **tmp = array_new(lines);
+ *     *tmp = l3;
+ *
+ *  // sort as Strings (also possible with int, long and custom functions)
+ *     array_qsorts(lines, 0);
+ *
+ *     char **last_line = array_get(lines, array_count(lines) - 1);
+ *     printf("Last line is now: %s\n", *last_line);
+ *     // -> last_line is now: 3. En réserve
+ *
+ *     array_loop(lines, line, char) {
+ *         printf("Line: %s\n", line);
+ *     }
+ *     // -> Line: 1. Première ligne
+ *     // -> Line: 2. En réserve
+ *     // -> Line: 3. À l'arrière
+ *
+ *     free_array(lines);
+ * ```
+ */
+
+#ifndef ARRAY_H
+#define ARRAY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+
+/**
+ * Declare a new <tt>TYPE *</tt> pointer and loop through the array with it.
+ *
+ * How to use:
+ * ```C
+ * array_loop(me, line, char) {
+ *     printf("Item: %s\n", line);
+ * }
+ * ```
+ */
+#define array_loop(me, ptr, TYPE) \
+       for (TYPE *ptr = array_first(me); ptr; ptr = array_next(me, ptr))
+
+/**
+ * Similar to <tt>array_loop</tt>, but add a counter <tt>i</tt> starting at 0.
+ *
+ * @see array_loop
+ *
+ * How to use:
+ * ```C
+ * array_loop_i(me, line, char, i) {
+ *     printf("Item n°%d: %s\n", i, line);
+ * }
+ * ```
+ *
+ * @note this macro does expand as 2 separate lines, surround with { } if needed
+ */
+#define array_loop_i(me, ptr, TYPE, i) \
+       size_t i = 0; \
+       for (TYPE *ptr = array_first(me); ptr; ptr = array_next(me, ptr), i++)
+
+/**
+ * @brief A simple auto-growing array-list
+ *
+ * The structure contains a private field (which you should not use) and the
+ * current count of how many items were added. You should probably not modify
+ * the count either (setting it higher is a bad idea and while it should be
+ * possible to set it lower, you are strongly advised to use
+ * <tt>array_cut_at</tt> instead).
+ *
+ * @see array_cut_at
+ */
+typedef struct {
+       char CNAME[10];
+       size_t count;
+       void *priv;
+} array_t;
+
+/**
+ * Create a new array.
+ *
+ * @note always identical to <tt>malloc</tt> + <tt>init_array</tt>
+ *
+ * @param elem_size the size of one element
+ * @param initial the initial number of items the buffer can hold (<b>not</b> 0)
+ *
+ * @see malloc()
+ * @see init_array(array_t *self)
+ *
+ * @return a new array (you must later call `free_array()` or `array_convert()`)
+ */
+array_t *new_array(size_t elem_size, size_t initial);
+
+/**
+ * Initialise a new array.
+ *
+ * @param elem_size the size of one element
+ * @param initial the initial number of items the buffer can hold (<b>not</b> 0)
+ */
+int init_array(array_t *self, size_t elem_size, size_t initial);
+
+/** 
+ * Free the resources held for the given array: you must not use it any more.
+ * Note that if you use pointers and not direct data, you may want to free
+ * those yourself first.
+ *
+ * @note always equivalent to <tt>uninit_array</tt> + <tt>free</tt>
+ *
+ * @see uninit_array(array_t *self)
+ * @see free(void *data)
+ * @see array_clear
+ * @see array_loop
+ */
+void free_array(array_t *me);
+
+/**
+ * Free the resources held for the given array: you must not use it any more.
+ * Note that if you use pointers and not direct data, you may want to free
+ * those yourself first.
+ *
+ * @see free_array(array_t *self)
+ * @see array_clear
+ * @see array_loop
+ */
+void uninit_array(array_t *me);
+
+/**
+ * Clear the array, that is, resets its current size to 0 (buffer unchanged).
+ *
+ * @note if you hold custom structures with owned resources in the array, you
+ * should deallocate them properly before
+ */
+void array_clear(array_t *me);
+
+/** 
+ * Convert the array to a block of memory where all values are adjacent.
+ *
+ * @note an extra NULL value is assured to be present as last element
+ *
+ * @return the data (you must later call `free()` on it)
+ */
+void *array_convert(array_t *me);
+
+/**
+ * Return a pointer to the internal storage used by this array.
+ * This is the same value as would return `array_convert()`,
+ * but the array is still valid.
+ *
+ * Be careful if you change the content (you should not).
+ *
+ * @note an extra NULL value is assured to be present as last element
+ *
+ * @return the internal storage area
+ */
+void *array_data(array_t *me);
+
+/** 
+ * Return the current number of elements in the array.
+ *
+ * @return the number of elements in the array
+ */
+size_t array_count(array_t *me);
+
+/**
+ * Create a new element in the array and return a pointer to it.
+ *
+ * @return a pointer to the (newly allocated) last element of the array
+ */
+void *array_new(array_t *me);
+
+/**
+ * Create <tt>n</tt> elements in the array and return a pointer to the
+ * first one ({see array_next(void *)} to get the next ones).
+ *
+ * @param n how many elements to add
+ *
+ * @return a pointer to the (newly allocated) first new element of the array
+ */
+void *array_newn(array_t *me, size_t n);
+
+/**
+ * Return a pointer to the first element of the array (for instance, if you
+ * store integers, it will be <tt>(int *)</tt>; if you store strings, it will
+ * be <tt>char **</tt>).
+ *
+ * @return a <i>pointer</i> to the first element, or NULL if no elements are
+ *             present
+ */
+void *array_first(array_t *me);
+
+/**
+ * Return a pointer to the last element of the array (for instance, if you
+ * store integers, it will be <tt>(int *)</tt>; if you store strings, it will
+ * be <tt>char **</tt>).
+ *
+ * @return a <i>pointer</i> to the last element, or NULL if no elements are
+ *             present
+ */
+void *array_last(array_t *me);
+
+/**
+ * Return the pointer to the previous element, or NULL if it was the first.
+ *
+ * @param ptr a pointer from an array (the array must be valid)
+ *
+ * @return the previous element, or NULL
+ */
+void *array_prev(array_t *me, void *ptr);
+
+/**
+ * Return the pointer to the next element, or NULL if it was the last.
+ *
+ * @param ptr a pointer from an array (the array must be valid)
+ *
+ * @return the next element, or NULL
+ */
+void *array_next(array_t *me, void *ptr);
+
+/**
+ * Retrieve the the pointer of an item.
+ * The address of the item will be returned.
+ *
+ * @param i the index of the element to retrieve
+ *
+ * @note if the index is out of bounds, you will get invalid data
+ *
+ * @return the pointer to the i'th element
+ */
+void *array_get(array_t *me, size_t i);
+
+/**
+ * Return a pointer to the last element of this array and remove it from the
+ * array, if the array is not empty.
+ *
+ * @note be careful, the memory pointed to by the element will be reused the
+ *             next time we add an element -- you should not use it after this; in
+ *             short, the return value is mainly so you can call <tt>free</tt> on
+ *             value pointed to by this pointer (<b>not</b> the pointer itself) if it
+ *             is a pointer to memory you own, or use it locally before continuing to
+ *             use the array
+ * @note in case this was not clear, do <b>not</b> call <tt>free</tt> on the
+ *             returned value
+ *
+ * @return a pointer to the last (now removed) item, or NULL if no element
+ */
+void *array_pop(array_t *me);
+
+/**
+ * Cut the array at the given size and return a pointer to the first element
+ * that was removed if any.
+ *
+ * @note be careful, the memory pointed to by the element(s) will be reused the
+ *             next time we add an element -- you should not use it after this; in
+ *             short, the return value is mainly so you can call <tt>free</tt> on
+ *             value(s) pointed to by this pointer (<b>not</b> the pointer itself) if
+ *             it is a pointer to memory you own, or use it locally before continuing
+ *             to use the array
+ * @note in case this was not clear, do <b>not</b> call <tt>free</tt> on the
+ *             returned value(s)
+ *
+ * @return a pointer to the first removed element, or NULL
+ */
+void *array_cut_at(array_t *me, size_t n);
+
+/** 
+ * Compact the array (resize the buffer so it is equals to the current number 
+ * of items in the array or size 1 if there are no items in the array).
+ */
+void array_compact(array_t *me);
+
+/**
+ * Sort the array with a call to `qsort()`.
+ * All the appropriate parameters are passed, except the sorting function.
+ *
+ * @param compar a custom comparison function
+ * @param (parameters)
+ *     * `itm1`: a **pointer** to one of your data element (for instance, 
+ *             if you store floats, it will be a `(float *)`, 
+ *             so you would need to cast it via `((float *)itm1)[0]`
+ *     * `itm2`: another **pointer** to compare with the first one
+ * @param (returns)
+ *     * `-1`: if element A is less than element B
+ *     *  `0`: if both elements are equals
+ *     *  `1`: if element A is more than element B
+ */
+void array_qsort(array_t *me, int (*compar)(const void *itm1, const void *itm2));
+
+/**
+ * Sort the array with `qsort()`, data is `char *`.
+ * 
+ * @param rev FALSE for normal order, TRUE for reverse order
+ *
+ * @see array_qsort
+ */
+void array_qsorts(array_t *me, int rev);
+
+/**
+ * Sort the array with `qsort()`, data is `int`.
+ * 
+ * @param rev FALSE for normal order, TRUE for reverse order
+ *
+ * @see array_qsort
+ */
+void array_qsorti(array_t *me, int rev);
+
+/** 
+ * Sort the array with `qsort()`, data is `long`.
+ * 
+ * @param rev FALSE for normal order, TRUE for reverse order
+ *
+ * @see array_qsort
+ */
+void array_qsortl(array_t *me, int rev);
+
+/**
+ * Sort the array with `qsort()`, data is `float`.
+ * 
+ * @param rev FALSE for normal order, TRUE for reverse order
+ *
+ * @see array_qsort
+ */
+void array_qsortf(array_t *me, int rev);
+
+/**
+ * Add an element to the array (will create a new item in the array and copy the
+ * data from the given element in it).
+ *
+ * @param data the memory position of the element to add
+ *
+ * @return FALSE if the array is too short and we cannot allocate enough 
+ *     contiguous memory for its needs
+ */
+int array_push(array_t *me, void *data);
+
+/**
+ * Add multiple elements to the array (will create new items in the array and
+ * copy the data from the given elements in them).
+ *
+ * @param data the memory position of the elements to add, adjacent to each
+ *  other
+ * @param n the number of elements to copy from `data`
+ *
+ * @return FALSE if the array is too short and we cannot allocate enough 
+ *     contiguous memory for its needs
+ */
+int array_pushn(array_t *me, void *data, size_t n);
+
+/**
+ * Retrieve the content of an item.
+ * The item will be copied to the given address location if it exists.
+ *
+ * @param target an address where to write a copy of the item
+ * @param i the index of the element to retrieve
+ *
+ * @return TRUE if the item exists (if <tt>i</tt> is an element of the array)
+ */
+int array_copy(array_t *me, void *target, size_t i);
+
+/**
+ * Retrieve the content of multiple items if they exist.
+ * The items will be copied in a sequence to the given address location.
+ *
+ * @param target an address where to write a copy of the items
+ * @param i the index of the first element to retrieve
+ * @param n the number of elements to retrieve
+ *
+ * @return TRUE if the item exists (if <tt>i</tt> to <tt>n</tt> are elements
+ *             of the array)
+ */
+int array_copyn(array_t *me, void *target, size_t i, size_t n);
+
+/**
+ * Set an element of the array to the given value.
+ * Can also append an element at the end of the array (i.e., <tt>i</tt> can be
+ * the size of the array and this will result in an array with one more
+ * element).
+ * Memory will be copied from the given data to the array.
+ *
+ * @param i the element index
+ * @param data the data that will replace the current value, or new data
+ *     to append after the current elements (you can add items just at
+ *     the end of the array (index = count), but it is not allowed to
+ *     set items after that index, so not to create holes)
+ *
+ * @return FALSE if the array is too short and we cannot allocate enough 
+ *     contiguous memory for its needs, or if the index is out of bounds
+ */
+int array_set(array_t *me, size_t i, void *data);
+
+/**
+ * Set elements of the array to the given value.
+ * Can also append elements at the end of the array (i.e., <tt>i</tt> can be
+ * the size of the array and this will result in an array with <tt>n</tt> more
+ * elements).
+ * Memory will be copied from the given data to the array.
+ *
+ * @param i the element index to start the insertion at
+ * @param data the data that will replace the current values, or new data
+ *     to append after the current elements (you can add items just at
+ *     the end of the array (index = count), but it is not allowed to
+ *     set items with `i` after that index, so not to create holes)
+ * @param n the number of elements to copy from `data` and to insert at `i`
+ *
+ * @return FALSE if the array is too short and we cannot allocate enough 
+ *     contiguous memory for its needs, or if the index is out of bounds
+ */
+int array_setn(array_t *me, size_t i, void *data, size_t n);
+
+#endif /* ARRAY_H */
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/base64.c b/base64.c
new file mode 100644 (file)
index 0000000..d151fe3
--- /dev/null
+++ b/base64.c
@@ -0,0 +1,117 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "base64.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+static char encoding_table[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
+               'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+               'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+               'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
+               'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
+
+static int decoding_table_flg = 0;
+static char decoding_table[256];
+
+static void init_dtable() {
+       if (!decoding_table_flg) {
+               for (int i = 0; i < 64; i++)
+                       decoding_table[(unsigned char) encoding_table[i]] = i;
+               decoding_table_flg = 1;
+       }
+}
+
+char *base64_encode(const char *data) {
+       size_t input_length = strlen(data);
+       size_t output_length = 4 * ((input_length + 2) / 3);
+
+       char *encoded_data = malloc(output_length + 1);
+       if (!encoded_data)
+               return NULL;
+
+       for (unsigned int i = 0, j = 0; i < input_length;) {
+               unsigned int octet_a = i < input_length ? (unsigned char) data[i++] : 0;
+               unsigned int octet_b = i < input_length ? (unsigned char) data[i++] : 0;
+               unsigned int octet_c = i < input_length ? (unsigned char) data[i++] : 0;
+
+               unsigned int triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
+
+               encoded_data[j++] = (char) encoding_table[(triple >> 3 * 6) & 0x3F];
+               encoded_data[j++] = (char) encoding_table[(triple >> 2 * 6) & 0x3F];
+               encoded_data[j++] = (char) encoding_table[(triple >> 1 * 6) & 0x3F];
+               encoded_data[j++] = (char) encoding_table[(triple >> 0 * 6) & 0x3F];
+       }
+
+       if ((input_length % 3) > 0)
+               encoded_data[output_length - 1] = '=';
+       if ((input_length % 3) == 1)
+               encoded_data[output_length - 2] = '=';
+
+       encoded_data[output_length] = '\0';
+
+       return encoded_data;
+}
+
+char *base64_decode(const char *data) {
+       init_dtable();
+
+       size_t input_length = strlen(data);
+       if (input_length % 4 != 0)
+               return NULL;
+
+       size_t output_length = ((input_length / 4) * 3);
+       if (data[input_length - 1] == '=')
+               output_length--;
+       if (data[input_length - 2] == '=')
+               output_length--;
+
+       char *decoded_data = malloc(output_length + 1);
+       if (!decoded_data)
+               return NULL;
+
+       for (unsigned int i = 0, j = 0; i < input_length; i += 4) {
+               unsigned int sextet_a = 0;
+               unsigned int sextet_b = 0;
+               unsigned int sextet_c = 0;
+               unsigned int sextet_d = 0;
+
+               if (data[i] != '=') {
+                       sextet_a = decoding_table[(unsigned char) data[i + 0]];
+                       sextet_b = decoding_table[(unsigned char) data[i + 1]];
+                       sextet_c = decoding_table[(unsigned char) data[i + 2]];
+                       sextet_d = decoding_table[(unsigned char) data[i + 3]];
+               }
+
+               unsigned int triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6)
+                               + (sextet_c << 1 * 6) + (sextet_d << 0 * 6);
+
+               if (j < output_length)
+                       decoded_data[j++] = (char) ((triple >> 2 * 8) & 0xFF);
+               if (j < output_length)
+                       decoded_data[j++] = (char) ((triple >> 1 * 8) & 0xFF);
+               if (j < output_length)
+                       decoded_data[j++] = (char) ((triple >> 0 * 8) & 0xFF);
+       }
+
+       decoded_data[output_length] = '\0';
+       return decoded_data;
+}
+
diff --git a/base64.h b/base64.h
new file mode 100644 (file)
index 0000000..b6c3f25
--- /dev/null
+++ b/base64.h
@@ -0,0 +1,71 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file base64.h
+ * @author Niki
+ * @date 2013 - 2022
+ *
+ * @brief Base64 encode and decode
+ *
+ * This file only provides 2 functions, <tt>base64_encode</tt> and
+ * <tt>base64_decode</tt>, which works on NUL-terminated strings and do what
+ * you expect them to.
+ *
+ * @see base64_encode(const char data[])
+ * @see base64_decode(const char data[])
+ */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef BASE64_H
+#define BASE64_H
+
+#include <stdlib.h>
+
+/**
+ * Encode the given data to Base64.
+ *
+ * @note can return NULL if there is not enough memory to allocated the answer
+ *
+ * @param data the data to encode
+ *
+ * @return a newly-allocated string for which you are responsible, or NULL
+ */
+char *base64_encode(const char data[]);
+
+/**
+ * Decode the given data to Base64.
+ *
+ * @note can return NULL if there is not enough memory to allocated the answer
+ *
+ * @param data the data to decode
+ *
+ * @return a newly-allocated string for which you are responsible, or NULL
+ */
+char *base64_decode(const char data[]);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/check/launcher.c b/check/launcher.c
new file mode 100644 (file)
index 0000000..855bf25
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2022 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "launcher.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+// Shared globals
+int launcher_color = -1;
+
+// Private functions
+static int has_colour();
+static char *waiting_color();
+static char *passed_color();
+static char *failed_color();
+static char *stop_color();
+
+// Private vars
+// static char BLEU[] = { (char) 27, '[', '3', '4', 'm', '\0' };
+// static char TEAL[] = { (char) 27, '[', '3', '6', 'm', '\0' };
+// static char GRIS[] = { (char) 27, '[', '3', '7', 'm', '\0' };
+static char VERT[] = { (char) 27, '[', '3', '2', 'm', '\0' };
+static char ROUGE[] = { (char) 27, '[', '3', '1', 'm', '\0' };
+static char ORANGE[] = { (char) 27, '[', '3', '3', 'm', '\0' };
+static char STOP[] = { (char) 27, '[', '0', 'm', '\0' };
+static char bs9[] = { 8, 8, 8, 8, 8, 8, 8, 8, 8, '\0' };
+
+// COLORTERM : if it exists and is not empty, the system will default to
+// colour mode (--color) as opposed to --no-color
+
+// CK_ENV : Gets the print mode from the environment variable CK_VERBOSITY,
+// which can have the values "silent", "minimal", "normal", "verbose". If the
+// variable is not found or the value is not recognised, the print mode is set
+// to CK_NORMAL.
+
+// How to start the program:
+// ========================
+//
+// $0
+//     test the code (normal tests only)
+// $0 --more
+//     run extra tests (if available; usually, they are specified as extra if they
+//  take long to process)
+//
+//
+// $0 --name NAME
+//     do not test, just format the name as if it was tested
+// $0 --passed
+//     format the line as if it was passes
+// $0 --failed 
+//     format the line as if it was failed
+
+char *tests_name = NULL;
+
+void test_init(const char name[]) {
+       int i;
+       int cols;
+       struct winsize ws;
+
+       ioctl(1, TIOCGWINSZ, &ws);
+       cols = ws.ws_col;
+
+       if (!tests_name) {
+               tests_name = malloc(sizeof(char) * (cols + 1));
+       }
+
+       for (i = 0; i < (cols + 1); i++) {
+               if (i < 4)
+                       tests_name[i] = ' ';
+               else
+                       tests_name[i] = '.';
+       }
+
+       strcpy(tests_name + 4, name);
+       tests_name[strlen(name) + 4] = ' ';
+       tests_name[cols - 6 - 1 - 4] = '\0';
+
+       fprintf(stderr, "%s", tests_name);
+       fprintf(stderr, "%s[%s%s%s]    ", " ", waiting_color(), " ?? ",
+                       stop_color());
+}
+
+void test_success() {
+       fprintf(stderr, "%s[%s%s%s]    ", bs9, passed_color(), " OK ",
+                       stop_color());
+}
+
+void test_failure() {
+       fprintf(stderr, "%s[%s%s%s]    ", bs9, failed_color(), "FAIL",
+                       stop_color());
+}
+
+int test_start(int more) {
+       int failed;
+       SRunner *runner;
+
+       runner = get_tests(more);
+
+       failed = 0;
+       if (runner) {
+               srunner_run_all(runner, CK_ENV);
+
+               failed = srunner_ntests_failed(runner);
+               srunner_free(runner);
+       } else {
+               printf(">>> No tests have been found <<<\n");
+       }
+
+       return failed;
+}
+
+int main(int argc, char **argv) {
+       int more = 0;
+       int cont = 1;
+
+       for (int i = 1; i < argc; i++) {
+               if (!strcmp("--name", argv[i])) {
+                       if ((i + 1) >= argc)
+                               return 1;
+                       test_init(argv[++i]);
+                       cont = 0;
+               } else if (!strcmp("--passed", argv[i])) {
+                       test_success();
+                       cont = 0;
+               } else if (!strcmp("--failed", argv[i])) {
+                       test_failure();
+                       cont = 0;
+               } else if (!strcmp("--more", argv[i])) {
+                       more = 1;
+               } else if (!strcmp("--color", argv[i])) {
+                       launcher_color = 1;
+               } else if (!strcmp("--no-color", argv[i])) {
+                       launcher_color = 0;
+               }
+       }
+
+       if (!cont)
+               return 0;
+
+       return test_start(more);
+}
+
+static int has_colour() {
+       if (launcher_color == -1) {
+               // TODO: could we do better?
+               const char *cterm = getenv("COLORTERM");
+               if (cterm && cterm[0])
+                       launcher_color = 1;
+               else
+                       launcher_color = 0;
+       }
+
+       return launcher_color;
+}
+
+static char *waiting_color() {
+       if (has_colour())
+               return ORANGE;
+       return "";
+}
+
+static char *passed_color() {
+       if (has_colour())
+               return VERT;
+       return "";
+}
+
+static char *failed_color() {
+       if (has_colour())
+               return ROUGE;
+       return "";
+}
+
+static char *stop_color() {
+       if (has_colour())
+               return STOP;
+       return "";
+}
diff --git a/check/launcher.h b/check/launcher.h
new file mode 100644 (file)
index 0000000..b1e6e63
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef _LAUNCHER_H
+#define _LAUNCHER_H
+
+#include <check.h>
+
+#define START(name) \
+START_TEST(name) {\
+       test_init(#name);\
+
+#define END \
+       test_success();\
+}\
+END_TEST\
+
+#define FAIL(...) \
+ck_abort_msg(__VA_ARGS__)\
+
+#define ASSERT_EQUALS_STR(title, expected, received) \
+       do { if (strcmp(expected, received)) { \
+ck_abort_msg("%s\n\tExpected: <%s>\n\tReceived: <%s>", title, expected, received); \
+}} while(0)
+
+#define ASSERT_EQUALS_INT(title, expected, received) \
+       do { if (expected != received) { \
+ck_abort_msg("%s\n\tExpected: %lld\n\tReceived: %lld", title, (long long)expected, (long long)received); \
+}} while(0)
+
+#define ASSERT_EQUALS_SIZE(title, expected, received) \
+       do { if (expected != received) { \
+ck_abort_msg("%s\n\tExpected: %zu\n\tReceived: %zu", title, expected, received); \
+}} while(0)
+
+extern int launcher_color;
+
+SRunner *get_tests(int more);
+
+void test_init(const char name[]);
+
+int test_start(int more);
+
+void test_success();
+
+void test_failure();
+
+#endif
+
diff --git a/cstring.c b/cstring.c
new file mode 100644 (file)
index 0000000..07215e7
--- /dev/null
+++ b/cstring.c
@@ -0,0 +1,773 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2011 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ Name:        cstring.c
+ Copyright:   niki (gpl3 or later) 2011
+ Author:      niki
+ Date:        2011-06-16
+ Description: cstring is a collection of helper functions to manipulate text
+ */
+
+#include "cstring.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+
+// For upper/lowercase
+#include <locale.h>
+
+#ifndef BUFFER_SIZE
+#define BUFFER_SIZE 81
+#endif
+
+#ifdef WIN32
+#define CSTRING_SEP '\\'
+#else
+#define CSTRING_SEP '/'
+#endif
+
+// Private functions
+
+typedef struct {
+       size_t buffer_length;
+} priv_t;
+
+/** Swap the data */
+static void cstring_swap(cstring_t *a, cstring_t *b);
+/** Change the case to upper -or- lower case (UTF8-compatible) */
+static void cstring_change_case(cstring_t *self, int up);
+/** For path-related functions */
+static void normalize_path(cstring_t *self);
+
+// Private variables
+
+static char *locale = NULL;
+
+// end of privates
+
+cstring_t *new_cstring() {
+       cstring_t *self = malloc(sizeof(cstring_t));
+       if (!init_cstring(self)) {
+               free(self);
+               self = NULL;
+       }
+
+       return self;
+}
+
+int init_cstring(cstring_t *self) {
+       strcpy(self->CNAME, "[CString]");
+
+       self->priv = malloc(sizeof(priv_t));
+       if (!self->priv)
+               return 0;
+       self->string = malloc(sizeof(char) * BUFFER_SIZE);
+       if (!self->string) {
+               free(self->priv);
+               return 0;
+       }
+
+       self->length = 0;
+       ((priv_t *) self->priv)->buffer_length = BUFFER_SIZE;
+       self->string[0] = '\0';
+
+       return 1;
+}
+
+void free_cstring(cstring_t *self) {
+       if (self)
+               uninit_cstring(self);
+
+       free(self);
+}
+
+void uninit_cstring(cstring_t *self) {
+       free(self->priv);
+       free(self->string);
+       self->priv = NULL;
+       self->string = NULL;
+       self->length = 0;
+       self->CNAME[0] = '!';
+}
+
+void cstring_swap(cstring_t *a, cstring_t *b) {
+       void *tmp_p;
+       char *tmp_s;
+       size_t tmp_l;
+
+       tmp_s = a->string;
+       tmp_l = a->length;
+       tmp_p = a->priv;
+
+       a->string = b->string;
+       a->length = b->length;
+       a->priv = b->priv;
+
+       b->string = tmp_s;
+       b->length = tmp_l;
+       b->priv = tmp_p;
+}
+
+int cstring_grow(cstring_t *self, int min_extra) {
+       priv_t *priv = ((priv_t *) self->priv);
+
+       size_t sz = priv->buffer_length;
+       size_t req = self->length + min_extra;
+
+       if (req > sz) {
+               if (sz > BUFFER_SIZE)
+                       sz *= 2;
+               else
+                       sz += BUFFER_SIZE;
+
+               if (req > sz)
+                       sz = req;
+
+               return cstring_grow_to(self, sz);
+       }
+
+       return 1;
+}
+
+int cstring_grow_to(cstring_t *self, int min_buffer) {
+       priv_t *priv = ((priv_t *) self->priv);
+
+       if (min_buffer > priv->buffer_length) {
+               priv->buffer_length = min_buffer;
+               void *mem = realloc(self->string, sizeof(char) * priv->buffer_length);
+
+               if (mem)
+                       self->string = (char *) mem;
+               else
+                       return 0;
+       }
+
+       return 1;
+}
+
+void cstring_compact(cstring_t *self) {
+       if (self != NULL) {
+               priv_t *priv = ((priv_t *) self->priv);
+
+               priv->buffer_length = self->length + 1;
+               self->string = (char *) realloc(self->string, self->length + 1);
+       }
+}
+
+int cstring_add_car(cstring_t *self, char source) {
+       if (!cstring_grow(self, 1))
+               return 0;
+
+       self->string[self->length] = source;
+       self->length++;
+       self->string[self->length] = '\0';
+
+       return 1;
+}
+
+int cstring_add(cstring_t *self, const char source[]) {
+       return cstring_addf(self, source, 0);
+}
+
+int cstring_addf(cstring_t *self, const char source[], size_t idx) {
+       return cstring_addfn(self, source, idx, 0);
+}
+
+int cstring_addn(cstring_t *self, const char source[], size_t n) {
+       return cstring_addfn(self, source, 0, n);
+}
+
+int cstring_addfn(cstring_t *self, const char source[], size_t idx, size_t n) {
+       size_t ss;
+
+       ss = strlen(source);
+       if (source && ss > idx && idx >= 0) {
+               ss -= idx;
+
+               if (n && n < ss)
+                       ss = n;
+
+               if (ss) {
+                       // "+1" for the added '\0'
+                       if (!cstring_grow(self, ss + 1))
+                               return 0;
+
+                       memcpy(self->string + self->length, source + idx, ss);
+                       self->length += ss;
+                       self->string[self->length] = '\0';
+               }
+       }
+
+       return 1;
+}
+
+int cstring_addp(cstring_t *self, const char *fmt, ...) {
+       va_list ap;
+       char empty = '\0';
+
+       va_start(ap, fmt);
+       int sz = vsnprintf(&empty, 0, fmt, ap);
+       va_end(ap);
+
+       char *tmp = malloc((sz + 1) * sizeof(char));
+       if (!tmp)
+               return 0;
+
+       va_start(ap, fmt);
+       sz = vsnprintf(tmp, sz + 1, fmt, ap);
+       va_end(ap);
+
+       int ok = cstring_add(self, tmp);
+       free(tmp);
+
+       return ok;
+}
+
+void cstring_cut_at(cstring_t *self, size_t size) {
+       if (self->length > size) {
+               self->string[size] = '\0';
+               self->length = size;
+       }
+}
+
+cstring_t *cstring_substring(const char self[], size_t start, size_t length) {
+       size_t sz = strlen(self);
+       cstring_t * sub = new_cstring();
+
+       if (start <= sz) {
+               const char *source = (self + start);
+
+               if (!length)
+                       cstring_add(sub, source);
+               else
+                       cstring_addn(sub, source, length);
+       }
+
+       return sub;
+}
+
+/*
+ clist *cstring_splitc(cstring *self, char delim, char quote) {
+ clist *list;
+ cstring *d, *q;
+
+ d = new_cstring();
+ q = new_cstring();
+
+ if (delim)
+ cstring_add_car(d, delim);
+ if (quote)
+ cstring_add_car(q, quote);
+
+ list = cstring_split(self, d, q);
+
+ free_cstring(d);
+ free_cstring(q);
+
+ return list;
+ }
+
+ clist *cstring_splits(cstring *self, const char delim[], const char quote[]) {
+ clist *list;
+ cstring *d, *q;
+
+ d = new_cstring();
+ q = new_cstring();
+
+ if (delim)
+ cstring_add(d, delim);
+ if (quote)
+ cstring_add(q, quote);
+
+ list = cstring_split(self, d, q);
+
+ free_cstring(d);
+ free_cstring(q);
+
+ return list;
+ }
+ clist *cstring_split(cstring *self, cstring *delim, cstring *quote) {
+ clist *list;
+ cstring *elem;
+ clist_node *node;
+ size_t i;
+ int in_quote;
+ int hasdelim;
+
+ hasdelim = delim && delim->length > 0;
+
+ list = clist_new();
+ in_quote = 0;
+ elem = NULL;
+ for (i = 0; i < self->length; i++) {
+ if (quote->length > 0 && cstring_starts_with(self, quote, i)) {
+ in_quote = !in_quote;
+ i += quote->length - 1;
+ } else {
+ if (elem == NULL) {
+ elem = new_cstring();
+ node = clist_node_new();
+ node->data = elem;
+ node->free_data = free_cstring;
+ clist_add(list, node);
+ }
+ if (!in_quote && hasdelim && cstring_starts_with(self, delim, i)) {
+ elem = new_cstring();
+ node = clist_node_new();
+ node->data = elem;
+ node->free_data = free_cstring;
+ clist_add(list, node);
+ i += delim->length - 1;
+ } else {
+ cstring_add_car(elem, self->string[i]);
+ }
+ }
+ }
+
+ return list;
+ }
+ */
+
+void cstring_reverse(char *self) {
+       size_t i;
+       size_t last;
+       char tmp;
+
+       size_t sz = strlen(self);
+       if (sz) {
+               last = sz - 1;
+               for (i = 0; i <= (last / 2); i++) {
+                       tmp = self[i];
+                       self[i] = self[last - i];
+                       self[last - i] = tmp;
+               }
+       }
+}
+
+int cstring_replace(cstring_t *self, const char from[], const char to[]) {
+       cstring_t *buffer;
+       size_t i;
+       size_t step;
+       int occur;
+
+       // easy optimisation:
+       if (!from || !from[0])
+               return 0;
+       if (from && to && from[0] && to[0] && !from[1] && !to[1])
+               return cstring_replace_car(self->string, from[0], to[0]);
+
+       // optimise for same-size strings?
+
+       step = strlen(from) - 1;
+       buffer = new_cstring();
+       occur = 0;
+       for (i = 0; i < self->length; i++) {
+               if (cstring_starts_with(self->string, from, i)) {
+                       cstring_add(buffer, to);
+                       i += step;
+                       occur++;
+               } else {
+                       cstring_add_car(buffer, self->string[i]);
+               }
+       }
+
+       cstring_swap(self, buffer);
+       free_cstring(buffer);
+       return occur;
+}
+
+int cstring_replace_car(char *self, char from, char to) {
+       size_t i;
+       int occur = 0;
+
+       for (i = 0; self[i]; i++) {
+               if (self[i] == from) {
+                       self[i] = to;
+                       occur++;
+               }
+       }
+
+       return occur;
+}
+
+int cstring_starts_with(const char string[], const char find[],
+               size_t start_idx) {
+       size_t i;
+
+       for (i = 0;
+                       string[start_idx + i] == find[i] && string[start_idx + i] != '\0'
+                                       && find[i] != '\0'; i++)
+               ;
+
+       return find[i] == '\0';
+}
+
+int cstring_ends_with(const char self[], const char find[]) {
+       size_t sz = strlen(self);
+       size_t sz_needle = strlen(find);
+       if (sz_needle <= sz) {
+               if (!strcmp(self + (sz - sz_needle), find))
+                       return 1;
+       }
+
+       return 0;
+}
+
+long cstring_find(const char self[], const char find[], size_t start_index) {
+       size_t sz = strlen(self);
+       if (sz > start_index) {
+               char *found = strstr(self + start_index, find);
+               if (found) {
+                       return (long) (found - self);
+               }
+       }
+
+       return -1;
+}
+
+long cstring_rfind(char self[], const char find[], long rstart_index) {
+       size_t sz = strlen(self);
+       size_t sz_needle = strlen(find);
+
+       if (rstart_index <= 0)
+               rstart_index += (sz - 1);
+
+       if (sz > rstart_index && sz_needle <= sz) {
+               for (size_t i = rstart_index;; i--) {
+                       if (cstring_starts_with(self, find, i))
+                               return i;
+
+                       if (!i)
+                               break;
+               }
+       }
+
+       return -1;
+}
+
+void cstring_clear(cstring_t *self) {
+       self->length = 0;
+       self->string[0] = '\0';
+}
+
+char *cstring_convert(cstring_t *self) {
+       char *string;
+
+       if (!self)
+               return NULL;
+
+       // Note: this could be skipped.
+       cstring_compact(self);
+
+       string = (self->string);
+       self->string = NULL;
+
+       free_cstring(self);
+
+       return string;
+}
+
+cstring_t *cstring_clone(const char self[]) {
+       if (self == NULL)
+               return NULL;
+
+       cstring_t *clone = new_cstring();
+       cstring_add(clone, self);
+
+       return clone;
+}
+
+void cstring_rtrim(cstring_t *self, char car) {
+       for (size_t i = self->length - 1; i >= 0; i--) {
+               if (self->string[i] != car)
+                       break;
+               self->string[i] = '\0';
+               self->length--;
+       }
+}
+
+void cstring_trim(cstring_t *self, char car) {
+       if (car == '\0')
+               return;
+
+       cstring_rtrim(self, car);
+
+       int i = 0;
+       while (self->string[i] == car)
+               i++;
+
+       if (i) {
+               cstring_t *tmp = new_cstring();
+               cstring_add(tmp, self->string + i);
+
+               cstring_swap(self, tmp);
+               free_cstring(tmp);
+       }
+}
+
+size_t cstring_remove_crlf(char *self) {
+       size_t sz = strlen(self);
+       if (sz && self[sz - 1] == '\n')
+               sz--;
+       if (sz && self[sz - 1] == '\r')
+               sz--;
+
+       self[sz] = '\0';
+
+       return sz;
+}
+
+void cstring_toupper(cstring_t *self) {
+       cstring_change_case(self, 1);
+}
+
+void cstring_tolower(cstring_t *self) {
+       cstring_change_case(self, 0);
+}
+
+void cstring_change_case(cstring_t *self, int up) {
+       // Change LC_ALL to LANG if not found
+       // TODO: only take part we need (also, this is still bad practise)
+       if (!locale) {
+               locale = setlocale(LC_ALL, NULL);
+               if (!locale || !locale[0] || !strcmp("C", locale)) {
+                       char *lang = getenv("LANG");
+                       if (lang && lang[0]) {
+                               locale = setlocale(LC_ALL, lang);
+                               if (!locale)
+                                       locale = "";
+                       }
+               }
+       }
+
+       cstring_t *rep;
+       mbstate_t state_from, state_to;
+       wchar_t wide;
+       char tmp[10];
+       size_t count;
+
+       // init the state (NULL = internal hidden state, not thread-safe)
+       memset(&state_from, '\0', sizeof(mbstate_t));
+       memset(&state_to, '\0', sizeof(mbstate_t));
+
+       rep = new_cstring();
+
+       size_t i = 0;
+       while (i < self->length) {
+               count = mbrtowc(&wide, self->string + i, self->length - i, &state_from);
+
+               //incomplete (should not happen)
+               if (count == (size_t) -2) {
+                       // return;
+                       cstring_add_car(rep, '_');
+                       i++;
+                       continue;
+               }
+               // invalid multibyte sequence
+               if (count == (size_t) -1) {
+                       // return;
+                       cstring_add_car(rep, '_');
+                       i++;
+                       continue;
+               }
+
+               // End of String (should not happen, see WHILE condition)
+               if (!count)
+                       break;
+
+               // char is ok
+               i += count;
+
+               if (up)
+                       wide = (wchar_t) towupper((wint_t) wide);
+               else
+                       wide = (wchar_t) towlower((wint_t) wide);
+
+               count = wcrtomb(tmp, wide, &state_to);
+               if (count == (size_t) -1) {
+                       // failed to convert :(
+                       cstring_add_car(rep, '_');
+               } else {
+                       tmp[count] = '\0';
+                       cstring_add(rep, tmp);
+               }
+       }
+
+       cstring_swap(self, rep);
+       free_cstring(rep);
+}
+
+static char buffer[BUFFER_SIZE];
+int cstring_readline(cstring_t *self, FILE *file) {
+       size_t size = 0;
+       int full_line;
+
+       // sanity check:
+       if (!file)
+               return 0;
+
+       buffer[BUFFER_SIZE - 1] = '\0'; // just in case
+
+       if (!feof(file)) {
+               cstring_clear(self);
+               buffer[0] = '\0';
+
+               // Note: fgets() could return NULL if EOF is reached
+               if (!fgets(buffer, (int) BUFFER_SIZE - 1, file))
+                       return 0;
+
+               size = strlen(buffer);
+               full_line = ((file && feof(file)) || size == 0
+                               || buffer[size - 1] == '\n');
+               size = cstring_remove_crlf(buffer);
+               cstring_add(self, buffer);
+
+               // No luck, we need to continue getting data
+               while (!full_line) {
+                       if (!fgets(buffer, (int) BUFFER_SIZE - 1, file))
+                               break;
+
+                       size = strlen(buffer);
+                       full_line = ((file && feof(file)) || size == 0
+                                       || buffer[size - 1] == '\n');
+                       size = cstring_remove_crlf(buffer);
+                       cstring_add(self, buffer);
+               }
+
+               return 1;
+       }
+
+       return 0;
+}
+
+static void normalize_path(cstring_t *self) {
+       while (self->length && self->string[self->length - 1] == CSTRING_SEP)
+               self->length--;
+       self->string[self->length] = '\0';
+}
+
+void cstring_add_path(cstring_t *self, const char subpath[]) {
+       while (self->length && self->string[self->length - 1] == CSTRING_SEP)
+               self->length--;
+       cstring_add_car(self, CSTRING_SEP);
+       if (subpath && subpath[0]) {
+               cstring_add(self, subpath);
+       }
+
+       normalize_path(self);
+}
+
+int cstring_pop_path(cstring_t *self, int how_many) {
+       int count = 0;
+       size_t tmp;
+       char first = '\0';
+
+       if (self->length)
+               first = self->string[0];
+
+       normalize_path(self);
+       for (int i = 0; i < how_many; i++) {
+               tmp = self->length;
+               while (self->length && self->string[self->length - 1] != CSTRING_SEP)
+                       self->length--;
+               while (self->length && self->string[self->length - 1] == CSTRING_SEP)
+                       self->length--;
+               if (self->length != tmp)
+                       count++;
+       }
+       normalize_path(self);
+
+       // Root is root of root
+       if (first == CSTRING_SEP && !self->length)
+               cstring_add_car(self, CSTRING_SEP);
+
+       return count;
+}
+
+char *cstring_basename(const char path[], const char ext[]) {
+       size_t i;
+       size_t sz = strlen(path);
+
+       i = sz;
+       while (i && path[i] != CSTRING_SEP)
+               i--;
+
+       cstring_t *rep;
+       if (path[i] != CSTRING_SEP) {
+               rep = cstring_clone(path);
+       } else {
+               rep = new_cstring();
+               cstring_addf(rep, path, i + 1);
+       }
+
+       if (ext && ext[0] && cstring_ends_with(rep->string, ext)) {
+               cstring_cut_at(rep, rep->length - strlen(ext));
+       }
+
+       return cstring_convert(rep);
+}
+
+char *cstring_dirname(const char path[]) {
+       cstring_t *rep = cstring_clone(path);
+       cstring_pop_path(rep, 1);
+       return cstring_convert(rep);
+}
+
+int cstring_is_utf8(cstring_t *self) {
+       size_t rep = mbstowcs(NULL, self->string, 0);
+       // -2 = invalid, -1 = not whole
+       return (rep != (size_t) -2) && (rep != (size_t) -1);
+}
+
+char *cstring_concat(const char str1[], ...) {
+       if (!str1)
+               return NULL;
+
+       va_list args;
+       size_t total;
+       size_t prec;
+       char *arg;
+       char *ptr;
+       char *rep;
+
+       total = strlen(str1);
+       va_start(args, str1);
+       while ((arg = va_arg(args, char *))) {
+               total += strlen(arg);
+       }
+       va_end(args);
+
+       rep = malloc(total * sizeof(char) + 1);
+       ptr = rep;
+       ptr = strcpy(ptr, str1);
+       prec = strlen(str1);
+
+       va_start(args, str1);
+       while ((arg = va_arg(args, char *))) {
+               ptr = strcpy(ptr + prec, arg);
+               prec = strlen(arg);
+       }
+       va_end(args);
+
+       return rep;
+}
diff --git a/cstring.h b/cstring.h
new file mode 100644 (file)
index 0000000..98679dc
--- /dev/null
+++ b/cstring.h
@@ -0,0 +1,507 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file cstring.h
+ * @author Niki
+ * @date 2013 - 2022
+ *
+ * @brief Some string utility functions
+ *
+ * This file implements some basic functions of a string, most often by working
+ * directly with <tt>char *</tt> (but when needed with the provided
+ * <tt>cstring</tt> object).
+ */
+
+#ifndef CSTRING_H
+#define CSTRING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/**
+ * @brief A NUL-byte terminated string, with a known length
+ *
+ * The structure contains a suite of characters terminated by a NUL byte
+ * (or just a NUL byte), and a length.
+ * It is advised NOT to modify either of those directly.
+ * You can use <tt>cstring_convert</tt> to get a <tt>char *</tt>, though
+ * (in this case, the cstring <b>MUST NOT</b> be used again, and you are
+ * responsible for freeing said <tt>char *</tt>).
+ *
+ * @see cstring_convert
+ */
+typedef struct {
+       char CNAME[10];
+       char *string;
+       size_t length;
+       void *priv;
+} cstring_t;
+
+/**
+ * Instantiate a new cstring.
+ *
+ * @note always identical to <tt>malloc</tt> + <tt>init_cstring</tt>
+ *
+ * Create (and allocate the memory for) a new cstring.
+ * Do not forget to call cstring_free(cstring) when done.
+ *
+ * @see malloc()
+ * @see init_cstring(cstring_t *self)
+ */
+cstring_t *new_cstring();
+
+/**
+ * Instantiate a new cstring.
+ *
+ * Create (and allocate the memory for) a new cstring.
+ * Do not forget to call uninit_cstring(cstring_t *self) when done.
+ *
+ * @see new_cstring()
+ * @see uninit_cstring(cstring_t *self)
+ */
+int init_cstring(cstring_t *self);
+
+/**
+ * Free the given cstring.
+ *
+ * Free all the resources allocated for this cstring.
+ *
+ * @note always equivalent to <tt>uninit_cstring</tt> + <tt>free</tt>
+ *
+ * @see uninit_cstring(cstring_t *self)
+ * @see free(void *data)
+ */
+void free_cstring(cstring_t *self);
+
+/**
+ * Free the given cstring.
+ *
+ * Free all the resources allocated for this cstring.
+ *
+ * @param self the cstring to free, which MUST NOT be used again afterward
+ */
+void uninit_cstring(cstring_t *self);
+
+/**
+ * Grow the cstring to accommodate that many characters in addition to those
+ * already held, if needed.
+ *
+ * @param self the string to grow
+ * @param min the minimum number of extra characters it should be able to hold
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ *             pressure)
+ */
+int cstring_grow(cstring_t *self, int min_extra);
+
+/**
+ * Grow the cstring to accommodate that many characters in total, if needed.
+ *
+ * @param self the string to grow
+ * @param min the minimum number of characters it should be able to hold
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ *             pressure)
+ */
+int cstring_grow_to(cstring_t *self, int min_buffer);
+
+/**
+ * Compact the memory used by this string.
+ *
+ * @param self the string to work on
+ */
+void cstring_compact(cstring_t *self);
+
+/**
+ * Add a char at the end of the given cstring.
+ *
+ * @param self the cstring to work on
+ * @param source the character to add
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ *             pressure)
+ */
+int cstring_add_car(cstring_t *self, char source);
+
+/**
+ * Add a string (a sequence of char that MUST end with '\0') at the end of the
+ * given cstring.
+ *
+ * @param self the string to work on
+ * @param source the string to add
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ *             pressure)
+ */
+int cstring_add(cstring_t *self, const char source[]);
+
+/**
+ * Add a string (a sequence of char that MUST end with '\0') at the end of the
+ * given cstring, starting from index.
+ *
+ * @param self the cstring to work on
+ * @param source the string to add
+ * @param idx the starting index at which to copy from the source
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ *             pressure)
+ */
+int cstring_addf(cstring_t *self, const char source[], size_t idx);
+
+/**
+ * Add a string (a sequence of char that MAY end with '\0') at the end of the
+ * given string, up to N chars long.
+ *
+ * @param self the string to work on
+ * @param source the string to add
+ * @param n the maximum number of chars to add (excluding the NUL byte), or 0
+ *             to add the whole source (which MUST then end with a '\0')
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ *             pressure)
+ */
+int cstring_addn(cstring_t *self, const char source[], size_t n);
+
+/**
+ * Add a string (a sequence of char that MAY end with '\0') at the end of the
+ * given cstring, starting from index, up to N chars long.
+ *
+ * @param self the string to work on
+ * @param source the string to add
+ * @param idx the starting index at which to copy from the source
+ * @param n the maximum number of chars to add (excluding the NUL byte) or 0
+ *             to add the whole source (which MUST then end with a '\0')
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ *             pressure)
+ */
+int cstring_addfn(cstring_t *self, const char source[], size_t idx, size_t n);
+
+/**
+ * Add a string via the usual <tt>printf</tt> formatters.
+ *
+ * @param self the string to add to
+ * @param fmt the required format specifiers (@{see printf})
+ * @param ... the printf-format parameters
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ *             pressure)
+ */
+int cstring_addp(cstring_t *self, const char *fmt, ...);
+
+/**
+ * Cut the string at the given size if it is greater.
+ *
+ * E.g.: it will have (at most) this many characters (without counting NULL) in
+ * it after.
+ *
+ * @param self the string to work on
+ * @param size the size to cut at (the maximum size of the cstring after this
+ *             operation, NUL excepted)
+ */
+void cstring_cut_at(cstring_t *self, size_t size);
+
+/**
+ * Create a substring of this one.
+ *
+ * @param self the string to work on
+ * @param start the index to start at
+ * @param length the number of characters to copy, 0 for 'up to the end'
+ *
+ * @return a newly allocated cstring
+ */
+cstring_t *cstring_substring(const char self[], size_t start, size_t length);
+
+/**
+ * Split a cstring into "smaller" cstrings every time the given separator is found.
+ * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty).
+ *
+ * @param self the cstring to work on
+ * @param delim the separator, which can be longer than one character
+ *
+ * @return a list of cstring
+ */
+//TODO: use a []
+//clist *cstring_split(cstring *self, cstring *delim, cstring *quote);
+/**
+ * Split a cstring into "smaller" cstrings every time the given separator (which MUST end in \0) is found.
+ * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty).
+ *
+ * @param self the cstring to work on
+ * @param delim the separator, which can be longer than one character and MUST end with \0
+ *
+ * @return a list of cstring
+ */
+//TODO: use a []
+//clist *cstring_splits(cstring *self, const char delim[], const char quote[]);
+/**
+ * Split a cstring into "smaller" cstrings every time the given separator is found.
+ * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty).
+ *
+ * @param self the cstring to work on
+ * @param delim the separator
+ *
+ * @return a list of cstring
+ */
+//TODO: use a []
+//clist *cstring_splitc(cstring *self, char delim, char quote);
+/**
+ * Reverse the given string.
+ *
+ * @param self the string to work on
+ */
+void cstring_reverse(char *self);
+
+/**
+ * Replace all occurrences of a string inside the given cstring by another.
+ *
+ * @param self the string to work on
+ * @param from the string to replace
+ * @param to the replacement string
+ *
+ * @return the number of occurrences changed
+ */
+int cstring_replace(cstring_t *self, const char from[], const char to[]);
+
+/**
+ * Replace all occurrences of a char inside the given string by another.
+ *
+ * @param self the string to work on
+ * @param from the char to replace
+ * @param to the replacement char
+ *
+ * @return the number of occurrences changed
+ */
+int cstring_replace_car(char *self, char from, char to);
+
+/**
+ * Check if the string starts with the given pattern.
+ *
+ * @param self the string to work on
+ * @param find the string to find
+ * @param start_idx the index at which to start the comparison
+ *
+ * @return 1 if it does
+ */
+int cstring_starts_with(const char self[], const char find[], size_t start_idx);
+
+/**
+ * Check if the string ends with the given pattern.
+ *
+ * @param self the string to work on
+ * @param find the string to find (if empty, will always be found)
+ * @param start_index the index at which to start the comparison
+ *
+ * @return 1 if it does
+ */
+int cstring_ends_with(const char self[], const char find[]);
+
+/**
+ * Find the given substring in this one.
+ *
+ * @param self the string to work on
+ * @param find the string to find
+ * @param start_index the index at which to start the search
+ *
+ * @return the start index of the found string if found, or a negative value
+ * if not
+ */
+long cstring_find(const char self[], const char find[], size_t rstart_index);
+
+/**
+ * Find the given substring in this one, but search in the reverse direction.
+ *
+ * @param self the string to work on
+ * @param find the string to find
+ * @param rstart_index the index at which to start the search, or 0 for
+ *        "end of string" (remember that it is reverse, you would then never
+ *        find anything with a real rstart_index of 0), or a negative value
+ *        to count from the end of the string (-2 means 2 character before the
+ *        end)
+ *
+ * @return the start index of the found string if found, or a negative value
+ * if not
+ */
+long cstring_rfind(char self[], const char find[], long rstart_index);
+
+/**
+ * Clear (truncate its size to 0) the given string.
+ *
+ * @param self the string to work on
+ */
+void cstring_clear(cstring_t *self);
+
+/**
+ * Convert this cstring into a string
+ * This means that you MUST NOT call cstring_free nor use the cstring anymore.
+ * NULL will return NULL.
+ *
+ * @param self the cstring to work on
+ */
+char *cstring_convert(cstring_t *self);
+
+/**
+ * Clone this string.
+ * NULL will return NULL.
+ *
+ * @param self the string to clone
+ */
+cstring_t *cstring_clone(const char self[]);
+
+/**
+ * Trim this cstring of all trailing 'car' instances.
+ *
+ * @param self the cstring to work on
+ * @param car the character to trim (right only)
+ *
+ * @return a right trimmed cstring
+ */
+void cstring_rtrim(cstring_t *self, char car);
+
+/**
+ * Trim this cstring of all 'car' instances from the start and/or the
+ * end of the string.
+ * 
+ * @param self the cstring to work on
+ * @param car the character to trim
+ * 
+ * @return a trimmed cstring
+ */
+void cstring_trim(cstring_t *self, char car);
+
+/**
+ * Remove the \r and \n sequence (or one OR the other) at the end of the string.
+ *
+ * @param self the string to change
+ *
+ * @return the new length of the string
+ */
+size_t cstring_remove_crlf(char *self);
+
+/**
+ * Change the case to upper-case (UTF-8 compatible, but the string MUST be
+ * whole).
+ *
+ * @note: if LC_ALL is not set or is set to C and a viable $LANG exists, it will
+ *             set LC_ALL to $LANG
+ *
+ * @param self the cstring to work on
+ */
+void cstring_toupper(cstring_t *self);
+
+/**
+ * Change the case to lower-case (UTF-8 compatible, but the string MUST be
+ * whole).
+ *
+ * @note: if LC_ALL is not set or is set to C and a viable $LANG exists, it will
+ *             set LC_ALL to $LANG
+ *
+ * @param self the cstring to work on
+ */
+void cstring_tolower(cstring_t *self);
+
+/**
+ * Read a whole line (CR, LN or CR+LN terminated) from the given file stream.
+ *
+ * @param self the cstring to read into
+ * @param file the file to read
+ *
+ * @return 1 if a line was read, 0 if not
+ */
+int cstring_readline(cstring_t *self, FILE *file);
+
+/**
+ * Add a path to the given cstring (if it is currently empty, it
+ * will result in a root path).
+ *
+ * Will be separated by a forward '/' except on non-standard systems
+ * that uses reverse slash (i.e., Windows).
+ *
+ * @param self the base cstring (empty for a root path)
+ * @param subpath the path component to add
+ */
+void cstring_add_path(cstring_t *self, const char subpath[]);
+
+/**
+ * Remove the <tt>how_many</tt> components of the path described by this
+ * cstring. Will ignore extra path separators and always trim it from the final
+ * result (i.e., <tt>some//path/</tt> is identical to <tt>some/path</tt>).
+ *
+ * @note popping "0" path will simply make sure the string does not end in "/"
+ *
+ * @param how_many how many path components to remove (for instance, to go from
+ *             <tt>/some/path/to/file</tt> to <tt>/some/path</tt> you would need 2)
+ */
+int cstring_pop_path(cstring_t *self, int how_many);
+
+/**
+ * Return the basename component of this path (for instance,
+ * '/home/user/file.ext' becomes 'file.ext').
+ *
+ * @param path the path to get the dir of (it can be a dir itself)
+ * @param ext the extension to remove if any (can be empty or NULL for none)
+ *
+ * @note the extension should include the "." if any
+ *
+ * @return a new string representing the parent directory
+ */
+char *cstring_basename(const char path[], const char ext[]);
+
+/**
+ * Return the dirname of this path (for instance,
+ * '/home/user/file.ext' becomes '/home/user').
+ *
+ * @param path the path to get the dir of (it can be a dir itself)
+ *
+ * @return a new string representing the parent directory
+ */
+char *cstring_dirname(const char path[]);
+
+/**
+ * Check if the string is a correct and whole UTF-8 string (i.e., it is indeed
+ * an UTF-8 string and doesn't contain incomplete UTF-8 sequences).
+ * 
+ * @return TRUE if it is UTF-8
+ */
+int cstring_is_utf8(cstring_t *self);
+
+/**
+ * Concat all the given string and return the concatenation as a newly allocated
+ * string that you now own.
+ *
+ * @note the last parameter <b>must</b> be NULL
+ *
+ * @note if NULL is passed as first parameter, NULL will be returned
+ *
+ * @param str1 the first string
+ *
+ * @return the concatenated string or NULL if str1 is NULL
+ */
+char *cstring_concat(const char str1[], ...);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/cutils.c b/cutils.c
new file mode 100644 (file)
index 0000000..1141c2c
--- /dev/null
+++ b/cutils.c
@@ -0,0 +1,44 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "cutils.h"
+#include "cstring.h"
+
+#ifndef strnlen
+size_t strnlen(const char *s, size_t maxlen) {
+       size_t i;
+       for (i = 0; s[i]; i++) {
+               if (i >= maxlen)
+                       return maxlen;
+       }
+
+       return i;
+}
+#endif
+
+#ifndef strdup
+char *strdup(const char *source) {
+       size_t sz = strlen(source);
+       char *new = malloc((sz + 1) * sizeof(char));
+       strcpy(new, source);
+       return new;
+}
+#endif
diff --git a/cutils.h b/cutils.h
new file mode 100644 (file)
index 0000000..0090a31
--- /dev/null
+++ b/cutils.h
@@ -0,0 +1,79 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file cutils.h
+ * @author Niki
+ * @date 2020 - 2022
+ *
+ * @brief Include all the other .h as well as C99-compatible
+ * <tt>strdup</tt>/<tt>strnlen</tt> functions if they are not already defined
+ */
+#ifndef CUTILS_H
+#define CUTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "cstring.h"
+#include "array.h"
+#include "desktop.h"
+#include "print.h"
+#include "timing.h"
+
+/* Helps with C99 compatibility for code that is not */
+
+#if _POSIX_C_SOURCE < 200809L && _XOPEN_SOURCE < 500
+#ifndef _GNU_SOURCE
+/**
+ * The strnlen() function returns the number of bytes in the string pointed to
+ * by s, excluding the terminating null byte ('\0'), but at most maxlen.
+ * In doing this, strnlen() looks only at the first maxlen characters in the
+ * string pointed to by s and never beyond s[maxlen-1].
+ *
+ * @return The strnlen() function returns strlen(s), if that is less than
+ *             maxlen, or maxlen if there is no null terminating ('\0') among the first
+ *             maxlen characters pointed to by s
+ */
+size_t strnlen(const char *s, size_t maxlen);
+#endif
+#endif
+#if _POSIX_C_SOURCE < 200809L && _XOPEN_SOURCE < 500
+/**
+ * The strdup() function returns a pointer to a new string which is a
+ * duplicate of the string s.  Memory for the new string is obtained with
+ * malloc(3), and can be freed with free(3).
+ *
+ *
+ * @return On success, the strdup() function returns a pointer to the duplicated
+ *     string.  It returns NULL if insufficient memory was available, with
+ *     errno set to indicate the error.
+ */
+char *strdup(const char *source);
+#endif
+
+/* */
+
+#endif // CUTILS_H
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/desktop.c b/desktop.c
new file mode 100644 (file)
index 0000000..1892811
--- /dev/null
+++ b/desktop.c
@@ -0,0 +1,339 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2021 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <dirent.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#include "cutils.h"
+
+#define EXT "desktop"
+
+/* Private functions */
+static int desktop_compare(const void *a, const void* b);
+static int desktop_test_file(const char filename[]);
+/* */
+
+desktop_t *new_desktop(const char filename[], int best_size) {
+       desktop_t *me = malloc(sizeof(desktop_t));
+       if (!init_desktop(me, filename, best_size)) {
+               free(me);
+               me = NULL;
+       }
+
+       return me;
+}
+
+int init_desktop(desktop_t *me, const char filename[], int best_size) {
+       strcpy(me->CNAME, "[Desktop]");
+
+       me->name = NULL;
+       me->exec = NULL;
+       me->icon = NULL;
+       me->icon_file = NULL;
+       me->children = NULL;
+       me->id = 0;
+
+       // Copy name
+       me->name = strdup(filename);
+
+       // Get extension an remove ".desktop" from name
+       char *ext = rindex(me->name, '.');
+       if (ext) {
+               size_t idot = (ext - me->name);
+               ext++;
+               if (!strcmp(ext, EXT))
+                       me->name[idot] = '\0';
+       }
+       if (ext)
+               ext = strdup(ext);
+
+       // If PNG of the same name, use as default icon
+       me->icon_file = desktop_find_icon(me->name, best_size);
+
+       // Pretify default name (remove dir part)
+       char *slash = rindex(me->name, '/');
+       if (slash && !slash[1]) {
+               slash[0] = '\0';
+               slash = rindex(me->name, '/');
+       }
+       if (slash) {
+               char *copy = strdup(slash + 1);
+               free(me->name);
+               me->name = copy;
+       }
+
+       // Try with the base name, too
+       if (!me->icon_file) {
+               me->icon_file = desktop_find_icon(me->name, best_size);
+       }
+
+       DIR *dp = opendir(filename);
+       if (dp) {
+               // Try to get the default folder icon if no icon yet
+               if (!me->icon_file) {
+                       me->icon_file = desktop_find_icon("folder", best_size);
+               }
+
+               me->children = new_array(sizeof(desktop_t), 32);
+               for (struct dirent *ep = readdir(dp); ep; ep = readdir(dp)) {
+                       if (!strcmp(ep->d_name, "."))
+                               continue;
+                       if (!strcmp(ep->d_name, ".."))
+                               continue;
+
+                       desktop_t *child = array_new(me->children);
+                       char *childname = cstring_concat(filename, "/", ep->d_name, NULL);
+                       if (!init_desktop(child, childname, best_size))
+                               array_pop(me->children);
+                       free(childname);
+               }
+
+               array_qsort(me->children, desktop_compare);
+
+               closedir(dp);
+               free(ext);
+               return 1;
+       }
+
+       // Only process ".desktop" files
+       if (!ext || strcmp(ext, EXT)) {
+               uninit_desktop(me);
+               free(ext);
+               return 0;
+       }
+
+       FILE *file;
+       cstring_t *line;
+       char *startsWith;
+
+       file = fopen(filename, "r");
+       if (file) {
+               line = new_cstring();
+               while (cstring_readline(line, file)) {
+                       startsWith = "Name=";
+                       if (cstring_starts_with(line->string, startsWith, 0)) {
+                               free(me->name);
+                               me->name = strdup(line->string + strlen(startsWith));
+                       }
+
+                       startsWith = "Exec=";
+                       if (cstring_starts_with(line->string, startsWith, 0)) {
+                               free(me->exec);
+                               me->exec = strdup(line->string + strlen(startsWith));
+                               // TODO: %f %F %u %U %i %c %k: inject values instead
+                               char *cars = "ifFuUck";
+                               for (char *ptr = index(me->exec, '%'); ptr;
+                                               ptr = index(ptr, '%')) {
+                                       if (index(cars, ptr[1])) {
+                                               ptr[0] = ' ';
+                                               ptr[1] = ' ';
+                                       }
+                                       ptr++;
+                               }
+                       }
+
+                       startsWith = "Icon=";
+                       if (cstring_starts_with(line->string, startsWith, 0)) {
+                               free(me->icon);
+                               me->icon = strdup(line->string + strlen(startsWith));
+                       }
+               }
+               free_cstring(line);
+               fclose(file);
+       }
+
+       // Find icon file linked to icon
+       if (me->icon && !me->icon_file) {
+               me->icon_file = desktop_find_icon(me->icon, best_size);
+       }
+       // ...or any we can find, actually
+       if (!me->icon_file) {
+               me->icon_file = desktop_find_icon(me->name, best_size);
+       }
+
+       free(ext);
+       return 1;
+}
+
+void free_desktop(desktop_t *me) {
+       if (me)
+               uninit_desktop(me);
+
+       free(me);
+}
+
+void uninit_desktop(desktop_t *me) {
+       free(me->name);
+       free(me->exec);
+       free(me->icon);
+       free(me->icon_file);
+       me->name = NULL;
+       me->exec = NULL;
+       me->icon = NULL;
+       me->icon_file = NULL;
+       free_array(me->children);
+       me->children = NULL;
+       me->CNAME[0] = '!';
+}
+
+desktop_t *desktop_find_id(array_t *children, int id) {
+       desktop_t *found = NULL;
+
+       array_loop(children, child, desktop_t)
+       {
+               if (child->id == id) {
+                       found = child;
+                       break;
+               }
+
+               if (child->children) {
+                       found = desktop_find_id(child->children, id);
+                       break;
+               }
+       }
+
+       return found;
+}
+
+/* Private functions */
+
+static int desktop_compare(const void *a, const void* b) {
+       desktop_t *me1 = (desktop_t *)a;
+       desktop_t *me2 = (desktop_t *)b;
+
+       if (me1->children && !(me2->children))
+               return -1;
+       if (!(me1->children) && me2->children)
+               return 1;
+
+       return strcmp(me1->name, me2->name);
+}
+
+static int desktop_test_file(const char filename[]) {
+       FILE *test;
+       DIR *test_dir;
+
+       test = fopen(filename, "r");
+       if (test) {
+               fclose(test);
+               test_dir = opendir(filename);
+               if (test_dir) {
+                       closedir(test_dir);
+               } else {
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
+#define TRY_DIR(a,b,c) \
+       do { \
+               tmp = cstring_concat(a, b, c, basename, ".png", NULL); \
+               if(desktop_test_file(tmp)) \
+                       return tmp; \
+               free(tmp); \
+       } while(0)
+
+static char *theme = NULL;
+static char *ltheme = NULL;
+char *desktop_find_icon(const char basename[], int icon_size) {
+       char *tmp = NULL;
+       char *home = getenv("HOME");
+       char icon_size_str[100];
+       sprintf(icon_size_str, "%dx%d", icon_size, icon_size);
+
+       if (!theme) {
+               tmp = cstring_concat(home, "/.gtkrc-2.0", NULL);
+               FILE *file = fopen(tmp, "r");
+               free(tmp);
+               if (file) {
+                       const char *startsWith = "gtk-icon-theme-name=";
+                       size_t n = strlen(startsWith);
+
+                       cstring_t *line = new_cstring();
+                       while (cstring_readline(line, file)) {
+                               if (cstring_starts_with(line->string, startsWith, 0)) {
+                                       free(theme);
+                                       if (line->string[n] == '"') {
+                                               theme = strdup(line->string + n + 1);
+                                               theme[strlen(theme) - 1] = '\0';
+                                       } else {
+                                               theme = strdup(line->string + n);
+                                       }
+                               }
+                       }
+                       free_cstring(line);
+                       fclose(file);
+
+                       if (!theme || !theme[0]) {
+                               theme = strdup("");
+                               ltheme = strdup("");
+                       } else {
+                               tmp = theme;
+                               theme = cstring_concat("/usr/share/icons/", tmp, "/", NULL);
+                               ltheme = cstring_concat(home, "/", ".icons/", tmp, "/", NULL);
+                               free(tmp);
+                       }
+
+               }
+       }
+
+       // Allow NULL
+       if (!basename || !basename[0])
+               return NULL;
+
+       // exact match
+       tmp = strdup(basename);
+       if (desktop_test_file(tmp))
+               return tmp;
+       free(tmp);
+
+       // same name, with ".png"
+       TRY_DIR("", "", "");
+
+       // local icons
+       TRY_DIR(home, "/.local/share/icons/", "");
+
+       // theme icons
+       if (theme && theme[0]) {
+               // exact size (apps, places)
+               TRY_DIR(theme, icon_size_str, "/apps/");
+               TRY_DIR(theme, icon_size_str, "/places/");
+
+               // scalable (apps, places)
+               TRY_DIR(ltheme, "scalable", "/apps/");
+               TRY_DIR(ltheme, "scalable", "/places/");
+       }
+
+       // shared icons, exact size (apps, places)
+       TRY_DIR("/usr/share/icons/hicolors/", icon_size_str, "/apps/");
+       TRY_DIR("/usr/share/icons/hicolors/", icon_size_str, "/places/");
+
+       // shared icons, scalable (apps, places)
+       TRY_DIR("/usr/share/icons/hicolors/scalable/apps/", "", "");
+       TRY_DIR("/usr/share/icons/hicolors/scalable/places/", "", "");
+
+       return NULL;
+}
diff --git a/desktop.h b/desktop.h
new file mode 100644 (file)
index 0000000..bf39398
--- /dev/null
+++ b/desktop.h
@@ -0,0 +1,146 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2021 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file desktop.h
+ * @author Niki
+ * @date 2021 - 2022
+ *
+ * @brief Manipulate <tt>.desktop</tt> files (as described by
+ * <a href='https://freedesktop.org/'>FreeDesktop.org</a>)
+ *
+ * This structure helps you manipulate <tt>.desktop</tt> files (as described by
+ * <a href='https://freedesktop.org/'>FreeDesktop.org</a>).
+ *
+ * @note the desktop object can use icons; for the selection of those, an exact
+ *             match will first be tried (same name as the <tt>desktop</tt> file, with
+ *             a <tt>.png</tt> extension), then we will look into the local
+ *             <tt>.local/share/icons</tt> and if we still haven't found an icon, into
+ *             the theme (first by looking for a <tt>best_size</tt> sized icon and if
+ *             not in <tt>scalable</tt>)
+ *
+ * @note we support the use of desktop objects for menu, too, and that includes
+ *             submenu items support
+ */
+#ifndef DESKTOP_H
+#define DESKTOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+#include "array.h"
+
+/**
+ * The structure used to represent desktop objects.
+ */
+struct {
+       char CNAME[10];
+       /** The user name of the desktop object. */
+       char *name;
+       /** The icon name, if any. */
+       char *icon;
+       /** The icon file that corresponds, if any. */
+       char *icon_file;
+       /** The EXEC command to start. */
+       char *exec;
+       /** The submenu items of this desktop object (for a menu/submenu). */
+       array_t *children;
+       /** A custom external ID for this desktop object, for your own use. */
+       int id;
+}typedef desktop_t;
+
+/**
+ * Create a new desktop object from the given <tt>.desktop</tt> file.
+ *
+ * @note always identical to <tt>malloc</tt> + <tt>init_desktop</tt>
+ *
+ * @param filename the path to the actual <tt>.desktop</tt> file
+ * @param best_size the default size for the icon (see icon selection in the
+ *             description of the {@see desktop} object
+ *
+ * @see malloc()
+ * @see init_desktop(desktop_t *self, const char filename[], int best_size)
+ * @see free_desktop(desktop_t *self)
+ *
+ * @return the desktop object
+ */
+desktop_t *new_desktop(const char filename[], int best_size);
+
+/**
+ * Create a new desktop object from the given <tt>.desktop</tt> file.
+ *
+ * @param filename the path to the actual <tt>.desktop</tt> file
+ * @param best_size the default size for the icon (see icon selection in the
+ *             description of the {@see desktop} object
+ *
+ * @see new_desktop(const char filename[], int best_size)
+ * @see uninit_desktop(desktop_t *self)
+ *
+ * @return TRUE if success (could fail if the target is not a <tt>.desktop</tt>
+ *             file
+ */
+int init_desktop(desktop_t *self, const char filename[], int best_size);
+
+/**
+ * Free the given desktop object.
+ *
+ * @note always equivalent to <tt>uninit_desktop</tt> + <tt>free</tt>
+ *
+ * @see uninit_desktop(desktop_t *self)
+ * @see free(void *data)
+ */
+void free_desktop(desktop_t *self);
+
+/**
+ * Free the resources used by the given desktop object -- do not use it anymore
+ * after this call.
+ */
+void uninit_desktop(desktop_t *self);
+
+/**
+ * Find a submenu item by the given ID ({@see desktop_set_id(desktop *, int)}).
+ *
+ * TODO: use full objects instead
+ * @param children the array of pointers to desktop objects to look through
+ * @param menu_id the ID of the submenu we want to find
+ *
+ * @return the given submenu if found, or NULL
+ */
+desktop_t *desktop_find_id(array_t *children, int menu_id);
+
+/**
+ * Look for the icon file related to this basename.
+ *
+ * @param basename the base name of the icon we want to look for
+ * @param icon_size the best_size to use for the icon (see the description of
+ *             the {@desktop} object)
+ *
+ * @return the path to the best related icon we found (you own it), or NULL
+ */
+char *desktop_find_icon(const char basename[], int icon_size);
+
+#endif /* DESKTOP_H */
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/net/net.c b/net/net.c
new file mode 100644 (file)
index 0000000..7ce1cb2
--- /dev/null
+++ b/net/net.c
@@ -0,0 +1,263 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2012 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <signal.h>
+
+#ifndef WIN32
+       #include <fcntl.h>
+       #include <netdb.h>
+#else
+       #include <ws2tcpip.h>
+#endif
+
+#include "net.h"
+
+#define bool int
+#define true 1
+#define false 0
+
+/**
+ * Get the sockaddr, IPv4 or IPv6.
+ * @param sa the socket address is in this structure
+ * @return the sockaddr_in or sockaddr_in6 inside this socketaddr,
+ *                depending if it is IPv4 or IPv6
+ */
+void *get_in_addr(struct sockaddr *sa);
+
+#ifndef WIN32
+/**
+ * Reap all zombie processes.
+ * This function will be called when a child process terminates, and
+ * will loop on all zombie processes to properly acknowledge them
+ * so they can die.
+ *
+ * @param s
+ */
+void sigchld_handler(int pid);
+
+void sigchld_handler(int pid) {
+       if (pid > 0 || pid < 0) pid = 0;
+
+       // Reap all zombie processes
+       while (waitpid(-1, NULL, WNOHANG) > 0);
+}
+#endif
+
+int net_init(){
+#if defined (WIN32)
+       WSADATA WSAData;
+       return !WSAStartup(MAKEWORD(2,2), &WSAData);
+#endif
+       return 1;
+}
+
+void net_cleanup(){
+#if defined (WIN32)
+       WSACleanup();
+#endif
+}
+
+int net_set_non_blocking(int fd) {
+       return net_set_blocking(fd, 0);
+}
+
+int net_set_blocking(int fd, int block) {
+       int flags;
+
+/* If they have O_NONBLOCK, use the POSIX way to do it */
+#if defined (O_NONBLOCK)
+       /* O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */
+       if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
+               flags = 0;
+       }
+       if (block) {
+               return fcntl(fd, F_SETFL, flags ^ O_NONBLOCK);
+       }
+       else {
+               return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+       }
+#else
+       flags = block?0:1;
+       return ioctl(fd, FIONBIO, (int)(&flags));
+#endif
+}
+
+int net_connect(const char server[], int port) {
+       int sockfd;
+       struct addrinfo hints, *servinfo, *p;
+       int rv;
+       char string[10];
+
+       memset(&hints, 0, sizeof(struct addrinfo));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+
+       // convert the port number to a string
+       sprintf(string, "%i%c", port, '\0');
+       rv = getaddrinfo(server, string, &hints, &servinfo);
+       //
+
+       if (rv != 0) {
+               // DO NOT dirty the stderr
+               //fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+               return -1;
+       }
+
+       // loop through all the results and connect to the first we can
+       for (p = servinfo; p != NULL; p = p->ai_next) {
+               if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))
+                               == -1) {
+                       //perror("client: socket");
+                       continue;
+               }
+
+               if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
+                       close(sockfd);
+                       //perror("client: connect");
+                       continue;
+               }
+
+               break;
+       }
+
+       if (p == NULL) {
+               //fprintf(stderr, "client: failed to connect\n");
+               return -1;
+       }
+       
+       freeaddrinfo(servinfo);
+
+       return sockfd;
+}
+
+int net_accept(int ssocketd) {
+       struct sockaddr_storage their_addr;
+       socklen_t sin_size;
+
+       sin_size = sizeof(their_addr);
+       return accept(ssocketd, (struct sockaddr *) &their_addr, &sin_size);
+}
+
+int net_listen(int port, int backlog) {
+#ifndef WIN32
+    struct sigaction sa;
+#endif
+       int sockfd;
+       struct addrinfo hints, *servinfo, *p;
+       char yes = 1;
+       int rv;
+       char string[10];
+
+       memset(&hints, 0, sizeof hints);
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_flags = AI_PASSIVE; // use my IP
+
+       // convert the port number to a string
+       sprintf(string, "%i%c", port, '\0');
+       rv = getaddrinfo(NULL, string, &hints, &servinfo);
+       //
+
+       if (rv != 0) {
+               // DO NOT dirty the stderr
+               //fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+               return -1;
+       }
+
+       // loop through all the results and bind to the first we can
+       for (p = servinfo; p != NULL; p = p->ai_next) {
+               if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
+                       //perror("server: socket");
+                       continue;
+               }
+
+               if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
+                       //perror("setsockopt");
+                       return -1;
+               }
+
+               if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
+                       close(sockfd);
+                       //perror("server: bind");
+                       continue;
+               }
+
+               break;
+       }
+
+       if (p == NULL) {
+               //fprintf(stderr, "server: failed to bind\n");
+               return -1;
+       }
+
+       // all done with this structure
+       freeaddrinfo(servinfo);
+       
+       if (listen(sockfd, backlog) == -1) {
+               //perror("listen");
+               return -1;
+       }
+
+#ifndef WIN32
+       sa.sa_handler = sigchld_handler;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+               //perror("sigaction");
+               return -1;
+       }
+#endif
+
+       return sockfd;
+}
+
+void net_close_socketd(int socketd) {
+       close(socketd);
+}
+
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+ssize_t net_write(int fd, const void *buf, size_t n) {
+       // In UNIX: send() with flag set to '0' == write()
+       // In WIN32: cannot write() to a socket
+       return send(fd, (char *)buf, n, MSG_NOSIGNAL);
+}
+
+ssize_t net_read(int fd, void *buf, size_t nbytes) {
+       // In UNIX: recv() with flag set to '0' == send()
+       // In WIN32: cannot read() from a socket
+       return recv(fd, (char *)buf, nbytes, 0);
+}
+
+void *get_in_addr(struct sockaddr *sa) {
+       if (sa->sa_family == AF_INET) {
+               return &(((struct sockaddr_in*) sa)->sin_addr);
+       }
+       else {
+               return &(((struct sockaddr_in6*) sa)->sin6_addr);
+       }
+}
+
diff --git a/net/net.h b/net/net.h
new file mode 100644 (file)
index 0000000..73140f3
--- /dev/null
+++ b/net/net.h
@@ -0,0 +1,164 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2012 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file net.h
+ * @author niki
+ * @date 2011 - 2022
+ *
+ * @brief Send/receive data from the network
+ *
+ * Allows you to make connections to/from a server, and to send/receive data
+ * through those connections.
+ */
+
+#ifndef NET_H
+#define NET_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// WHY ??
+#ifdef _WIN32
+#ifndef WIN32
+#define WIN32
+#endif
+#endif
+
+#ifdef WIN32
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x501
+#endif
+#include <winsock2.h>
+#include <ws2tcpip.h>
+typedef SSIZE_T ssize_t;
+#define close(s) closesocket(s)
+#define ioctl(a,b,c) ioctlsocket(a,b,c)
+#pragma comment(lib, "wsock32.lib");
+#pragma comment(lib, "ws2_32.lib");
+#else
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <sys/wait.h>
+#endif
+
+/* for ssize_t */
+#include <sys/types.h>
+
+/**
+ * You must call this function before doing any network related operation,
+ * because of some legacy WIN32 rule.
+ * (It is a noop on all other platforms.)
+ *
+ * @return FALSE when the Windows host does not support WinSock and thus,
+ *             no network for you (you still need to call net_cleanup)
+ */
+int net_init();
+
+/**
+ * You must call this function after you are done using network related
+ * operations within this DLL, because of some legacy WIN32 rule.
+ * (It is a noop on all other platforms.)
+ */
+void net_cleanup();
+
+/**
+ * Set the given socket to (non-)blocking I/O mode.
+ * This function can work with file sockets, too.
+ *
+ * @param fd the file descriptor or socket to change
+ * @param block TRUE to block, FALSE not to block
+ * 
+ * @return TRUE if success
+ */
+int net_set_blocking(int fd, int block);
+
+/**
+ * Connect to this server on this port, and return a socket descriptor
+ * to write to or read from it.
+ *
+ * @param server the server to connect to
+ * @param port the port to connect on
+ *
+ * @return the server socket or a negative value on error
+ */
+int net_connect(const char server[], int port);
+
+/**
+ * Open a port and returns a (server) socket descriptor from which you can
+ * accept connections.
+ *
+ * @param port the port to connect on
+ * @param backlog the maximum number of client connections we will queue for
+ *             this socket until you handle them
+ *
+ * @return the server socket, or a negative value on error
+ */
+int net_listen(int port, int backlog);
+
+/**
+ * Block (or not) and wait for a client to connect on this socket. 
+ * When this is done, return a socket to this specific client/server
+ * connection. It can takes the connections from a queue,
+ * as defined in net_listen.
+ *
+ * @param ssocketd the server socket on which to accept a connection
+ *
+ * @return the socket, or a negative value on error
+ */
+int net_accept(int ssocketd);
+
+/**
+ * Close a socket (or a server socket).
+ *
+ * @param socketd the (server) socket to close
+ */
+void net_close_socketd(int socketd);
+
+/**
+ * Write to this socket, as you would with a file.
+ *
+ * @param fd the socket to write to
+ * @param buf the buffer to read from
+ * @param n the number of bytes to write
+ *
+ * @return the actual number of bytes written or a negative number if error
+ */
+ssize_t net_write(int fd, const void *buf, size_t n);
+
+/**
+ * Read from this socket, as you would with a file.
+ *
+ * @param fd the socket to read from
+ * @param buf the buffer to write to
+ * @param nbytes the number of bytes to read
+ *
+ * @return the actual number of bytes read
+ */
+ssize_t net_read(int fd, void *buf, size_t nbytes);
+
+#endif
+
+#ifdef __cplusplus
+extern}
+#endif
diff --git a/print.h b/print.h
new file mode 100644 (file)
index 0000000..f0872a6
--- /dev/null
+++ b/print.h
@@ -0,0 +1,62 @@
+/**
+ * @file print.h
+ * @author Samantaz Fox
+ * @date 2019
+ *
+ * @brief File name and line numbers
+ *
+ * Small utility header designed to print file name and line number
+ * along with debug/info messages.
+ *
+ * Usage example:
+ *   ERROR("program exited with code %d\n", getErrorCode() );
+ *
+ * Known issues:
+ *   - Must take at least one argument in addition to format string
+ *
+ *
+ * Copyright 2019 (C) Samantaz Fox
+ * https://github.com/SamantazFox/Micro-projects/raw/master/library/print.h
+ *
+ * This file is in the public domain.
+ * Feel free to copy, modify or redistribute it!
+*/
+
+#ifndef __PRINT_H__
+#define __PRINT_H__
+
+
+#ifdef __cplusplus
+ #include <cstdio>
+#else
+ #include <stdio.h>
+#endif
+
+
+#define TRACE(_format, ...) \
+       do { \
+               fprintf(stdout, "[Trace] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+       } while (0)
+
+#define DEBUG(_format, ...) \
+       do { \
+               fprintf(stdout, "[Debug] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+       } while (0)
+
+#define INFO(_format, ...) \
+       do { \
+               fprintf(stdout, "[Info ] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+       } while (0)
+
+#define WARN(_format, ...) \
+       do { \
+               fprintf(stderr, "[Warn ] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+       } while (0)
+
+#define ERROR(_format, ...) \
+       do { \
+               fprintf(stderr, "[Error] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+       } while (0)
+
+
+#endif  /* !__PRINT_H__ */
diff --git a/timing.h b/timing.h
new file mode 100644 (file)
index 0000000..3895e49
--- /dev/null
+++ b/timing.h
@@ -0,0 +1,62 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/** 
+ * @file timing.h
+ * @author Niki
+ * @date 2020 - 2024
+ *
+ * @brief Timing macros START and STOP
+ *
+ * 2 macro are provided to print the elapsed time between the 2 to stdout.
+ */
+
+#ifndef TIMING_H
+#define TIMING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/time.h>
+
+/**
+ * Start the timer.
+ */
+#define START struct timeval TIMING_start, TIMING_stop; \
+       /* 1 usec = 0.000001 s */ \
+       char cusec[7]; \
+       gettimeofday(&TIMING_start, NULL);
+
+/**
+ * Stop the timer and print the elapsed time to stdout.
+ */
+#define STOP gettimeofday(&TIMING_stop, NULL); \
+       TIMING_stop.tv_sec  = TIMING_stop.tv_sec  - TIMING_start.tv_sec; \
+       TIMING_stop.tv_usec = TIMING_stop.tv_usec - TIMING_start.tv_usec; \
+       sprintf(cusec, "%0.6d", TIMING_stop.tv_usec); \
+       printf("TIME: %d.%s sec\n", TIMING_stop.tv_sec, cusec); \
+       gettimeofday(&TIMING_start, NULL);
+
+#endif // TIMING_H
+
+#ifdef __cplusplus
+}
+#endif
+