summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Nutt <gnutt@nuttx.org>2013-04-20 11:42:59 -0600
committerGregory Nutt <gnutt@nuttx.org>2013-04-20 11:42:59 -0600
commitc7f1128c566c6e7ce92f13ce3d10bd66de185520 (patch)
tree902a068694cc8304cc4b5582d4042266e13d9b85
parent728748a818e89be630c6aba2041cf5eb1d66289a (diff)
downloadnuttx-c7f1128c566c6e7ce92f13ce3d10bd66de185520.tar.gz
nuttx-c7f1128c566c6e7ce92f13ce3d10bd66de185520.tar.bz2
nuttx-c7f1128c566c6e7ce92f13ce3d10bd66de185520.zip
Add a tool to auto-generated documentation of NuttX configuration variables (still a work in progress)
-rw-r--r--nuttx/ChangeLog5
-rw-r--r--nuttx/tools/kconfig2html.c1225
2 files changed, 1230 insertions, 0 deletions
diff --git a/nuttx/ChangeLog b/nuttx/ChangeLog
index bf74d92fe..0216356ea 100644
--- a/nuttx/ChangeLog
+++ b/nuttx/ChangeLog
@@ -4576,3 +4576,8 @@
handles the architecture independent part of _exit(). _exit()
is used internally, but if it is called from the user, it should
unregister any atexit() or on_exit() functions (2013-4-18).
+ * tools/kconfig2html.c: This is the beginning of a tool to
+ replace the hand-generated documentation of the NuttX configruation
+ variables with auto-generated documentation. The initial checkin
+ is an incomplete, poorly structured prototype that I hope to
+ evolve into a useful tool (2014-4-20).
diff --git a/nuttx/tools/kconfig2html.c b/nuttx/tools/kconfig2html.c
new file mode 100644
index 000000000..80c598f1f
--- /dev/null
+++ b/nuttx/tools/kconfig2html.c
@@ -0,0 +1,1225 @@
+/****************************************************************************
+ * tools/kconfig2html.c
+ *
+ * Copyright (C) 2013 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+#include <libgen.h>
+#include <errno.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define LINE_SIZE 1024
+#define SCRATCH_SIZE 1024
+#define MAX_DEPENDENCIES 100
+#define MAX_LEVELS 100
+
+#define TMPFILE_NAME "kconfig2html-tmp.dat"
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+enum token_type_e
+{
+ TOKEN_NONE = 0,
+ TOKEN_NOTRESERVED,
+ TOKEN_CONFIG,
+ TOKEN_BOOL,
+ TOKEN_INT,
+ TOKEN_HEX,
+ TOKEN_STRING,
+ TOKEN_DEFAULT,
+ TOKEN_HELP,
+ TOKEN_MENU,
+ TOKEN_ENDMENU,
+ TOKEN_CHOICE,
+ TOKEN_ENDCHOICE,
+ TOKEN_IF,
+ TOKEN_ENDIF,
+ TOKEN_SOURCE
+};
+
+enum config_type_e
+{
+ VALUE_NONE = 0,
+ VALUE_INT,
+ VALUE_HEX,
+ VALUE_BOOL,
+ 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_TMPFILE_OPEN_FAILURE,
+ ERROR_KCONFIG_OPEN_FAILURE,
+ ERROR_TOO_MANY_DEPENDENCIES,
+ ERROR_DEPENDENCIES_UNDERFLOW,
+ ERROR_NESTING_TOO_DEEP,
+ ERROR_NESTING_UNDERFLOW
+};
+
+struct reserved_s
+{
+ enum token_type_e ttype;
+ const char *tname;
+};
+
+union value_u
+{
+ char *s;
+ int i;
+ bool b;
+};
+
+struct config_s
+{
+ enum config_type_e ctype;
+ char *cname;
+ char *cdesc;
+ char *cdefault;
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static char g_line[LINE_SIZE+1];
+static char g_scratch[SCRATCH_SIZE+1];
+static FILE *g_outfile;
+static FILE *g_tmpfile;
+static char *g_lasts;
+static bool g_debug;
+static bool g_internal;
+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 struct reserved_s g_reserved[] =
+{
+ {TOKEN_CONFIG, "config"},
+ {TOKEN_BOOL, "bool"},
+ {TOKEN_INT, "int"},
+ {TOKEN_HEX, "hex"},
+ {TOKEN_STRING, "string"},
+ {TOKEN_DEFAULT, "default"},
+ {TOKEN_HELP, "help"},
+ {TOKEN_HELP, "---help---"},
+ {TOKEN_MENU, "menu"},
+ {TOKEN_ENDMENU, "endmenu"},
+ {TOKEN_CHOICE, "choice"},
+ {TOKEN_ENDCHOICE, "endchoice"},
+ {TOKEN_SOURCE, "source"},
+ {TOKEN_IF, "if"},
+ {TOKEN_ENDIF, "endif"},
+ {TOKEN_NOTRESERVED, NULL} /* Terminates list */
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static void show_usage(const char *progname, int exitcode)
+{
+ fprintf(stderr, "USAGE: %s [-d] [-i] [-a <apps directory>] {-o <out file>] [<Kconfig root>]\n", progname);
+ fprintf(stderr, " %s [-h]\n\n", progname);
+ fprintf(stderr, "Where:\n\n");
+ fprintf(stderr, "\t-a : Select relative path to the apps/ directory. Theis path is relative\n");
+ fprintf(stderr, "\t to the <Kconfig directory>. Default: ../apps\n");
+ fprintf(stderr, "\t-o : Send output to <out file>. Default: Output goes to stdout\n");
+ fprintf(stderr, "\t-i : Show hidden, internal configuration variables\n");
+ fprintf(stderr, "\t-d : Enable debug output\n");
+ fprintf(stderr, "\t-h : Prints this message and exits\n");
+ fprintf(stderr, "\t<Kconfig root> is the directory containing the root Kconfig file.\n");
+ fprintf(stderr, "\t Default <Kconfig directory>: .\n");
+ exit(exitcode);
+}
+
+/* Skip over any spaces */
+
+static char *skip_space(char *ptr)
+{
+ while (*ptr && isspace((int)*ptr)) ptr++;
+ return ptr;
+}
+
+/* Debug output */
+
+static void debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (g_debug)
+ {
+ va_start(ap, fmt);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+}
+
+/* HTML output */
+
+static void output(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)vfprintf(g_outfile, fmt, ap);
+ va_end(ap);
+}
+
+static void body(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)vfprintf(g_tmpfile, fmt, ap);
+ va_end(ap);
+}
+
+static char *dequote(char *ptr)
+{
+ int len;
+
+ /* Check if there is a traiing 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;
+}
+
+/* Read the next line from the Kconfig file */
+
+static char *read_line(FILE *stream)
+{
+ char *ptr;
+
+ for (;;)
+ {
+ g_line[LINE_SIZE] = '\0';
+ if (!fgets(g_line, LINE_SIZE, stream))
+ {
+ return NULL;
+ }
+ else
+ {
+ /* 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')
+ {
+ return ptr;
+ }
+ }
+ }
+}
+
+/* Read the next line from the Kconfig file */
+
+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;
+}
+
+#ifndef HAVE_STRTOK_R
+static char *MY_strtok_r(char *str, const char *delim, char **saveptr)
+{
+ char *pbegin;
+ char *pend = NULL;
+
+ /* Decide if we are starting a new string or continuing from
+ * the point we left off.
+ */
+
+ if (str)
+ {
+ pbegin = str;
+ }
+ else if (saveptr && *saveptr)
+ {
+ pbegin = *saveptr;
+ }
+ else
+ {
+ return NULL;
+ }
+
+ /* Find the beginning of the next token */
+
+ for (;
+ *pbegin && strchr(delim, *pbegin) != NULL;
+ pbegin++);
+
+ /* If we are at the end of the string with nothing
+ * but delimiters found, then return NULL.
+ */
+
+ if (!*pbegin)
+ {
+ return NULL;
+ }
+
+ /* Find the end of the token */
+
+ for (pend = pbegin + 1;
+ *pend && strchr(delim, *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.
+ */
+
+ if (saveptr)
+ {
+ *saveptr = pend;
+ }
+
+ return pbegin;
+}
+
+#define strtok_r MY_strtok_r
+#endif
+
+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;
+}
+
+static char *getstring(char *ptr)
+{
+ char *endptr;
+
+ /* Search for the leading quotation mark */
+
+ ptr = findchar(ptr, '"');
+ if (ptr)
+ {
+ /* Skip over the quote */
+
+ ptr++;
+
+ /* Search for the trailing quotation mark */
+
+ endptr = findchar(ptr, '"');
+ if (endptr)
+ {
+ /* Replace the final quote with a NUL */
+
+ *endptr = '\0';
+ }
+ }
+
+ return ptr;
+}
+
+static void push_dependency(const char *dependency)
+{
+ int ndx = g_ndependencies;
+
+ if (ndx >= MAX_DEPENDENCIES)
+ {
+ fprintf(stderr, "Too many dependencies, aborting\n");
+ exit(ERROR_TOO_MANY_DEPENDENCIES);
+ }
+
+ g_dependencies[ndx] = strdup(dependency);
+ g_ndependencies = ndx + 1;
+}
+
+static void pop_dependency(void)
+{
+ int ndx = g_ndependencies - 1;
+ if (ndx < 0)
+ {
+ fprintf(stderr, "Dependency underflow, aborting\n");
+ exit(ERROR_DEPENDENCIES_UNDERFLOW);
+ }
+
+ if (g_dependencies[ndx])
+ {
+ free(g_dependencies[ndx]);
+ g_dependencies[ndx] = NULL;
+ }
+
+ g_ndependencies = ndx;
+}
+
+static void incr_level(void)
+{
+ int ndx = g_level;
+
+ if (ndx >= MAX_LEVELS)
+ {
+ fprintf(stderr, "Nesting level is too deep, aborting\n");
+ exit(ERROR_NESTING_TOO_DEEP);
+ }
+
+ g_paranum[ndx] = 1;
+ g_level = ndx + 1;
+}
+
+static void decr_level(void)
+{
+ int ndx = g_level;
+
+ g_paranum[ndx] = '\0';
+ ndx--;
+
+ if (ndx < 0)
+ {
+ fprintf(stderr, "Nesting level underflow, aborting\n");
+ exit(ERROR_NESTING_UNDERFLOW);
+ }
+
+ g_level = ndx;
+}
+
+static void incr_paranum(void)
+{
+ int ndx = g_level - 1;
+
+ if (ndx < 0)
+ {
+ fprintf(stderr, "Nesting level underflow, aborting\n");
+ exit(ERROR_NESTING_UNDERFLOW);
+ }
+
+ g_paranum[ndx]++;
+}
+
+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;
+}
+
+static const char *type2str(enum config_type_e valtype)
+{
+ switch (valtype)
+ {
+ case VALUE_BOOL:
+ return "Boolean";
+
+ case VALUE_INT:
+ return "Integer";
+
+ case VALUE_HEX:
+ return "Hexadecimal";
+
+ case VALUE_STRING:
+ return "String";
+
+ default:
+ break;
+ }
+
+ return "Unknown";
+}
+
+static inline char *process_config(FILE *stream, const char *configname, const char *kconfigdir)
+{
+ enum token_type_e tokid;
+ struct config_s config;
+ bool help;
+ const char *paranum;
+ char *token;
+ char *ptr;
+
+ /* Get the configuration information */
+
+ memset(&config, 0, sizeof(struct config_s));
+ config.cname = strdup(configname);
+
+ /* Process each line in the configuration */
+
+ help = false;
+
+ while ((ptr = read_line(stream)) != NULL)
+ {
+ /* Process the first token on the Kconfig file line */
+
+ g_lasts = NULL;
+ token = strtok_r(ptr, " ", &g_lasts);
+ if (token != NULL)
+ {
+ tokid = tokenize(token);
+ switch (tokid)
+ {
+ case TOKEN_BOOL:
+ {
+ /* Save the type of the configuration variable */
+
+ config.ctype = VALUE_BOOL;
+
+ /* Get the description following the type */
+
+ ptr = getstring(g_lasts);
+ if (ptr)
+ {
+ config.cdesc = strdup(ptr);
+ }
+
+ /* Indicate that the line has been consumed */
+
+ token = NULL;
+ }
+ break;
+
+ case TOKEN_INT:
+ {
+ /* Save the type of the configuration variable */
+
+ config.ctype = VALUE_INT;
+
+ /* Get the description following the type */
+
+ ptr = getstring(g_lasts);
+ if (ptr)
+ {
+ config.cdesc = strdup(ptr);
+ }
+
+ /* Indicate that the line has been consumed */
+
+ token = NULL;
+ }
+ break;
+
+ case TOKEN_HEX:
+ {
+ /* Save the type of the configuration variable */
+
+ config.ctype = VALUE_HEX;
+
+ /* Get the description following the type */
+
+ ptr = getstring(g_lasts);
+ if (ptr)
+ {
+ config.cdesc = strdup(ptr);
+ }
+
+ /* Indicate that the line has been consumed */
+
+ token = NULL;
+ }
+ break;
+
+ case TOKEN_STRING:
+ {
+ /* Save the type of the configuration variable */
+
+ config.ctype = VALUE_STRING;
+
+ /* Get the description following the type */
+
+ ptr = getstring(g_lasts);
+ if (ptr)
+ {
+ config.cdesc = strdup(ptr);
+ }
+
+ /* Indicate that the line has been consumed */
+
+ token = NULL;
+ }
+ break;
+
+ case TOKEN_DEFAULT:
+ {
+ char *value = strtok_r(NULL, " ", &g_lasts);
+ config.cdefault = strdup(value);
+ token = NULL;
+ }
+ break;
+
+ case TOKEN_HELP:
+ {
+ help = true;
+ token = NULL;
+ }
+ break;
+
+ default:
+ {
+ debug("Unhandled token: %s\n", token);
+ }
+ break;
+ }
+
+ /* Break out on the first unhandled token */
+
+ if (token != NULL)
+ {
+ break;
+ }
+ }
+ }
+
+ /* Is this an internal configuration varaible with no description?
+ * Were we asked to show these internal variables? If not then
+ * don't output anything.
+ */
+
+ if (config.cdesc || g_internal)
+ {
+ /* Print the configuration variable name and the short description */
+
+ body("<h3><a name=\"%s\">", config.cname);
+
+ /* If we are not in a choice block, than give the variable a paragraph
+ * number and put it in the table of contents.
+ */
+
+ if (!g_inchoice)
+ {
+ paranum = get_paranum();
+ output("<li><a href=\"#%s\">%s <code>CONFIG_%s</code>",
+ config.cname, paranum, config.cname);
+ body("%s ", paranum);
+ incr_paranum();
+ }
+
+ body("<code>CONFIG_%s</code>", config.cname);
+
+ if (config.cdesc)
+ {
+ if (!g_inchoice)
+ {
+ output(": %s", config.cdesc);
+ }
+
+ body(": %s", config.cdesc);
+ }
+
+ body("</a></h3>\n");
+ if (!g_inchoice)
+ {
+ output("</a></li>\n");
+ }
+
+ /* Configuration description is indented */
+
+ body("<ul>\n");
+
+ /* Print the type of the configuration variable */
+
+ if (config.ctype != VALUE_NONE)
+ {
+ body(" <li><i>Type</i>: %s</li>\n", type2str(config.ctype));
+ }
+
+ /* Print the default value of the configuration variable */
+
+ if (config.cdefault)
+ {
+ body(" <li><i>Default</i>: %s</li>\n", config.cdefault);
+ }
+
+ /* Print the list of dependencies (if any) */
+
+ if (g_ndependencies > 0)
+ {
+ int i;
+
+ body(" <li><i>Dependencies</i>: %s", g_dependencies[0]);
+ for (i = 1; i < g_ndependencies; i++)
+ {
+ body(", %s\n", g_dependencies[i]);
+ }
+ body("</li>\n");
+ }
+
+ /* Show the configuration file */
+
+ body(" <li><i>Kconfig file</i>: <code>%s/Kconfig</code>\n", kconfigdir);
+
+ /* Print any help text */
+
+ if (help)
+ {
+ }
+ else if (!config.cdesc)
+ {
+ body("<p>This is a hidden, internal configuration variable that cannot be explicitly set by the user.</p>\n");
+ }
+
+ /* End of configuration description */
+
+ body("</ul>\n");
+ }
+
+ /* Free allocated memory */
+
+ if (config.cname)
+ {
+ free(config.cname);
+ }
+
+ if (config.cdesc)
+ {
+ free(config.cdesc);
+ }
+
+ if (config.cdefault)
+ {
+ free(config.cdefault);
+ }
+
+ return token;
+}
+
+static void process_kconfigfile(const char *kconfigdir); /* Forward reference */
+static char *parse_kconfigfile(FILE *stream, const char *kconfigdir)
+{
+ enum token_type_e tokid;
+ const char *paranum;
+ char *token;
+ char *ptr;
+
+ /* Process each line in the Kconfig file */
+
+ while ((ptr = read_line(stream)) != NULL)
+ {
+ /* Process the first token on the Kconfig file line */
+
+ g_lasts = NULL;
+ token = strtok_r(ptr, " ", &g_lasts);
+ while (token != NULL)
+ {
+ tokid = tokenize(token);
+
+ switch (tokid)
+ {
+ case TOKEN_SOURCE:
+ {
+ /* Get the relative path from the Kconfig file line */
+
+ char *relpath = strtok_r(NULL, " ", &g_lasts);
+
+ /* Remove optional quoting */
+
+ relpath = dequote(relpath);
+ if (relpath)
+ {
+ char *subdir = dirname(relpath);
+ char *dirpath;
+
+ /* 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);
+ }
+
+ debug("parse_kconfigfile: Recursing for TOKEN_SOURCE\n");
+ debug(" relpath: %s\n", relpath);
+ debug(" subdir: %s\n", subdir);
+ debug(" dirpath: %s\n", dirpath);
+
+ /* Then recurse */
+
+ process_kconfigfile(dirpath);
+ token = NULL;
+ free(dirpath);
+ }
+
+ /* Set the token string to NULL to indicate that we need to read the next line */
+
+ token = NULL;
+ }
+ break;
+
+ case TOKEN_CONFIG:
+ {
+ char *configname = strtok_r(NULL, " ", &g_lasts);
+ token = process_config(stream, configname, kconfigdir);
+ }
+ break;
+
+ case TOKEN_MENU:
+ {
+ char *menuname = getstring(g_lasts);
+
+ paranum = get_paranum();
+ if (menuname)
+ {
+ output("<li><a href=\"#menu_%d\">%s Menu: %s</a></li>\n",
+ g_menu_number, paranum, menuname);
+ output("<ul>\n");
+ body("\n<h1><a name=\"menu_%d\">%s Menu: %s</a></h1>\n",
+ g_menu_number, paranum, menuname);
+ }
+ else
+ {
+ output("<li><a href=\"#menu_%d\">%s Menu</a></li>\n",
+ g_menu_number, paranum);
+ body("\n<h1><a name=\"menu_%d\">%s Menu</a></h1>\n",
+ g_menu_number, paranum);
+ }
+ g_menu_number++;
+
+ /* Increment the nesting level */
+
+ incr_level();
+
+ debug("parse_kconfigfile: Recursing for TOKEN_MENU\n");
+ debug(" kconfigdir: %s\n", kconfigdir);
+ debug(" level: %d\n", g_level);
+
+ /* Then recurse */
+
+ token = parse_kconfigfile(stream, kconfigdir);
+ }
+ break;
+
+ case TOKEN_CHOICE:
+ {
+ paranum = get_paranum();
+ body("\n<h3>%s Choice</h3>\n<ul>\n", paranum);
+
+ output("<li><a href=\"#choice_%d\">%s Choice</a></li>\n",
+ g_choice_number, paranum);
+ body("\n<h3><a name=\"choice_%d\">%s Choice</a></h3>\n",
+ g_choice_number, paranum);
+ g_choice_number++;
+
+ /* Increment the nesting level */
+
+ incr_level();
+
+ debug("parse_kconfigfile: Recursing for TOKEN_CHOICE\n");
+ debug(" kconfigdir: %s\n", kconfigdir);
+ debug(" level: %d\n", g_level);
+
+ /* Then recurse */
+
+ g_inchoice++;
+ token = parse_kconfigfile(stream, kconfigdir);
+ }
+ break;
+
+ case TOKEN_ENDCHOICE:
+ {
+ /* Reduce body indentation level */
+
+ body("</ul>\n");
+ g_inchoice--;
+
+ /* Decrement the nesting level */
+
+ decr_level();
+ incr_paranum();
+ return NULL;
+ }
+ break;
+
+ case TOKEN_ENDMENU:
+ {
+ /* Reduce table of contents indentation level */
+
+ output("</ul>\n");
+
+ /* Decrement the nesting level */
+
+ decr_level();
+ incr_paranum();
+ return NULL;
+ }
+ break;
+
+ case TOKEN_IF:
+ {
+ char *dependency = strtok_r(NULL, " ", &g_lasts);
+ push_dependency(dependency);
+ token = NULL;
+ }
+ break;
+
+ case TOKEN_ENDIF:
+ {
+ pop_dependency();
+ token = NULL;
+ }
+ break;
+
+ default:
+ {
+ /* Set token to NULL to skip to the next line */
+
+ debug("Unhandled token: %s\n", token);
+ token = NULL;
+ }
+ break;
+ }
+ }
+ }
+
+ return token;
+}
+
+static void process_kconfigfile(const char *kconfigdir)
+{
+ FILE *stream;
+ char *kconfigpath;
+
+ /* Create the full path to the Kconfig file */
+
+ asprintf(&kconfigpath, "%s/Kconfig", kconfigdir);
+ 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)
+ {
+ fprintf(stderr, "open %s failed: %s\n", kconfigpath, strerror(errno));
+ exit(ERROR_KCONFIG_OPEN_FAILURE);
+ }
+
+ /* Process each line in the Kconfig file */
+
+ parse_kconfigfile(stream, kconfigdir);
+
+ /* Close the Kconfig file and release the memory holding the full path */
+
+ fclose(stream);
+ free(kconfigpath);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+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_internal = false;
+ g_kconfigroot = ".";
+ g_appsdir = "../apps";
+ g_outfile = stdout;
+ outfile = NULL;
+
+ while ((ch = getopt(argc, argv, ":dha:o:")) > 0)
+ {
+ switch (ch)
+ {
+ case 'a' :
+ g_appsdir = optarg;
+ break;
+
+ case 'o' :
+ outfile = optarg;
+ break;
+
+ case 'i' :
+ g_internal = true;
+ break;
+
+ case 'h' :
+ show_usage(argv[0], 0);
+
+ case 'd' :
+ g_debug = true;
+ break;
+
+ case '?' :
+ fprintf(stderr, "Unrecognized option: %c\n", optopt);
+ show_usage(argv[0], ERROR_UNRECOGNIZED_OPTION);
+
+ case ':' :
+ fprintf(stderr, "Missing option argument, option: %c\n", optopt);
+ show_usage(argv[0], ERROR_MISSING_OPTION_ARGUMENT);
+
+ break;
+ fprintf(stderr, "Unexpected option: %c\n", ch);
+ show_usage(argv[0], ERROR_UNEXPECTED_OPTION);
+ }
+ }
+
+ if (optind < argc)
+ {
+ g_kconfigroot = argv[optind];
+ optind++;
+ }
+
+ debug("Using <Kconfig directory>: %s\n", g_kconfigroot);
+ debug("Using <apps directory>: %s\n", g_appsdir);
+ debug("Using <out file>: %s\n", outfile ? outfile : "stdout");
+
+ if (optind < argc)
+ {
+ fprintf(stderr, "Unexpected garbage at the end of the line\n");
+ show_usage(argv[0], ERROR_TOO_MANY_ARGUMENTS);
+ }
+
+ /* Open the output file (if any) */
+
+ if (outfile)
+ {
+ g_outfile = fopen(outfile, "w");
+ if (!g_outfile)
+ {
+ fprintf(stderr, "open %s failed: %s\n", outfile, strerror(errno));
+ exit(ERROR_OUTFILE_OPEN_FAILURE);
+ }
+ }
+
+ /* Open the temporary file */
+
+ g_tmpfile = fopen(TMPFILE_NAME, "w");
+ if (!g_tmpfile)
+ {
+ fprintf(stderr, "open %s failed: %s\n", TMPFILE_NAME, strerror(errno));
+ exit(ERROR_TMPFILE_OPEN_FAILURE);
+ }
+
+ /* Get the current date string in the scratch buffer */
+
+ now = time(NULL);
+ ptm = localtime(&now);
+ (void)strftime(g_scratch, SCRATCH_SIZE, "%B %d, %Y", ptm);
+
+ /* Output header boilerplater */
+
+ output("<html>\n");
+ output("<head>\n");
+ output("<title>NuttX Configuration Options</title>\n");
+ output("</head>\n");
+ output("<body background=\"backgd.gif\">\n");
+ output("<hr><hr>\n");
+ output("<table width =\"100%%\">\n");
+ output("<tr align=\"center\" bgcolor=\"#e4e4e4\">\n");
+ output("<td>\n");
+ output("<h1><big><font color=\"#3c34ec\"><i>NuttX Configuration Variables</i></font></big></h1>\n");
+ output("<p>Last Updated: %s</p>\n", g_scratch);
+ output("</td>\n");
+ output("</tr>\n");
+ output("</table>\n");
+ output("<center><h1>Table of contents</h1></center>\n");
+ output("<ul>\n");
+
+ incr_level();
+ paranum = get_paranum();
+ output("<li><a href=\"#menu_%d\">%s NuttX Configuration Variables</a></li>\n",
+ g_menu_number, paranum);
+ body("<h1><a name=\"menu_%d\">%s NuttX Configuration Variables</a></h1>\n",
+ g_menu_number, paranum);
+ g_menu_number++;
+
+ /* Process the Kconfig files through recursive descent */
+
+ process_kconfigfile(g_kconfigroot);
+
+ /* Terminate the table of contents */
+
+ output("</ul>\n");
+
+ /* Close the temporary file and copy it to the output file */
+
+ fclose(g_tmpfile);
+ g_tmpfile = fopen(TMPFILE_NAME, "r");
+ if (!g_tmpfile)
+ {
+ fprintf(stderr, "open %s failed: %s\n", TMPFILE_NAME, strerror(errno));
+ exit(ERROR_TMPFILE_OPEN_FAILURE);
+ }
+
+ while ((ch = getc(g_tmpfile)) != EOF)
+ {
+ (void)putc(ch, g_outfile);
+ }
+
+ /* Close and remove the temporary file again */
+
+ fclose(g_tmpfile);
+ unlink(TMPFILE_NAME);
+
+ /* Output trailer boilerplater */
+
+ output("</body>\n");
+ output("</html>\n");
+
+ /* Close the output file (if any) and the temporary file*/
+
+ if (outfile)
+ {
+ fclose(g_outfile);
+ }
+
+ return 0;
+}