From 0bbe7319da25f6efb2e40737b0b9044a086bbc67 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Sat, 20 Apr 2013 14:18:08 -0600 Subject: Add suppport for choice prompts and help text to the Kconfig documentation tool --- nuttx/Kconfig | 8 +- nuttx/tools/README.txt | 8 + nuttx/tools/kconfig2html.c | 512 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 479 insertions(+), 49 deletions(-) diff --git a/nuttx/Kconfig b/nuttx/Kconfig index 7b98704bd..8bf8fadda 100644 --- a/nuttx/Kconfig +++ b/nuttx/Kconfig @@ -96,7 +96,8 @@ config APPS_DIR the NuttX top build direcory. If you had an application directory and the NuttX directory each in separate directory trees like this: - + +
 		build
 		|-nuttx
 		|  |
@@ -104,6 +105,7 @@ config APPS_DIR
 		`-application
 		|
 		`- Makefile
+		
Then you would set CONFIG_APPS_DIR=../application. @@ -238,11 +240,13 @@ config ARCH_STDBOOL_H However, that header includes logic to redirect the inclusion of an architecture specific header file like: +
 		#ifdef CONFIG_ARCH_STDBOOL_H
 		#  include 
 		#else
 		...
 		#endif
+		
Recall that that include path, include/arch, is a symbolic link and will refer to a version of stdbool.h at nuttx/arch//include/stdbool.h. @@ -259,9 +263,11 @@ config ARCH_MATH_H will then include the architecture-specific version of math.h that you must provide at nuttx/arch/>architecture #ifdef CONFIG_ARCH_MATH_H # include #endif + So for the architectures that define ARCH_MATH_H=y, include/math.h will be the redirecting math.h header file; for the architectures diff --git a/nuttx/tools/README.txt b/nuttx/tools/README.txt index 8df4a7783..6331e7bd6 100644 --- a/nuttx/tools/README.txt +++ b/nuttx/tools/README.txt @@ -82,6 +82,14 @@ cmdconfig.c This C file can be used to build a utility for comparing two NuttX configuration files. +kconfig2html.c +-------------- + + This is a C file that can be used build a utility for converting the + NuttX configuration in the Kconfig files to an HTML document. This + auto-generated documentation will, eventually, replace the manually + updated configuratin documentation that is fallling woefully behind. + mkexport.sh and Makefile.export ------------------------------- diff --git a/nuttx/tools/kconfig2html.c b/nuttx/tools/kconfig2html.c index 80c598f1f..337769302 100644 --- a/nuttx/tools/kconfig2html.c +++ b/nuttx/tools/kconfig2html.c @@ -56,6 +56,7 @@ #define SCRATCH_SIZE 1024 #define MAX_DEPENDENCIES 100 #define MAX_LEVELS 100 +#define TAB_SIZE 4 #define TMPFILE_NAME "kconfig2html-tmp.dat" @@ -78,6 +79,7 @@ enum token_type_e TOKEN_ENDMENU, TOKEN_CHOICE, TOKEN_ENDCHOICE, + TOKEN_PROMPT, TOKEN_IF, TOKEN_ENDIF, TOKEN_SOURCE @@ -128,6 +130,12 @@ struct config_s char *cdefault; }; +struct choice_s +{ + char *cprompt; + char *cdefault; +}; + /**************************************************************************** * Private Data ****************************************************************************/ @@ -139,6 +147,7 @@ static FILE *g_tmpfile; static char *g_lasts; static bool g_debug; static bool g_internal; +static bool g_preread; static const char *g_kconfigroot; static const char *g_appsdir; static int g_paranum[MAX_LEVELS]; @@ -163,6 +172,7 @@ static struct reserved_s g_reserved[] = {TOKEN_ENDMENU, "endmenu"}, {TOKEN_CHOICE, "choice"}, {TOKEN_ENDCHOICE, "endchoice"}, + {TOKEN_PROMPT, "prompt"}, {TOKEN_SOURCE, "source"}, {TOKEN_IF, "if"}, {TOKEN_ENDIF, "endif"}, @@ -177,6 +187,14 @@ static struct reserved_s g_reserved[] = * Private Functions ****************************************************************************/ +/**************************************************************************** + * 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) { fprintf(stderr, "USAGE: %s [-d] [-i] [-a ] {-o ] []\n", progname); @@ -193,7 +211,13 @@ static void show_usage(const char *progname, int exitcode) exit(exitcode); } -/* Skip over any spaces */ +/**************************************************************************** + * Name: skip_space + * + * Description: + * Skip over any spaces + * + ****************************************************************************/ static char *skip_space(char *ptr) { @@ -201,7 +225,13 @@ static char *skip_space(char *ptr) return ptr; } -/* Debug output */ +/**************************************************************************** + * Name: debug + * + * Description: + * Debug output (conditional) + * + ****************************************************************************/ static void debug(const char *fmt, ...) { @@ -215,7 +245,13 @@ static void debug(const char *fmt, ...) } } -/* HTML output */ +/**************************************************************************** + * Name: output + * + * Description: + * Output to the final HTML file + * + ****************************************************************************/ static void output(const char *fmt, ...) { @@ -226,6 +262,14 @@ static void output(const char *fmt, ...) va_end(ap); } +/**************************************************************************** + * Name: body + * + * Description: + * HTML body output to a temporary file. + * + ****************************************************************************/ + static void body(const char *fmt, ...) { va_list ap; @@ -235,6 +279,14 @@ static void body(const char *fmt, ...) va_end(ap); } +/**************************************************************************** + * Name: dequote + * + * Description: + * Remove quotation marks from a string. + * + ****************************************************************************/ + static char *dequote(char *ptr) { int len; @@ -270,6 +322,15 @@ static char *dequote(char *ptr) return ptr; } +/**************************************************************************** + * Name: read_line + * + * Description: + * Read a new line, skipping over leading white space and ignore lines + * that contain only comments. + * + ****************************************************************************/ + /* Read the next line from the Kconfig file */ static char *read_line(FILE *stream) @@ -278,39 +339,52 @@ static char *read_line(FILE *stream) 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 */ + /* Is there already valid data in the line buffer? This can happen while parsing + * help text and we read one line too far. + */ - for (ptr = g_line; *ptr; ptr++) + if (!g_preread) + { + g_line[LINE_SIZE] = '\0'; + if (!fgets(g_line, LINE_SIZE, stream)) { - if (isspace(((int)*ptr))) - { - *ptr = ' '; - } + return NULL; } + } - /* Skip any leading whitespace. Ignore empty lines and lines that - * contain only comments. - */ + g_preread = false; - ptr = skip_space(g_line); - if (*ptr && *ptr != '#' && *ptr != '\n') + /* Replace all whitespace characters with spaces to simplify parsing */ + + for (ptr = g_line; *ptr; ptr++) + { + if (isspace(((int)*ptr))) { - return 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 */ +/**************************************************************************** + * Name: tokenize + * + * Description: + * Check if this string corresponds to a string in the reserved word table. + * + ****************************************************************************/ -enum token_type_e tokenize(const char *token) +static enum token_type_e tokenize(const char *token) { struct reserved_s *ptr; @@ -325,6 +399,14 @@ enum token_type_e tokenize(const char *token) return ptr->ttype; } +/**************************************************************************** + * Name: MY_strtok_r + * + * Description: + * A replacement that can be used if your platform does not support strtok_r. + * + ****************************************************************************/ + #ifndef HAVE_STRTOK_R static char *MY_strtok_r(char *str, const char *delim, char **saveptr) { @@ -395,6 +477,16 @@ static char *MY_strtok_r(char *str, const char *delim, char **saveptr) #define strtok_r MY_strtok_r #endif +/**************************************************************************** + * 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; @@ -426,6 +518,14 @@ static char *findchar(char *ptr, char ch) return NULL; } +/**************************************************************************** + * Name: getstring + * + * Description: + * Extract a quoted string + * + ****************************************************************************/ + static char *getstring(char *ptr) { char *endptr; @@ -453,6 +553,14 @@ static char *getstring(char *ptr) return ptr; } +/**************************************************************************** + * 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; @@ -467,6 +575,14 @@ static void push_dependency(const char *dependency) g_ndependencies = ndx + 1; } +/**************************************************************************** + * Name: pop_dependency + * + * Description: + * Remove the last, pushed dependency + * + ****************************************************************************/ + static void pop_dependency(void) { int ndx = g_ndependencies - 1; @@ -485,6 +601,14 @@ static void pop_dependency(void) g_ndependencies = ndx; } +/**************************************************************************** + * Name: incr_level + * + * Description: + * Increment the paragraph numbering level + * + ****************************************************************************/ + static void incr_level(void) { int ndx = g_level; @@ -499,6 +623,14 @@ static void incr_level(void) g_level = ndx + 1; } +/**************************************************************************** + * Name: decr_level + * + * Description: + * Decrease the paragraph numbering level. + * + ****************************************************************************/ + static void decr_level(void) { int ndx = g_level; @@ -515,6 +647,14 @@ static void decr_level(void) g_level = ndx; } +/**************************************************************************** + * Name: incr_paranum + * + * Description: + * Increment the paragraph number at this level + * + ****************************************************************************/ + static void incr_paranum(void) { int ndx = g_level - 1; @@ -528,6 +668,14 @@ static void incr_paranum(void) 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]; @@ -548,6 +696,15 @@ static const char *get_paranum(void) 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) @@ -571,7 +728,140 @@ static const char *type2str(enum config_type_e valtype) return "Unknown"; } -static inline char *process_config(FILE *stream, const char *configname, const char *kconfigdir) +/**************************************************************************** + * 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) +{ + char *ptr; + int help_indent = 0; + int indent; + bool blank; + bool done; + bool newpara; + + /* Read each comment line */ + + newpara = true; + for (;;) + { + /* Read the next line of comment text */ + + g_line[LINE_SIZE] = '\0'; + if (!fgets(g_line, LINE_SIZE, 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 (!newpara) + { + body("

