/**************************************************************************** * tools/kconfig2html.c * * SPDX-License-Identifier: Apache-2.0 * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #undef USE_JQUERY #define LINE_SIZE 1024 #define SCRATCH_SIZE 2048 #define MAX_DEPENDENCIES 128 #define MAX_LEVELS 100 #define MAX_SELECT 64 #define MAX_DEFAULTS 196 #define TAB_SIZE 4 #define VAR_SIZE 80 #define HTML_VAR_SIZE (2*VAR_SIZE + 64) #define BODYFILE_NAME ".k2h-body.dat" #define APNDXFILE_NAME ".k2h-apndx.dat" /**************************************************************************** * Private Types ****************************************************************************/ enum token_type_e { TOKEN_NONE = 0, TOKEN_NOTRESERVED, TOKEN_COMMENT, TOKEN_CONFIG, TOKEN_MENUCONFIG, TOKEN_BOOL, TOKEN_TRISTATE, TOKEN_INT, TOKEN_HEX, TOKEN_STRING, TOKEN_DEFAULT, TOKEN_RANGE, TOKEN_SELECT, TOKEN_DEPENDS, TOKEN_ON, TOKEN_OPTION, TOKEN_HELP, TOKEN_MAINMENU, TOKEN_MENU, TOKEN_ENDMENU, TOKEN_CHOICE, TOKEN_ENDCHOICE, TOKEN_PROMPT, TOKEN_IF, TOKEN_ENDIF, TOKEN_SOURCE }; enum config_type_e { VALUE_NONE = 0, VALUE_INT, VALUE_HEX, VALUE_BOOL, VALUE_TRISTATE, VALUE_STRING }; enum error_e { ERROR_UNRECOGNIZED_OPTION = 1, ERROR_MISSING_OPTION_ARGUMENT, ERROR_UNEXPECTED_OPTION, ERROR_TOO_MANY_ARGUMENTS, ERROR_OUTFILE_OPEN_FAILURE, ERROR_BODYFILE_OPEN_FAILURE, ERROR_APNDXFILE_OPEN_FAILURE, ERROR_KCONFIG_OPEN_FAILURE, ERROR_APPENDFILE_OPEN_FAILURE, ERROR_MENU_LEVEL_UNDERRUN, ERROR_TOO_MANY_DEFAULTS, ERROR_MISSING_DEFAULT_VALUE, ERROR_GARBAGE_AFTER_DEFAULT, ERROR_DEFAULT_UNDERFLOW, ERROR_TOO_MANY_SELECT, ERROR_TOO_MANY_DEPENDENCIES, ERROR_DEPENDENCIES_UNDERFLOW, ERRROR_MISSING_ON_AFTER_DEPENDS, ERRROR_ON_AFTER_DEPENDS, ERROR_NESTING_TOO_DEEP, ERROR_NESTING_UNDERFLOW }; typedef void (*output_t)(const char *fmt, ...); struct reserved_s { enum token_type_e ttype; const char *tname; }; struct default_item_s { char *d_default; char *d_dependency; }; struct default_s { int d_nitems; struct default_item_s d_item[MAX_DEFAULTS]; }; struct select_s { int s_nvar; char *s_varname[MAX_SELECT]; }; struct config_s { enum config_type_e c_type; char *c_name; char *c_desc; char *c_lower; char *c_upper; struct default_s c_default; struct select_s c_select; int c_ndependencies; }; struct choice_s { char *c_prompt; struct default_s c_default; int c_ndependencies; }; struct menu_s { char *m_name; int m_ndependencies; }; /**************************************************************************** * Private Data ****************************************************************************/ static char g_line[LINE_SIZE + 1]; static char g_scratch[SCRATCH_SIZE + 1]; static FILE *g_outfile; static FILE *g_bodyfile; static FILE *g_apndxfile; static char *g_lnptr; static bool g_debug; static bool g_preread; static const char *g_kconfigroot; static const char *g_appsdir; static int g_paranum[MAX_LEVELS]; static int g_level; static char *g_dependencies[MAX_DEPENDENCIES]; static int g_ndependencies; static int g_inchoice; static int g_menu_number; static int g_choice_number; static int g_toggle_number; static const char g_delimiters[] = " ,"; static struct reserved_s g_reserved[] = { {TOKEN_COMMENT, "comment"}, {TOKEN_CONFIG, "config"}, {TOKEN_MENUCONFIG, "menuconfig"}, {TOKEN_BOOL, "bool"}, {TOKEN_TRISTATE, "tristate"}, {TOKEN_INT, "int"}, {TOKEN_HEX, "hex"}, {TOKEN_STRING, "string"}, {TOKEN_DEFAULT, "default"}, {TOKEN_RANGE, "range"}, {TOKEN_SELECT, "select"}, {TOKEN_DEPENDS, "depends"}, {TOKEN_ON, "on"}, {TOKEN_OPTION, "option"}, {TOKEN_HELP, "help"}, {TOKEN_HELP, "---help---"}, {TOKEN_MAINMENU, "mainmenu"}, {TOKEN_MENU, "menu"}, {TOKEN_ENDMENU, "endmenu"}, {TOKEN_CHOICE, "choice"}, {TOKEN_ENDCHOICE, "endchoice"}, {TOKEN_PROMPT, "prompt"}, {TOKEN_SOURCE, "source"}, {TOKEN_IF, "if"}, {TOKEN_ENDIF, "endif"}, {TOKEN_NOTRESERVED, NULL} }; /**************************************************************************** * Public Data ****************************************************************************/ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: debug * * Description: * Debug output (conditional) * ****************************************************************************/ static void debug(const char *fmt, ...) { va_list ap; if (g_debug) { va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } } /**************************************************************************** * Name: error * * Description: * Error output (unconditional) * ****************************************************************************/ static void error(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } /**************************************************************************** * Name: output * * Description: * Output to the final HTML file * ****************************************************************************/ static void output(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(g_outfile, fmt, ap); va_end(ap); } /**************************************************************************** * Name: body * * Description: * HTML body output to a temporary file. * ****************************************************************************/ static void body(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(g_bodyfile, fmt, ap); va_end(ap); } /**************************************************************************** * Name: appendix * * Description: * Output to a appendix file. * ****************************************************************************/ static void appendix(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(g_apndxfile, fmt, ap); va_end(ap); } /**************************************************************************** * Name: append_file * * Description: * Append the specified file to the output file and remove it. * ****************************************************************************/ static void append_file(const char *filename) { FILE *stream; int ch; /* Open the file for reading */ stream = fopen(filename, "r"); if (!stream) { error("open %s failed: %s\n", filename, strerror(errno)); exit(ERROR_APPENDFILE_OPEN_FAILURE); } /* Copy the file to the output */ while ((ch = getc(stream)) != EOF) { putc(ch, g_outfile); } /* Close and remove the file */ fclose(stream); unlink(filename); } /**************************************************************************** * Name: show_usage * * Description: * Show usage of this program and exit with the specified error code * ****************************************************************************/ static void show_usage(const char *progname, int exitcode) { error("USAGE: " "%s [-d] [-a ] {-o ] []\n", progname); error(" %s [-h]\n\n", progname); error("Where:\n\n"); error("\t-a : Select relative path to the apps/ directory." " Theis path is relative\n"); error("\t to the . Default: ../apps\n"); error("\t-o : Send output to . " "Default: Output goes to stdout\n"); error("\t-d : Enable debug output\n"); error("\t-h : Prints this message and exits\n"); error("\t " "is the directory containing the root Kconfig file.\n"); error("\t Default : .\n"); exit(exitcode); } /**************************************************************************** * Name: skip_space * * Description: * Skip over any spaces * ****************************************************************************/ static char *skip_space(char *ptr) { while (*ptr && isspace((int)*ptr)) ptr++; return ptr; } /**************************************************************************** * Name: dequote * * Description: * Remove quotation marks from a string. * ****************************************************************************/ static char *dequote(char *ptr) { int len; /* Check if there is a trailing quote */ len = strlen(ptr); if (ptr[len - 1] == '"') { /* Yes... replace it with a terminator */ ptr[len - 1] = '\0'; len--; } /* Is there a leading quote? */ if (ptr[0] == '"') { /* Yes.. skip over the leading quote */ ptr++; len--; } /* Handle the case where nothing is left after dequoting */ if (len <= 0) { ptr = NULL; } return ptr; } /**************************************************************************** * Name: htmlize_character * * Description: * Transfer and HTML-ize a character. Convert characters: * * " " quotation mark * ' ' apostrophe * & & ampersand * < < less-than * > > greater-than * ****************************************************************************/ static int htmlize_character(char *dest, char ch) { const char *str; /* Transfer the character from into the destination buffer, perform the * conversion only if the character is one of the special characters. */ str = NULL; switch (ch) { case '"': str = """; break; case '\'': str = "'"; break; case '&': str = "&"; break; case '<': str = "<"; break; case '>': str = ">"; break; default: *dest++ = ch; *dest = '\0'; return 1; } /* Transfer a string */ *dest = '\0'; strcat(dest, str); return strlen(str); } /**************************************************************************** * Name: htmlize_text * * Description: * HTML-ize a free-text string. This function performs the conversions of * in htmlize_character() for a text string. * ****************************************************************************/ static char *htmlize_text(const char *src) { char *dest = g_scratch; /* We may get here with the source pointer equal to NULL (or a pointer to * a NUL string). Return the * disfavor. */ if (!src || !*src) { return NULL; } /* Transfer each character from the source string into the scratch buffer */ for (; *src; src++) { /* Expand characters as necessary. NOTE: There is no check if the * HTML-expanded text overflows the g_scratch[] buffer. If you see * errors, be suspicious. */ dest += htmlize_character(dest, *src); } return g_scratch; } /**************************************************************************** * Name: htmlize_expression * * Description: * HTML-ize an expression of configuration variables. This function * performs the same conversions as in htmlize_character(), but also * expands and adds hyper links for configuration variables. * ****************************************************************************/ static char *htmlize_expression(const char *src) { char varname[VAR_SIZE + 1]; char htmlvar[HTML_VAR_SIZE + 1]; char *dest = g_scratch; char ch = '\0'; char lastc; /* We may get here with the source pointer equal to NULL. Return the * disfavor. */ if (!src) { return NULL; } /* Transfer each character from the source string into the scratch buffer */ dest = g_scratch; *dest = '\0'; while (*src) { /* Remember the last character and advance to the next character */ lastc = ch; ch = *src; /* Skip control characters and out-of-range 7-bit ASCII characters */ if (*src < 0x20 || *src > 0x7e) { src++; continue; } /* Output no more than one consecutive space character. This depends * on the fact that kconfig_line has replaces all of the forms of * whitespace with a space character. */ if (*src == ' ') { if (lastc != ' ') { *dest++ = *src; *dest = '\0'; } src++; continue; } /* Concatenate variable name strings. There strings probably begin * with a uppercase letter, but here all alphanumeric values (plus '_'_ * are concatenated. */ if (isalnum(((int)*src)) || *src == '_') { int namlen = 0; do { /* Don't overflow the tiny variable name buffer */ if (namlen >= VAR_SIZE) { error("Configuration variable name too long\n"); break; } /* Add the next character to the name */ varname[namlen] = *src++; namlen++; varname[namlen] = '\0'; } while (isalnum(((int)*src)) || *src == '_'); /* HTML-ize the name into our bigger, local scratch buffer */ snprintf(htmlvar, HTML_VAR_SIZE, "CONFIG_%s", varname, varname); /* Then transfer the string into the scratch buffer */ strcat(dest, htmlvar); dest += strlen(htmlvar); } /* All that remains are space and the punctuation characters */ else { /* Expand characters as necessary */ dest += htmlize_character(dest, *src); src++; } } return g_scratch; } /**************************************************************************** * Name: read_line * * Description: * Read a new line from the Kconfig file into the g_line[] buffer, using * the g_scratch buffer if necessary to concatenate lines that end with a * line continuation character (backslash). * ****************************************************************************/ static char *read_line(FILE *stream) { char *ptr; int len; g_lnptr = NULL; /* Read the next line */ g_line[LINE_SIZE] = '\0'; if (!fgets(g_line, LINE_SIZE, stream)) { return NULL; } /* Loop to handle continuation lines */ for (; ; ) { /* How long is the line so far? */ len = strlen(g_line); /* Remove any newline character at the end of the buffer */ if (g_line[len - 1] == '\n') { len--; g_line[len] = '\0'; } /* Does this continue on the next line? Note that this check * could erroneoulsy combine two lines if a comment line ends with * a line continuation... Don't do that! */ if (g_line[len - 1] != '\\') { /* No.. return now */ g_lnptr = g_line; return g_line; } /* Yes.. Replace the backslash with a space delimiter */ g_line[len - 1] = ' '; /* Read the next line into the scratch buffer */ g_scratch[SCRATCH_SIZE] = '\0'; if (!fgets(g_scratch, SCRATCH_SIZE, stream)) { return NULL; } /* Skip any leading whitespace and copy the rest of the next line * into the line buffer. Note that the leading white space is * replaced with a single character to serve as a delimiter. */ ptr = skip_space(g_scratch); strncpy(&g_line[len], ptr, LINE_SIZE - len); } } /**************************************************************************** * Name: kconfig_line * * Description: * Read a new line, skipping over leading white space and ignore lines * that contain only comments. * ****************************************************************************/ static char *kconfig_line(FILE *stream) { char *ptr; for (; ; ) { /* Read the next line from the Kconfig file */ /* Is there already valid data in the line buffer? This can happen * while parsing help text and we read one line too far. */ if (!g_preread) { /* Read the next line */ if (!read_line(stream)) { return NULL; } } g_preread = false; /* Replace all whitespace characters with spaces to simplify parsing */ for (ptr = g_line; *ptr; ptr++) { if (isspace(((int)*ptr))) { *ptr = ' '; } } /* Skip any leading whitespace. Ignore empty lines and lines that * contain only comments. */ ptr = skip_space(g_line); if (*ptr && *ptr != '#' && *ptr != '\n') { g_lnptr = ptr; return ptr; } } } /**************************************************************************** * Name: tokenize * * Description: * Check if this string corresponds to a string in the reserved word table. * ****************************************************************************/ static enum token_type_e tokenize(const char *token) { struct reserved_s *ptr; for (ptr = g_reserved; ptr->tname; ptr++) { if (strcmp(token, ptr->tname) == 0) { break; } } return ptr->ttype; } /**************************************************************************** * Name: findchar * * Description: * Find a character in a string. This differs from strchr() because it * skips over quoted characters. For example, if you are searching for * '"', encountering '"' will terminate the search, but "\"" will not. * ****************************************************************************/ static char *findchar(char *ptr, char ch) { bool escaped = false; /* Search for the leading quotation marked */ for (; *ptr; ptr++) { if (escaped) { /* Skip over this character and reset the escaped flag */ escaped = false; } else if (*ptr == '\\') { /* Set the escaped flag to skip over the next character */ escaped = true; } else if (*ptr == ch) { /* We have found the (unescaped) character we are looking for */ return ptr; } } return NULL; } /**************************************************************************** * Name: get_token * * Description: * Get the next delimited token from the line buffer. * ****************************************************************************/ static char *get_token(void) { char *pbegin; char *pend = NULL; /* The position to begin/resume parsing is in g_lnptr. */ if (g_lnptr && *g_lnptr) { pbegin = g_lnptr; } else { return NULL; } /* Find the beginning of the next token */ for (; *pbegin && strchr(g_delimiters, *pbegin) != NULL; pbegin++); /* If we are at the end of the string with nothing * but delimiters found, then return NULL. */ if (!*pbegin) { g_lnptr = pbegin; return NULL; } /* Get if the token is a quoted string */ if (*pbegin == '"') { /* Search for the trailing quotation mark */ pend = findchar(pbegin + 1, '"'); /* Did we find the trailing quotation mark */ if (pend) { /* Yes.. skip over it */ pend++; } } else { /* Find the end of the token */ for (pend = pbegin + 1; *pend && strchr(g_delimiters, *pend) == NULL; pend++); } /* pend either points to the end of the string or to * the first delimiter after the string. */ if (*pend) { /* Turn the delimiter into a null terminator */ *pend++ = '\0'; } /* Save the pointer where we left off and return the * beginning of the token. */ g_lnptr = pend; return pbegin; } /**************************************************************************** * Name: get_html_string * * Description: * Extract a quoted string from the line buffer, dequote it, and make it * HTML ready. * ****************************************************************************/ static char *get_html_string(void) { char *pbegin; char *pend; int len; /* Search for the leading quotation mark in the line buffer */ pbegin = strchr(g_lnptr, '"'); if (pbegin) { /* We found the leading quote. Skip over the leading quote */ pbegin++; } else { /* The string is unquoted. The beginning of the string is here, * skipping over any leading whitespace. */ pbegin = skip_space(g_lnptr); } /* Search for the trailing quotation mark. If there is none, then * the string goes to the end of the line. */ pend = findchar(pbegin, '"'); if (pend) { /* Replace the final quote with a NUL. g_lnptr is set to * the next valid character after the terminating quote. */ *pend = '\0'; g_lnptr = pend + 1; } else { /* Get the length of the string. Return NULL if all that is * left on the line is a NUL string. */ len = strlen(pbegin); if (len < 1) { return NULL; } /* Use the rest of the line. g_lnptr is set to point at the * terminating NUL. */ pend = pbegin + len; g_lnptr = pend; } return htmlize_text(pbegin); } /**************************************************************************** * Name: push_dependency * * Description: * Add the new dependency to the current list of dependencies. * ****************************************************************************/ static void push_dependency(const char *dependency) { int ndx = g_ndependencies; if (ndx >= MAX_DEPENDENCIES) { error("Too many dependencies, aborting\n"); exit(ERROR_TOO_MANY_DEPENDENCIES); } g_dependencies[ndx] = strdup(dependency); g_ndependencies = ndx + 1; } /**************************************************************************** * Name: pop_dependency * * Description: * Remove the last, pushed dependency * ****************************************************************************/ static void pop_dependency(void) { int ndx = g_ndependencies - 1; if (ndx < 0) { error("Dependency underflow, aborting\n"); exit(ERROR_DEPENDENCIES_UNDERFLOW); } if (g_dependencies[ndx]) { free(g_dependencies[ndx]); g_dependencies[ndx] = NULL; } g_ndependencies = ndx; } /**************************************************************************** * Name: incr_level * * Description: * Increment the paragraph numbering level * ****************************************************************************/ static void incr_level(void) { int ndx = g_level; if (ndx >= MAX_LEVELS) { error("Nesting level is too deep, aborting\n"); exit(ERROR_NESTING_TOO_DEEP); } g_paranum[ndx] = 1; g_level = ndx + 1; } /**************************************************************************** * Name: decr_level * * Description: * Decrease the paragraph numbering level. * ****************************************************************************/ static void decr_level(void) { int ndx = g_level; g_paranum[ndx] = '\0'; ndx--; if (ndx < 0) { error("Nesting level underflow, aborting\n"); exit(ERROR_NESTING_UNDERFLOW); } g_level = ndx; } /**************************************************************************** * Name: incr_paranum * * Description: * Increment the paragraph number at this level * ****************************************************************************/ static void incr_paranum(void) { int ndx = g_level - 1; if (ndx < 0) { error("Nesting level underflow, aborting\n"); exit(ERROR_NESTING_UNDERFLOW); } g_paranum[ndx]++; } /**************************************************************************** * Name: get_paranum * * Description: * Return a string for this paragraph (uses g_scratch[]). * ****************************************************************************/ static const char *get_paranum(void) { char buffer[16]; int i; g_scratch[0] = '\0'; for (i = 0; i < g_level; i++) { if (i > 0) { strcat(g_scratch, "."); } snprintf(buffer, 16, "%d", g_paranum[i]); strcat(g_scratch, buffer); } return g_scratch; } /**************************************************************************** * Name: type2str * * Description: * Return a string given a member of the configuration variable type * enumeration. * ****************************************************************************/ static const char *type2str(enum config_type_e valtype) { switch (valtype) { case VALUE_BOOL: return "Boolean"; case VALUE_TRISTATE: return "Tristate"; case VALUE_INT: return "Integer"; case VALUE_HEX: return "Hexadecimal"; case VALUE_STRING: return "String"; default: break; } return "Unknown"; } /**************************************************************************** * Name: process_help * * Description: * Read and generate HTML for the help text that is expected after the * configuration configuration variable description. * ****************************************************************************/ static inline void process_help(FILE *stream, output_t outfunc) { char *ptr; int help_indent = 0; int indent; bool blank; bool done; bool newpara; bool preformatted; /* Read each comment line */ newpara = true; preformatted = false; for (; ; ) { /* Read the next line of comment text */ if (!read_line(stream)) { break; } /* What is the indentation level? The first help line sets the * indentation level. The first line encounter with lower * indentation terminates the help. */ ptr = g_line; indent = 0; blank = false; done = false; while (!done) { int ch = (int)*ptr; switch (ch) { case ' ': indent++; ptr++; break; case '\t': indent += TAB_SIZE; ptr++; break; case '#': #if 0 blank = true; #endif done = true; break; case '\n': case '\0': blank = true; done = true; break; default: done = true; break; } } /* Blank lines are a special case */ if (blank) { /* Avoid putting an empty paragraph at the end of the help */ if (preformatted) { outfunc("\n"); preformatted = false; } if (!newpara) { outfunc("

\n"); newpara = true; } continue; } /* Check the indentation level */ if (indent == 0) { g_preread = true; break; } else if (!help_indent) { help_indent = indent; } else if (indent < help_indent) { g_preread = true; break; } /* Add the next line of help text */ if (newpara) { outfunc("

\n"); newpara = false; } /* Lines that are indented at greater levels are assumed to be * pre-formatted text. This is not part of the Kconfig language but * rather simply a NuttX Kconfig convention. */ if (indent > help_indent) { if (!preformatted) { outfunc("

    \n");
                  newpara = false;
                  preformatted = true;
                }
    
              outfunc("%s\n", htmlize_text(ptr));
            }
          else
            {
              if (preformatted)
                {
                  outfunc("
\n"); preformatted = false; } outfunc(" %s", htmlize_text(ptr)); } } if (!newpara) { outfunc("\n

\n"); } if (preformatted) { outfunc("\n"); } } /**************************************************************************** * Name: process_default * * Description: * Read and parse the Kconfig default statement. * ****************************************************************************/ static void process_default(FILE *stream, struct default_s *defp) { enum token_type_e tokid; char *token; int ndx; /* Check if we have space for another default value */ ndx = defp->d_nitems; if (ndx >= MAX_DEFAULTS) { error("Too many default values\n"); exit(ERROR_TOO_MANY_DEFAULTS); } /* Get the next token which will be the value of the default */ token = get_token(); if (!token) { error("Missing default value\n"); exit(ERROR_MISSING_DEFAULT_VALUE); } defp->d_item[ndx].d_default = strdup(token); defp->d_item[ndx].d_dependency = NULL; /* Check if the default value is followed by "depends on" */ token = get_token(); if (token) { /* Yes.. something follows the default value. */ tokid = tokenize(token); if (tokid != TOKEN_IF) { error("Unrecognized garbage after default value\n"); exit(ERROR_GARBAGE_AFTER_DEFAULT); } /* The rest of the line is the dependency */ defp->d_item[ndx].d_dependency = strdup(g_lnptr); } /* Update the number of defaults we have encountered in this block */ defp->d_nitems++; } /**************************************************************************** * Name: print_default * * Description: * Output and the list of defaults to the HTML body file. * ****************************************************************************/ static void print_default(struct default_s *defp, output_t outfunc) { struct default_item_s *item; int i; /* Check if there are any default value */ if (defp->d_nitems > 0) { /* Yes, output the defaults differently if there is only one */ if (defp->d_nitems == 1) { /* Output the Default */ item = &defp->d_item[0]; outfunc("
  • \n"); outfunc(" Default: %s\n", item->d_default); /* Output the dependency */ if (item->d_dependency) { outfunc("

    \n"); outfunc(" Dependency:\n"); outfunc(" %s\n", htmlize_expression(item->d_dependency)); outfunc("

    \n"); } outfunc("
  • \n"); } else { /* Output a sub-list of defaults. */ outfunc("
  • \n"); outfunc(" Default Values:\n"); outfunc("
      \n"); for (i = 0; i < defp->d_nitems; i++) { /* Output the Default */ item = &defp->d_item[i]; outfunc("
    • \n"); outfunc(" Default: %s\n", item->d_default); /* Output the dependency */ if (item->d_dependency) { outfunc("

      \n"); outfunc(" Dependency:\n"); outfunc(" %s\n", htmlize_expression(item->d_dependency)); outfunc("

      \n"); } } outfunc("
    \n"); outfunc("
  • \n"); } } } /**************************************************************************** * Name: free_default * * Description: * Output and the list of defaults to the HTML body file. * ****************************************************************************/ static void free_default(struct default_s *defp) { struct default_item_s *item; int i; /* Free strings for each default */ for (i = 0; i < defp->d_nitems; i++) { /* Free the default value string */ item = &defp->d_item[i]; free(item->d_default); /* Free any dependency on the default */ if (item->d_dependency) { free(item->d_dependency); } } } /**************************************************************************** * Name: process_dependson * * Description: * Parse a "depends on" dependency and add the new dependency to the * stack of dependencies. * ****************************************************************************/ static void process_dependson(void) { char *value = get_token(); if (strcmp(value, "on") != 0) { error("Expected \"on\" after \"depends\"\n"); exit(ERRROR_ON_AFTER_DEPENDS); } push_dependency(htmlize_expression(g_lnptr)); } /**************************************************************************** * Name: print_dependencies * * Description: * Output the current stack of dependencies * ****************************************************************************/ static void print_dependencies(output_t outfunc) { int i; if (g_ndependencies > 0) { outfunc("
  • Dependencies: %s", g_dependencies[0]); for (i = 1; i < g_ndependencies; i++) { outfunc(", %s\n", g_dependencies[i]); } outfunc("
  • \n"); } } /**************************************************************************** * Name: free_dependencies * * Description: * Pop dependencies from the stack. * ****************************************************************************/ static void free_dependencies(int ndependencies) { int i; for (i = 0; i < ndependencies; i++) { pop_dependency(); } } /**************************************************************************** * Name: process_config * * Description: * Process one configuration variable paragraph * ****************************************************************************/ static inline char *process_config(FILE *stream, const char *varname, const char *kconfigdir, const char *kconfigname) { enum token_type_e tokid; struct config_s config; output_t outfunc; bool help; bool hidden; const char *paranum; char *token; char *ptr; int i; /* Get the configuration information */ memset(&config, 0, sizeof(struct config_s)); config.c_name = strdup(varname); /* Process each line in the configuration */ help = false; token = NULL; while ((ptr = kconfig_line(stream)) != NULL) { /* Process the first token on the Kconfig file line */ token = get_token(); if (token != NULL) { tokid = tokenize(token); switch (tokid) { case TOKEN_BOOL: case TOKEN_TRISTATE: { /* Save the type of the configuration variable */ config.c_type = tokid == TOKEN_BOOL ? VALUE_BOOL : VALUE_TRISTATE; /* Get the description following the type */ ptr = get_html_string(); if (ptr) { config.c_desc = strdup(ptr); } /* Indicate that the line has been consumed */ token = NULL; } break; case TOKEN_INT: { /* Save the type of the configuration variable */ config.c_type = VALUE_INT; /* Get the description following the type */ ptr = get_html_string(); if (ptr) { config.c_desc = strdup(ptr); } /* Indicate that the line has been consumed */ token = NULL; } break; case TOKEN_HEX: { /* Save the type of the configuration variable */ config.c_type = VALUE_HEX; /* Get the description following the type */ ptr = get_html_string(); if (ptr) { config.c_desc = strdup(ptr); } /* Indicate that the line has been consumed */ token = NULL; } break; case TOKEN_STRING: { /* Save the type of the configuration variable */ config.c_type = VALUE_STRING; /* Get the description following the type */ ptr = get_html_string(); if (ptr) { config.c_desc = strdup(ptr); } /* Indicate that the line has been consumed */ token = NULL; } break; case TOKEN_DEFAULT: { process_default(stream, &config.c_default); token = NULL; } break; case TOKEN_RANGE: { char *value = get_token(); if (value) { config.c_lower = strdup(value); value = get_token(); if (value) { config.c_upper = strdup(value); } } token = NULL; } break; case TOKEN_SELECT: { char *value; int ndx; ndx = config.c_select.s_nvar; if (ndx >= MAX_SELECT) { error("Too many 'select' lines\n"); exit(ERROR_TOO_MANY_SELECT); } value = get_token(); config.c_select.s_varname[ndx] = strdup(value); config.c_select.s_nvar = ndx + 1; token = NULL; } break; case TOKEN_DEPENDS: { process_dependson(); config.c_ndependencies++; token = NULL; } break; case TOKEN_OPTION: { token = NULL; /* Ignored */ } break; case TOKEN_HELP: { help = true; token = NULL; } break; default: { debug("CONFIG_%s: Terminating token: %s\n", config.c_name, token); } break; } /* Break out on the help token (or the first unhandled token) */ if (help || token != NULL) { break; } } } /* Is this an internal configuration variable with no description? * If so, send the output to the appendix file. */ hidden = (config.c_desc == NULL); outfunc = hidden ? appendix : body; hidden |= g_inchoice; /* Print the configuration variable name and the short description */ outfunc("

    ", config.c_name); /* If we are not in a choice block, than give the variable a paragraph * number and put it in the table of contents. */ if (!hidden) { paranum = get_paranum(); output("
  • %s CONFIG_%s", config.c_name, paranum, config.c_name); outfunc("%s ", paranum); incr_paranum(); } outfunc("CONFIG_%s", config.c_name); /* Output the short description in the paragraph title (if we have one) */ if (config.c_desc) { if (!hidden) { output(": %s", config.c_desc); } outfunc(": %s", config.c_desc); } outfunc("
  • \n"); if (!hidden) { output("\n"); } /* Configuration description is indented */ outfunc("
      \n"); /* Print the type of the configuration variable */ if (config.c_type != VALUE_NONE) { outfunc("
    • Type: %s
    • \n", type2str(config.c_type)); } /* Print the default values of the configuration variable */ print_default(&config.c_default, outfunc); /* Print the range of values of the configuration variable */ if (config.c_lower || config.c_upper) { outfunc("
    • Range:\n"); if (config.c_lower) { outfunc(" %s", config.c_lower); } outfunc(" -", config.c_lower); if (config.c_upper) { outfunc(" %s", config.c_upper); } outfunc("
    • \n"); } /* Print the default value of the configuration variable auto-selected by * this setting */ if (config.c_select.s_nvar > 0) { outfunc("
    • Selects: " "CONFIG_%s", config.c_select.s_varname[0], config.c_select.s_varname[0]); for (i = 1; i < config.c_select.s_nvar; i++) { outfunc(", CONFIG_%s", config.c_select.s_varname[i], config.c_select.s_varname[i]); } outfunc("
    • \n"); } /* Print the list of dependencies (if any) */ print_dependencies(outfunc); /* Show the configuration file. */ outfunc("
    • Kconfig file: %s/%s\n", kconfigdir, kconfigname); /* Print any help text */ if (help) { process_help(stream, outfunc); token = NULL; } /* End of configuration description */ outfunc("
    \n"); /* Free allocated memory */ free_dependencies(config.c_ndependencies); free_default(&config.c_default); if (config.c_name) { free(config.c_name); } if (config.c_desc) { free(config.c_desc); } if (config.c_lower) { free(config.c_lower); } if (config.c_upper) { free(config.c_upper); } if (config.c_select.s_nvar > 0) { for (i = 0; i < config.c_select.s_nvar; i++) { free(config.c_select.s_varname[i]); } } return token; } /**************************************************************************** * Name: process_choice * * Description: * Process a choice paragraph * ****************************************************************************/ static char *parse_kconfigfile(FILE *stream, const char *kconfigdir, const char *kconfigfile); /* Forward reference */ static inline char *process_choice(FILE *stream, const char *kconfigdir, const char *kconfigname) { enum token_type_e tokid; struct choice_s choice; const char *paranum; char *token = NULL; char *ptr; bool help = false; /* Get the choice information */ memset(&choice, 0, sizeof(struct choice_s)); /* Process each line in the choice */ while ((ptr = kconfig_line(stream)) != NULL) { /* Process the first token on the Kconfig file line */ token = get_token(); if (token != NULL) { tokid = tokenize(token); switch (tokid) { case TOKEN_PROMPT: { /* Get the prompt string */ ptr = get_html_string(); if (ptr) { choice.c_prompt = strdup(ptr); } /* Indicate that the line has been consumed */ token = NULL; } break; case TOKEN_DEFAULT: { process_default(stream, &choice.c_default); token = NULL; } break; case TOKEN_DEPENDS: { process_dependson(); choice.c_ndependencies++; token = NULL; } break; case TOKEN_HELP: { help = true; token = NULL; } break; default: { debug("Choice: Terminating token: %s\n", token); } break; } /* Break out on the help token (or the first unhandled token) */ if (help || token != NULL) { break; } } } paranum = get_paranum(); output("
  • %s Choice", g_choice_number, paranum); body("\n

    %s Choice", g_choice_number, paranum); if (choice.c_prompt) { output(": %s", choice.c_prompt); body(": %s", choice.c_prompt); } output("

  • \n"); body("\n"); g_choice_number++; /* Print the default values of the configuration variable */ body("
      \n"); print_default(&choice.c_default, body); /* Print the list of dependencies (if any) */ print_dependencies(body); /* Show the configuration file. * REVISIT: Shows wrong file name if the name of the Kconfig file is not * Kconfig. */ body("
    • Kconfig file: %s/%s\n
    • ", kconfigdir, kconfigname); /* Print any help text */ if (help) { process_help(stream, body); token = NULL; } body("
    \n"); /* Then show the choice options */ body("

    Choice Options:

    "); body("
      \n"); /* Free allocated memory */ free_dependencies(choice.c_ndependencies); free_default(&choice.c_default); if (choice.c_prompt) { free(choice.c_prompt); } /* Increment the paragraph level */ incr_level(); debug("process_choice: TOKEN_CHOICE\n"); debug(" kconfigdir: %s\n", kconfigdir); debug(" kconfigname: %s\n", kconfigname); debug(" level: %d\n", g_level); /* Then return in choice mode */ g_inchoice++; return token; } /**************************************************************************** * Name: process_menu * * Description: * Process a menu paragraph * ****************************************************************************/ static inline char *process_menu(FILE *stream, const char *kconfigdir, const char *kconfigname) { enum token_type_e tokid; struct menu_s menu; const char *paranum; char *menuname; char *token = NULL; /* Get the menu information */ memset(&menu, 0, sizeof(struct menu_s)); /* Get the menu name */ menuname = get_html_string(); menu.m_name = strdup(menuname); /* Process each line in the choice */ while (kconfig_line(stream) != NULL) { /* Process the first token on the Kconfig file line */ token = get_token(); if (token != NULL) { tokid = tokenize(token); switch (tokid) { case TOKEN_DEPENDS: { process_dependson(); menu.m_ndependencies++; token = NULL; } break; default: { debug("Menu: Terminating token: %s\n", token); } break; } /* Break out on the first unhandled token */ if (token != NULL) { break; } } } /* Output menu information */ paranum = get_paranum(); if (menu.m_name) { output("
    • " "%s Menu: %s
    • \n", g_menu_number, g_menu_number, paranum, menu.m_name); body("\n

      %s Menu: %s

      \n", g_menu_number, paranum, menu.m_name); } else { output("
    • " "%s Menu
    • \n", g_menu_number, g_menu_number, paranum); body("\n

      %s Menu

      \n", g_menu_number, paranum); } /* Output logic to toggle the contents below the menu in the table of * contents. */ #ifdef USE_JQUERY output("" "Expand\n", g_menu_number, g_toggle_number, g_toggle_number); #else output("" "Expand\n", g_menu_number, g_toggle_number); #endif output("
        \n", g_toggle_number); g_menu_number++; g_toggle_number++; /* Print the list of dependencies (if any) */ body("
          \n"); print_dependencies(body); /* Show the configuration file */ body("
        • Kconfig file: %s/%s\n", kconfigdir, kconfigname); body("
        \n"); /* Free any allocated memory */ free_dependencies(menu.m_ndependencies); if (menu.m_name) { free(menu.m_name); } /* Increment the paragraph level */ incr_level(); debug("process_menu: TOKEN_MENU\n"); debug(" kconfigdir: %s\n", kconfigdir); debug(" kconfigname: %s\n", kconfigname); debug(" level: %d\n", g_level); /* Return the terminating token */ return token; } /**************************************************************************** * Name: parse_kconfigfile * * Description: * Parse a Kconfig file. * ****************************************************************************/ static void process_kconfigfile(const char *kconfigdir, const char *kconfigname); /* Forward reference */ static char *parse_kconfigfile(FILE *stream, const char *kconfigdir, const char *kconfigname) { enum token_type_e tokid; char *token = NULL; /* Process each line in the Kconfig file */ while (kconfig_line(stream) != NULL) { /* Process the first token on the Kconfig file line */ token = get_token(); while (token != NULL) { tokid = tokenize(token); switch (tokid) { case TOKEN_SOURCE: { /* Get the relative path from the Kconfig file line */ char *source = get_token(); /* Remove optional quoting */ source = dequote(source); if (source) { char *configname = basename(source); char *subdir = dirname(source); char *dirpath; /* Check for an absolute path */ if (source[0] == '/') { dirpath = strdup(subdir); } else { /* Check if the directory path contains $APPSDIR */ char *appsdir = strstr(subdir, "$APPSDIR"); if (appsdir) { char *tmp = appsdir + strlen("$APPSDIR"); *appsdir = '\0'; asprintf(&dirpath, "%s/%s%s%s", g_kconfigroot, subdir, g_appsdir, tmp); } else { asprintf(&dirpath, "%s/%s", g_kconfigroot, subdir); } } configname = strdup(configname); debug("parse_kconfigfile: " "Recursing for TOKEN_SOURCE\n"); debug(" source: %s\n", source); debug(" subdir: %s\n", subdir); debug(" dirpath: %s\n", dirpath); debug(" configname: %s\n", configname); /* Then recurse */ process_kconfigfile(dirpath, configname); token = NULL; free(dirpath); free(configname); } /* Set the token string to NULL to indicate that * we need to read the next line */ token = NULL; } break; case TOKEN_CONFIG: case TOKEN_MENUCONFIG: { char *varname = get_token(); token = process_config(stream, varname, kconfigdir, kconfigname); } break; case TOKEN_COMMENT: case TOKEN_MAINMENU: { token = NULL; /* ignored */ } break; case TOKEN_MENU: { token = process_menu(stream, kconfigdir, kconfigname); } break; case TOKEN_CHOICE: { token = process_choice(stream, kconfigdir, kconfigname); } break; case TOKEN_ENDCHOICE: { /* Reduce body indentation level */ body("
      \n"); g_inchoice--; /* Decrement the paragraph level */ decr_level(); incr_paranum(); token = NULL; } break; case TOKEN_ENDMENU: { /* Reduce table of contents indentation level. NOTE that * this also terminates the toggle block that * began with the matching
        */ output("
      \n"); /* Decrement the paragraph level */ decr_level(); incr_paranum(); token = NULL; } break; case TOKEN_IF: { char *dependency = get_token(); push_dependency(htmlize_expression(dependency)); token = NULL; } break; case TOKEN_ENDIF: { pop_dependency(); token = NULL; } break; default: { /* Set token to NULL to skip to the next line. */ error("File %s/%s Unhandled token: %s\n", kconfigdir, kconfigname, token); token = NULL; } break; } } } return token; } /**************************************************************************** * Name: process_kconfigfile * * Description: * Open and parse a Kconfig file * ****************************************************************************/ static void process_kconfigfile(const char *kconfigdir, const char *kconfigname) { FILE *stream; char *kconfigpath; /* Create the full path to the Kconfig file */ asprintf(&kconfigpath, "%s/%s", kconfigdir, kconfigname); debug("process_kconfigfile: Entry\n"); debug(" kconfigdir: %s\n", kconfigdir); debug(" kconfigpath: %s\n", kconfigpath); debug(" level: %d\n", g_level); /* Open the Kconfig file */ stream = fopen(kconfigpath, "r"); if (!stream) { error("open %s failed: %s\n", kconfigpath, strerror(errno)); exit(ERROR_KCONFIG_OPEN_FAILURE); } /* Process each line in the Kconfig file */ parse_kconfigfile(stream, kconfigdir, kconfigname); /* Close the Kconfig file and release the memory holding the full path */ fclose(stream); free(kconfigpath); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: main * * Description: * Program entry point. * ****************************************************************************/ int main(int argc, char **argv, char **envp) { char *outfile; const char *paranum; time_t now; struct tm *ptm; int ch; /* Parse command line options */ g_debug = false; g_kconfigroot = "."; g_appsdir = "../apps"; g_outfile = stdout; outfile = NULL; while ((ch = getopt(argc, argv, ":dhia:o:")) > 0) { switch (ch) { case 'a' : g_appsdir = optarg; break; case 'o' : outfile = optarg; break; case 'h' : show_usage(argv[0], 0); case 'd' : g_debug = true; break; case '?' : error("Unrecognized option: %c\n", optopt); show_usage(argv[0], ERROR_UNRECOGNIZED_OPTION); case ':' : error("Missing option argument, option: %c\n", optopt); show_usage(argv[0], ERROR_MISSING_OPTION_ARGUMENT); default: error("Unexpected option: %c\n", ch); show_usage(argv[0], ERROR_UNEXPECTED_OPTION); } } if (optind < argc) { g_kconfigroot = argv[optind]; optind++; } debug("Using : %s\n", g_kconfigroot); debug("Using : %s\n", g_appsdir); debug("Using : %s\n", outfile ? outfile : "stdout"); if (optind < argc) { error("Unexpected garbage at the end of the line\n"); show_usage(argv[0], ERROR_TOO_MANY_ARGUMENTS); } /* Open the output file (if any). The output file will hold the * Table of Contents as the HTML document is generated. */ if (outfile) { g_outfile = fopen(outfile, "w"); if (!g_outfile) { error("open %s failed: %s\n", outfile, strerror(errno)); exit(ERROR_OUTFILE_OPEN_FAILURE); } } /* Open the temporary file that holds the HTML body. The HTML * body will be appended after the Table of contents. */ g_bodyfile = fopen(BODYFILE_NAME, "w"); if (!g_bodyfile) { error("open %s failed: %s\n", BODYFILE_NAME, strerror(errno)); exit(ERROR_BODYFILE_OPEN_FAILURE); } /* Open the temporary file that holds the appendix. The appendix * will be appended after the HTML body. */ g_apndxfile = fopen(APNDXFILE_NAME, "w"); if (!g_apndxfile) { error("open %s failed: %s\n", APNDXFILE_NAME, strerror(errno)); exit(ERROR_APNDXFILE_OPEN_FAILURE); } /* Get the current date string in the scratch buffer */ now = time(NULL); ptm = localtime(&now); strftime(g_scratch, SCRATCH_SIZE, "%B %d, %Y", ptm); /* Output header boilerplater */ output("\n"); output("\n"); output("NuttX Configuration Options\n"); output("\n"); output("\n"); output("

      \n"); output("\n"); output("\n"); output("\n"); output("\n"); output("
      \n"); output("

      " "NuttX Configuration Variables

      \n"); output("

      Last Updated: %s

      \n", g_scratch); output("
      \n"); #ifdef USE_JQUERY output("\n"); output("\n"); #else output("\n"); #endif output("

      \n"); output("\n"); output(" \n"); output(" \n"); output(" \n"); output("
      \n"); output("

      Table of Contents

      \n"); output("
      \n"); output("
        \n"); incr_level(); paranum = get_paranum(); output("
      • %s Menu: Main
      • \n", g_menu_number, paranum); body("\n"); body(" \n"); body(" \n"); body(" \n"); body("
        \n"); body("

        %s Menu: Main

        \n", g_menu_number, paranum); body("
        \n"); g_menu_number++; /* Increment the paragraph level again: * Everything is included within the main menu. */ incr_level(); /* Tell the reader that this is an auto-generated file */ body("

        \n"); body(" Overview.\n"); body(" The NuttX RTOS is highly configurable.\n"); body(" The NuttX configuration files are maintained using the " "kconfig-frontends tool.\n"); body(" That configuration tool uses Kconfig " "files that can be found through the NuttX source tree.\n"); body(" Each Kconfig files contains " "declarations of configuration variables.\n"); body(" Each configuration variable provides one configuration " "option for the NuttX RTOS.\n"); body(" This configurable options are described in this document.\n"); body("

        \n"); body("

        \n"); body(" Main Menu.\n"); body(" The normal way to start the NuttX configuration is to enter " "this command line from the NuttX build directory: " "make menuconfig.\n"); body(" Note that NuttX must first be configured before " "this command so that the configuration file (.config) " "is present in the top-level build directory.\n"); body(" The main menu is the name give to the opening menu display " "after this command is executed.\n"); body("

        \n"); body("

        \n"); body(" Maintenance Note.\n"); body(" This documentation was auto-generated using the " "kconfig2html tool\n"); body(" That tool analyzes the NuttX Kconfig " "files and generates this HTML document.\n"); body(" This HTML document file should not be edited manually.\n"); body(" In order to make changes to this document, " "you should instead modify the Kconfig file(s) " "that were used to generated this document and then execute the " "kconfig2html again " "to regenerate the HTML document file.\n"); body("

        \n"); /* Process the Kconfig files through recursive descent */ process_kconfigfile(g_kconfigroot, "Kconfig"); /* Terminate the table of contents */ output("
      • " "Appendix A: Hidden Configuration Variables
      • \n"); output("
      \n"); /* Close the HMTL body file and copy it to the output file */ fclose(g_bodyfile); append_file(BODYFILE_NAME); /* Output introductory information for the appendix */ output("\n"); output(" \n"); output(" \n"); output(" \n"); output("
      \n"); output(" " "

      Appendix A: Hidden Configuration Variables

      \n"); output("
      \n"); output("

      \n"); output(" This appendix holds internal configurations variables that " "are not visible to the user.\n"); output(" These settings are presented out-of-context because " "they cannot be directly controlled by the user.\n"); output(" Many of these settings are selected automatically and " "indirectly when other, visible configuration variables " "are selected.\n"); output(" One purpose of these hidden configuration variables " "is to control menuing in the kconfig-frontends " "configuration tool.\n"); output(" Many configuration variables with a form like " "CONFIG_ARCH_HAVE_feature, for example, " "are used only to indicate that the selected architecture supports " "feature and so addition selection associated with " "feature will become accessible to the user.\n"); output("

      \n"); output("
        \n"); /* Close the appendix file and copy it to the output file */ fclose(g_apndxfile); append_file(APNDXFILE_NAME); /* Output trailer boilerplater */ output("
      \n"); output("\n"); output("\n"); /* Close the output file (if any) and the temporary file */ if (outfile) { fclose(g_outfile); } return 0; }