shayla

a simple yet fast static site builder
Log | Files | Refs | Submodules | README | LICENSE | git clone https://git.ne02ptzero.me/git/shayla

commit 7816c7870e3bb1674b4460273c0c1697107140ce
parent 15b023254970d957753e8de7c05687847fad4a92
Author: Louis Solofrizzo <lsolofrizzo@online.net>
Date:   Wed, 19 Sep 2018 20:08:09 +0200

Args: Add command line argument line parsing

This commit introduce full argument parsing in shayla. There is several
options in the code, and since the --help option is covering all of
them, I will not go one-by-one in this commit message.

On the minor side of this patch, I fixed a memory leak in the 'add_file'
function, and changed the compilation standard from C99 to C11.

Signed-off-by: Louis Solofrizzo <lsolofrizzo@online.net>

Diffstat:
MCMakeLists.txt | 4++--
Aargs.c | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aargs.h | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdir.c | 3+++
Mfile.h | 19+++++++++++++++++++
Mmain.c | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mshayla.h | 8++++++++
Mstr.h | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutils.h | 1+
9 files changed, 779 insertions(+), 5 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -20,7 +20,7 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wshadow") # Don't shadow variables set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wunreachable-code") # Warn if the compiler detects that code will never be executed set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wswitch-enum") # Force a switch on an enum to have all the cases possible -set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) #SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg") @@ -38,7 +38,7 @@ include_directories("./sundown/html") link_libraries(pthread) add_executable(${SDOW_NAME} main.c - #args.c + args.c #file.c #parser.c #parse_header.c diff --git a/args.c b/args.c @@ -0,0 +1,256 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <args.h> +#include <str.h> +#include <string.h> +#include <stdio.h> +#include <file.h> + +static void *read_arg_default(const char *str) +{ + UNUSED_ARG(str); + return (void *)0x1; +} + +static void *read_arg_str(const char *str) +{ + if (str == NULL) + return NULL; + + return strdup(str); +} + +static void *read_arg_int(const char *str) +{ + int *ptr; + + ptr = malloc(sizeof(int)); + if (unlikely(ptr == NULL)) + return NULL; + + if (!str_to_int32(str, ptr, BASE_TEN)) + { + free(ptr); + return NULL; + } + return ptr; +} + +static void *read_arg_float(const char *str) +{ + float *ptr; + + ptr = malloc(sizeof(float)); + if (unlikely(ptr == NULL)) + return NULL; + + if (!str_to_float(str, ptr)) + { + free(ptr); + return NULL; + } + return ptr; +} + +static void *read_arg_file(const char *str) +{ + if (file_exist(str)) + return strdup(str); + return NULL; +} + +typedef void *(*opt_arg_read_fn_t)(const char *str); + +static const opt_arg_read_fn_t g_check_arg[] = +{ + [OPT_TYPE_NONE] = read_arg_default, + [OPT_TYPE_STRING] = read_arg_str, + [OPT_TYPE_INT] = read_arg_int, + [OPT_TYPE_FLOAT] = read_arg_float, + [OPT_TYPE_FILE] = read_arg_file, +}; + +static bool check_required_args(opts_t *args, size_t args_size) +{ + bool ret = true; + + for (size_t i = 0; i < args_size; i++) + { + if (args[i].required == true && args[i].result == NULL) + { + if (args[i].letter != '\0') + fprintf(stderr, "Error: Option '-%c' is mandatory\n", args[i].letter); + else + fprintf(stderr, "Error: Argument '--%s' is mandatory\n", args[i].name); + ret = false; + } + } + + return ret; +} + +static bool read_single_opt_arg(char opt, opts_t *args, size_t args_size, const char **av, int *cur, int ac) +{ + opts_t *arg = NULL; + + for (size_t i = 0; i < args_size; i++) + { + if (args[i].letter == opt) + { + arg = (opts_t *)&(args[i]); + break; + } + } + + if (arg == NULL) + { + fprintf(stderr, "Error: Unknown option '%c'\n", opt); + return false; + } + + if (*cur + 1 >= ac && arg->arg) + { + fprintf(stderr, "Error: Option '%c' require an argument\n", opt); + return false; + } + + if (arg->arg) + (*cur)++; + + arg->result = g_check_arg[arg->option_type](av[*cur]); + if (arg->result == NULL) + { + fprintf(stderr, "Error: Argument '%s' is invalid for option '%c'\n", av[*cur], opt); + return false; + } + return true; +} + +static bool read_long_opt_arg(const char *opt, opts_t *args, size_t args_size, const char **av, int *cur, int ac) +{ + opts_t *arg = NULL; + + for (size_t i = 0; i < args_size; i++) + { + if (args[i].name == NULL) + continue; + + if (strcmp(args[i].name, opt) == 0) + { + arg = (opts_t *)&(args[i]); + break; + } + } + + if (arg == NULL) + { + fprintf(stderr, "Error: Unknown option '%s'\n", opt); + return false; + } + + if (*cur + 1 >= ac && arg->arg) + { + fprintf(stderr, "Error: Option '%s' require an argument\n", opt); + return false; + } + + if (arg->arg) + (*cur)++; + + arg->result = g_check_arg[arg->option_type](av[*cur]); + if (arg->result == NULL) + { + fprintf(stderr, "Error: Argument '%s' is invalid for option '%s'\n", av[*cur], opt); + return false; + } + + return true; + +} + +#define STR_IS_SINGLE_OPT(str) (strlen(str) >= 2 && str[0] == '-' && str[1] != '-') +#define STR_IS_LONG_OPT(str) (strlen(str) >= 3 && str[0] == '-' && str[1] == '-' && str[2] != '-') +#define STR_IS_OPT_STOP_READ(str) (strlen(str) == 2 && str[0] == '-' && str[1] == '-') +bool parse_args(const int ac, const char **av, opts_t *args, size_t args_size, void (*help_fn)(void)) +{ + const char *current = NULL; + + for (int i = 0; i < ac; i++) + { + current = av[i]; + + if (current[0] != '-') + continue; + + if (STR_IS_SINGLE_OPT(current)) + { + for (size_t j = 1; j < strlen(current); j++) + { + if (current[j] == 'h') + { + help_fn(); + exit(0); + } + + if (!read_single_opt_arg(current[j], args, args_size, av, &i, ac)) + goto error; + } + } + else if (STR_IS_LONG_OPT(current)) + { + if (strcmp(current + 2, "help") == 0) + { + help_fn(); + exit(0); + } + + if (!read_long_opt_arg(current + 2, args, args_size, av, &i, ac)) + goto error; + } + else if (STR_IS_OPT_STOP_READ(current)) + { + goto stop_read; + } + else + { + fprintf(stderr, "Error: Parse error '%s'\n", current); + goto error; + } + } + + if (!check_required_args(args, args_size)) + return false; + + return true; + +error: + help_fn(); + return false; + +stop_read: + return true; +} + +void free_args(opts_t *args, size_t args_size) +{ + for (size_t i = 0; i < args_size; i++) + { + if (args[i].result != (void *)0x1) + free(args[i].result); + + args[i].result = NULL; + } +} diff --git a/args.h b/args.h @@ -0,0 +1,175 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef BASE_ARGS_H +# define BASE_ARGS_H + +# include <utils.h> + +# include <stdbool.h> +# include <stdlib.h> + +typedef enum { + OPT_TYPE_NONE = 0, + OPT_TYPE_STRING, + OPT_TYPE_INT, + OPT_TYPE_FLOAT, + OPT_TYPE_FILE, +} e_opt_type_t; + +# define SINGLE_OPT(l) .letter = l +# define LONG_OPT(str) .name = str +# define REQUIRED .required = true +# define ARG_TYPE(type) .option_type = type, .arg = true + +# define GET_STR_ARG(opts, id) (char *)((opts)[id].result) +# define GET_INT_ARG(opts, id) *((int *)((opts)[id].result)) +# define GET_FLOAT_ARG(opts, id) *((float *)((opts)[id].result)) +# define GET_IPV4_ARG(opts, id) *((ipv4_t *)((opts)[id].result)) +# define IS_ARG_HERE(opts, id) ((opts)[id].result != NULL) +# define GET_FILE_ARG GET_STR_ARG + +typedef struct { + const char letter; + const e_opt_type_t option_type; + const bool arg; + const bool required; + const char *name; + void *result; +} opts_t; + +/*! + * \brief Parse command line arguments with preset options + * + * \param[in] ac Number of command line arguments + * \param[in] av Command line arguments + * \param[in,out] args Preset arguments + * \param[in] args_size Number of preset arguments + * \param[in] help_fn Callback help function + * + * The purpose of this function is to parse command line arguments, and return + * an usable structure for option treatment. + * This structure is called "Preset Arguments" and should look like the + * following: + * ``` + * static opts_t g_variable_name[] = { + * [OPT_X] = + * { + * ARG_FLAG + * }, + * [OPT_Y] = + * { + * [...] + * }; + * ``` + * + * OPT_X and Y are used to define flag 'names'; There are to be used later + * in order to retrieve information about them. One can use the implicit array + * order of GCC, but I strongly advise against it: It is much clearer with + * defines. Therefore, you must define OPT_X to an integer value, starting from + * 0. Those defines shall served as array keys, so the order must be a coherent + * one: 0,1,2,3,...,X + * ``` + * #define OPT_X 0 + * #define OPT_Y 1 + * ``` + * + * In the short code above, you can see a 'ARG_FLAG'. These are the macros + * that you can flag your option with. There are to be used like a list + * enumeration: + * ``` + * ARG_FLAG_1, + * ARG_FLAG_2, + * ARG_FLAG_WITH_PARAM(5) + * ``` + * Here is the list of all the availables flags for an argument: + * - SINGLE_OPT(letter): + * The command line flag shall be one with a single letter: + * > -z -a + * 'letter' is an argument that defines the flag letter; If 'a' where to + * be used, the flag would be '-a'. + * + * - LONG_OPT(string): + * The command line flag shall be one with an entire word: + * > --verbose, --debug + * 'string' is an argument that defines the flag word; If the string + * 'hello' where to be used, the flag would be '--hello' + * + * - REQUIRED: + * This flag indicates that the corresponding flag must be met in The + * command line. It is worth noting that if a required argument is not met + * in the command line, this function shall return false + * + * - TYPE(type): + * This flag defines the type of argument that a flag shall receive. + * The 'type' argument is used to describe the said type; One must + * use one of the value present in e_opt_type_t: + * - OPT_TYPE_STRING: Simple string + * - OPT_TYPE_INT: 4 bytes integer + * - OPT_TYPE_FLOAT: Floating point integer + * - OPT_TYPE_FILE: An existing file + * - OPT_TYPE_IPV4: A valid IPv4 + * + * It is worth noting that if an argument is not in the expected format + * this function shall call the help_fn callback then exit(3) the main + * thread. + * + * With this structure filled, one must call the present function in order + * to parse the command line. If a parse error happens, or a memory error, + * this function shall call the help_fn callback, then exit(3) the main + * thread. + * + * If everything is successfull, one can retrieve the parsed value with the + * following mechanic: + * GET_XXX_ARG(g_variable_name, OPT_X); + * Here, 'g_variable_name' is the name of the opts_t array that was used to + * call this function. + * 'OPT_X' is the flag index. + * + * The 'GET_XXX_ARG' represent one of the many calls that can be used to + * retrieve an argument in the correct format: + * - GET_STR_ARG: Get a string argument (char *) + * - GET_INT_ARG: Get a 4 bytes integer argument (int) + * - GET_FLOAT_ARG: Get a floating point argument (float) + * - GET_IPV4_ARG: Get an ipv4 argument (ipv4_t) + * - GET_FILE_ARG: Alias to GET_STR_ARG + * + * In all thoses cases, the return variable has been safely allocated by + * this function, and are freed by free_args. + * + * If one must use a flag without an argument (a presence flag), he must use: + * IS_ARG_HERE(g_variable_name, OPT_X) + * This macro shall return true if the corresponding flag has been parsed, + * false otherwise. + * + * \return true on parse success, otherwise false + */ +__NON_NULL_ALL +bool parse_args(const int ac, const char **av, opts_t *args, size_t args_size, + void (*help_fn)(void)); + +/*! + * \brief Free a opts_t array + * + * \param[in,out] args Array to free + * \param[in] args_size Array size + */ +__NON_NULL_ALL +void free_args(opts_t *args, size_t args_size); + +# ifdef __cplusplus +} +# endif /* __cplusplus */ +#endif /* BASE_ARGS_H */ diff --git a/dir.c b/dir.c @@ -74,7 +74,10 @@ static bool add_file(list_head_t *files, const char *path, const char *ext, size if (ext != NULL) { if (!str_ends_with(path, ext)) + { + file_free(tmp); return true; + } } list_add_tail(&tmp->list, files); diff --git a/file.h b/file.h @@ -117,4 +117,23 @@ static inline void file_list_free(list_head_t *files) } } +/*! + * \brief Check if a file is there or not + * + * \param[in] str File path + * \note This function will call 2 syscalls on success, 1 on failure. + * + * \return true if the file exist, false otherwise + */ +__INLINE_FUNCTION +static inline bool file_exist(const char *str) +{ + int fd = open(str, O_RDONLY); + bool result = (fd != -1); + + if (result) + close(fd); + return result; +} + #endif /* FILE_H */ diff --git a/main.c b/main.c @@ -16,15 +16,202 @@ #include <log.h> #include <shayla.h> #include <dir.h> +#include <args.h> +#include <inttypes.h> -int main(void) +enum { + OPT_VERSION = 0, + OPT_TITLE, + OPT_SRC, + OPT_LAYOUTS, + OPT_DEST, + OPT_ROOT, + OPT_FAVICON, + OPT_URL, + OPT_IMG +}; + +static opts_t options[] = { + [OPT_VERSION] = { + LONG_OPT("version"), + SINGLE_OPT('v') + }, + [OPT_TITLE] = { + LONG_OPT("title"), + SINGLE_OPT('t'), + ARG_TYPE(OPT_TYPE_STRING) + }, + [OPT_SRC] = { + LONG_OPT("src"), + SINGLE_OPT('s'), + ARG_TYPE(OPT_TYPE_STRING) + }, + [OPT_LAYOUTS] = { + LONG_OPT("layouts"), + SINGLE_OPT('l'), + ARG_TYPE(OPT_TYPE_STRING) + }, + [OPT_DEST] = { + LONG_OPT("dest"), + SINGLE_OPT('d'), + ARG_TYPE(OPT_TYPE_STRING) + }, + [OPT_ROOT] = { + LONG_OPT("root"), + SINGLE_OPT('r'), + ARG_TYPE(OPT_TYPE_STRING) + }, + [OPT_FAVICON] = { + LONG_OPT("favicon"), + SINGLE_OPT('f'), + ARG_TYPE(OPT_TYPE_FILE) + }, + [OPT_URL] = { + LONG_OPT("url"), + SINGLE_OPT('u'), + ARG_TYPE(OPT_TYPE_STRING) + }, + [OPT_IMG] = { + LONG_OPT("img"), + SINGLE_OPT('i'), + ARG_TYPE(OPT_TYPE_STRING) + } +}; + +#define usage(str, ...) fprintf(stdout, str "\n", ##__VA_ARGS__) +static void help(void) +{ + usage("Usage: " SDOWN_NAME " -[vhtsldrfui]"); + usage("Generate an HTML static site for markdown sources."); + usage ("If used with no options, " SDOWN_NAME " will look for directory in the current path."); + usage(""); + usage("Options:"); + usage(" -v, --version Print software version"); + usage(" -h, --help Show this message"); + usage(" -t, --title=TITLE Title to be used in the final site"); + usage(" -s, --src=DIR Markdown sources directory"); + usage(" -l, --layouts=DIR Layouts directory"); + usage(" -d, --dest=DIR Destination directory"); + usage(" -r, --root=ROOT Root URL of the website"); + usage(" -f, --favicon=FILE Favicon to use"); + usage(" -u, --url=URL Url of the website"); + usage(" -i, --img=DIR Images directory"); + usage(""); + usage("Default required tree:"); + usage(" ."); + usage(" ├── img"); + usage(" ├── layouts"); + usage(" │ ├── footer.html"); + usage(" │ ├── header.html"); + usage(" │ └── intro.html"); + usage(" ├── markdown"); + usage(" └── styles"); + usage(""); + usage("All posts passed to " SDOWN_NAME " (either in the default 'markdown'"); + usage("directory or through the --src option), *must* contain the following"); + usage("header:"); + usage(" ---"); + usage(" title: Article title"); + usage(" summary: Article summary"); + usage(" route: route-of-the-article"); + usage(" ---"); + usage("Optionnal headers can be passed to:"); + usage("- 'date': Date of the article, in YYYY-MM-DD format. If this option"); + usage(" is not here, shayla will look for the last modified timestamp from"); + usage(" the filesystem in order to establish a chronology."); + usage("- 'list': Boolean option, whether or not the article should be listed"); + usage(" on the index.html. Default is true."); + usage(""); + usage("Layouts can be used to define header / footer content in the final"); + usage("website. " SDOWN_NAME " will look for them under the layouts directory"); + usage("(either in the default 'layouts' directory or through the --layouts"); + usage("option), and will expect HTML files named 'footer.html', 'header.html'"); + usage("and 'footer.html'. Any other file will be ignored."); + usage(""); + usage("This program is free software: you can redistribute it and/or modify"); + usage("it under the terms of the GNU General Public License as published by"); + usage("the Free Software Foundation, either version 3 of the License, or"); + usage("(at your option) any later version."); + usage(""); + usage("Sources are available at <https://git.ne02ptzero.me/shayla/>"); + usage("Louis Solofrizzo <louis at ne02ptzero dot me>"); +} + +static void version(void) +{ + usage(SDOWN_NAME " " SDOWN_MAJOR "." SDOWN_MINOR); + usage("License GPLv3+: GNU GPL version 3 or later " + "<https://gnu.org/licenses/gpl.html>."); + usage("This is free software: you are free to change and redistribute it."); + usage("There is NO WARRANTY, to the extent permitted by law."); + usage(""); + usage("Sources are available at <https://git.ne02ptzero.me/shayla/>"); + usage("Louis Solofrizzo <louis at ne02ptzero dot me>"); +} +#undef usage + +typedef struct { + size_t off; + const char *def; +} shayla_opt_t; + +static void shayla_options(shayla_t *ctx) +{ + static const shayla_opt_t addrs[] = { +#define shayla_offset(member) offsetof(shayla_t, member) + [OPT_TITLE] = { shayla_offset(title), SDOWN_NAME }, + [OPT_SRC] = { shayla_offset(posts_dir), "markdown" }, + [OPT_LAYOUTS] = { shayla_offset(layouts_dir), "layouts" }, + [OPT_DEST] = { shayla_offset(dest), "build" }, + [OPT_ROOT] = { shayla_offset(root), "/"}, + [OPT_FAVICON] = { shayla_offset(favicon), NULL }, + [OPT_URL] = { shayla_offset(url), NULL }, + [OPT_IMG] = { shayla_offset(img_dir), "img" } +#undef shayla_offset + }; + + STATIC_ARRAY_FOREACH_I(i, const shayla_opt_t *opt, addrs) + { + uintptr_t ptr; + char **str; + + if (opt->off == 0) + continue; + + ptr = (uintptr_t)ctx + (uintptr_t)opt->off; + str = (char **)ptr; + + if (IS_ARG_HERE(options, i)) + *str = strdup(GET_STR_ARG(options, i)); + else if (opt->def != NULL) + *str = strdup(opt->def); + } +} + +int main(int ac, const char **av) { shayla_t ctx = { 0 }; + int ret = 1; shayla_init(&ctx); - list_dir(&ctx.posts, ".", ".md"); + if (!parse_args(ac, av, options, COUNT_OF(options), help)) + return 1; + + if (IS_ARG_HERE(options, OPT_VERSION)) + { + version(); + ret = 0; + goto end; + } + + shayla_options(&ctx); + + list_dir(&ctx.posts, ctx.posts_dir, ".md"); + ret = 0; +end: + free_args(options, COUNT_OF(options)); shayla_dtr(&ctx); - return 0; + return ret; } diff --git a/shayla.h b/shayla.h @@ -53,6 +53,14 @@ static inline void shayla_init(shayla_t *ptr) static inline void shayla_dtr(shayla_t *ptr) { file_list_free(&ptr->posts); + free((char *)ptr->posts_dir); + free((char *)ptr->layouts_dir); + free((char *)ptr->img_dir); + free((char *)ptr->title); + free((char *)ptr->dest); + free((char *)ptr->root); + free((char *)ptr->url); + free((char *)ptr->favicon); } #endif /* SHAYLA_H */ diff --git a/str.h b/str.h @@ -16,6 +16,15 @@ #ifndef STR_H # define STR_H +# include <stdlib.h> +# include <string.h> +# include <errno.h> +# include <assert.h> +# include <stdio.h> + +# include <utils.h> +# define BASE_TEN 10 + /*! * \brief Check if a string ends with another string * @@ -29,4 +38,120 @@ static inline bool str_ends_with(const char *str, const char *ext) return strcmp(str + strlen(str) - strlen(ext), ext) == 0; } +/*! + * \brief Convert a string to a 4 bytes integer + * + * \param[in] in Input string + * \param[out] out Output integer + * \param[in] base Numeric base + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_int32(const char *in, int *out, int base) +{ + char *end; + + errno = 0; + + *out = strtol(in, &end, base); + + if (end == in || strlen(end) > 0 || errno) + return false; + return true; +} + +/*! + * \brief Convert a string to a 1 byte integer + * + * \param[in] in Input string + * \param[out] out Output integer + * \param[in] base Numeric base + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_uint8(const char *in, unsigned char *out, int base) +{ + char *end; + int tmp; + + errno = 0; + + tmp = strtol(in, &end, base); + + if (end == in || strlen(end) > 0 || errno) + return false; + if (tmp < 0 || tmp > 255) + return false; + + *out = (unsigned char)tmp; + return true; +} + +/*! + * \brief Convert a string to a floating point + * + * \param[in] in Input string + * \param[out] out Output floating integer + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_float(const char *in, float *out) +{ + char *end; + + errno = 0; + + *out = strtof(in, &end); + + if (end == in || strlen(end) > 0 || errno) + return false; + return true; +} + +/*! + * \brief Convert a string to a boolean + * + * \param[in] in Input string + * \param[out] out Output boolean + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_bool(const char *in, bool *out) +{ + if (strcmp(in, "true") == 0) + { + *out = true; + return true; + } + else if (strcmp(in, "false") == 0) + { + *out = false; + return true; + } + + return false; +} + +/*! + * \brief Convert a string to a percentage + * + * \param[in] in Input string + * \param[out] out Output percentage + * + * \return true on success, false on failure + */ +__INLINE_FUNCTION __NON_NULL_ALL +static inline bool str_to_percentage(const char *in, unsigned char *out) +{ + if (!str_to_uint8(in, out, BASE_TEN) || *out > 100) + return false; + + return true; +} + + #endif /* STR_H */ diff --git a/utils.h b/utils.h @@ -32,6 +32,7 @@ # define STR_NULL_OR_EMPTY(str) (str == NULL || (str != NULL && *str == '\0')) # define FREE(ptr) free(ptr); ptr = NULL; # define UNUSED_ARG(arg) (void)arg +# define STRLEN(str) str == NULL ? 0 : strlen(str) /* Magic things */ # undef offsetof