| /**************************************************************************** |
| * tools/kconfig2html.c |
| * |
| * 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 <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 |
| ****************************************************************************/ |
| |
| #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 <apps directory>] {-o <out file>] [<Kconfig root>]\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 <Kconfig directory>. Default: ../apps\n"); |
| error("\t-o : Send output to <out file>. " |
| "Default: Output goes to stdout\n"); |
| error("\t-d : Enable debug output\n"); |
| error("\t-h : Prints this message and exits\n"); |
| error("\t<Kconfig root> " |
| "is the directory containing the root Kconfig file.\n"); |
| error("\t Default <Kconfig directory>: .\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, |
| "<a href=\"#CONFIG_%s\"><code>CONFIG_%s</code></a>", |
| 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("</pre></ul>\n"); |
| preformatted = false; |
| } |
| |
| if (!newpara) |
| { |
| outfunc("</p>\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("<p>\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(" <ul><pre>\n"); |
| newpara = false; |
| preformatted = true; |
| } |
| |
| outfunc("%s\n", htmlize_text(ptr)); |
| } |
| else |
| { |
| if (preformatted) |
| { |
| outfunc("</pre></ul>\n"); |
| preformatted = false; |
| } |
| |
| outfunc(" %s", htmlize_text(ptr)); |
| } |
| } |
| |
| if (!newpara) |
| { |
| outfunc("\n</p>\n"); |
| } |
| |
| if (preformatted) |
| { |
| outfunc("</pre></ul>\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(" <li>\n"); |
| outfunc(" <i>Default</i>: %s\n", item->d_default); |
| |
| /* Output the dependency */ |
| |
| if (item->d_dependency) |
| { |
| outfunc(" <p>\n"); |
| outfunc(" <i>Dependency:</i>\n"); |
| outfunc(" %s\n", htmlize_expression(item->d_dependency)); |
| outfunc(" </p>\n"); |
| } |
| |
| outfunc(" </li>\n"); |
| } |
| else |
| { |
| /* Output a sub-list of defaults. */ |
| |
| outfunc(" <li>\n"); |
| outfunc(" <i>Default Values</i>:\n"); |
| outfunc(" <ul>\n"); |
| |
| for (i = 0; i < defp->d_nitems; i++) |
| { |
| /* Output the Default */ |
| |
| item = &defp->d_item[i]; |
| outfunc(" <li>\n"); |
| outfunc(" <i>Default</i>: %s\n", item->d_default); |
| |
| /* Output the dependency */ |
| |
| if (item->d_dependency) |
| { |
| outfunc(" <p>\n"); |
| outfunc(" <i>Dependency:</i>\n"); |
| outfunc(" %s\n", |
| htmlize_expression(item->d_dependency)); |
| outfunc(" </p>\n"); |
| } |
| } |
| |
| outfunc(" </ul>\n"); |
| outfunc(" </li>\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(" <li><i>Dependencies</i>: %s", g_dependencies[0]); |
| |
| for (i = 1; i < g_ndependencies; i++) |
| { |
| outfunc(", %s\n", g_dependencies[i]); |
| } |
| |
| outfunc("</li>\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("<h3><a name=\"CONFIG_%s\">", 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("<li><a href=\"#CONFIG_%s\">%s <code>CONFIG_%s</code>", |
| config.c_name, paranum, config.c_name); |
| outfunc("%s ", paranum); |
| incr_paranum(); |
| } |
| |
| outfunc("<code>CONFIG_%s</code>", 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("</a></h3>\n"); |
| |
| if (!hidden) |
| { |
| output("</a></li>\n"); |
| } |
| |
| /* Configuration description is indented */ |
| |
| outfunc("<ul>\n"); |
| |
| /* Print the type of the configuration variable */ |
| |
| if (config.c_type != VALUE_NONE) |
| { |
| outfunc(" <li><i>Type</i>: %s</li>\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(" <li><i>Range</i>:\n"); |
| if (config.c_lower) |
| { |
| outfunc(" %s", config.c_lower); |
| } |
| |
| outfunc(" -", config.c_lower); |
| |
| if (config.c_upper) |
| { |
| outfunc(" %s", config.c_upper); |
| } |
| |
| outfunc("</li>\n"); |
| } |
| |
| /* Print the default value of the configuration variable auto-selected by |
| * this setting |
| */ |
| |
| if (config.c_select.s_nvar > 0) |
| { |
| outfunc(" <li><i>Selects</i>: <a href=\"#CONFIG_%s\">" |
| "<code>CONFIG_%s</code></a>", |
| config.c_select.s_varname[0], config.c_select.s_varname[0]); |
| |
| for (i = 1; i < config.c_select.s_nvar; i++) |
| { |
| outfunc(", <a href=\"#CONFIG_%s\"><code>CONFIG_%s</code></a>", |
| config.c_select.s_varname[i], |
| config.c_select.s_varname[i]); |
| } |
| |
| outfunc("</li>\n"); |
| } |
| |
| /* Print the list of dependencies (if any) */ |
| |
| print_dependencies(outfunc); |
| |
| /* Show the configuration file. */ |
| |
| outfunc(" <li><i>Kconfig file</i>: <code>%s/%s</code>\n", |
| kconfigdir, kconfigname); |
| |
| /* Print any help text */ |
| |
| if (help) |
| { |
| process_help(stream, outfunc); |
| token = NULL; |
| } |
| |
| /* End of configuration description */ |
| |
| outfunc("</ul>\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("<li><a href=\"#choice_%d\">%s Choice", g_choice_number, paranum); |
| body("\n<h3><a name=\"choice_%d\">%s Choice", g_choice_number, paranum); |
| |
| if (choice.c_prompt) |
| { |
| output(": %s", choice.c_prompt); |
| body(": %s", choice.c_prompt); |
| } |
| |
| output("</a></li>\n"); |
| body("</a></h3>\n"); |
| g_choice_number++; |
| |
| /* Print the default values of the configuration variable */ |
| |
| body("<ul>\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(" <li><i>Kconfig file</i>: <code>%s/%s</code>\n</li>", |
| kconfigdir, kconfigname); |
| |
| /* Print any help text */ |
| |
| if (help) |
| { |
| process_help(stream, body); |
| token = NULL; |
| } |
| |
| body("</ul>\n"); |
| |
| /* Then show the choice options */ |
| |
| body("<p><b>Choice Options:</b></p>"); |
| body("<ul>\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("<li><a name=\"menu_%d_toc\">" |
| "<a href=\"#menu_%d\">%s Menu: %s</a></a></li>\n", |
| g_menu_number, g_menu_number, paranum, menu.m_name); |
| body("\n<h1><a name=\"menu_%d\">%s Menu: %s</a></h1>\n", |
| g_menu_number, paranum, menu.m_name); |
| } |
| else |
| { |
| output("<li><a name=\"menu_%d_toc\">" |
| "<a href=\"#menu_%d\">%s Menu</a></a></li>\n", |
| g_menu_number, g_menu_number, paranum); |
| body("\n<h1><a name=\"menu_%d\">%s Menu</a></h1>\n", |
| g_menu_number, paranum); |
| } |
| |
| /* Output logic to toggle the contents below the menu in the table of |
| * contents. |
| */ |
| |
| #ifdef USE_JQUERY |
| output("<a id=\"link_%d\" " |
| "href=\"#menu_%d_toc\" onclick=\"toggle('toggle_%d', 'link_%d')\">" |
| "Expand</a>\n", |
| g_menu_number, g_toggle_number, g_toggle_number); |
| #else |
| output("<a href=\"#menu_%d_toc\" onclick=\"toggle('toggle_%d', this)\">" |
| "Expand</a>\n", |
| g_menu_number, g_toggle_number); |
| #endif |
| output("<ul id=\"toggle_%d\" style=\"display:none\">\n", |
| g_toggle_number); |
| |
| g_menu_number++; |
| g_toggle_number++; |
| |
| /* Print the list of dependencies (if any) */ |
| |
| body("<ul>\n"); |
| print_dependencies(body); |
| |
| /* Show the configuration file */ |
| |
| body(" <li><i>Kconfig file</i>: <code>%s/%s</code>\n", |
| kconfigdir, kconfigname); |
| body("</ul>\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("</ul>\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 <ul> |
| */ |
| |
| output("</ul>\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 <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) |
| { |
| 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("<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"); |
| |
| #ifdef USE_JQUERY |
| output("<script src=\"http://code.jquery.com/jquery-1.9.1.js\">" |
| "</script>\n"); |
| output("<script type=\"text/javascript\">\n"); |
| output("function toggle(list_id, link_id) {\n"); |
| output(" var list = $('#' + list_id);\n"); |
| output(" var link = $('#' + link_id);\n"); |
| output(" if (list.is(\":visible\")) {\n"); |
| output(" list.hide();\n"); |
| output(" link.text('Expand');\n"); |
| output(" } else {\n"); |
| output(" list.show();\n"); |
| output(" link.text('Collapse');\n"); |
| output(" }\n"); |
| output("}\n"); |
| output("</script>\n"); |
| #else |
| output("<script type=\"text/javascript\">\n"); |
| output("function toggle(id, link) {\n"); |
| output(" var e = document.getElementById(id);\n"); |
| output(" if (e.style.display == '') {\n"); |
| output(" e.style.display = 'none';\n"); |
| output(" link.innerHTML = 'Expand';\n"); |
| output(" } else {\n"); |
| output(" e.style.display = '';\n"); |
| output(" link.innerHTML = 'Collapse';\n"); |
| output(" }\n"); |
| output("}\n"); |
| output("</script>\n"); |
| #endif |
| |
| output("<hr><hr>\n"); |
| output("<table width =\"100%%\">\n"); |
| output(" <tr bgcolor=\"#e4e4e4\">\n"); |
| output(" <td>\n"); |
| output(" <h1>Table of Contents</h1>\n"); |
| output(" </td>\n"); |
| output(" </tr>\n"); |
| output("</table>\n"); |
| output("<ul>\n"); |
| |
| incr_level(); |
| paranum = get_paranum(); |
| output("<li><a href=\"#menu_%d\">%s Menu: Main</a></li>\n", |
| g_menu_number, paranum); |
| |
| body("<table width =\"100%%\">\n"); |
| body(" <tr bgcolor=\"#e4e4e4\">\n"); |
| body(" <td>\n"); |
| body(" <a name=\"menu_%d\"><h1>%s Menu: Main</h1></a>\n", |
| g_menu_number, paranum); |
| body(" </td>\n"); |
| body(" </tr>\n"); |
| body("</table>\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("<p>\n"); |
| body(" <b>Overview</b>.\n"); |
| body(" The NuttX RTOS is highly configurable.\n"); |
| body(" The NuttX configuration files are maintained using the " |
| "kconfig-frontends</a> tool.\n"); |
| body(" That configuration tool uses <code>Kconfig</code> " |
| "files that can be found through the NuttX source tree.\n"); |
| body(" Each <code>Kconfig</code> 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("</p>\n"); |
| body("<p>\n"); |
| body(" <b>Main Menu</b>.\n"); |
| body(" The normal way to start the NuttX configuration is to enter " |
| "this command line from the NuttX build directory: " |
| "<code>make menuconfig</code>.\n"); |
| body(" Note that NuttX must first be configured <i>before</i> " |
| "this command so that the configuration file (<code>.config</code>) " |
| "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("</p>\n"); |
| body("<p>\n"); |
| body(" <b>Maintenance Note</b>.\n"); |
| body(" This documentation was auto-generated using the " |
| "kconfig2html tool\n"); |
| body(" That tool analyzes the NuttX <code>Kconfig</code> " |
| "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 <code>Kconfig</code> file(s) " |
| "that were used to generated this document and then execute the " |
| "<code>kconfig2html</code> again " |
| "to regenerate the HTML document file.\n"); |
| body("</p>\n"); |
| |
| /* Process the Kconfig files through recursive descent */ |
| |
| process_kconfigfile(g_kconfigroot, "Kconfig"); |
| |
| /* Terminate the table of contents */ |
| |
| output("<li><a href=\"#appendixa\">" |
| "Appendix A: Hidden Configuration Variables</a></li>\n"); |
| output("</ul>\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("<table width =\"100%%\">\n"); |
| output(" <tr bgcolor=\"#e4e4e4\">\n"); |
| output(" <td>\n"); |
| output(" <a name=\"appendixa\">" |
| "<h1>Appendix A: Hidden Configuration Variables</h1></a>\n"); |
| output(" </td>\n"); |
| output(" </tr>\n"); |
| output("</table>\n"); |
| |
| output("<p>\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 " |
| "<code>CONFIG_ARCH_HAVE_</code><i>feature</i>, for example, " |
| "are used only to indicate that the selected architecture supports " |
| "<i>feature</i> and so addition selection associated with " |
| "<i>feature</i> will become accessible to the user.\n"); |
| output("</p>\n"); |
| output("<ul>\n"); |
| |
| /* Close the appendix file and copy it to the output file */ |
| |
| fclose(g_apndxfile); |
| append_file(APNDXFILE_NAME); |
| |
| /* Output trailer boilerplater */ |
| |
| output("</ul>\n"); |
| output("</body>\n"); |
| output("</html>\n"); |
| |
| /* Close the output file (if any) and the temporary file */ |
| |
| if (outfile) |
| { |
| fclose(g_outfile); |
| } |
| |
| return 0; |
| } |