2778 lines
67 KiB
C
2778 lines
67 KiB
C
/****************************************************************************
|
|
* tools/kconfig2html.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#define _GNU_SOURCE 1
|
|
#include <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;
|
|
}
|