help-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Bash ini builtin implementation feedback


From: Jesse Hathaway
Subject: Re: Bash ini builtin implementation feedback
Date: Mon, 12 Jul 2021 11:01:49 -0500

Thanks folks for the help, if anyone is interested in reading the full post
I just posted it on lobste.rs:

    https://lobste.rs/s/6wjuk4/writing_bash_builtin_c_parse_ini_configs

On Thu, Jul 1, 2021 at 9:15 AM Jesse Hathaway <jesse@mbuki-mvuki.org> wrote:
>
> I thought it would be fun to write a blog post on Bash builtins. I have never
> written a builtin before, but I thought one that parses ini config files would
> provide a good example. I have a working implementation using the inih
> library[1]. I would love any feedback on the code as I have written very 
> little
> C during my career.
>
> The full source code is below, the Makefile can be found in its repo,
> https://github.com/lollipopman/bash-ini-builtin-blog-post.
>
> Yours kindly, Jesse Hathaway
>
> [1]: https://github.com/benhoyt/inih
>
> #include "inih/ini.h"
> #include "loadables.h"
> #include <errno.h>
>
> char *ini_doc[] = {
>     "Read an ini config from stdin input into a set of associative arrays.",
>     "",
>     "Reads an ini config from stdin input into a set of associative arrays.",
>     "The sections of the ini config are added to an associative array",
>     "specified by the `-a TOC` argument. The keys and values are then added 
> to",
>     "associate arrays prefixed by the `TOC` name and suffixed by their ini",
>     "section name, `<TOC>_<INI_SECTION_NAME>`.",
>     "",
>     "Example:",
>     "",
>     "  Input input.ini:",
>     "    [sec1]",
>     "    foo = bar",
>     "",
>     "    [sec2]",
>     "    biz = baz",
>     "",
>     "  Result:",
>     "    $ ini -a conf < input.ini",
>     "    $ declare -p conf",
>     "    declare -A conf=([sec1]=\"true\" [sec2]=\"true\" )",
>     "    $ declare -p conf_sec1",
>     "    declare -A conf_sec1=([foo]=\"bar\" )",
>     "    $ declare -p conf_sec2",
>     "    declare -A conf_sec2=([biz]=\"baz\" )",
>     "",
>     "If the `-u FD` argument is passed the ini config is read from the `FD`",
>     "file descriptor rather than from stdin.",
>     (char *)NULL};
>
> /* User data for inih callback handler */
> typedef struct {
>   char *toc_var_name;
> } ini_conf;
>
> /* This is the inih handler called for every new section and for every name 
> and
>  * value in a section. This function creates and populates our associative
>  * arrays in Bash. Both for the TOC array as well as for the individual 
> section
>  * arrays, <TOC>_<INI_SECTION_NAME> */
> static int handler(void *user, const char *section, const char *name,
>                    const char *value) {
>   ini_conf *conf = (ini_conf *)user;
>   char *toc_var_name = conf->toc_var_name;
>   int vflags;
>   if (!name && !value) {
>     vflags = 0;
>     vflags |= (1 << 0);
>     vflags |= (1 << 1);
>     SHELL_VAR *toc_var = find_or_make_array_variable(toc_var_name, vflags);
>     if (!toc_var) {
>       builtin_error("Could not make %s", toc_var_name);
>       return 0;
>     }
>     bind_assoc_variable(toc_var, toc_var_name, strdup(section), "true", 0);
>     return 1;
>   }
>   if (!name) {
>     builtin_error("Malformed ini, name is NULL!");
>     return 0;
>   }
>   if (!value) {
>     builtin_error("Malformed ini, value is NULL!");
>     return 0;
>   }
>   /* Create <TOC>_<INI_SECTION_NAME> */
>   char *sep = "_";
>   size_t sec_size = strlen(toc_var_name) + strlen(section) + strlen(sep) +
>                     1; // +1 for the null-terminator
>   char *sec_var_name = malloc(sec_size);
>   char *sec_end = sec_var_name + sec_size - 1;
>   char *p = memccpy(sec_var_name, toc_var_name, '\0', sec_size);
>   if (!p) {
>     builtin_error("Unable to create section name");
>     return 0;
>   }
>   p = memccpy(p - 1, sep, '\0', sec_end - p + 2);
>   if (!p) {
>     builtin_error("Unable to create section name");
>     return 0;
>   }
>   p = memccpy(p - 1, section, '\0', sec_end - p + 2);
>   if (!p) {
>     builtin_error("Unable to create section name");
>     return 0;
>   }
>   if (!legal_identifier(sec_var_name)) {
>     sh_invalidid(sec_var_name);
>     free(sec_var_name);
>     return 0;
>   }
>   vflags = 0;
>   vflags |= (1 << 0);
>   vflags |= (1 << 1);
>   SHELL_VAR *sec_var = find_or_make_array_variable(sec_var_name, vflags);
>   if (!sec_var) {
>     builtin_error("Could not make %s", sec_var_name);
>     free(sec_var_name);
>     return 0;
>   }
>   bind_assoc_variable(sec_var, sec_var_name, strdup(name), strdup(value), 0);
>   free(sec_var_name);
>   return 1;
> }
>
> /* This is essentially the main function for the ini builtin, it does arg
>  * parsing and then calls the inih function to parse the provided ini FD */
> int ini_builtin(list) WORD_LIST *list;
> {
>   intmax_t intval;
>   int opt, code;
>   int fd = 0;
>   char *toc_var_name = NULL;
>   reset_internal_getopt();
>   while ((opt = internal_getopt(list, "a:u:")) != -1) {
>     switch (opt) {
>     case 'a':
>       toc_var_name = list_optarg;
>       break;
>     case 'u':
>       code = legal_number(list_optarg, &intval);
>       if (code == 0 || intval < 0 || intval != (int)intval) {
>         builtin_error("%s: invalid file descriptor specification", 
> list_optarg);
>         return (EXECUTION_FAILURE);
>       }
>       fd = (int)intval;
>       if (sh_validfd(fd) == 0) {
>         builtin_error("%d: invalid file descriptor: %s", fd, strerror(errno));
>         return (EXECUTION_FAILURE);
>       }
>       break;
>     case GETOPT_HELP:
>       builtin_help();
>       return (EX_USAGE);
>     default:
>       builtin_usage();
>       return (EX_USAGE);
>     }
>   }
>   if (!toc_var_name) {
>     builtin_usage();
>     return (EX_USAGE);
>   }
>   ini_conf conf = {};
>   conf.toc_var_name = strdup(toc_var_name);
>   FILE *file = fdopen(fd, "r");
>   if (ini_parse_file(file, handler, &conf) < 0) {
>     builtin_error("Unable to read from fd: %d", fd);
>     return (EXECUTION_FAILURE);
>   }
>   return (EXECUTION_SUCCESS);
> }
>
> /* Provides Bash with information about the builtin */
> struct builtin ini_struct = {
>     "ini",                /* Builtin name */
>     ini_builtin,          /* Function implementing the builtin */
>     BUILTIN_ENABLED,      /* Initial flags for builtin */
>     ini_doc,              /* Array of long documentation strings. */
>     "ini -a TOC [-u FD]", /* Usage synopsis; becomes short_doc */
>     0                     /* Reserved for internal use */
> };



reply via email to

[Prev in Thread] Current Thread [Next in Thread]