From 25ba0ff7231f86138dc52420770ec23e30bd0bca Mon Sep 17 00:00:00 2001 From: Tobias Reisinger Date: Fri, 17 Apr 2020 01:38:25 +0200 Subject: [PATCH] add: config loading --- CMakeLists.txt | 2 + confini.c | 5014 +++++++++++++++++++++++++++++++++++ controller.ini | 44 + database.c | 2 +- drivers/gpio.c | 6 +- drivers/piface.c | 6 +- handlers/loop.c | 28 +- helpers/load_config.c | 69 + include/config.h | 39 +- include/confini.h | 547 ++++ include/constants.h | 33 + include/drivers.h | 5 +- include/enums.h | 23 +- include/helpers.h | 5 + include/logger.h | 2 +- include/models/controller.h | 3 +- include/models/relay.h | 2 +- main.c | 22 + models/controller.c | 10 +- models/controller_load.c | 3 +- models/controller_save.c | 8 - models/relay.c | 1 - 22 files changed, 5799 insertions(+), 75 deletions(-) create mode 100644 confini.c create mode 100644 controller.ini create mode 100644 helpers/load_config.c create mode 100644 include/confini.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d03031..f9cacd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,8 @@ aux_source_directory(helpers HELPERS_SRC) aux_source_directory(handlers HANDLERS_SRC) aux_source_directory(drivers DRIVERS_SRC) +configure_file("controller.ini" "controller.ini" COPYONLY) + target_sources(controller PRIVATE ${SRC_DIR} ${MODELS_SRC} ${HELPERS_SRC} ${HANDLERS_SRC} ${DRIVERS_SRC}) target_include_directories(controller PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/confini.c b/confini.c new file mode 100644 index 0000000..63ca3c7 --- /dev/null +++ b/confini.c @@ -0,0 +1,5014 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ +/* Please make sure that the TAB width in your editor is set to 4 spaces */ + +/** + + @file confini.c + @brief libconfini functions + @author Stefano Gioffré + @copyright GNU General Public License, version 3 or any later version + @version 1.14.0 + @date 2016-2020 + @see https://madmurphy.github.io/libconfini + +**/ + + + /*/| + (_|_) _ _ _ __ _ _ + | (_) |__ ___ ___ _ __ / _(_)_ __ (_) + | | | '_ \ / __/ _ \| '_ \| |_| | '_ \| | + | | | |_) | (_| (_) | | | | _| | | | | | + |_|_|_.__/ \___\___/|_| |_|_| |_|_| |_|_| _ _ + ( | ) + |/*/ + + + +/** + + + @def INIFORMAT_TABLE_AS(_____) + + Content of the table: + + - Bits 1-19: INI syntax + - Bits 20-22: INI semantics + - Bits 23-24: Human syntax (disabled entries) + + + + @typedef int (* IniStatsHandler) ( + IniStatistics * statistics, + void * user_data + ) + + @param statistics + A pointer to the #IniStatistics to handle + @param user_data + The custom argument previously passed to the caller function + + + + @typedef int (* IniDispHandler) ( + IniDispatch *dispatch, + void * user_data + ) + + @param dispatch + A pointer to the #IniDispatch to handle + @param user_data + The custom argument previously passed to the caller function + + + + @typedef int (* IniStrHandler) ( + char * ini_string, + size_t string_length, + size_t string_num, + IniFormat format, + void * user_data + ) + + @param ini_string + The INI string to handle + @param string_length + The length of the INI string in bytes + @param string_num + The unique number that identifies @p ini_string within a + sequence of INI strings; it equals zero if @p ini_string is the + first or the only member of the sequence + @param format + The format of the INI file from which @p ini_string has been extracted + @param user_data + The custom argument previously passed to the caller function + + + + @typedef int (* IniSubstrHandler) ( + const char * ini_string, + size_t fragm_offset, + size_t fragm_length, + size_t fragm_num, + IniFormat format, + void * user_data + ) + + @param ini_string + The INI string containing the fragment to handle + @param fragm_offset + The offset of the selected fragment in bytes + @param fragm_length + The length of the selected fragment in bytes + @param fragm_num + The unique number that identifies the selected fragment within a + sequence of fragments of @p ini_string; it equals zero if the + fragment is the first or the only member of the sequence + @param format + The format of the INI file from which @p ini_string has been + extracted + @param user_data + The custom argument previously passed to the caller function + + + + @struct IniFormat + + @property IniFormat::delimiter_symbol + The key-value delimiter character (ASCII only allowed); if set + to `\0`, any space is delimiter + (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`); if, within the format + given, `IniFormat::delimiter_symbol` matches a metacharacter + (`'\\'`, `'\''`, `'\"'`), its role as metacharacter will have + higher priority than its role as delimiter symbol (i.e., the + format will have no key-value delimiter); you may use the + #IniDelimiters `enum` for this. + @property IniFormat::case_sensitive + If set to `true`, string comparisons will be always + case-sensitive. + @property IniFormat::semicolon_marker + The rule of the semicolon character (use `enum` + #IniCommentMarker for this). + @property IniFormat::hash_marker + The rule of the hash character (use `enum` #IniCommentMarker for + this). + @property IniFormat::section_paths + Defines whether and how the format supports sections (use `enum` + #IniSectionPaths for this). + @property IniFormat::multiline_nodes + Defines which class of entries are allowed to be multi-line (use + `enum` #IniMultiline for this). + @property IniFormat::no_spaces_in_names + If set to `true`, key and section names containing spaces (even + within quotes) will be rendered as #INI_UNKNOWN. Note that + setting #IniFormat::delimiter_symbol to #INI_ANY_SPACE will not + automatically set this option to `true` (spaces will still be + allowed in section names). + @property IniFormat::no_single_quotes + If set to `true`, the single-quote character (`'`) will be + considered as a normal character. + @property IniFormat::no_double_quotes + If set to `true`, the double-quote character (`"`) will be + considered as a normal character. + @property IniFormat::implicit_is_not_empty + If set to `true`, implicit keys (see @ref libconfini) will + be always dispatched using the values given by the global + variables #INI_GLOBAL_IMPLICIT_VALUE and + #INI_GLOBAL_IMPLICIT_V_LEN for the fields #IniDispatch::value + and to #IniDispatch::v_len respectively; if set to `false`, + implicit keys will be considered to be empty keys. + @property IniFormat::do_not_collapse_values + If set to `true`, sequences of one or more spaces in values + (`/\s+/`) will be dispatched verbatim. + @property IniFormat::preserve_empty_quotes + If set to `true`, and if single/double quotes are + metacharacters, ensures that, within values, empty strings + enclosed between quotes (`""` or `''`) will not be collapsed + together with the spaces that surround them. This option is + useful for values containing space-delimited arrays, in order to + preserve their empty members -- as in, for instance: + `coordinates = "" ""`. Note that, in section and key names, + empty strings enclosed between quotes are _always_ collapsed + together with their surrounding spaces. + @property IniFormat::disabled_after_space + If set to `true`, what follows `/[#;]\s/` is allowed to be + parsed as a disabled entry. + @property IniFormat::disabled_can_be_implicit + If set to `false`, comments that do not contain a key-value + delimiter will never be parsed as disabled keys, but always as + simple comments (even if the format supports implicit keys). + + + + @struct IniStatistics + + @property IniStatistics::format + The format of the INI file (see #IniFormat) + @property IniStatistics::bytes + The size in bytes of the parsed file + @property IniStatistics::members + The size in number of members (nodes) of the parsed file -- this + number always equals the number of dispatches that will be sent + by #load_ini_file(), #load_ini_path() or #strip_ini_cache() + + + + @struct IniDispatch + + @property IniDispatch::format + The format of the INI file (see #IniFormat) + @property IniDispatch::type + The dispatch type (see `enum` #IniNodeType) + @property IniDispatch::data + #IniDispatch::data can contain a comment, a section path or a + key name depending, on #IniDispatch::type; cannot be `NULL` + @property IniDispatch::value + It can be the value of a key element, an empty string or it can + point to the address pointed by the global variable + #INI_GLOBAL_IMPLICIT_VALUE (_the latter is the only case in + which `IniDispatch::value` can be `NULL`_) + @property IniDispatch::append_to + The current section path; cannot be `NULL` + @property IniDispatch::d_len + The length of the string #IniDispatch::data + @property IniDispatch::v_len + The length of the string #IniDispatch::value + @property IniDispatch::at_len + The length of the string #IniDispatch::append_to + @property IniDispatch::dispatch_id + The dispatch number (the first dispatch is number zero) + + +**/ + + + + /*\ + |*| + |*| LOCAL ENVIRONMENT + |*| ________________________________ + \*/ + + + + /* PREPROCESSOR PREAMBLE */ + + +/* String concatenation facilities */ +#define __PP_CAT__(STR1, STR2) STR1##STR2 +#define __PP_UCAT__(STR1, STR2) STR1##_##STR2 +#define __PP_EVALUCAT__(STR1, STR2) __PP_UCAT__(STR1, STR2) + + + + /* PREPROCESSOR ENVIRONMENT */ + + +#ifndef CONFINI_IO_FLAVOR +/** + + @brief The I/O API to use (possibly overridden via + `-DCONFINI_IO_FLAVOR=[FLAVOR]`) + + Possible values are `CONFINI_STANDARD` and `CONFINI_POSIX` + +**/ +#define CONFINI_IO_FLAVOR CONFINI_STANDARD +#endif + + + + /* PREPROCESSOR GLUE */ + + +#define _LIBCONFINI_PRIVATE_ITEM_(GROUP, ITEM) \ + __PP_EVALUCAT__(__PP_CAT__(_LIB, GROUP), __PP_CAT__(ITEM, _)) +#define _LIBCONFINI_CURRENT_FLAVOR_GET_(NAME) \ + _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, NAME) +#define _LIBCONFINI_IS_FLAVOR_(NAME) \ + _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == \ + _LIBCONFINI_PRIVATE_ITEM_(NAME, FLAVOR) + + + + /* AVAILABLE I/O FLAVORS */ + + +/* `-DCONFINI_IO_FLAVOR=CONFINI_STANDARD` (C99 Standard, default, REQUIRED) */ +#define _LIBCONFINI_STANDARD_SEOF_FN_(FILEPTR) fseek(FILEPTR, 0, SEEK_END) +#define _LIBCONFINI_STANDARD_FT_FN_(FILEPTR) ftell(FILEPTR) +#define _LIBCONFINI_STANDARD_FT_T_ long signed int +/* Any unique non-zero integer to identify this I/O API */ +#define _LIBCONFINI_STANDARD_FLAVOR_ 1 + +/* `-DCONFINI_IO_FLAVOR=CONFINI_POSIX` */ +#define _LIBCONFINI_POSIX_SEOF_FN_(FILEPTR) fseeko(FILEPTR, 0, SEEK_END) +#define _LIBCONFINI_POSIX_FT_FN_(FILEPTR) ftello(FILEPTR) +#define _LIBCONFINI_POSIX_FT_T_ off_t +/* Any unique non-zero integer to identify this I/O API */ +#define _LIBCONFINI_POSIX_FLAVOR_ 2 + +/* Define `_POSIX_C_SOURCE` macro when `CONFINI_IO_FLAVOR == CONFINI_POSIX` */ +#if _LIBCONFINI_IS_FLAVOR_(CONFINI_POSIX) +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#endif + +/* It is possible to add other I/O APIs here. Feel free to contribute! */ + + + + /* CHECKS */ + + +#if _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == 0 +#error Unsupported I/O API defined in macro CONFINI_IO_FLAVOR +#endif + + + + /* HEADERS */ + +#include +#include "confini.h" + + + + /* ALIASES */ + + +#define _LIBCONFINI_FALSE_ 0 +#define _LIBCONFINI_TRUE_ 1 +#define _LIBCONFINI_CHARBOOL_ unsigned char +#define _LIBCONFINI_SIMPLE_SPACE_ ' ' +#define _LIBCONFINI_HT_ '\t' +#define _LIBCONFINI_FF_ '\f' +#define _LIBCONFINI_VT_ '\v' +#define _LIBCONFINI_CR_ '\r' +#define _LIBCONFINI_LF_ '\n' +#define _LIBCONFINI_BACKSLASH_ '\\' +#define _LIBCONFINI_OPEN_SECTION_ '[' +#define _LIBCONFINI_CLOSE_SECTION_ ']' +#define _LIBCONFINI_SUBSECTION_ '.' +#define _LIBCONFINI_SEMICOLON_ ';' +#define _LIBCONFINI_HASH_ '#' +#define _LIBCONFINI_DOUBLE_QUOTES_ '"' +#define _LIBCONFINI_SINGLE_QUOTES_ '\'' +#define _LIBCONFINI_SEEK_EOF_(FILEPTR) \ + _LIBCONFINI_CURRENT_FLAVOR_GET_(SEOF_FN)(FILEPTR) +#define _LIBCONFINI_FTELL_(FILEPTR) \ + _LIBCONFINI_CURRENT_FLAVOR_GET_(FT_FN)(FILEPTR) +#define _LIBCONFINI_OFF_T_ \ + _LIBCONFINI_CURRENT_FLAVOR_GET_(FT_T) + + + + /* FUNCTIONAL MACROS AND CONSTANTS */ + + +/* The character that will replace sequences of one or more spaces (`/\s+/`) */ +#define _LIBCONFINI_COLLAPSED_ _LIBCONFINI_SIMPLE_SPACE_ + + +/* + + These may be any character in theory... But after the left-trim of each node + leading spaces work pretty well as metacharacters... + +*/ +/* Internal marker of standard comments */ +#define _LIBCONFINI_SC_INT_MARKER_ _LIBCONFINI_SIMPLE_SPACE_ +/* Internal marker of inline comments */ +#define _LIBCONFINI_IC_INT_MARKER_ _LIBCONFINI_HT_ + + +/* + + Checks whether a character can be escaped within a given format + +*/ +#define _LIBCONFINI_IS_ESC_CHAR_(CHR, FMT) ( \ + CHR == _LIBCONFINI_BACKSLASH_ ? \ + !INIFORMAT_HAS_NO_ESC(FMT) \ + : CHR == _LIBCONFINI_DOUBLE_QUOTES_ ? \ + !FMT.no_double_quotes \ + : \ + CHR == _LIBCONFINI_SINGLE_QUOTES_ && !FMT.no_single_quotes \ + ) + + +/* + + Checks whether a character represents any marker within a given format + +*/ +#define _LIBCONFINI_IS_ANY_MARKER_(CHR, FMT) ( \ + CHR == _LIBCONFINI_HASH_ ? \ + FMT.hash_marker != INI_IS_NOT_A_MARKER \ + : \ + CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER \ + ) + + +/* + + Checks whether a character represents any marker except `INI_IGNORE` within a + given format + +*/ +#define _LIBCONFINI_IS_COM_MARKER_(CHR, FMT) ( \ + CHR == _LIBCONFINI_HASH_ ? \ + FMT.hash_marker != INI_IS_NOT_A_MARKER && FMT.hash_marker != INI_IGNORE \ + : \ + CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER && FMT.semicolon_marker != INI_IGNORE \ + ) + + +/* + + Checks whether a character represents a marker of type `INI_DISABLED_OR_COMMENT` + within a given format + +*/ +#define _LIBCONFINI_IS_DIS_MARKER_(CHR, FMT) ( \ + CHR == _LIBCONFINI_HASH_ ? \ + FMT.hash_marker == INI_DISABLED_OR_COMMENT \ + : \ + CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_DISABLED_OR_COMMENT \ + ) + + +/* + + Checks whether a character represents a marker of type `INI_IGNORE` within a + given format + +*/ +#define _LIBCONFINI_IS_IGN_MARKER_(CHR, FMT) ( \ + CHR == _LIBCONFINI_HASH_ ? \ + FMT.hash_marker == INI_IGNORE \ + : \ + CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_IGNORE \ + ) + + +/* + + Checks whether a pointer is within the range + `INI_GLOBAL_IMPLICIT_VALUE >= PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN` + +*/ +#define _LIBCONFINI_IMPLICIT_RANGE_(PTR) \ + (PTR >= INI_GLOBAL_IMPLICIT_VALUE && PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN) + + +/* + + Maybe in the future there will be support for UTF-8 casefold (although probably + not -- see "Code considerations" in the Manual), but for now only ASCII... + +*/ +#define _LIBCONFINI_CHR_CASEFOLD_(CHR) (CHR > 0x40 && CHR < 0x5b ? CHR | 0x60 : CHR) + + +/* + + Possible depths of `_LIBCONFINI_SPACES_` (see function #is_some_space()). + + Please, consider the following three constants as belonging together to a + virtual opaque `enum`. + +*/ +#define _LIBCONFINI_WITH_EOL_ -1 +#define _LIBCONFINI_NO_EOL_ 1 +#define _LIBCONFINI_JUST_S_T_ 3 + + +/* + + Other constants related to `_LIBCONFINI_SPACES_` + +*/ +#define _LIBCONFINI_EOL_IDX_ 0 +#define _LIBCONFINI_SPALEN_ 6 + + +/* + + The list of space characters -- do not change its order or its content! + +*/ +static const char _LIBCONFINI_SPACES_[_LIBCONFINI_SPALEN_] = { + _LIBCONFINI_LF_, + _LIBCONFINI_CR_, + _LIBCONFINI_VT_, + _LIBCONFINI_FF_, + _LIBCONFINI_HT_, + _LIBCONFINI_SIMPLE_SPACE_ +}; + + +/** + + @brief A list of possible string representations of boolean pairs + + There may be infinite pairs here. Each pair must be organized according to the + following order: + + 1. Signifier of `false` + 2. Signifier of `true` + + @note Everything **must** be lowercase in this list. + +**/ +static const char * const INI_BOOLEANS[][2] = { + { "no", "yes" }, + { "false", "true" }, + { "off", "on" } +}; + + + + /* ABSTRACT UTILITIES */ + + +/** + + @brief Checks whether a character is a space + @param chr The target character + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return A boolean: `true` if the character matches, `false` otherwise + +**/ +static inline _LIBCONFINI_CHARBOOL_ is_some_space (const char chr, const int8_t depth) { + register int8_t idx = depth; + while (++idx < _LIBCONFINI_SPALEN_ && chr != _LIBCONFINI_SPACES_[idx]); + return idx < _LIBCONFINI_SPALEN_; +} + + +/** + + @brief Soft left trim -- does not change the buffer + @param str The target string + @param offs The offset where to start the left trim + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The offset of the first non-space character + +**/ +static inline size_t ltrim_s (const char * const str, const size_t offs, const int8_t depth) { + register size_t idx = offs; + while (is_some_space(str[idx++], depth)); + return idx - 1; +} + + +/** + + @brief Hard left trim -- **does** change the buffer + @param str The target string + @param offs The offset where to start the left trim + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The offset of the first non-space character + +**/ +static inline size_t ltrim_h (char * const str, const size_t offs, const int8_t depth) { + register size_t idx = offs; + while (is_some_space(str[idx], depth)) { str[idx++] = '\0'; } + return idx; +} + + +/** + + @brief Shifting left trim -- **does** change the buffer + @param str The target string + @param offs The offset where to start the left trim + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The new length of the string + +**/ +static inline size_t ltrim_hh (char * const str, const size_t offs, const int8_t depth) { + register size_t idx_d = offs, idx_s = offs; + while (is_some_space(str[idx_s++], depth)); + if (--idx_s - idx_d) { + while ((str[idx_d++] = str[idx_s++])); + for (idx_s = idx_d; str[idx_s]; str[idx_s++] = '\0'); + return idx_d - 1; + } + while (str[idx_s++]); + return idx_s - 1; +} + + +/** + + @brief Soft right trim -- does not change the buffer + @param str The target string + @param len The length of the string + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The length of the string until the last non-space character + +**/ +static inline size_t rtrim_s (const char * const str, const size_t len, const int8_t depth) { + register size_t idx = len + 1; + while (--idx > 0 && is_some_space(str[idx - 1], depth)); + return idx; +} + + +/** + + @brief Hard right trim -- **does** change the buffer + @param str The target string + @param len The length of the string + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The new length of the string + +**/ +static inline size_t rtrim_h (char * const str, const size_t len, const int8_t depth) { + register size_t idx = len; + while (idx > 0 && is_some_space(str[idx - 1], depth)) { str[--idx] = '\0'; } + return idx; +} + + +/** + + @brief Unescaped soft right trim (right trim of `/(?:\s|\\[\n\r])+$/`) + -- does not change the buffer + @param str The target string + @param len The length of the string + @return The length of the string until the last non-space character + +**/ +static inline size_t urtrim_s (const char * const str, const size_t len) { + + register uint8_t abcd = 1; + register size_t idx = len; + + + /* \ /\ + \ */ continue_urtrim: /* \ + \/ ______________________ \ */ + + + if (idx < 1) { + + return idx; + + } + + switch (str[--idx]) { + + case _LIBCONFINI_VT_: + case _LIBCONFINI_FF_: + case _LIBCONFINI_HT_: + case _LIBCONFINI_SIMPLE_SPACE_: + + abcd = 1; + goto continue_urtrim; + + case _LIBCONFINI_LF_: + case _LIBCONFINI_CR_: + + abcd = 3; + goto continue_urtrim; + + case _LIBCONFINI_BACKSLASH_: + + if (abcd >>= 1) { + + goto continue_urtrim; + + } + + } + + return idx + 1; + +} + + +/** + + @brief Converts an ASCII string to lower case + @param str The target string + @return Nothing + +**/ +static inline void string_tolower (char * const str) { + for (register char * chrptr = str; *chrptr; chrptr++) { + *chrptr = _LIBCONFINI_CHR_CASEFOLD_(*chrptr); + } +} + + + + /* CONCRETE UTILITIES */ + + +/** + + @brief Unparsed hard left trim (left trim of + `/^(?:\s+|\\[\n\r]|''|"")+/`) -- **does** change the buffer + @param srcstr The target string (it may contain multi-line + escape sequences) + @param offs The offset where to start the left trim + @param format The format of the INI file + @return The offset of the first non-trivial character + +**/ +static inline size_t qultrim_h (char * const srcstr, const size_t offs, const IniFormat format) { + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 Erase the previous character + FLAG_64 Erase this character + FLAG_128 Continue the loop + + */ + + register uint8_t abcd = (format.no_double_quotes ? 130 : 128) | format.no_single_quotes; + size_t idx = offs; + + do { + + abcd = !(abcd & 28) && is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ? + (abcd & 207) | 64 + : !(abcd & 12) && (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) ? + ( + abcd & 16 ? + (abcd & 239) | 96 + : + abcd | 64 + ) + : !(abcd & 25) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + abcd & 4 ? + (abcd & 235) | 96 + : + (abcd & 143) | 4 + ) + : !(abcd & 22) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + abcd & 8 ? + (abcd & 231) | 96 + : + (abcd & 159) | 8 + ) + : srcstr[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd & 159) ^ 16 + : + abcd & 31; + + + if (abcd & 32) { + + srcstr[idx - 1] = '\0'; + + } + + if (abcd & 64) { + + srcstr[idx] = '\0'; + + } + + idx++; + + } while (abcd & 128); + + return abcd & 28 ? idx - 2 : idx - 1; + +} + + +/** + + @brief Soft left trim within an unparsed disabled entry (left trim of + `/(?:(?:^|\\?[\n\r])[ \t\v\f]*(?:#(?:[ \t\v\f]|''|"")*)?)+/`) + -- does not change the buffer + @param srcstr The target string (it may contain multi-line + escape sequences) + @param offs The offset where to start the left trim + @param format The format of the INI file + @return The offset of the first non-trivial character + +**/ +static inline size_t dqultrim_s (const char * const srcstr, const size_t offs, const IniFormat format) { + + /* + + Mask `abcd` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 A new line has just begun + FLAG_64 Continue the left trim + + */ + + + register uint16_t abcd = format.no_single_quotes | + (format.no_double_quotes << 1) | + 96; + + register size_t idx = offs; + + do { + + abcd = is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ? + ( + abcd & 28 ? + abcd & 63 + : + abcd + ) + : srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ? + ( + abcd & 12 ? + (abcd & 47) | 32 + : + (abcd & 111) | 32 + ) + : srcstr[idx] == _LIBCONFINI_BACKSLASH_ ? + ( + abcd & 28 ? + (abcd & 31) | 16 + : + (abcd & 95) | 16 + ) + : (abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? + abcd & 79 + : !(abcd & 54) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 95) ^ 8 + : !(abcd & 57) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 95) ^ 4 + : srcstr[idx] ? + abcd & 31 + : + abcd & 19; + + + idx++; + + } while (abcd & 64); + + return abcd & 28 ? idx - 2 : idx - 1; + +} + + +/** + + @brief Gets the position of the first occurence out of quotes of a + given character, stopping after a given number of charcters + @param str The string where to search + @param chr The character to to search + @param len The maximum number of characters to read + @param format The format of the INI file + @return The offset of the first occurence of @p chr, or @p len if + @p chr has not been not found + +**/ +static inline size_t getn_metachar_pos (const char * const str, const char chr, const size_t len, const IniFormat format) { + + size_t idx = 0; + + /* + + Mask `abcd` (5 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + + */ + + for ( + + register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; + + idx < len && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_WITH_EOL_))); + + abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16 + : !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8 + : !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4 + : abcd & 15, + idx++ + + ); + + return idx; + +} + + +/** + + @brief Gets the position of the first occurence out of quotes of a + given character + @param str The string where to search + @param chr The character to to search + @param format The format of the INI file + @return The offset of the first occurence of @p chr or the length of + @p str if @p chr has not been not found + +**/ +static inline size_t get_metachar_pos (const char * const str, const char chr, const IniFormat format) { + + size_t idx = 0; + + /* + + Mask `abcd` (5 bits used): + + --> As in #getn_metachar_pos() + + */ + + for ( + + register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; + + str[idx] && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_NO_EOL_))); + + abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16 + : !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8 + : !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4 + : abcd & 15, + idx++ + + ); + + return idx; + +} + + +/** + + @brief Replaces `/\\(\n\r?|\r\n?)[\t \v\f]*[#;]/` or `/\\(\n\r?|\r\n?)/` + with `"$1"` + @param srcstr The target string (it may contain multi-line + escape sequences) + @param len Length of the string + @param is_disabled The string represents a disabled entry + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t unescape_cr_lf (char * const srcstr, const size_t len, const _LIBCONFINI_CHARBOOL_ is_disabled, const IniFormat format) { + + register size_t idx_s = 0, idx_d = 0; + register uint8_t eol_i = _LIBCONFINI_EOL_IDX_; + register _LIBCONFINI_CHARBOOL_ is_escaped = _LIBCONFINI_FALSE_; + size_t probe; + + while (idx_s < len) { + + if ( + is_escaped && ( + srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i] || srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^= 1] + ) + ) { + + srcstr[idx_d - 1] = srcstr[idx_s++]; + + if (srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^ 1]) { + + srcstr[idx_d++] = srcstr[idx_s++]; + + } + + if (is_disabled) { + + probe = ltrim_s(srcstr, idx_s, _LIBCONFINI_NO_EOL_); + + if (_LIBCONFINI_IS_DIS_MARKER_(srcstr[probe], format)) { + + idx_s = probe + 1; + + } + + } + + is_escaped = _LIBCONFINI_FALSE_; + + } else { + + is_escaped = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ? + !is_escaped + : + _LIBCONFINI_FALSE_; + + + srcstr[idx_d++] = srcstr[idx_s++]; + + } + + } + + for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0'); + + return idx_d; + +} + + +/** + + @brief Sanitizes a section path + @param secpath The section path + @param format The format of the INI file + @return The new length of the string + + Out of quotes, similar to ECMAScript + `secpath.replace(/\.*\s*$|(?:\s*(\.))+\s*|^\s+/g, "$1").replace(/\s+/g, " ")` + + A section path can start with a dot (append), but cannot end with a dot. Spaces + surrounding dots will be removed. Fragments surrounded by single or double + quotes (if these are enabled) are prevented from changes. + +**/ +static size_t sanitize_section_path (char * const secpath, const IniFormat format) { + + /* + + Mask `abcd` (12 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 These are initial spaces + FLAG_64 This is a space out of quotes + FLAG_128 This is a dot out of quotes + FLAG_256 This is anything *but* an opening single/double quote + FLAG_512 Don't ignore the last two characters + FLAG_1024 Don't overwrite the previous character + FLAG_2048 Path contains at least one name + + */ + + register uint16_t abcd = (format.no_double_quotes ? 1826 : 1824) | format.no_single_quotes; + register size_t idx_s = 0, idx_d = 0; + + for (; secpath[idx_s]; idx_s++) { + + /* Revision #2 */ + + abcd = !(abcd & 12) && is_some_space(secpath[idx_s], _LIBCONFINI_WITH_EOL_) ? + ( + abcd & 224 ? + (abcd & 3055) | 832 + : + (abcd & 4079) | 1856 + ) + : !(abcd & 12) && secpath[idx_s] == _LIBCONFINI_SUBSECTION_ ? + ( + abcd & (abcd & 32 ? 128 : 192) ? + (abcd & 2959) | 896 + : + (abcd & 3983) | 1920 + ) + : !(abcd & 25) && secpath[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + ~abcd & 4 ? + (abcd & 3839) | 1540 + : abcd & 256 ? + (abcd & 3867) | 3840 + : + (abcd & 3579) | 1280 + ) + : !(abcd & 22) && secpath[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + ~abcd & 8 ? + (abcd & 3839) | 1544 + : abcd & 256 ? + (abcd & 3863) | 3840 + : + (abcd & 3575) | 1280 + ) + : secpath[idx_s] == _LIBCONFINI_BACKSLASH_ ? + ((abcd & 3871) | 3840) ^ 16 + : + (abcd & 3855) | 3840; + + + if (abcd & 512) { + + secpath[ + abcd & 1024 ? + idx_d++ + : idx_d ? + idx_d - 1 + : + idx_d + ] = !(~abcd & 384) ? + _LIBCONFINI_SUBSECTION_ + : !(~abcd & 320) ? + _LIBCONFINI_COLLAPSED_ + : + secpath[idx_s]; + + } else if (idx_d) { + + idx_d--; + + } + + } + + for ( + + idx_s = idx_d && (abcd & 2048) && (abcd & 192) ? + --idx_d + : + idx_d; + + secpath[idx_s]; + + secpath[idx_s++] = '\0' + + ); + + return idx_d; + +} + + +/** + + @brief Out of quotes similar to ECMAScript + `ini_string.replace(/''|""/g, "").replace(/^[\n\r]\s*|\s+/g, " ")` + @param ini_string The string to collapse -- multi-line escape + sequences must be already unescaped at + this stage + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t collapse_everything (char * const ini_string, const IniFormat format) { + + /* + + Mask `abcd` (9 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 This is *not* a space out of quotes + FLAG_64 This is an opening single/double quote + FLAG_128 Don't ignore this character + FLAG_256 Jump this character and the one before this + + */ + + register size_t idx_s = 0, idx_d = 0; + + register uint16_t abcd = (is_some_space(*ini_string, _LIBCONFINI_WITH_EOL_) ? 128 : 160) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + + for (; ini_string[idx_s]; idx_s++) { + + /* Revision #2 */ + + abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ? + ( + abcd & 32 ? + (abcd & 143) | 128 + : + abcd & 47 + ) + : !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + ~abcd & 4 ? + (abcd & 239) | 196 + : abcd & 64 ? + (abcd & 299) | 256 + : + (abcd & 171) | 160 + ) + : !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + ~abcd & 8 ? + (abcd & 239) | 200 + : abcd & 64 ? + (abcd & 295) | 256 + : + (abcd & 167) | 160 + ) + : ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ? + ((abcd & 191) | 160) ^ 16 + : + (abcd & 175) | 160; + + + if (abcd & 256) { + + idx_d--; + + } else if (abcd & 128) { + + ini_string[idx_d++] = abcd & 44 ? ini_string[idx_s] : _LIBCONFINI_COLLAPSED_; + + } + + } + + for ( + + idx_s = !(abcd & 32) && idx_d ? + --idx_d + : + idx_d; + + ini_string[idx_s]; + + ini_string[idx_s++] = '\0' + + ); + + return idx_d; + +} + + +/** + + @brief Out of quotes similar to ECMAScript + `ini_string.replace(/\s+/g, " ")` + @param ini_string The string to collapse -- multi-line escape + sequences must be already unescaped at this + stage + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t collapse_spaces (char * const ini_string, const IniFormat format) { + + /* + + Mask `abcd` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 This is a space out of quotes + FLAG_64 Jump this character + + */ + + register uint8_t abcd = (format.no_double_quotes ? 34 : 32) | format.no_single_quotes; + register size_t idx_s = 0; + size_t idx_d = 0; + + for (; ini_string[idx_s]; idx_s++) { + + /* Revision #1 */ + + abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ? + ( + abcd & 32 ? + (abcd & 111) | 64 + : + (abcd & 47) | 32 + ) + : !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 15) ^ 4 + : !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 15) ^ 8 + : ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ? + (abcd & 31) ^ 16 + : + abcd & 15; + + + if (~abcd & 64) { + + ini_string[idx_d++] = abcd & 32 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx_s]; + + } + + } + + for ( + + idx_s = (abcd & 32) && idx_d ? + --idx_d + : + idx_d; + + ini_string[idx_s]; + + ini_string[idx_s++] = '\0' + + ); + + return idx_d; + +} + + +/** + + @brief Similar to ECMAScript `str.replace(/''|""/g, "")` + @param str The string to collapse + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t collapse_empty_quotes (char * const str, const IniFormat format) { + + /* + + Mask `abcd` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 This is an opening single/double quote + FLAG_64 These are empty quotes + + */ + + register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; + register size_t lshift = ltrim_s(str, 0, _LIBCONFINI_WITH_EOL_), idx = lshift; + + for (; str[idx]; idx++) { + + /* Revision #1 */ + + abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd & 31) ^ 16 + : !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + ~abcd & 40 ? + ((abcd & 47) | 32) ^ 8 + : + (abcd & 71) | 64 + ) + : !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + ~abcd & 36 ? + ((abcd & 47) | 32) ^ 4 + : + (abcd & 75) | 64 + ) + : + abcd & 15; + + + str[idx - lshift] = str[idx]; + + if (abcd & 64) { + + lshift += 2; + + } + + } + + for (idx -= lshift; str[idx]; str[idx++] = '\0'); + + return rtrim_h(str, idx - lshift, _LIBCONFINI_WITH_EOL_); + +} + + +/** + + @brief Removes all comment initializers (`#` and/or `;`) from the + beginning of each line of a comment + @param srcstr The comment to parse (it may contain multi-line + escape sequences) + @param len The length of @p srcstr + @param format The format of the INI file + @return The new length of the string + + - In multi-line comments: `srcstr.replace(/^[#;]+|(\n\r?|\r\n?)[\t \v\f]*[#;]+/g, "$1")` + - In single-line comments: `srcstr.replace(/^[#;]+/, "")` + + The argument @p srcstr may begin with a comment initializer (`#` or `;` + depending on the format), or with the character that immediately follows it. + +**/ +static size_t uncomment (char * const srcstr, size_t len, const IniFormat format) { + + register size_t idx_s = 0, idx_d = 0; + + if (format.multiline_nodes == INI_MULTILINE_EVERYWHERE) { + + /* + + The comment can be multi-line + + */ + + /* + + Mask `abcd` (6 bits used): + + FLAG_1 Don't erase any character + FLAG_2 We are in an odd sequence of backslashes + FLAG_4 This new line character is escaped + FLAG_8 This character is a comment character and follows + `/(\n\s*|\r\s*)/` + FLAG_16 This character is a part of a group of spaces that follow + a new line (`/(\n|\r)[\t \v\f]+/`) + FLAG_32 This character is *not* a new line character (`/[\r\n]/`) + + */ + + for (register uint8_t abcd = 8; idx_s < len; idx_s++) { + + abcd = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ? + ((abcd & 35) | 32) ^ 2 + : srcstr[idx_s] == _LIBCONFINI_LF_ || srcstr[idx_s] == _LIBCONFINI_CR_ ? + (abcd << 1) & 4 + : !(abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format) ? + (abcd & 40) | 8 + : !(abcd & 40) && is_some_space(srcstr[idx_s], _LIBCONFINI_NO_EOL_) ? + (abcd & 57) | 16 + : + (abcd & 33) | 32; + + + if (!(abcd & 25)) { + + srcstr[abcd & 4 ? idx_d - 1 : idx_d++] = srcstr[idx_s]; + + } else if (!(abcd & 28)) { + + idx_d++; + + } + + } + + } else { + + /* + + The comment cannot be multi-line + + */ + + for (; idx_s < len && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format); idx_s++); + + if (!idx_s) { + + return len; + + } + + for (; idx_s < len; srcstr[idx_d++] = srcstr[idx_s++]); + + } + + for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0'); + + return idx_d; + +} + + +/** + + @brief Tries to determine the type of a member "as if it was active" + @param srcstr String containing an individual node (it may + contain multi-line escape sequences) + @param len Length of the node + @param allow_implicit A boolean: `true` if keys without a key-value + delimiter are allowed, `false` otherwise + @param format The format of the INI file + @return The node type (see header) + +**/ +static uint8_t get_type_as_active ( + const char * const srcstr, + const size_t len, + const _LIBCONFINI_CHARBOOL_ allow_implicit, + const IniFormat format +) { + + const _LIBCONFINI_CHARBOOL_ invalid_delimiter = _LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format); + + if ( + !len || _LIBCONFINI_IS_ANY_MARKER_(*srcstr, format) || ( + *((unsigned char *) srcstr) == format.delimiter_symbol && !invalid_delimiter + ) + ) { + + return INI_UNKNOWN; + + } + + register uint16_t abcd; + register size_t idx; + + if (format.section_paths != INI_NO_SECTIONS && *srcstr == _LIBCONFINI_OPEN_SECTION_) { + + if (format.no_spaces_in_names) { + + /* + + Search for the CLOSE SECTION character and possible spaces in names + -- i.e., ECMAScript `/[^\.\s]\s+[^\.\s]/g.test(srcstr)`. The + algorithm is made more complex by the fact that LF and CR characters + are still escaped at this stage. + + */ + + /* + + Mask `abcd` (10 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Only one level of nesting is allowed (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is a space + FLAG_128 What follows cannot contain spaces + FLAG_256 Continue the loop + FLAG_512 Section path is *not* valid + + */ + + + idx = 1; + + abcd = (format.section_paths == INI_ONE_LEVEL_ONLY ? 772: 768) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + + do { + + /* Revision #2 */ + + abcd = idx >= len ? + abcd & 767 + : !(abcd & 42) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 991) ^ 16 + : !(abcd & 49) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 991) ^ 8 + : srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ? + (abcd & 991) | 64 + : is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ? + ( + ~abcd & 32 ? + abcd | 64 + : ~abcd & 192 ? + (abcd & 991) | 192 + : + (abcd & 767) | 128 + ) + : !(abcd & 28) && srcstr[idx] == _LIBCONFINI_SUBSECTION_ ? + ( + ~abcd & 224 ? + abcd & 799 + : + abcd & 767 + ) + : !(abcd & 24) && srcstr[idx] == _LIBCONFINI_CLOSE_SECTION_ ? + ( + ~abcd & 224 ? + abcd & 159 + : + abcd & 767 + ) + : srcstr[idx] == _LIBCONFINI_BACKSLASH_ ? + ( + ~abcd & 32 ? + abcd | 32 + : ~abcd & 192 ? + (abcd & 991) | 128 + : + (abcd & 735) + ) + : ~abcd & 192 ? + (abcd & 927) | 128 + : + (abcd & 671) | 128; + + + idx++; + + } while (abcd & 256); + + if (abcd & 512) { + + return INI_UNKNOWN; + + } + + } else if ((idx = getn_metachar_pos(srcstr, _LIBCONFINI_CLOSE_SECTION_, len, format) + 1) > len) { + + return INI_UNKNOWN; + + } + + /* + + Scan for possible non-space characters following the CLOSE SECTION + character: if found the node cannot represent a section path (but it can + possibly represent a key). Empty quotes surrounded by spaces will be + tolerated. + + */ + + /* + + Recycling variable `abcd` (6 bits used)...: + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 Continue the loop + + */ + + abcd = 32 | (format.no_double_quotes << 1) | format.no_single_quotes; + + + /* \ /\ + \ */ nonspace_check: /* \ + \/ ______________________ \ */ + + + if (abcd) { + + if (idx >= len) { + + return INI_SECTION; + + } + + switch (srcstr[idx++]) { + + case _LIBCONFINI_VT_: + case _LIBCONFINI_FF_: + case _LIBCONFINI_HT_: + case _LIBCONFINI_SIMPLE_SPACE_: + + abcd = abcd & 28 ? 0 : abcd & 47; + goto nonspace_check; + + case _LIBCONFINI_LF_: + case _LIBCONFINI_CR_: + + abcd = abcd & 12 ? 0 : abcd & 47; + goto nonspace_check; + + case _LIBCONFINI_BACKSLASH_: + + abcd = abcd & 28 ? 0 : abcd | 16; + goto nonspace_check; + + case _LIBCONFINI_DOUBLE_QUOTES_: + + abcd = abcd & 22 ? 0 : (abcd & 47) ^ 8; + goto nonspace_check; + + case _LIBCONFINI_SINGLE_QUOTES_: + + abcd = abcd & 25 ? 0 : (abcd & 47) ^ 4; + goto nonspace_check; + + } + + } + + } + + /* + + It can be just a key... + + */ + + if (invalid_delimiter && !allow_implicit) { + + return INI_UNKNOWN; + + } + + /* + + Recycling variable `abcd` (2 bits used)...: + + FLAG_1 The delimiter **must** be present + FLAG_2 Search for spaces in names + + */ + + abcd = (format.no_spaces_in_names << 1) | (allow_implicit ? 0 : 1); + + if (abcd) { + + idx = getn_metachar_pos(srcstr, (char) format.delimiter_symbol, len, format); + + if ((abcd & 1) && idx == len) { + + return INI_UNKNOWN; + + } + + if (abcd & 2) { + + idx = urtrim_s(srcstr, idx); + + do { + + if (is_some_space(srcstr[--idx], _LIBCONFINI_WITH_EOL_)) { + + return INI_UNKNOWN; + + } + + } while (idx); + + } + + } + + return INI_KEY; + +} + + +/** + + @brief Examines a (single-/multi-line) segment and checks whether + it contains more than just one node + @param srcstr Segment to examine (it may contain multi-line + escape sequences) + @param format The format of the INI file + @return Number of entries found + +**/ +static size_t further_cuts (char * const srcstr, const IniFormat format) { + + /* + + Shared flags of mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Do not allow disabled entries after space (const) + FLAG_8 Formats supports multi-line entries everywhere (const) + FLAG_16 Formats supports multi-line entries everywhere except in + comments (const) + FLAG_32 Unescaped single quotes are odd right now + FLAG_64 Unescaped double quotes are odd right now + FLAG_128 We are in an odd sequence of backslashes + + */ + + register uint16_t abcd = ((format.disabled_after_space << 2) ^ 4) | + (format.no_double_quotes << 1) | + format.no_single_quotes | ( + format.multiline_nodes == INI_MULTILINE_EVERYWHERE ? + 8 + : format.multiline_nodes == INI_BUT_COMMENTS ? + 16 + : + 0 + ); + + + register size_t idx; + size_t focus_at, unparsable_at, search_at = 0, num_entries = 0; + + + /* \ /\ + \ */ search_for_cuts: /* \ + \/ ______________________ \ */ + + + if (!srcstr[search_at]) { + + return num_entries; + + } + + unparsable_at = 0; + + abcd = _LIBCONFINI_IS_DIS_MARKER_(srcstr[search_at], format) && ( + !(abcd & 4) || !is_some_space(srcstr[search_at + 1], _LIBCONFINI_NO_EOL_) + ) ? + (abcd & 31) | 2560 + : _LIBCONFINI_IS_IGN_MARKER_(srcstr[search_at], format) ? + (abcd & 8 ? (abcd & 31) | 1024 : abcd & 31) + : (abcd & 8) && ( + srcstr[search_at] == _LIBCONFINI_IC_INT_MARKER_ || _LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format) + ) ? + (abcd & 31) | 3072 + : + (abcd & 31) | 2048; + + + if (abcd & 2048) { + + num_entries++; + + } + + if (abcd & 1536) { + + /* + + Node starts with `/[;#]/` and can be a disabled entry in any format, or + a simple comment or a block that must be ignored in multi-line formats + + */ + + /* + + Mask `abcd` (14 bits used): + + FLAG_256 This or the previous character was not a space + FLAG_512 We are in a disabled entry or a comment (semi-const) + FLAG_1024 We are in a simple comment or in a block that must be ignored + and format supports multi-line entries (semi-const) + FLAG_2048 We are *not* in a block that must be ignored (semi-const) + FLAG_4096 We have *not* just found an inline comment nested within a + disabled entry + FLAG_8192 We had previously found an inline comment nested in this + segment, but the entry that preceded it had been checked and + did not seem to represent a valid disabled entry + + NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at + the beginning of the function; all other flags are already set + to zero. For the meaning of flags FLAG_1-FLAG_128 see the + beginning of the function. + + */ + + idx = ltrim_s(srcstr, search_at + 1, _LIBCONFINI_NO_EOL_) - 1; + + + /* \ /\ + \ */ inactive_cut: /* \ + \/ ______________________ \ */ + + + switch (srcstr[++idx]) { + + case '\0': + + /* End of string */ + + if (~abcd & 8) { + + /* + + Check if this is a valid disabled entry. If it is not, + search for line breaks. + + If the code has reached this point it means that according + to the format disabled entries can be multi-line but + comments cannot, and #get_type_as_active() has never been + invoked on this entry. + + */ + + focus_at = dqultrim_s(srcstr, search_at, format); + + if ( + srcstr[focus_at] && !get_type_as_active( + srcstr + focus_at, + idx - focus_at, + format.disabled_can_be_implicit, + format + ) + ) { + + srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_; + unparsable_at = search_at + 1; + + } + + } + + break; + + case _LIBCONFINI_LF_: + case _LIBCONFINI_CR_: + + /* + + Line break has been found in a multi-line disabled entry or + a comment. Search for `/\\(?:\n\r?|\r\n?)\s*[^;#]/`. + + */ + + focus_at = dqultrim_s(srcstr, search_at, format); + idx = ltrim_s(srcstr, idx + 1, _LIBCONFINI_WITH_EOL_); + + if ( + abcd & 2048 ? + !( + _LIBCONFINI_IS_DIS_MARKER_(srcstr[idx], format) && (abcd & 24) && ( + (~abcd & 516) || !is_some_space(srcstr[idx + 1], _LIBCONFINI_NO_EOL_) + ) + ) && !( + _LIBCONFINI_IS_COM_MARKER_(srcstr[idx], format) && (abcd & 8) && ( + ((abcd ^ 512) & 8704) || !get_type_as_active( + srcstr + focus_at, + idx - focus_at, + format.disabled_can_be_implicit, + format + ) + ) + ) + : + !_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format) + ) { + + rtrim_h(srcstr, idx, _LIBCONFINI_WITH_EOL_); + search_at = qultrim_h(srcstr, idx, format); + goto search_for_cuts; + + } + + /* + + No case break here, keep it like this! `case /[ \t\v\f]/` must + follow (switch case fallthrough). + + */ + + case _LIBCONFINI_VT_: + case _LIBCONFINI_FF_: + case _LIBCONFINI_HT_: + case _LIBCONFINI_SIMPLE_SPACE_: + + abcd = (abcd & 15999) | 4096; + goto inactive_cut; + + case _LIBCONFINI_BACKSLASH_: + + abcd = (abcd | 4352) ^ 128; + goto inactive_cut; + + default: + + abcd = !(abcd & 1376) && (~abcd & 8200) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? + (abcd & 12159) | 256 + : !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd & 16255) | 4352) ^ 64 + : !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd & 16255) | 4352) ^ 32 + : + (abcd & 16255) | 4352; + + + if (abcd & 4096) { + + goto inactive_cut; + + } + + if (~abcd & 8192) { + + /* + + Inline comment has been found in a (supposedly) disabled entry. + + */ + + focus_at = dqultrim_s(srcstr, search_at, format); + + if (get_type_as_active( + srcstr + focus_at, + idx - focus_at, + format.disabled_can_be_implicit, + format + )) { + + if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) { + + srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_; + num_entries++; + + } + + srcstr[idx - 1] = '\0'; + + if (abcd & 8) { + + goto inactive_cut; + + } + + unparsable_at = idx + 1; + + } else { + + abcd |= 8192; + srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_; + + if (abcd & 8) { + + goto inactive_cut; + + } + + unparsable_at = search_at + 1; + + } + + } + + /* No case break here (last case) */ + + } + + } else if (_LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format)) { + + /* + + Node starts with `/[;#]/` but cannot be multi-line or represent a + disabled entry + + */ + + unparsable_at = search_at + 1; + + } else { + + /* + + Node is active: search for inline comments + + */ + + /* + + Recycling variable `abcd` (11 bits used)...: + + FLAG_256 Comment marker follows an escaped new line made of only one + character (i.e., `"\\\n"` or `"\\\r"` but neither `"\\\r\n"` + or `"\\\n\r"`) + FLAG_512 This was neither a hash nor a semicolon character + FLAG_1024 This was not a space + + NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at the + beginning of the function; all other flags are already set to zero + (see previous usage of `abcd` within this function), with the only + exception of FLAG_2048, which I am going to overwrite immediately. + For the meaning of flags FLAG_1-FLAG_128 see the beginning of the + function. + + */ + + abcd = (abcd & 2047) | 1536; + idx = search_at; + + + /* \ /\ + \ */ active_cut: /* \ + \/ ______________________ \ */ + + + switch (srcstr[++idx]) { + + case '\0': + + /* End of string */ + break; + + case _LIBCONFINI_VT_: + case _LIBCONFINI_FF_: + case _LIBCONFINI_HT_: + case _LIBCONFINI_SIMPLE_SPACE_: + + abcd = (abcd & 639) | 512; + goto active_cut; + + case _LIBCONFINI_LF_: + case _LIBCONFINI_CR_: + + abcd = (abcd & 639) | ((abcd << 1) & 256) | 512; + goto active_cut; + + case _LIBCONFINI_BACKSLASH_: + + abcd = ((abcd & 1791) | 1536) ^ 128; + goto active_cut; + + default: + + abcd = _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? + abcd & 1407 + : !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd & 1791) | 1536) ^ 64 + : !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd & 1791) | 1536) ^ 32 + : + (abcd & 1791) | 1536; + + if (abcd & 1760) { + + goto active_cut; + + } + + /* + + Inline comment has been found in an active entry. + + */ + + if (abcd & 256) { + + /* + + Remove the backslash if the comment immediately follows an + escaped new line expressed by one chararacter + (`/\\[\r\n]/`). In case of CR + LF or LF + CR + (`/\\\n\r|\\\r\n/`) the backslash will be removed later by + #strip_ini_cache(). + + */ + + srcstr[idx - 2] = '\0'; + + } + + srcstr[idx - 1] = '\0'; + + if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) { + + srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_; + + if (abcd & 8) { + + search_at = idx; + goto search_for_cuts; + + } + + num_entries++; + + } else if (abcd & 8) { + + search_at = idx; + goto search_for_cuts; + + } + + unparsable_at = idx + 1; + /* No case break here (last case) */ + + } + + } + + if (unparsable_at) { + + /* + + Cut unparsable multi-line comments + + */ + + for (idx = unparsable_at; srcstr[idx]; idx++) { + + if (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) { + + search_at = qultrim_h(srcstr, idx, format); + goto search_for_cuts; + + } + + } + + } + + return num_entries; + +} + +/** @startfnlist **/ + + + + /*\ + |*| + |*| GLOBAL ENVIRONMENT + |*| ________________________________ + \*/ + + + + /* LIBRARY'S MAIN FUNCTIONS */ + + + /** @utility{strip_ini_cache} **/ +/** + + @brief Parses and tokenizes a buffer containing an INI file, then + dispatches its content to a custom callback + @param ini_source The buffer containing the INI file to tokenize + @param ini_length The length of @p ini_source without counting the + NUL terminator (if any -- se below) + @param format The format of the INI file + @param f_init The function that will be invoked before the + first dispatch, or `NULL` + @param f_foreach The function that will be invoked for each + dispatch, or `NULL` + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + The @p ini_source parameter must be a valid pointer to a buffer of size + @p ini_length + 1 and cannot be `NULL`. The @p ini_source string does not need + to be NUL-terminated, but _it does need one extra byte where to append a NUL + terminator_ -- in fact, as soon as this function is invoked, + `ini_source[ini_length]` will be immediately set to `\0`. + + In most cases, as when using `strlen()` for computing @p ini_length, this is not + a concern, since `ini_source[ini_length]` will always be `\0` by the very + definition of `strlen()`, and will only get overwritten with the same value. + However, if you are passing a substring of a string, for example the fragment + `foo=bar` of the string `foo=barracuda`, you must expect the string to be + immediately truncated into `foo=bar\0acuda`. + + In other words, @p ini_source must point to a memory location where at least + `ini_length + 1` bytes are freely usable. + + The user given function @p f_init (see #IniStatsHandler data type) will be + invoked with two arguments: `statistics` (a pointer to an #IniStatistics + structure containing some properties about the file read) and `user_data` (the + custom argument @p user_data previously passed). If @p f_init returns a non-zero + value the caller function will be interrupted. + + The user given function @p f_foreach (see #IniDispHandler data type) will be + invoked with two arguments: `dispatch` (a pointer to an #IniDispatch structure + containing the parsed member of the INI file) and `user_data` (the custom + argument @p user_data previously passed). If @p f_foreach returns a non-zero + value the caller function will be interrupted. + + After invoking `strip_ini_cache()`, the buffer pointed by the @p ini_source + parameter must be considered as a _corrupted buffer_ and should be freed or + overwritten. For more information about this function, please refer to the + @ref libconfini. + + The parsing algorithms used by **libconfini** are able to parse any type of file + encoded in 8-bit code units, as long as the characters that match the regular + expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in + ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of + platform-specific conventions. + + @note In order to be null-byte-injection-safe, before dispatching the parsed + content this function will strip all `NUL` characters possibly present + in the buffer (with the exception of the last one). + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, + #CONFINI_EOOR. + + @include topics/strip_ini_cache.c + +**/ +int strip_ini_cache ( + register char * const ini_source, + const size_t ini_length, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +) { + + const _LIBCONFINI_CHARBOOL_ valid_delimiter = !_LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format); + _LIBCONFINI_CHARBOOL_ tmp_bool; + register size_t idx, tmp_fast_size_t_1, tmp_fast_size_t_2; + size_t tmp_size_t_1, tmp_size_t_2; + + ini_source[ini_length] = '\0'; + + /* + + PART ONE: Examine and isolate each segment + + */ + + #define __ISNT_ESCAPED__ tmp_bool + #define __LSHIFT__ tmp_fast_size_t_1 + #define __EOL_N__ tmp_fast_size_t_2 + #define __N_MEMBERS__ tmp_size_t_1 + #define __NODE_AT__ tmp_size_t_2 + + /* UTF-8 BOM */ + __LSHIFT__ = *((unsigned char *) ini_source) == 0xEF && + *((unsigned char *) ini_source + 1) == 0xBB && + *((unsigned char *) ini_source + 2) == 0xBF + ? 3 : 0; + + + for ( + + __N_MEMBERS__ = 0, + __EOL_N__ = _LIBCONFINI_EOL_IDX_, + __ISNT_ESCAPED__ = _LIBCONFINI_TRUE_, + __NODE_AT__ = 0, + idx = __LSHIFT__; + + idx < ini_length; + + idx++ + + ) { + + ini_source[idx - __LSHIFT__] = ini_source[idx]; + + if (ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__] || ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__ ^= 1]) { + + if (format.multiline_nodes == INI_NO_MULTILINE || __ISNT_ESCAPED__) { + + ini_source[idx - __LSHIFT__] = '\0'; + __N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format); + __NODE_AT__ = idx - __LSHIFT__ + 1; + + } else if (ini_source[idx + 1] == _LIBCONFINI_SPACES_[__EOL_N__ ^ 1]) { + + idx++; + ini_source[idx - __LSHIFT__] = ini_source[idx]; + + } + + __ISNT_ESCAPED__ = _LIBCONFINI_TRUE_; + + } else if (ini_source[idx] == _LIBCONFINI_BACKSLASH_) { + + __ISNT_ESCAPED__ = !__ISNT_ESCAPED__; + + } else if (ini_source[idx]) { + + __ISNT_ESCAPED__ = _LIBCONFINI_TRUE_; + + } else { + + /* Remove `NUL` characters from the buffer (if any) */ + __LSHIFT__++; + + } + + } + + const size_t real_length = idx - __LSHIFT__; + + while (idx > real_length) { + + ini_source[--idx] = '\0'; + + } + + __N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format); + + /* Debug */ + + /* + + for (size_t tmp = 0; tmp < ini_length + 1; tmp++) { + putchar(ini_source[tmp] == 0 ? '$' : ini_source[tmp]); + } + putchar('\n'); + + */ + + IniStatistics this_doc = { + .format = format, + .bytes = ini_length, + .members = __N_MEMBERS__ + }; + + if (f_init && f_init(&this_doc, user_data)) { + + return CONFINI_IINTR; + + } + + #undef __NODE_AT__ + #undef __N_MEMBERS__ + #undef __EOL_N__ + #undef __LSHIFT__ + #undef __ISNT_ESCAPED__ + + /* + + PART TWO: Dispatch the parsed input + + */ + + if (!f_foreach) { + + return CONFINI_SUCCESS; + + } + + #define __ITER__ tmp_fast_size_t_1 + #define __NODE_AT__ tmp_fast_size_t_2 + #define __PARENT_IS_DISABLED__ tmp_bool + #define __REAL_PARENT_LEN__ tmp_size_t_1 + #define __CURR_PARENT_LEN__ tmp_size_t_2 + + __REAL_PARENT_LEN__ = 0, __CURR_PARENT_LEN__ = 0; + + char + * curr_parent_str = ini_source + real_length, + * subparent_str = curr_parent_str, + * real_parent_str = curr_parent_str; + + IniDispatch dsp = { + .format = format, + .dispatch_id = 0 + }; + + __PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_; + + for (__NODE_AT__ = 0, idx = 0; idx <= real_length; idx++) { + + if (ini_source[idx]) { + + continue; + + } + + if (!ini_source[__NODE_AT__] || _LIBCONFINI_IS_IGN_MARKER_(ini_source[__NODE_AT__], format)) { + + __NODE_AT__ = idx + 1; + continue; + + } + + if (dsp.dispatch_id >= this_doc.members) { + + return CONFINI_EOOR; + + } + + dsp.data = ini_source + __NODE_AT__; + dsp.d_len = idx - __NODE_AT__; + + /* Set `dsp.value` to an empty string */ + dsp.value = ini_source + idx; + + if ( + _LIBCONFINI_IS_DIS_MARKER_(*dsp.data, format) && ( + format.disabled_after_space || !is_some_space(dsp.data[1], _LIBCONFINI_NO_EOL_) + ) + ) { + + __ITER__ = dqultrim_s(dsp.data, 0, format); + + dsp.type = get_type_as_active( + dsp.data + __ITER__, + dsp.d_len - __ITER__, + format.disabled_can_be_implicit, + format + ); + + if (dsp.type) { + + dsp.data += __ITER__; + dsp.d_len -= __ITER__; + + /* + + // Not strictly needed... + for (; __ITER__ > 0; dsp.data[--__ITER__] = '\0'); + + */ + + } + + dsp.type |= 4; + + } else { + + switch (*dsp.data) { + + default: + + if (!_LIBCONFINI_IS_ANY_MARKER_(*dsp.data, format)) { + + dsp.type = get_type_as_active(dsp.data, dsp.d_len, _LIBCONFINI_TRUE_, format); + break; + + } + + /* + + No case break here, keep it like this! + `case _LIBCONFINI_SC_INT_MARKER_` must follow + (switch case fallthrough). + + */ + + case _LIBCONFINI_SC_INT_MARKER_: + + /* + + // Not strictly needed... + *dsp.data = '\0'; + + */ + + dsp.type = INI_COMMENT; + + break; + + case _LIBCONFINI_IC_INT_MARKER_: + + /* + + // Not strictly needed... + *dsp.data = '\0'; + + */ + + dsp.type = INI_INLINE_COMMENT; + /* No case break here (last case) */ + + } + + } + + if (__CURR_PARENT_LEN__ && *subparent_str) { + + __ITER__ = 0; + + do { + + curr_parent_str[__ITER__ + __CURR_PARENT_LEN__] = subparent_str[__ITER__]; + + } while (subparent_str[__ITER__++]); + + __CURR_PARENT_LEN__ += __ITER__ - 1; + subparent_str = curr_parent_str + __CURR_PARENT_LEN__; + + } + + if (__PARENT_IS_DISABLED__ && !(dsp.type & 4)) { + + real_parent_str[__REAL_PARENT_LEN__] = '\0'; + __CURR_PARENT_LEN__ = __REAL_PARENT_LEN__; + curr_parent_str = real_parent_str; + __PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_; + + } else if (!__PARENT_IS_DISABLED__ && dsp.type == INI_DISABLED_SECTION) { + + __REAL_PARENT_LEN__ = __CURR_PARENT_LEN__; + real_parent_str = curr_parent_str; + __PARENT_IS_DISABLED__ = _LIBCONFINI_TRUE_; + + } + + dsp.append_to = curr_parent_str; + dsp.at_len = __CURR_PARENT_LEN__; + + if (dsp.type == INI_COMMENT || dsp.type == INI_INLINE_COMMENT) { + + dsp.d_len = uncomment(++dsp.data, dsp.d_len - 1, format); + + } else if (format.multiline_nodes != INI_NO_MULTILINE) { + + dsp.d_len = unescape_cr_lf(dsp.data, dsp.d_len, dsp.type & 4, format); + + } + + switch (dsp.type) { + + /* + + case INI_UNKNOWN: + + // Do nothing + + break; + + */ + + case INI_SECTION: + case INI_DISABLED_SECTION: + + *dsp.data++ = '\0'; + __ITER__ = getn_metachar_pos(dsp.data, _LIBCONFINI_CLOSE_SECTION_, dsp.d_len, format); + + while (dsp.data[__ITER__]) { + + dsp.data[__ITER__++] = '\0'; + + } + + dsp.d_len = format.section_paths == INI_ONE_LEVEL_ONLY ? + collapse_everything(dsp.data, format) + : + sanitize_section_path(dsp.data, format); + + + if (format.section_paths == INI_ONE_LEVEL_ONLY || *dsp.data != _LIBCONFINI_SUBSECTION_) { + + /* + + Append to root (this is an absolute path) + + */ + + curr_parent_str = dsp.data; + __CURR_PARENT_LEN__ = dsp.d_len; + subparent_str = ini_source + idx; + dsp.append_to = subparent_str; + dsp.at_len = 0; + + } else if (format.section_paths == INI_ABSOLUTE_ONLY || !__CURR_PARENT_LEN__) { + + /* + + Append to root and remove the leading dot (parent is root or + relative paths are not allowed) + + */ + + curr_parent_str = ++dsp.data; + __CURR_PARENT_LEN__ = --dsp.d_len; + subparent_str = ini_source + idx; + dsp.append_to = subparent_str; + dsp.at_len = 0; + + } else if (dsp.d_len != 1) { + + /* + + Append to the current parent (this is a relative path + and parent is not root) + + */ + + subparent_str = dsp.data; + + } + + if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) { + + string_tolower(dsp.data); + + } + + break; + + case INI_KEY: + case INI_DISABLED_KEY: + + if ( + valid_delimiter && ( + __ITER__ = getn_metachar_pos(dsp.data, (char) dsp.format.delimiter_symbol, dsp.d_len, format) + ) < dsp.d_len + ) { + + dsp.data[__ITER__] = '\0'; + dsp.value = dsp.data + __ITER__ + 1; + + + switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) { + + case 0: dsp.v_len = collapse_everything(dsp.value, format); break; + + case 1: dsp.v_len = collapse_empty_quotes(dsp.value, format); break; + + case 2: dsp.v_len = collapse_spaces(dsp.value, format); break; + + case 4: + + dsp.value += ltrim_h(dsp.value, 0, _LIBCONFINI_WITH_EOL_); + dsp.v_len = rtrim_h(dsp.value, dsp.d_len + dsp.data - dsp.value, _LIBCONFINI_WITH_EOL_); + /* No case break here (last case) */ + + } + + } else if (format.implicit_is_not_empty) { + + dsp.value = INI_GLOBAL_IMPLICIT_VALUE; + dsp.v_len = INI_GLOBAL_IMPLICIT_V_LEN; + + } + + dsp.d_len = collapse_everything(dsp.data, format); + + if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) { + + string_tolower(dsp.data); + + } + + break; + + case INI_COMMENT: + case INI_INLINE_COMMENT: + + dsp.append_to = ini_source + idx; + dsp.at_len = 0; + /* No case break here (last case) */ + + } + + if (f_foreach(&dsp, user_data)) { + + return CONFINI_FEINTR; + + } + + dsp.dispatch_id++; + __NODE_AT__ = idx + 1; + + } + + #undef __CURR_PARENT_LEN__ + #undef __REAL_PARENT_LEN__ + #undef __PARENT_IS_DISABLED__ + #undef __NODE_AT__ + #undef __ITER__ + + return CONFINI_SUCCESS; + +} + + + /** @utility{load_ini_file} **/ +/** + + @brief Parses an INI file and dispatches its content to a custom + callback using a `FILE` structure as argument + @param ini_file The `FILE` handle pointing to the INI file to + parse + @param format The format of the INI file + @param f_init The function that will be invoked before the + first dispatch, or `NULL` + @param f_foreach The function that will be invoked for each + dispatch, or `NULL` + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + @note This function is absent if the `--without-io-api` option was passed to + the `configure` script when the library was compiled + + The @p ini_file parameter must be a `FILE` handle with read privileges. On some + platforms, such as Microsoft Windows, it might be needed to add the binary + specifier to the mode string (`"b"`) in order to prevent discrepancies between + the physical size of the file and its computed size: + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + FILE * my_file = fopen("example.conf", "rb"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + For the two parameters @p f_init and @p f_foreach see function + #strip_ini_cache(). + + The parsing algorithms used by **libconfini** are able to parse any type of file + encoded in 8-bit code units, as long as the characters that match the regular + expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in + ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of + platform-specific conventions. + + @note In order to be null-byte-injection safe, `NUL` characters, if present in + the file, will be removed from the dispatched strings. + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, + #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, #CONFINI_EFBIG. + + @include topics/load_ini_file.c + +**/ +int load_ini_file ( + FILE * const ini_file, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +) { + + _LIBCONFINI_OFF_T_ file_size; + + if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) { + + return CONFINI_EBADF; + + } + + if (file_size > SIZE_MAX) { + + return CONFINI_EFBIG; + + } + + char * const cache = (char *) malloc((size_t) file_size + 1); + + if (!cache) { + + return CONFINI_ENOMEM; + + } + + rewind(ini_file); + + if (fread(cache, 1, (size_t) file_size, ini_file) < file_size) { + + free(cache); + return CONFINI_EIO; + + } + + const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data); + + free(cache); + return return_value; + +} + + + /** @utility{load_ini_path} **/ +/** + + @brief Parses an INI file and dispatches its content to a custom + callback using a path as argument + @param path The path of the INI file + @param format The format of the INI file + @param f_init The function that will be invoked before the + first dispatch, or `NULL` + @param f_foreach The function that will be invoked for each + dispatch, or `NULL` + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + @note This function is absent if the `--without-io-api` option was passed to + the `configure` script when the library was compiled + + For the two parameters @p f_init and @p f_foreach see function + #strip_ini_cache(). + + The parsing algorithms used by **libconfini** are able to parse any type of file + encoded in 8-bit code units, as long as the characters that match the regular + expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in + ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of + platform-specific conventions. + + @note In order to be null-byte-injection safe, `NUL` characters, if present in + the file, will be removed from the dispatched strings. + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, + #CONFINI_ENOENT, #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, + #CONFINI_EFBIG. + + @include topics/load_ini_path.c + +**/ +int load_ini_path ( + const char * const path, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +) { + + FILE * const ini_file = fopen(path, "rb"); + + if (!ini_file) { + + return CONFINI_ENOENT; + + } + + _LIBCONFINI_OFF_T_ file_size; + + if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) { + + return CONFINI_EBADF; + + } + + if (file_size > SIZE_MAX) { + + return CONFINI_EFBIG; + + } + + char * const cache = (char *) malloc((size_t) file_size + 1); + + if (!cache) { + + return CONFINI_ENOMEM; + + } + + rewind(ini_file); + + if (fread(cache, 1, (size_t) file_size, ini_file) < file_size) { + + free(cache); + return CONFINI_EIO; + + } + + /* No checks here, as there is nothing we can do about it... */ + fclose(ini_file); + + const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data); + + free(cache); + return return_value; + +} + + + + /* OTHER UTILITIES (NOT REQUIRED BY LIBCONFINI'S MAIN FUNCTIONS) */ + + + /** @utility{ini_string_match_ss} **/ +/** + + @brief Compares two simple strings and checks whether they match + @param simple_string_a The first simple string + @param simple_string_b The second simple string + @param format The format of the INI file + @return A boolean: `true` if the two strings match, `false` otherwise + + Simple strings are user-given strings or the result of #ini_string_parse(). The + @p format argument is used for the following fields: + + - `format.case_sensitive` + +**/ +bool ini_string_match_ss ( + const char * const simple_string_a, + const char * const simple_string_b, + const IniFormat format +) { + + register size_t idx = 0; + + if (format.case_sensitive) { + + do { + + if (simple_string_a[idx] != simple_string_b[idx]) { + + return _LIBCONFINI_FALSE_; + + } + + } while (simple_string_a[idx++]); + + return _LIBCONFINI_TRUE_; + + } + + do { + + if (_LIBCONFINI_CHR_CASEFOLD_(simple_string_a[idx]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string_b[idx])) { + + return _LIBCONFINI_FALSE_; + + } + + } while (simple_string_a[idx++]); + + return _LIBCONFINI_TRUE_; + +} + + + /** @utility{ini_string_match_si} **/ +/** + + @brief Compares a simple string and an INI string and and checks + whether they match + @param ini_string The INI string escaped according to + @p format + @param simple_string The simple string + @param format The format of the INI file + @return A boolean: `true` if the two strings match, `false` otherwise + + This function grants that the result of the comparison between a simple string + and an INI string + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + printf( + "%s\n", + ini_string_match_si(my_simple_string, my_ini_string, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + will always match the result of the _literal_ comparison between the simple + string and the INI string after the latter has been parsed by + #ini_string_parse() when `format.do_not_collapse_values` is set to `false`. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + ini_string_parse(my_ini_string, format); + + printf( + "%s\n", + ini_string_match_ss(my_simple_string, my_ini_string, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + INI strings are the strings typically dispatched by #load_ini_file(), + #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three + escape sequences `\\`, `\'` and `\"`. Simple strings are user-given strings or + the result of #ini_string_parse(). + + In order to be suitable for both names and values, **this function always + considers sequences of one or more spaces out of quotes in the INI string as + collapsed**, even when `format.do_not_collapse_values` is set to `true`. + + The @p format argument is used for the following fields: + + - `format.case_sensitive` + - `format.no_double_quotes` + - `format.no_single_quotes` + - `format.multiline_nodes` + + @include topics/ini_string_match_si.c + +**/ +bool ini_string_match_si ( + const char * const simple_string, + const char * const ini_string, + const IniFormat format +) { + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Format supports escape sequences (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 This is an escaped single/double quote in a format that supports + single/double quotes + FLAG_64 This is a space + FLAG_128 Skip this character + + */ + + register uint8_t abcd = INIFORMAT_HAS_NO_ESC(format) ? + 67 + : + 68 | (format.no_double_quotes << 1) | format.no_single_quotes; + + register size_t idx_i = 0; + size_t idx_s = 0, nbacksl = 0; + + + /* \ /\ + \ */ si_match: /* \ + \/ ______________________ \ */ + + + if ((abcd & 4) && ini_string[idx_i] == _LIBCONFINI_BACKSLASH_) { + + for (abcd &= 63, nbacksl++; ini_string[++idx_i] == _LIBCONFINI_BACKSLASH_; nbacksl++); + + } + + abcd = !(abcd & 10) && ini_string[idx_i] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 16 + ) + : !(abcd & 17) && ini_string[idx_i] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 8 + ) + : (abcd & 24) || !is_some_space(ini_string[idx_i], _LIBCONFINI_WITH_EOL_) ? + abcd & 31 + : abcd & 64 ? + (abcd & 223) | 128 + : + (abcd & 95) | 64; + + + if (nbacksl) { + + nbacksl = ((abcd & 32 ? nbacksl : nbacksl + 1) >> 1) + 1; + + while (--nbacksl) { + + if (simple_string[idx_s++] != _LIBCONFINI_BACKSLASH_) { + + return _LIBCONFINI_FALSE_; + + } + + } + + } + + if ((abcd & 128) || ((abcd & 64) && !simple_string[idx_s])) { + + idx_i++; + goto si_match; + + } + + if ( + abcd & 64 ? + simple_string[idx_s] != _LIBCONFINI_COLLAPSED_ || !simple_string[idx_s + 1] + : format.case_sensitive ? + ini_string[idx_i] != simple_string[idx_s] + : + _LIBCONFINI_CHR_CASEFOLD_(ini_string[idx_i]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string[idx_s]) + ) { + + return _LIBCONFINI_FALSE_; + + } + + idx_s++; + + if (ini_string[idx_i++]) { + + goto si_match; + + } + + return _LIBCONFINI_TRUE_; + +} + + + /** @utility{ini_string_match_ii} **/ +/** + + @brief Compares two INI strings and checks whether they match + @param ini_string_a The first INI string unescaped according to + @p format + @param ini_string_b The second INI string unescaped according to + @p format + @param format The format of the INI file + @return A boolean: `true` if the two strings match, `false` otherwise + + This function grants that the result of the comparison between two INI strings + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + printf( + "%s\n", + ini_string_match_ii(my_ini_string_1, my_ini_string_2, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + will always match the result of the _literal_ comparison between the same two + INI strings after these have been parsed by #ini_string_parse() when + `format.do_not_collapse_values` is set to `false`. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + ini_string_parse(my_ini_string_1, format); + ini_string_parse(my_ini_string_2, format); + + printf("%s\n", + ini_string_match_ss(my_ini_string_1, my_ini_string_2, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + INI strings are the strings typically dispatched by #load_ini_file(), + #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three + escape sequences `\\`, `\'` and `\"`. + + In order to be suitable for both names and values, **this function always + considers sequences of one or more spaces out of quotes in both strings as + collapsed**, even when `format.do_not_collapse_values` is set to `true`. + + The @p format argument is used for the following fields: + + - `format.case_sensitive` + - `format.no_double_quotes` + - `format.no_single_quotes` + - `format.multiline_nodes` + +**/ +bool ini_string_match_ii ( + const char * const ini_string_a, + const char * const ini_string_b, + const IniFormat format +) { + + const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format); + register uint8_t side = 1; + register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_; + uint8_t abcd_pair[2]; + const char * chrptr_pair[2] = { ini_string_a, ini_string_b }; + size_t nbacksl_pair[2]; + + /* + + Masks `abcd_pair[0]` and `abcd_pair[1]` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes and format supports + escape sequences + FLAG_32 This is a space + FLAG_64 Skip this character + + */ + + abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes; + + + /* \ /\ + \ */ ii_match: /* \ + \/ ______________________ \ */ + + + nbacksl_pair[side] = 0; + + if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) { + + for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++); + + abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15; + + if ( + ( + (abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_ + ) && ( + (abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_ + ) + ) { + + nbacksl_pair[side]++; + + } + + } else { + + abcd_pair[side] = !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd_pair[side] & 111) | 64) ^ 4 + : !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd_pair[side] & 111) | 64) ^ 8 + : !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ? + (abcd_pair[side] & 111) | 96 + : *chrptr_pair[side] ? + abcd_pair[side] & 47 + : + abcd_pair[side] & 15; + + + if (abcd_pair[side] & 64) { + + chrptr_pair[side]++; + goto ii_match; + + } + + } + + if (side && turn_allowed) { + + side ^= 1; + goto ii_match; + + } + + turn_allowed = _LIBCONFINI_TRUE_; + + if (*nbacksl_pair || nbacksl_pair[1]) { + + if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) { + + return _LIBCONFINI_FALSE_; + + } + + side = 1; + goto ii_match; + + } + + if ( + ( + abcd_pair[side ^ 1] & 32 ? + !(abcd_pair[side] & 32) + : + abcd_pair[(side ^= 1) ^ 1] & 32 + ) && *chrptr_pair[side] + ) { + + if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) { + + return _LIBCONFINI_FALSE_; + + } + + abcd_pair[side ^ 1] &= 95; + turn_allowed = _LIBCONFINI_FALSE_; + goto ii_match; + + } + + if ( + format.case_sensitive ? + **chrptr_pair != *chrptr_pair[1] + : + _LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1]) + ) { + + return _LIBCONFINI_FALSE_; + + } + + if (**chrptr_pair) { + + (*chrptr_pair)++; + + } + + if (*chrptr_pair[1]) { + + chrptr_pair[1]++; + + } + + if (**chrptr_pair || *chrptr_pair[1]) { + + *abcd_pair &= 95; + abcd_pair[1] &= 95; + side = 1; + goto ii_match; + + } + + return _LIBCONFINI_TRUE_; + +} + + + /** @utility{ini_array_match} **/ +/** + + @brief Compares two INI arrays and checks whether they match + @param ini_string_a The first INI array + @param ini_string_b The second INI array + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return A boolean: `true` if the two arrays match, `false` otherwise + + This function grants that the result of the comparison between two INI arrays + will always match the the _literal_ comparison between the individual members + of both arrays after these have been parsed, one by one, by #ini_string_parse() + (with `format.do_not_collapse_values` set to `false`). + + This function can be used, with `'.'` as delimiter, to compare section paths. + + INI strings are the strings typically dispatched by #load_ini_file(), + #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three + escape sequences `\\`, `\'` and `\"`. + + In order to be suitable for both names and values, **this function always + considers sequences of one or more spaces out of quotes in both strings as + collapsed**, even when `format.do_not_collapse_values` is set to `true`. + + The @p format argument is used for the following fields: + + - `format.case_sensitive` + - `format.no_double_quotes` + - `format.no_single_quotes` + - `format.multiline_nodes` + +**/ +bool ini_array_match ( + const char * const ini_string_a, + const char * const ini_string_b, + const char delimiter, + const IniFormat format +) { + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + return ini_string_match_ii(ini_string_a, ini_string_b, format); + + } + + const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format); + register uint8_t side = 1; + register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_; + uint8_t abcd_pair[2]; + size_t nbacksl_pair[2]; + const char * chrptr_pair[2] = { + ini_string_a + ltrim_s(ini_string_a, 0, _LIBCONFINI_WITH_EOL_), + ini_string_b + ltrim_s(ini_string_b, 0, _LIBCONFINI_WITH_EOL_) + }; + + /* + + Masks `abcd_pair[0]` and `abcd_pair[1]` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes and format supports + escape sequences + FLAG_32 This is a space + FLAG_64 This is a delimiter + FLAG_128 Skip this character + + */ + + abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes; + + + /* \ /\ + \ */ delimited_match: /* \ + \/ ______________________ \ */ + + + nbacksl_pair[side] = 0; + + if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) { + + for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++); + + abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15; + + if ( + ( + (abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_ + ) && ( + (abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_ + ) + ) { + + nbacksl_pair[side]++; + + } + + } else { + + abcd_pair[side] = !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ? + ( + delimiter || (abcd_pair[side] & 64) ? + (abcd_pair[side] & 239) | 160 + : + (abcd_pair[side] & 111) | 96 + ) + : delimiter && !(abcd_pair[side] & 12) && *chrptr_pair[side] == delimiter ? + (abcd_pair[side] & 111) | 96 + : !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd_pair[side] & 175) | 128) ^ 4 + : !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd_pair[side] & 175) | 128) ^ 8 + : *chrptr_pair[side] ? + abcd_pair[side] & 47 + : delimiter ? + abcd_pair[side] & 15 + : + (abcd_pair[side] & 79) ^ 64; + + + if (abcd_pair[side] & 128) { + + chrptr_pair[side]++; + goto delimited_match; + + } + + } + + if (side && turn_allowed) { + + side ^= 1; + goto delimited_match; + + } + + turn_allowed = _LIBCONFINI_TRUE_; + + if (*nbacksl_pair || nbacksl_pair[1]) { + + if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) { + + return _LIBCONFINI_FALSE_; + + } + + side = 1; + goto delimited_match; + + } + + if ((*abcd_pair ^ abcd_pair[1]) & 64) { + + return _LIBCONFINI_FALSE_; + + } + + if ( + !( + abcd_pair[side ^ 1] & 32 ? + abcd_pair[side] & 96 + : + (abcd_pair[(side ^= 1) ^ 1] & 96) ^ 32 + ) && *chrptr_pair[side] + ) { + + if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) { + + return _LIBCONFINI_FALSE_; + + } + + abcd_pair[side ^ 1] &= 223; + turn_allowed = _LIBCONFINI_FALSE_; + goto delimited_match; + + } + + if (~*abcd_pair & 64) { + + if ( + format.case_sensitive ? + **chrptr_pair != *chrptr_pair[1] + : + _LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1]) + ) { + + return _LIBCONFINI_FALSE_; + + } + + *abcd_pair &= 223; + abcd_pair[1] &= 223; + + } + + if (**chrptr_pair) { + + (*chrptr_pair)++; + + } + + if (*chrptr_pair[1]) { + + chrptr_pair[1]++; + + } + + if (**chrptr_pair || *chrptr_pair[1]) { + + side = 1; + goto delimited_match; + + } + + return _LIBCONFINI_TRUE_; + +} + + + /** @utility{ini_unquote} **/ +/** + + @brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes + (if single/double quotes are considered metacharacters in + respect to the format given) + @param ini_string The string to be unescaped + @param format The format of the INI file + @return The new length of the string + + This function is very similar to #ini_string_parse(), except that does not + bother collapsing the sequences of more than one space that might result from + removing empty quotes. Its purpose is to be used to parse key and section names, + since these are always dispatched as already collapsed. In order to parse + values, or array parts listed in values, please use #ini_string_parse(). + + If you only need to compare @p ini_string with another string, consider to use + #ini_string_match_si() and #ini_string_match_ii() instead of parsing the former + and perform a simple comparison afterwards. These two functions are in fact able + to check directly for equality between unparsed INI strings without actually + modifiyng them. + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). If the string does not contain quotes, or if quotes are + considered to be normal characters, no changes will be made. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus + the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. + + The @p format argument is used for the following fields: + + - `format.no_single_quotes` + - `format.no_double_quotes` + - `format.multiline_nodes` + + @include topics/ini_string_parse.c + +**/ +size_t ini_unquote (char * const ini_string, const IniFormat format) { + + if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; + + } + + register size_t idx = 0; + + if (INIFORMAT_HAS_NO_ESC(format)) { + + /* + + There are no escape sequences... I will just return the length of the + string... + + */ + + while (ini_string[idx++]); + + return idx - 1; + + } + + size_t lshift = 0, nbacksl = 0; + + /* + + Mask `abcd` (6 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 This is an unescaped single quote and format supports single + quotes + FLAG_32 This is an unescaped double quote and format supports double + quotes + + */ + + for (register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; ini_string[idx]; idx++) { + + abcd = !(abcd & 6) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 47) | 32 + : !(abcd & 9) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 31) | 16 + : + abcd & 15; + + + if (!(nbacksl & 1) && (abcd & 48)) { + + abcd ^= (abcd >> 2) & 12; + lshift++; + continue; + + } + + if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) { + + nbacksl++; + + } else { + + if ((nbacksl & 1) && (abcd & 48)) { + + lshift++; + + } + + lshift += nbacksl >> 1; + nbacksl = 0; + + } + + if (lshift) { + + ini_string[idx - lshift] = ini_string[idx]; + + } + + } + + lshift += nbacksl >> 1; + + for (idx -= lshift; ini_string[idx]; ini_string[idx++] = '\0'); + + return idx - lshift; + +} + + + /** @utility{ini_string_parse} **/ +/** + + @brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes + (if single/double quotes are considered metacharacters in + respect to the format given); if the format allows it, sequences + of one or more spaces out of quotes will be collapsed + @param ini_string The string to be unescaped + @param format The format of the INI file + @return The new length of the string + + This function is meant to be used to parse values. In order to parse key and + section names please use #ini_unquote(). + + If you only need to compare @p ini_string with another string, consider to use + #ini_string_match_si() and #ini_string_match_ii() instead of parsing the former + and perform a simple comparison afterwards. These two functions are in fact able + to check directly for equality between unparsed INI strings without actually + modifying them. + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). If `format.do_not_collapse_values` is set to non-zero, spaces + surrounding empty quotes will be collapsed together with the latter. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus + the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. + + The @p format argument is used for the following fields: + + - `format.no_single_quotes` + - `format.no_double_quotes` + - `format.multiline_nodes` + - `format.do_not_collapse_values` + + @note `format.multiline_nodes` is used only to figure out whether there are + escape sequences or not. For all other purposes new line characters will + be considered to be equal to any other space character, even if the + format is not multi-line -- new line characters should never appear in + non-multi-line formats. + + @include topics/ini_string_parse.c + +**/ +size_t ini_string_parse (char * const ini_string, const IniFormat format) { + + if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Do not collapse spaces within members (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 This is an *escaped* single/double quote and format supports + single/double quotes + FLAG_64 This is a space + FLAG_128 Skip this character + + */ + + register uint8_t abcd = (format.do_not_collapse_values ? 68 : 64) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t idx, lshift; + + if (format.multiline_nodes == INI_NO_MULTILINE) { + + switch (abcd) { + + case 64 | 2 | 1 : + + /* + + There are no escape sequences, but spaces might still need to + be collapsed. + + */ + + for (idx = 0, lshift = 0; ini_string[idx]; idx++) { + + abcd = !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? 3 : abcd & 64 ? 195 : 67; + + if (abcd & 128) { + + lshift++; + + } else { + + ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx]; + + } + + } + + for ( + + idx -= (abcd & 64) && lshift < idx ? + ++lshift + : + lshift; + + ini_string[idx]; + + ini_string[idx++] = '\0' + + ); + + return idx - lshift; + + case 64 | 4 | 2 | 1 : + + /* + + There are no escape sequences and spaces don't need to be + collapsed, but left and right trim might still be necessary. + + */ + + return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_); + + } + + } + + /* + + There might be escape sequences... + + */ + + size_t nbacksl = 0; + + for (idx = lshift = 0; ini_string[idx]; idx++) { + + abcd = !(abcd & 10) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 16 + ) + : !(abcd & 17) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 8 + ) + : (abcd & 28) || !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? + abcd & 31 + : abcd & 64 ? + (abcd & 223) | 128 + : + (abcd & 95) | 64; + + + if (abcd & 128) { + + lshift++; + continue; + + } + + if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) { + + nbacksl++; + + } else { + + if (abcd & 32) { + + lshift++; + + } + + lshift += nbacksl >> 1; + nbacksl = 0; + + } + + ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx]; + + } + + lshift += nbacksl >> 1; + + for ( + + idx -= (abcd & 64) && lshift < idx ? + ++lshift + : + lshift; + + ini_string[idx]; + + ini_string[idx++] = '\0' + + ); + + return (abcd & 28) ^ 4 ? + idx - lshift + : + rtrim_h(ini_string, idx - lshift, _LIBCONFINI_WITH_EOL_); + +} + + + /** @utility{ini_array_get_length} **/ +/** + + @brief Gets the length of a stringified INI array in number of members + @param ini_string The stringified array (it can be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The length of the INI array + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + +**/ +size_t ini_array_get_length ( + const char * const ini_string, + const char delimiter, + const IniFormat format +) { + + if (!ini_string) { + + return 0; + + } + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + return 1; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Delimiter is not any space (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is a space + FLAG_128 This is a delimiter + + */ + + register uint8_t abcd = (delimiter ? 64 : 68) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t counter = 0; + + for (size_t idx = 0; ini_string[idx]; idx++) { + + /* Revision #1 */ + + abcd = !(abcd & 28) && ini_string[idx] == delimiter ? + (abcd & 159) | 128 + : !(abcd & 24) && is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? + ( + (abcd & 68) ^ 4 ? + (abcd & 95) | 64 + : + (abcd & 223) | 192 + ) + : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd & 63) ^ 32 + : !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 31) ^ 16 + : !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 31) ^ 8 + : + abcd & 31; + + + if (abcd & 128) { + + counter++; + + } + + } + + return !counter || (~abcd & 68) ? + counter + 1 + : + counter; + +} + + + /** @utility{ini_array_foreach} **/ +/** + + @brief Calls a custom function for each member of a stringified INI + array, without modifying the content of the buffer -- useful for + read-only (`const`) stringified arrays + @param ini_string The stringified array (it can be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @param f_foreach The function that will be invoked for each array + member + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + The user given function @p f_foreach (see #IniSubstrHandler data type) will be + invoked with six arguments: `ini_string`, `memb_offset` (the offset of the + member in bytes), `memb_length` (the length of the member in bytes), `memb_num` + (the offset of the member in number of members), `format` (the format of the INI + file), `user_data` (the custom argument @p user_data previously passed). If + @p f_foreach returns a non-zero value the function #ini_array_foreach() will be + interrupted. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_FEINTR. + + @include topics/ini_array_foreach.c. + +**/ +int ini_array_foreach ( + const char * const ini_string, + const char delimiter, + const IniFormat format, + const IniSubstrHandler f_foreach, + void * const user_data +) { + + if (!ini_string) { + + return CONFINI_SUCCESS; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Delimiter is not any space (const) + FLAG_8 Unescaped single quotes are odd until now + FLAG_16 Unescaped double quotes are odd until now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is not a delimiter + FLAG_128 Stop the loop + + */ + + register size_t idx; + size_t offs = ltrim_s(ini_string, 0, _LIBCONFINI_WITH_EOL_); + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + idx = 0; + + while (ini_string[idx++]); + + return f_foreach(ini_string, offs, rtrim_s(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ? + CONFINI_FEINTR + : + CONFINI_SUCCESS; + + } + + register uint8_t abcd = (delimiter ? 4 : 0) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t memb_num = 0; + + idx = offs; + + do { + + abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ? + abcd & 159 + : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd | 64) ^ 32 + : !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd & 223) | 64) ^ 16 + : !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd & 223) | 64) ^ 8 + : ini_string[idx] ? + (abcd & 223) | 64 + : + 128; + + + if (!(abcd & 88)) { + + if ( + f_foreach( + ini_string, + offs, + rtrim_s(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_), + memb_num++, + format, + user_data + ) + ) { + + return CONFINI_FEINTR; + + } + + offs = abcd & 128 ? idx + 1 : ltrim_s(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_); + + } + + idx = abcd & 216 ? idx + 1 : offs; + + } while (!(abcd & 128) && ((abcd & 92) || ini_string[idx])); + + return CONFINI_SUCCESS; + +} + + + /** @utility{ini_array_shift} **/ +/** + + @brief Shifts the location pointed by @p ini_strptr to the next member + of the INI array (without modifying the content of the buffer), + or to `NULL` if the INI array has no more members -- useful for + read-only (`const`) stringified arrays + @param ini_strptr The memory location of the stringified array -- + it cannot be `NULL`, but it can point to `NULL` + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The length of the array member that has been left behind + + Usually @p ini_strptr comes from an #IniDispatch (but any other string may be + used as well). + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_shift.c + +**/ +size_t ini_array_shift (const char ** const ini_strptr, const char delimiter, const IniFormat format) { + + size_t toklen = 0; + + if (*ini_strptr && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + if (!delimiter) { + + toklen = ltrim_s(*ini_strptr, 0, _LIBCONFINI_WITH_EOL_); + + } + + toklen += get_metachar_pos(*ini_strptr + toklen, delimiter, format); + *ini_strptr += toklen; + toklen = rtrim_s(*ini_strptr - toklen, toklen, _LIBCONFINI_WITH_EOL_); + + if (**ini_strptr) { + + *ini_strptr += ltrim_s(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_); + + if (delimiter || **ini_strptr) { + + return toklen; + + } + + } + + } + + *ini_strptr = (char *) 0; + return toklen; + +} + + + /** @utility{ini_array_collapse} **/ +/** + + @brief Compresses the distribution of the data in a stringified INI + array by removing all the white spaces that surround its + delimiters, empty quotes, collapsable spaces, etc + @param ini_string The stringified array + @param delimiter The delimiter between the array members -- + if zero (`INI_ANY_SPACE`) any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The new length of the stringified array + + Out of quotes similar to ECMAScript + `ini_string.replace(new RegExp("^\\s+|\\s*(?:(" + delimiter + ")\\s*|($))", "g"), "$1$2")`. + If #INI_ANY_SPACE (`0`) is used as delimiter, one or more different spaces + (`/[\t \v\f\n\r]+/`) will be always collapsed to one space, independently of + what the format says. + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + This function can be useful before invoking `memcpy()` using @p ini_string as + source, when saving memory is a priority. + + The @p format argument is used for the following fields: + + - `format.no_single_quotes` + - `format.no_double_quotes` + - `format.do_not_collapse_values` + - `format.preserve_empty_quotes` + + Examples: + + 1. Using comma as delimiter: + - Before: ` first   ,    second   + ,   third   ,  etc.  ` + - After: `first,second,third,etc.` + 2. Using `INI_ANY_SPACE` as delimiter: + - Before: `  first    second    + third     etc.   ` + - After: `first second third etc.` + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus + the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_collapse.c + + @note The actual space occupied by the array might get reduced further after + each member is parsed by #ini_string_parse(). + +**/ +size_t ini_array_collapse (char * const ini_string, const char delimiter, const IniFormat format) { + + if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; + + } + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) { + + case 0: return collapse_everything(ini_string, format); + case 1: return collapse_empty_quotes(ini_string, format); + case 2: return collapse_spaces(ini_string, format); + case 3: return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_); + + } + + } + + /* + + Mask `abcd` (16 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Do not collapse spaces within members (const) + FLAG_8 Preserve empty quotes (const) + FLAG_16 Any space is delimiter (const) + FLAG_32 Unescaped single quotes are odd right now + FLAG_64 Unescaped double quotes are odd right now + FLAG_128 We are in an odd sequence of backslashes + FLAG_256 This is *not* a delimiter out of quotes + FLAG_512 This is *not* a space out of quotes + FLAG_1024 These are some quotes + FLAG_2048 These are some quotes or among the last spaces are some empty + quotes + FLAG_4096 Save current `idx_d` in `fallback` + FLAG_8192 Restore `idx_d` from `fallback` before writing + FLAG_16384 Decrease `idx_d` before writing + FLAG_32768 Keep increasing `idx_d` after writing + + */ + + size_t idx_s = 0, idx_d = 0, fallback = 0; + + register uint16_t abcd = (delimiter ? 0 : 16) | + (format.preserve_empty_quotes << 3) | + (format.do_not_collapse_values << 2) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + + for (; ini_string[idx_s]; idx_s++) { + + /* Revision #1 */ + + abcd = !(abcd & 112) && ini_string[idx_s] == delimiter ? + ( + (abcd & 536) && ((abcd & 1560) ^ 8) && ((abcd & 1560) ^ 1544) && ((abcd & 1304) ^ 1032) ? + (abcd & 33407) | 33280 + : + (abcd & 41599) | 41472 + ) + : !(abcd & 96) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ? + ( + !((abcd & 1816) ^ 1800) ? + (abcd & 43391) | 40960 + : !(~abcd & 1560) ? + (abcd & 41087) | 40960 + : !((abcd & 536) ^ 528) || !((abcd & 1560) ^ 536) || !((abcd & 1560) ^ 1048) ? + (abcd & 32895) | 32768 + : !(abcd & 540) || !((abcd & 1564) ^ 8) || !((abcd & 536) ^ 16) || !((abcd & 1560) ^ 24) ? + abcd & 2431 + : ((abcd & 540) ^ 4) && ((abcd & 796) ^ 12) && ((abcd & 1564) ^ 12) && ((abcd & 1308) ^ 1032) ? + (abcd & 39295) | 36864 + : + (abcd & 35199) | 32768 + ) + : !(abcd & 193) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + !((abcd & 3896) ^ 8) ? + (abcd & 44927) | 44064 + : !((abcd & 3896) ^ 2056) ? + (abcd & 36735) | 36128 + : !((abcd & 1056) ^ 32) ? + (abcd & 33631) | 33536 + : !(abcd & 40) || !(~abcd & 1064) ? + ((abcd & 36735) | 35840) ^ 32 + : ((abcd & 1064) ^ 1032) && ((abcd & 1064) ^ 1056) ? + (abcd & 40831) | 39968 + : + ((abcd & 20351) | 19456) ^ 32 + ) + : !(abcd & 162) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + !((abcd & 3928) ^ 8) ? + (abcd & 44927) | 44096 + : !((abcd & 3928) ^ 2056) ? + (abcd & 36735) | 36160 + : !((abcd & 1088) ^ 64) ? + (abcd & 33599) | 33536 + : !(abcd & 72) || !(~abcd & 1096) ? + ((abcd & 36735) | 35840) ^ 64 + : ((abcd & 1096) ^ 1088) && ((abcd & 1096) ^ 1032) ? + (abcd & 40831) | 40000 + : + ((abcd & 20351) | 19456) ^ 64 + ) + : ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ? + ( + (abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ? + ((abcd & 33791) | 33536) ^ 128 + : + ((abcd & 41983) | 41728) ^ 128 + ) + : (abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ? + (abcd & 33663) | 33536 + : + (abcd & 41855) | 41728; + + + ini_string[ + abcd & 16384 ? + --idx_d + : abcd & 8192 ? + (idx_d = fallback) + : abcd & 4096 ? + (fallback = idx_d) + : + idx_d + ] = (abcd & 1636) && ((abcd & 1392) ^ 16) ? + ini_string[idx_s] + : + _LIBCONFINI_COLLAPSED_; + + + if (abcd & 32768) { + + idx_d++; + + } + + } + + for ( + + idx_s = ((abcd & 16) && !idx_d) || (!(~abcd & 1040) && idx_d < 4) ? + (idx_d = 0) + : !(abcd & 536) || !(~abcd & 1544) || !((abcd & 1560) ^ 8) || !((abcd & 1304) ^ 1032) ? + (idx_d = fallback) + : !((abcd & 1624) ^ 1104) || !((abcd & 1592) ^ 1072) ? + (idx_d -= 2) + : ((abcd & 1552) ^ 16) && ((abcd & 632) ^ 16) && ((abcd & 1624) ^ 1616) && ((abcd & 1592) ^ 1584) ? + idx_d + : + --idx_d; + + ini_string[idx_s]; + + ini_string[idx_s++] = '\0' + + ); + + return idx_d; + +} + + + /** @utility{ini_array_break} **/ +/** + + @brief Replaces the first delimiter found (together with the spaces + that surround it) with `\0` + @param ini_string The stringified array (it can be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return A pointer to the remaining INI array or `NULL` if the remaining + array is empty + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + Similarly to `strtok_r()` this function can be used only once for a given + string. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will return `NULL`. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_break.c + +**/ +char * ini_array_break (char * const ini_string, const char delimiter, const IniFormat format) { + + if (ini_string && !_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + char * remnant; + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + remnant = ini_string; + + while (*remnant++); + + rtrim_h(ini_string, remnant - ini_string - 1, _LIBCONFINI_WITH_EOL_); + + } else { + + remnant = ini_string + get_metachar_pos(ini_string, delimiter, format); + + if (*remnant) { + + *remnant = '\0'; + rtrim_h(ini_string, remnant - ini_string, _LIBCONFINI_WITH_EOL_); + remnant += ltrim_h(remnant, 1, _LIBCONFINI_WITH_EOL_); + + if (delimiter || *remnant) { + + return remnant; + + } + + } + + } + + } + + return (char *) 0; + +} + + + /** @utility{ini_array_release} **/ +/** + + @brief Replaces the first delimiter found (together with the spaces + that surround it) with `\0`, then shifts the location pointed by + @p ini_strptr to the next member of the INI array, or to `NULL` + if the INI array has no more members + @param ini_strptr The memory location of the stringified array -- + it cannot be `NULL`, but it can point to `NULL` + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The array member that has been released + + Usually @p ini_strptr comes from an #IniDispatch (but any other string may be + used as well). + + Similarly to `strtok_r()` this function can be used only once for a given + string. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will set @p ini_strptr to `NULL`. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_release.c + +**/ +char * ini_array_release (char ** const ini_strptr, const char delimiter, const IniFormat format) { + + char * const token = *ini_strptr; + + if (token && !_LIBCONFINI_IMPLICIT_RANGE_(token) && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + *ini_strptr += get_metachar_pos(*ini_strptr, delimiter, format); + + if (**ini_strptr) { + + **ini_strptr = '\0'; + rtrim_h(token, *ini_strptr - token, _LIBCONFINI_WITH_EOL_); + *ini_strptr += ltrim_h(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_); + + if (delimiter || **ini_strptr) { + + return token; + + } + + } + + } + + *ini_strptr = (char *) 0; + return token; + +} + + + /** @utility{ini_array_split} **/ +/** + + @brief Splits a stringified INI array into NUL-separated members and + calls a custom function for each member + @param ini_string The stringified array (it cannot be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @param f_foreach The function that will be invoked for each array + member + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + The user given function @p f_foreach (see #IniStrHandler data type) will be + invoked with five arguments: `member` (the member of the array), `memb_length` + (the length of the member in bytes), `memb_num` (the offset of the member in + number of members), `format` (the format of the INI file), `user_data` (the + custom argument @p user_data previously passed). If @p f_foreach returns a + non-zero value the function #ini_array_split() will be interrupted. + + Similarly to `strtok_r()` this function can be used only once for a given + string. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE or is `NULL` this + function is no-op and will return an error code. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_EROADDR, #CONFINI_FEINTR. + + @include topics/ini_array_split.c. + +**/ +int ini_array_split ( + char * const ini_string, + const char delimiter, + const IniFormat format, + const IniStrHandler f_foreach, + void * const user_data +) { + + if (!ini_string || _LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + return CONFINI_EROADDR; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Delimiter is not any space (const) + FLAG_8 Unescaped single quotes are odd until now + FLAG_16 Unescaped double quotes are odd until now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is not a delimiter + FLAG_128 Stop the loop + + */ + + register size_t idx; + size_t offs = ltrim_h(ini_string, 0, _LIBCONFINI_WITH_EOL_); + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + idx = 0; + + while (ini_string[idx++]); + + return f_foreach(ini_string + offs, rtrim_h(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ? + CONFINI_FEINTR + : + CONFINI_SUCCESS; + + } + + register uint8_t abcd = (delimiter ? 4 : 0) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t memb_num = 0; + + idx = offs; + + + do { + + abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ? + abcd & 159 + : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd | 64) ^ 32 + : !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd & 223) | 64) ^ 16 + : !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd & 223) | 64) ^ 8 + : ini_string[idx] ? + (abcd & 223) | 64 + : + 128; + + + if (!(abcd & 88)) { + + ini_string[idx] = '\0'; + + if ( + f_foreach( + ini_string + offs, + rtrim_h(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_), + memb_num++, + format, + user_data + ) + ) { + + return CONFINI_FEINTR; + + } + + offs = abcd & 128 ? idx + 1 : ltrim_h(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_); + + } + + idx = abcd & 216 ? idx + 1 : offs; + + } while (!(abcd & 128) && ((abcd & 92) || ini_string[idx])); + + return CONFINI_SUCCESS; + +} + + + /** @utility{ini_global_set_lowercase_mode} **/ +/** + + @brief Sets the value of the global variable + #INI_GLOBAL_LOWERCASE_MODE + @param lowercase The new value + @return Nothing + + If @p lowercase is `true`, key and section names in case-insensitive INI formats + will be dispatched lowercase, verbatim otherwise (default value: `true`). + + @warning This function changes the value of one or more global variables. In + order to be thread-safe this function should be used only once at + beginning of execution, or otherwise a mutex logic must be + introduced. + +**/ +void ini_global_set_lowercase_mode (const bool lowercase) { + + INI_GLOBAL_LOWERCASE_MODE = lowercase; + +} + + + /** @utility{ini_global_set_implicit_value} **/ +/** + + @brief Sets the value to be to be assigned to implicit keys + @param implicit_value The string to be used as implicit value + (usually `"YES"`, or `"TRUE"`) + @param implicit_v_len The length of @p implicit_value (usually + `0`, independently of its real length) + @return Nothing + + @warning This function changes the value of one or more global variables. In + order to be thread-safe this function should be used only once at + beginning of execution, or otherwise a mutex logic must be + introduced. + + @include topics/ini_global_set_implicit_value.c + +**/ +void ini_global_set_implicit_value (char * const implicit_value, const size_t implicit_v_len) { + + INI_GLOBAL_IMPLICIT_VALUE = implicit_value; + INI_GLOBAL_IMPLICIT_V_LEN = implicit_v_len; + +} + + + /** @utility{ini_fton} **/ +/** + + @brief Calculates the #IniFormatNum of an #IniFormat + @param source The #IniFormat to compute + @return The unique unsigned integer that identifies the format given + +**/ +IniFormatNum ini_fton (const IniFormat source) { + + #define __INIFORMAT_ID__(NAME, OFFSET, SIZE, DEFVAL) (source.NAME << OFFSET) | + + return INIFORMAT_TABLE_AS(__INIFORMAT_ID__) 0; + + #undef __INIFORMAT_ID__ + +} + + + /** @utility{ini_ntof} **/ +/** + + @brief Constructs a new #IniFormat according to an #IniFormatNum + @param format_num The #IniFormatNum to parse + @return The new #IniFormat constructed + + @note If @p format_num `>` `16777215` it will be truncated to 24 bits. + +**/ +IniFormat ini_ntof (const IniFormatNum format_num) { + + #define __MAX_1_BITS__ 1 + #define __MAX_2_BITS__ 3 + #define __MAX_3_BITS__ 7 + #define __MAX_4_BITS__ 15 + #define __MAX_5_BITS__ 31 + #define __MAX_6_BITS__ 63 + #define __MAX_7_BITS__ 127 + #define __MAX_8_BITS__ 255 + #define __INIFORMAT_PROPERTIES__(NAME, OFFSET, SIZE, DEFVAL) \ + (unsigned char) ((format_num >> OFFSET) & __MAX_##SIZE##_BITS__), + + return (IniFormat) { INIFORMAT_TABLE_AS(__INIFORMAT_PROPERTIES__) }; + + #undef __INIFORMAT_PROPERTIES__ + #undef __MAX_8_BITS__ + #undef __MAX_7_BITS__ + #undef __MAX_6_BITS__ + #undef __MAX_5_BITS__ + #undef __MAX_4_BITS__ + #undef __MAX_3_BITS__ + #undef __MAX_2_BITS__ + #undef __MAX_1_BITS__ + +} + + + /** @utility{ini_get_bool} **/ +/** + + @brief Checks whether a string matches one of the booleans listed in + the private constant #INI_BOOLEANS (case-insensitive) + @param ini_string A string to check (it can be `NULL`) + @param when_fail A value that is returned if no matching + boolean is found + @return The matching boolean (`0` or `1`) or @p when_fail if + @p ini_string does not contain a valid INI boolean + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + @include miscellanea/typed_ini.c + +**/ +int ini_get_bool (const char * const ini_string, const int when_fail) { + + if (!ini_string) { + + return when_fail; + + } + + register uint8_t bool_idx; + register size_t pair_idx, chr_idx; + + for (pair_idx = 0; pair_idx < sizeof(INI_BOOLEANS) / sizeof(const char * const [2]); pair_idx++) { + + for (bool_idx = 0; bool_idx < 2; bool_idx++) { + + chr_idx = 0; + + while (_LIBCONFINI_CHR_CASEFOLD_(ini_string[chr_idx]) == INI_BOOLEANS[pair_idx][bool_idx][chr_idx]) { + + if (!ini_string[chr_idx++]) { + + return (int) bool_idx; + + } + + } + + } + + } + + return when_fail; + +} + + + + /* LINKS - In case you don't have `#include ` in your source */ + + +/** + + @alias{ini_get_int} + Pointer to + [`atoi()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoi) + @alias{ini_get_lint} + Pointer to + [`atol()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atol) + @alias{ini_get_llint} + Pointer to + [`atoll()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoll) + @alias{ini_get_double} + Pointer to + [`atof()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atof) + +**/ + +int (* const ini_get_int) (const char * ini_string) = atoi; + +long int (* const ini_get_lint) (const char * ini_string) = atol; + +long long int (* const ini_get_llint) (const char * ini_string) = atoll; + +double (* const ini_get_double) (const char * ini_string) = atof; + +/* Legacy support -- please **do not use this**! */ +#ifdef ini_get_float +#undef ini_get_float +#endif +double (* const ini_get_float) (const char * ini_string) = atof; + + + + /* GLOBAL VARIABLES */ + + +bool INI_GLOBAL_LOWERCASE_MODE = _LIBCONFINI_FALSE_; + +char * INI_GLOBAL_IMPLICIT_VALUE = (char *) 0; + +size_t INI_GLOBAL_IMPLICIT_V_LEN = 0; + + + +/** @endfnlist **/ + +/* EOF */ + diff --git a/controller.ini b/controller.ini new file mode 100644 index 0000000..1145dca --- /dev/null +++ b/controller.ini @@ -0,0 +1,44 @@ +[controller] +name = new emgauwa device +discovery-port = 4421 +relay-count = 10 + +[relay-0] +driver = piface +pin = 0 + +[relay-1] +driver = piface +pin = 1 + +[relay-2] +driver = gpio +pin = 11 + +[relay-3] +driver = gpio +pin = 13 + +[relay-4] +driver = gpio +pin = 15 + +[relay-5] +driver = gpio +pin = 8 + +[relay-6] +driver = gpio +pin = 10 + +[relay-7] +driver = gpio +pin = 12 + +[relay-8] +driver = gpio +pin = 16 + +[relay-9] +driver = gpio +pin = 18 diff --git a/database.c b/database.c index 4c75c80..e294a89 100644 --- a/database.c +++ b/database.c @@ -3,7 +3,7 @@ #include #include -#include +#include void database_setup(MDB_env **mdb_env) diff --git a/drivers/gpio.c b/drivers/gpio.c index 11c370e..0fd78b9 100644 --- a/drivers/gpio.c +++ b/drivers/gpio.c @@ -5,10 +5,10 @@ #include void -driver_gpio_set(relay_t *relay, int value) +driver_gpio_set(int pin, int value) { // disable "unused parameter" warning (happens when using wiring_debug) - (void)relay; + (void)pin; (void)value; - digitalWrite(relay->number, value); + digitalWrite(pin, value); } diff --git a/drivers/piface.c b/drivers/piface.c index ecc8c2f..d3a1313 100644 --- a/drivers/piface.c +++ b/drivers/piface.c @@ -5,10 +5,10 @@ #include void -driver_piface_set(relay_t *relay, int value) +driver_piface_set(int pin, int value) { // disable "unused parameter" warning (happens when using wiring_debug) - (void)relay; + (void)pin; (void)value; - digitalWrite(DRIVER_PIFACE_GPIO_BASE + relay->number, value); + digitalWrite(DRIVER_PIFACE_GPIO_BASE + pin, value); } diff --git a/handlers/loop.c b/handlers/loop.c index be7042e..32f2645 100644 --- a/handlers/loop.c +++ b/handlers/loop.c @@ -19,28 +19,22 @@ handler_loop(controller_t *controller) for(uint_fast8_t i = 0; i < controller->relay_count; ++i) { relay_t *relay = controller->relays[i]; + int is_active = 0; if(relay_is_active(relay, time(NULL))) { LOG_DEBUG("relay %d is active", i); - if(relay->number >= 2) - { - driver_gpio_set(relay, HIGH); - } - else - { - driver_piface_set(relay, HIGH); - } + is_active = 1; } - else + switch(global_config.relay_configs[i].driver) { - if(relay->number >= 2) - { - driver_gpio_set(relay, LOW); - } - else - { - driver_piface_set(relay, LOW); - } + case RELAY_DRIVER_GPIO: + driver_gpio_set(global_config.relay_configs[i].pin, is_active); + break; + case RELAY_DRIVER_PIFACE: + driver_piface_set(global_config.relay_configs[i].pin, is_active); + break; + default: + LOG_WARN("relay %d is not using a driver", i); } } } diff --git a/helpers/load_config.c b/helpers/load_config.c new file mode 100644 index 0000000..a762ac0 --- /dev/null +++ b/helpers/load_config.c @@ -0,0 +1,69 @@ +#include +#include + +#include +#include +#include +#include + +#define CONFINI_IS_KEY(SECTION, KEY) \ + (ini_array_match(SECTION, disp->append_to, '.', disp->format) && \ + ini_string_match_ii(KEY, disp->data, disp->format)) + +int +helper_load_config(IniDispatch *disp, void *config_void) +{ + config_t *config = (config_t*)config_void; + char relay_section_name[10]; // "relay-255\0" is longest name + + if(disp->type == INI_KEY) + { + if(CONFINI_IS_KEY("controller", "name")) + { + strncpy(config->name, disp->value, MAX_NAME_LENGTH); + config->name[MAX_NAME_LENGTH] = '\0'; + return 0; + } + if(CONFINI_IS_KEY("controller", "discovery-port")) + { + config->discovery_port = atoi(disp->value); + return 0; + } + if(CONFINI_IS_KEY("controller", "relay-count")) + { + config->relay_count = atoi(disp->value); + config->relay_configs = malloc(sizeof(config_relay_t) * config->relay_count); + for(uint8_t i = 0; i < config->relay_count; ++i) + { + config->relay_configs[i].driver= RELAY_DRIVER_NONE; + } + LOG_TRACE("config relay-count set to %u", config->relay_count); + return 0; + } + for(uint8_t i = 0; i < config->relay_count; ++i) + { + sprintf(relay_section_name, "relay-%u", i); + if(CONFINI_IS_KEY(relay_section_name, "pin")) + { + config->relay_configs[i].pin = atoi(disp->value); + return 0; + } + if(CONFINI_IS_KEY(relay_section_name, "driver")) + { + if(strcasecmp(disp->value, "gpio") == 0) + { + config->relay_configs[i].driver = RELAY_DRIVER_GPIO; + return 0; + } + if(strcasecmp(disp->value, "piface") == 0) + { + config->relay_configs[i].driver = RELAY_DRIVER_PIFACE; + return 0; + } + LOG_WARN("invalid driver '%s' in section '%s'", disp->value, relay_section_name); + return 0; + } + } + } + return 0; +} diff --git a/include/config.h b/include/config.h index 5f4c031..24da946 100644 --- a/include/config.h +++ b/include/config.h @@ -1,32 +1,25 @@ #ifndef CONTROLLER_CONFIG_H #define CONTROLLER_CONFIG_H -#include +#include -/** - * @brief Limit the maximum length of a controller/relay/etc name - * - * The NULL terminator is not included. Arrays of length #MAX_NAME_LENGTH + 1 are required. - */ -#define MAX_NAME_LENGTH 128 +#include +#include -/** - * @brief Maximum number of dbs for the databases for the MDB_env - * - * Used when calling mdb_env_set_maxdbs() in database_setup() - */ -#define MDB_MAXDBS 8 +typedef struct +{ + uint8_t pin; + relay_driver_t driver; +} config_relay_t; -/** - * @brief Indicates to which level to log - * - * @see include/log_levels.h - */ -#define LOG_LEVEL LOG_LEVEL_DEBUG +typedef struct +{ + char name[MAX_NAME_LENGTH + 1]; + uint16_t discovery_port; + uint8_t relay_count; + config_relay_t *relay_configs; +} config_t; -/** - * @brief How many milli seconds to wait until poll timeout in main loop - */ -#define ACCEPT_TIMEOUT_MSECONDS 1000 +extern config_t global_config; #endif //CONTROLLER_CONFIG_H diff --git a/include/confini.h b/include/confini.h new file mode 100644 index 0000000..c4c0068 --- /dev/null +++ b/include/confini.h @@ -0,0 +1,547 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ + +/** + + @file confini.h + @brief libconfini header + @author Stefano Gioffré + @copyright GNU General Public License, version 3 or any later version + @version 1.14.0 + @date 2016-2020 + @see https://madmurphy.github.io/libconfini + +**/ + + +#ifndef _LIBCONFINI_HEADER_ +#define _LIBCONFINI_HEADER_ + + + +#include +#include +#include + + + +#ifdef __cplusplus +extern "C" { +#endif + + + +/* PRIVATE (HEADER-SCOPED) MACROS */ + + +#define __INIFORMAT_TABLE_CB_FIELDS__(NAME, OFFSET, SIZE, DEFVAL) \ + unsigned char NAME:SIZE; +#define __INIFORMAT_TABLE_CB_DEFAULT__(NAME, OFFSET, SIZE, DEFVAL) DEFVAL, +#define __INIFORMAT_TABLE_CB_ZERO__(NAME, OFFSET, SIZE, DEFVAL) 0, +#define _LIBCONFINI_INIFORMAT_TYPE_ \ + struct IniFormat { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_FIELDS__) } +#define _LIBCONFINI_DEFAULT_FORMAT_ \ + { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_DEFAULT__) } +#define _LIBCONFINI_UNIXLIKE_FORMAT_ \ + { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_ZERO__) } + + + +/* PUBLIC MACROS */ + + +/** + @brief Calls a user-given macro (that accepts four arguments) for each row + of the table +**/ +/* + NOTE: The following table and the order of its rows **define** (and link + together) both the #IniFormat and #IniFormatNum data types declared in this + header +*/ +#define INIFORMAT_TABLE_AS(_____) /* IniFormat table *\ + + NAME BIT SIZE DEFAULT + */\ + _____( delimiter_symbol, 0, 7, INI_EQUALS ) \ + _____( case_sensitive, 7, 1, false )/* + */\ + _____( semicolon_marker, 8, 2, INI_DISABLED_OR_COMMENT ) \ + _____( hash_marker, 10, 2, INI_DISABLED_OR_COMMENT ) \ + _____( section_paths, 12, 2, INI_ABSOLUTE_AND_RELATIVE ) \ + _____( multiline_nodes, 14, 2, INI_MULTILINE_EVERYWHERE )/* + */\ + _____( no_single_quotes, 16, 1, false ) \ + _____( no_double_quotes, 17, 1, false ) \ + _____( no_spaces_in_names, 18, 1, false ) \ + _____( implicit_is_not_empty, 19, 1, false ) \ + _____( do_not_collapse_values, 20, 1, false ) \ + _____( preserve_empty_quotes, 21, 1, false ) \ + _____( disabled_after_space, 22, 1, false ) \ + _____( disabled_can_be_implicit, 23, 1, false ) + + + +/** + @brief Checks whether a format does **not** support escape sequences +**/ +#define INIFORMAT_HAS_NO_ESC(FORMAT) \ + (FORMAT.multiline_nodes == INI_NO_MULTILINE && \ + FORMAT.no_double_quotes && FORMAT.no_single_quotes) + + + +/* PUBLIC TYPEDEFS */ + + +/** + @brief 24-bit bitfield representing the format of an INI file (INI + dialect) +**/ +typedef _LIBCONFINI_INIFORMAT_TYPE_ IniFormat; + + +/** + @brief Global statistics about an INI file +**/ +typedef struct IniStatistics { + const IniFormat format; + const size_t bytes; + const size_t members; +} IniStatistics; + + +/** + @brief Dispatch of a single INI node +**/ +typedef struct IniDispatch { + const IniFormat format; + uint8_t type; + char * data; + char * value; + const char * append_to; + size_t d_len; + size_t v_len; + size_t at_len; + size_t dispatch_id; +} IniDispatch; + + +/** + @brief The unique ID of an INI format (24-bit maximum) +**/ +typedef uint32_t IniFormatNum; + + +/** + @brief Callback function for handling an #IniStatistics structure +**/ +typedef int (* IniStatsHandler) ( + IniStatistics * statistics, + void * user_data +); + + +/** + @brief Callback function for handling an #IniDispatch structure +**/ +typedef int (* IniDispHandler) ( + IniDispatch * dispatch, + void * user_data +); + + +/** + @brief Callback function for handling an INI string belonging to a + sequence of INI strings +**/ +typedef int (* IniStrHandler) ( + char * ini_string, + size_t string_length, + size_t string_num, + IniFormat format, + void * user_data +); + + +/** + @brief Callback function for handling a selected fragment of an INI string +**/ +typedef int (* IniSubstrHandler) ( + const char * ini_string, + size_t fragm_offset, + size_t fragm_length, + size_t fragm_num, + IniFormat format, + void * user_data +); + + + +/* PUBLIC FUNCTIONS */ + + +extern int strip_ini_cache ( + register char * const ini_source, + const size_t ini_length, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +); + + +extern int load_ini_file ( + FILE * const ini_file, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +); + + +extern int load_ini_path ( + const char * const path, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +); + + +extern bool ini_string_match_ss ( + const char * const simple_string_a, + const char * const simple_string_b, + const IniFormat format +); + + +extern bool ini_string_match_si ( + const char * const simple_string, + const char * const ini_string, + const IniFormat format +); + + +extern bool ini_string_match_ii ( + const char * const ini_string_a, + const char * const ini_string_b, + const IniFormat format +); + + +extern bool ini_array_match ( + const char * const ini_string_a, + const char * const ini_string_b, + const char delimiter, + const IniFormat format +); + + +extern size_t ini_unquote ( + char * const ini_string, + const IniFormat format +); + + +extern size_t ini_string_parse ( + char * const ini_string, + const IniFormat format +); + + +extern size_t ini_array_get_length ( + const char * const ini_string, + const char delimiter, + const IniFormat format +); + + +extern int ini_array_foreach ( + const char * const ini_string, + const char delimiter, + const IniFormat format, + const IniSubstrHandler f_foreach, + void * const user_data +); + + +extern size_t ini_array_shift ( + const char ** const ini_strptr, + const char delimiter, + const IniFormat format +); + + +extern size_t ini_array_collapse ( + char * const ini_string, + const char delimiter, + const IniFormat format +); + + +extern char * ini_array_break ( + char * const ini_string, + const char delimiter, + const IniFormat format +); + + +extern char * ini_array_release ( + char ** const ini_strptr, + const char delimiter, + const IniFormat format +); + + +extern int ini_array_split ( + char * const ini_string, + const char delimiter, + const IniFormat format, + const IniStrHandler f_foreach, + void * const user_data +); + + +extern void ini_global_set_lowercase_mode ( + const bool lowercase +); + + +extern void ini_global_set_implicit_value ( + char * const implicit_value, + const size_t implicit_v_len +); + + +extern IniFormatNum ini_fton ( + const IniFormat format +); + + +extern IniFormat ini_ntof ( + const IniFormatNum format_id +); + + +extern int ini_get_bool ( + const char * const ini_string, + const int when_fail +); + + + +/* PUBLIC LINKS */ + + +extern int (* const ini_get_int) ( + const char * ini_string +); + + +extern long int (* const ini_get_lint) ( + const char * ini_string +); + + +extern long long int (* const ini_get_llint) ( + const char * ini_string +); + + +extern double (* const ini_get_double) ( + const char * ini_string +); + + +/** + @brief Legacy support, soon to be replaced with a `float` data type -- + please **do not use `ini_get_float()`!** +**/ +#define ini_get_float \ + _Pragma("GCC warning \"function `ini_get_float()` is deprecated for parsing a `double` data type; use `ini_get_double()` instead\"") \ + ini_get_double + + + +/* PUBLIC CONSTANTS AND VARIABLES */ + + +/** + @brief Error mask (flags not present in user-generated interruptions) +**/ +#define CONFINI_ERROR 252 + + +/** + @brief Error codes +**/ +enum ConfiniInterruptNo { + CONFINI_SUCCESS = 0, /**< There have been no interruptions, everything + went well [value=0] **/ + CONFINI_IINTR = 1, /**< Interrupted by the user during `f_init()` + [value=1] **/ + CONFINI_FEINTR = 2, /**< Interrupted by the user during `f_foreach()` + [value=2] **/ + CONFINI_ENOENT = 4, /**< File inaccessible [value=4] **/ + CONFINI_ENOMEM = 5, /**< Error allocating virtual memory [value=5] **/ + CONFINI_EIO = 6, /**< Error reading the file [value=6] **/ + CONFINI_EOOR = 7, /**< Out-of-range error: callbacks are more than + expected [value=7] **/ + CONFINI_EBADF = 8, /**< The stream specified is not a seekable stream + [value=8] **/ + CONFINI_EFBIG = 9, /**< File too large [value=9] **/ + CONFINI_EROADDR = 10 /**< Address is read-only [value=10] **/ +}; + + +/** + @brief INI node types +**/ +enum IniNodeType { + INI_UNKNOWN = 0, /**< This is a node impossible to categorize + [value=0] **/ + INI_VALUE = 1, /**< Not used by **libconfini** (values are + dispatched together with keys) -- but + available for user's implementations + [value=1] **/ + INI_KEY = 2, /**< This is a key [value=2] **/ + INI_SECTION = 3, /**< This is a section or a section path + [value=3] **/ + INI_COMMENT = 4, /**< This is a comment [value=4] **/ + INI_INLINE_COMMENT = 5, /**< This is an inline comment [value=5] **/ + INI_DISABLED_KEY = 6, /**< This is a disabled key [value=6] **/ + INI_DISABLED_SECTION = 7 /**< This is a disabled section path + [value=7] **/ +}; + + +/** + @brief Common array and key-value delimiters (but a delimiter may also be + any other ASCII character not present in this list) +**/ +enum IniDelimiters { + INI_ANY_SPACE = 0, /**< In multi-line INIs: + `/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`, in + non-multi-line INIs: `/[\t \v\f])+/` **/ + INI_EQUALS = '=', /**< Equals character (`=`) **/ + INI_COLON = ':', /**< Colon character (`:`) **/ + INI_DOT = '.', /**< Dot character (`.`) **/ + INI_COMMA = ',' /**< Comma character (`,`) **/ +}; + + +/** + @brief Possible values of #IniFormat::semicolon_marker and + #IniFormat::hash_marker (i.e., meaning of `/\s+;/` and `/\s+#/` in + respect to a format) +**/ +enum IniCommentMarker { + INI_DISABLED_OR_COMMENT = 0, /**< This marker opens a comment or a + disabled entry **/ + INI_ONLY_COMMENT = 1, /**< This marker opens a comment **/ + INI_IGNORE = 2, /**< This marker opens a comment that has + been marked for deletion and must not + be dispatched or counted **/ + INI_IS_NOT_A_MARKER = 3 /**< This is not a marker at all, but a + normal character instead **/ +}; + + +/** + @brief Possible values of #IniFormat::section_paths +**/ +enum IniSectionPaths { + INI_ABSOLUTE_AND_RELATIVE = 0, /**< Section paths starting with a dot + express nesting to the current parent, + to root otherwise **/ + INI_ABSOLUTE_ONLY = 1, /**< Section paths starting with a dot will + be cleaned of their leading dot and + appended to root **/ + INI_ONE_LEVEL_ONLY = 2, /**< Format supports sections, but the dot + does not express nesting and is not a + meta-character **/ + INI_NO_SECTIONS = 3 /**< Format does *not* support sections -- + `/\[[^\]]*\]/g`, if any, will be + treated as keys! **/ +}; + + +/** + @brief Possible values of #IniFormat::multiline_nodes +**/ +enum IniMultiline { + INI_MULTILINE_EVERYWHERE = 0, /**< Comments, section paths and keys + -- disabled or not -- are allowed + to be multi-line **/ + INI_BUT_COMMENTS = 1, /**< Only section paths and keys -- + disabled or not -- are allowed to + be multi-line **/ + INI_BUT_DISABLED_AND_COMMENTS = 2, /**< Only active section paths and + active keys are allowed to be + multi-line **/ + INI_NO_MULTILINE = 3 /**< Multi-line escape sequences are + disabled **/ +}; + + +/** + @brief A model format for standard INI files +**/ +static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_FORMAT_; + + +/** + @brief A model format for Unix-like .conf files (where space characters + are delimiters between keys and values) +**/ +/* All fields are set to `0` here. */ +static const IniFormat INI_UNIXLIKE_FORMAT = _LIBCONFINI_UNIXLIKE_FORMAT_; + + +/** + @brief If set to `true`, key and section names in case-insensitive INI + formats will be dispatched lowercase, verbatim otherwise (default + value: `false`) +**/ +extern bool INI_GLOBAL_LOWERCASE_MODE; + + +/** + @brief Value to be assigned to implicit keys (default value: `NULL`) +**/ +extern char * INI_GLOBAL_IMPLICIT_VALUE; + + +/** + @brief Length of the value assigned to implicit keys (default value: `0`) +**/ +extern size_t INI_GLOBAL_IMPLICIT_V_LEN; + + + +/* CLEAN THE PRIVATE ENVIRONMENT */ + + +#undef _LIBCONFINI_UNIXLIKE_FORMAT_ +#undef _LIBCONFINI_DEFAULT_FORMAT_ +#undef _LIBCONFINI_INIFORMAT_TYPE_ +#undef __INIFORMAT_TABLE_CB_ZERO__ +#undef __INIFORMAT_TABLE_CB_DEFAULT__ +#undef __INIFORMAT_TABLE_CB_FIELDS__ + + + +/* END OF `_LIBCONFINI_HEADER_` */ + + +#ifdef __cplusplus +} +#endif + + +#endif + + + +/* EOF */ + diff --git a/include/constants.h b/include/constants.h index dcc9a1e..6f8e4d1 100644 --- a/include/constants.h +++ b/include/constants.h @@ -1,5 +1,38 @@ +#ifndef CONTROLLER_CONTANTS_H +#define CONTROLLER_CONTANTS_H + +#include + #define SECONDS_PER_DAY 86400 // 60 * 60 * 24 #define SECONDS_PER_MINUTE 60 #define POLL_FDS_COUNT 2 + +/** + * @brief Limit the maximum length of a controller/relay/etc name + * + * The NULL terminator is not included. Arrays of length #MAX_NAME_LENGTH + 1 are required. + */ +#define MAX_NAME_LENGTH 128 + +/** + * @brief Maximum number of dbs for the databases for the MDB_env + * + * Used when calling mdb_env_set_maxdbs() in database_setup() + */ +#define MDB_MAXDBS 8 + +/** + * @brief Indicates to which level to log + * + * @see include/log_levels.h + */ +#define LOG_LEVEL LOG_LEVEL_DEBUG + +/** + * @brief How many milli seconds to wait until poll timeout in main loop + */ +#define ACCEPT_TIMEOUT_MSECONDS 1000 + +#endif /* CONTROLLER_CONTANTS_H */ diff --git a/include/drivers.h b/include/drivers.h index b33c140..6539f7b 100644 --- a/include/drivers.h +++ b/include/drivers.h @@ -2,13 +2,14 @@ #define CONTROLLER_DRIVERS_H #include +#include #define DRIVER_PIFACE_GPIO_BASE 200 void -driver_piface_set(relay_t *relay, int value); +driver_piface_set(int pin, int value); void -driver_gpio_set(relay_t *relay, int value); +driver_gpio_set(int pin, int value); #endif /* CONTROLLER_DRIVERS_H */ diff --git a/include/enums.h b/include/enums.h index c009ada..87f093f 100644 --- a/include/enums.h +++ b/include/enums.h @@ -1,21 +1,21 @@ #ifndef CONTROLLER_ENUMS_H #define CONTROLLER_ENUMS_H -enum poll_fgs +typedef enum { POLL_FDS_DISCOVERY, POLL_FDS_COMMAND -}; +} poll_fds_t; -enum discovery_mapping +typedef enum { DISCOVERY_MAPPING_ID = 0, DISCOVERY_MAPPING_NAME = 1, DISCOVERY_MAPPING_COMMAND_PORT = 2, DISCOVERY_MAPPING_RELAY_COUNT = 3, -}; +} discovery_mapping_t; -enum control_mapping +typedef enum { COMMAND_MAPPING_CODE = 0, COMMAND_MAPPING_NAME = 1, @@ -23,9 +23,9 @@ enum control_mapping COMMAND_MAPPING_SCHEDULE_ID = 3, COMMAND_MAPPING_PERIODS_COUNT = 4, COMMAND_MAPPING_PERIODS_BLOB = 5, -}; +} control_mapping_t; -enum command_code +typedef enum { COMMAND_CODE_GET_TIME = 1, COMMAND_CODE_GET_ID = 2, @@ -35,6 +35,13 @@ enum command_code COMMAND_CODE_GET_SCHEDULE = 103, COMMAND_CODE_SET_RELAY_NAME = 104, COMMAND_CODE_GET_RELAY_NAME = 105, -}; +} command_code_t; + +typedef enum +{ + RELAY_DRIVER_NONE = 0, + RELAY_DRIVER_GPIO = 1, + RELAY_DRIVER_PIFACE = 2, +} relay_driver_t; #endif /* CONTROLLER_ENUMS_H */ diff --git a/include/helpers.h b/include/helpers.h index b1bd8a5..4a599fd 100644 --- a/include/helpers.h +++ b/include/helpers.h @@ -1,6 +1,8 @@ #ifndef CONTROLLER_HELPERS_H #define CONTROLLER_HELPERS_H +#include + int helper_connect_tcp_server(char* host, uint16_t port); @@ -22,4 +24,7 @@ helper_get_port(int sock); int helper_open_discovery_socket(uint16_t discovery_port); +int +helper_load_config(IniDispatch *disp, void *config_void); + #endif /* CONTROLLER_HELPERS_H */ diff --git a/include/logger.h b/include/logger.h index 3740f1c..9bfac04 100644 --- a/include/logger.h +++ b/include/logger.h @@ -21,7 +21,7 @@ char _LOGGER_TIMESTAMP[_LOGGER_TIMESTAMP_SIZE]; char* logger_get_timestamp(); -#define _LOGGER_MESSAGE(msg) COLOR_NONE " %s %s:%d:%s: " msg "\n", logger_get_timestamp(), __FILENAME__, __LINE__, __func__ +#define _LOGGER_MESSAGE(msg) " %s %s:%d:%s: " COLOR_NONE msg "\n", logger_get_timestamp(), __FILENAME__, __LINE__, __func__ #if LOG_LEVEL >= LOG_LEVEL_TRACE #define LOG_TRACE(msg, ...) fprintf(stdout, COLOR_TRACE "[TRACE]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__) diff --git a/include/models/controller.h b/include/models/controller.h index 17f3d08..9ceebec 100644 --- a/include/models/controller.h +++ b/include/models/controller.h @@ -48,8 +48,7 @@ typedef enum DB_KEY_CONTROLLER_NAME = 1, DB_KEY_CONTROLLER_COMMAND_PORT = 2, DB_KEY_CONTROLLER_DISCOVERY_PORT = 3, - DB_KEY_CONTROLLER_RELAY_COUNT = 4, - DB_KEY_CONTROLLER_RELAYS = 5, + DB_KEY_CONTROLLER_RELAYS = 4, } db_key_controller_e; /** diff --git a/include/models/relay.h b/include/models/relay.h index 8ce1a6f..43a5d9b 100644 --- a/include/models/relay.h +++ b/include/models/relay.h @@ -5,7 +5,7 @@ #include #include -#include +#include #include typedef struct diff --git a/main.c b/main.c index 0e1752e..5a233a2 100644 --- a/main.c +++ b/main.c @@ -20,6 +20,9 @@ #include #include #include +#include + +config_t global_config; static MDB_env *mdb_env; static controller_t *this_controller; @@ -39,6 +42,8 @@ terminate(int signum) controller_free(this_controller); + free(global_config.relay_configs); + exit(signum); } @@ -60,6 +65,23 @@ main(int argc, char** argv) signal(SIGABRT, terminate); signal(SIGTERM, terminate); + /******************** LOAD CONFIG ********************/ + + char ini_file_name[] = "controller.ini"; + FILE * const ini_file = fopen(ini_file_name, "rb"); + if(ini_file == NULL) + { + LOG_ERROR("%s file was not found", ini_file_name); + exit(1); + } + if(load_ini_file( ini_file, INI_DEFAULT_FORMAT, NULL, helper_load_config, &global_config)) + { + LOG_ERROR("unable to parse ini file"); + exit(1); + } + + fclose(ini_file); + if(sizeof(time_t) < 8) { LOG_WARN("this system is not using 8-bit time"); diff --git a/models/controller.c b/models/controller.c index 03a96d3..77ffff9 100644 --- a/models/controller.c +++ b/models/controller.c @@ -6,6 +6,8 @@ #include #include +#include +#include controller_t* controller_create(void) @@ -13,10 +15,12 @@ controller_create(void) controller_t *new_controller = malloc(sizeof(*new_controller)); uuid_generate(new_controller->id); - strcpy(new_controller->name, "new emgauwa device"); + strncpy(new_controller->name, global_config.name, MAX_NAME_LENGTH); + new_controller->name[MAX_NAME_LENGTH] = '\0'; + new_controller->command_port = 0; - new_controller->discovery_port = 4421; - new_controller->relay_count = 10; + new_controller->discovery_port = global_config.discovery_port; + new_controller->relay_count = global_config.relay_count; new_controller->relays = malloc(sizeof(relay_t) * new_controller->relay_count); uint8_t i; diff --git a/models/controller_load.c b/models/controller_load.c index b6f862d..4c0fb19 100644 --- a/models/controller_load.c +++ b/models/controller_load.c @@ -70,8 +70,7 @@ controller_load(MDB_env *mdb_env) controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_DISCOVERY_PORT, &value); new_controller->discovery_port = ((uint16_t*)value.mv_data)[0]; - controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_RELAY_COUNT, &value); - new_controller->relay_count = ((uint8_t*)value.mv_data)[0]; + new_controller->relay_count = global_config.relay_count; mdb_txn_abort(mdb_txn); // transaction is read only diff --git a/models/controller_save.c b/models/controller_save.c index 60881d3..d99950a 100644 --- a/models/controller_save.c +++ b/models/controller_save.c @@ -78,14 +78,6 @@ controller_save(controller_t *controller, MDB_env *mdb_env) return 1; } - value.mv_size = sizeof(controller->relay_count); - value.mv_data = &controller->relay_count; - if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_RELAY_COUNT, value)) - { - LOG_ERROR("failed to save relay count"); - return 1; - } - mdb_txn_commit(mdb_txn); for(uint8_t i = 0; i < controller->relay_count; ++i) diff --git a/models/relay.c b/models/relay.c index ed9b73b..87ee257 100644 --- a/models/relay.c +++ b/models/relay.c @@ -2,7 +2,6 @@ #include #include -#include #include #include