\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) + { + body("

\n"); + newpara = false; + } + + body(" %s", ptr); + } + + if (!newpara) + { + body("

\n"); + } +} + +/**************************************************************************** + * Name: process_config + * + * Description: + * Process one configuration variable paragraph + * + ****************************************************************************/ + +static inline char *process_config(FILE *stream, const char *configname, + const char *kconfigdir) { enum token_type_e tokid; struct config_s config; @@ -702,9 +992,9 @@ static inline char *process_config(FILE *stream, const char *configname, const c break; } - /* Break out on the first unhandled token */ + /* Break out on the help token (or the first unhandled token) */ - if (token != NULL) + if (help || token != NULL) { break; } @@ -793,6 +1083,8 @@ static inline char *process_config(FILE *stream, const char *configname, const c if (help) { + process_help(stream); + token = NULL; } else if (!config.cdesc) { @@ -824,6 +1116,134 @@ static inline char *process_config(FILE *stream, const char *configname, const c return token; } +/**************************************************************************** + * Name: process_choice + * + * Description: + * Process a choice paragraph + * + ****************************************************************************/ + +static char *parse_kconfigfile(FILE *stream, const char *kconfigdir); /* Forward reference */ +static inline char *process_choice(FILE *stream, const char *kconfigdir) +{ + enum token_type_e tokid; + struct choice_s choice; + const char *paranum; + char *token; + char *ptr; + + /* Get the choice information */ + + memset(&choice, 0, sizeof(struct choice_s)); + + /* Process each line in the choice */ + + 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_PROMPT: + { + /* Get the prompt string */ + + ptr = getstring(g_lasts); + if (ptr) + { + choice.cprompt = strdup(ptr); + } + + /* Indicate that the line has been consumed */ + + token = NULL; + } + break; + + case TOKEN_DEFAULT: + { + char *value = strtok_r(NULL, " ", &g_lasts); + choice.cdefault = strdup(value); + token = NULL; + } + break; + + default: + { + debug("Unhandled token: %s\n", token); + } + break; + } + + /* Break out on the first unhandled token */ + + if (token != NULL) + { + break; + } + } + } + + paranum = get_paranum(); + output("
  • %s Choice", g_choice_number, paranum); + body("\n

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

  • \n"); + body("\n"); + g_choice_number++; + + /* Show the configuration file */ + + body("

    Kconfig file: %s/Kconfig\n

    ", kconfigdir); + body("

    Options:

    ", kconfigdir); + body("
      \n"); + + /* Free allocated memory */ + + if (choice.cprompt) + { + free(choice.cprompt); + } + + if (choice.cdefault) + { + free(choice.cdefault); + } + + /* Increment the nesting level */ + + incr_level(); + + debug("process_choice: Recursing for TOKEN_CHOICE\n"); + debug(" kconfigdir: %s\n", kconfigdir); + debug(" level: %d\n", g_level); + + /* Then recurse */ + + g_inchoice++; + return parse_kconfigfile(stream, kconfigdir); +} + +/**************************************************************************** + * Name: parse_kconfigfile + * + * Description: + * Parse a Kconfig file. + * + ****************************************************************************/ + static void process_kconfigfile(const char *kconfigdir); /* Forward reference */ static char *parse_kconfigfile(FILE *stream, const char *kconfigdir) { @@ -938,27 +1358,7 @@ static char *parse_kconfigfile(FILE *stream, const char *kconfigdir) case TOKEN_CHOICE: { - paranum = get_paranum(); - body("\n

      %s Choice

      \n
        \n", paranum); - - output("
      • %s Choice
      • \n", - g_choice_number, paranum); - body("\n

        %s Choice

        \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); + token = process_choice(stream, kconfigdir); } break; @@ -1021,6 +1421,14 @@ static char *parse_kconfigfile(FILE *stream, const char *kconfigdir) return token; } +/**************************************************************************** + * Name: process_kconfigfile + * + * Description: + * Open and parse a Kconfig file + * + ****************************************************************************/ + static void process_kconfigfile(const char *kconfigdir) { FILE *stream; @@ -1057,6 +1465,14 @@ static void process_kconfigfile(const char *kconfigdir) * Public Functions ****************************************************************************/ +/**************************************************************************** + * Name: main + * + * Description: + * Program entry point. + * + ****************************************************************************/ + int main(int argc, char **argv, char **envp) { char *outfile; -- cgit v1.2.3