977 lines
29 KiB
C
Executable File
977 lines
29 KiB
C
Executable File
/*
|
|
* OSCam WebIf Wiki generator
|
|
* Parses Wiki markdown files and generates C code with help texts
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <dirent.h>
|
|
|
|
#include "../config.h"
|
|
#ifdef WITH_COMPRESS_WEBIF
|
|
#include "../minilzo/minilzo.h"
|
|
#define USE_COMPRESSION 1
|
|
#endif
|
|
|
|
#define MAX_WIKI_ENTRIES 1024
|
|
#define MAX_PARAM_LEN 64
|
|
#define MAX_CONFIG_LEN 32
|
|
#define MAX_SECTION_LEN 32
|
|
#define MAX_TEXT_LEN 4096
|
|
#define MAX_LINE_LEN 1024
|
|
#define MAX_FLAG_LEN 64
|
|
|
|
/* is_defined.txt content - loaded at runtime */
|
|
static char *is_defined_content = NULL;
|
|
static size_t is_defined_len = 0;
|
|
|
|
/* Safe string copy - always null-terminates */
|
|
static void safe_strncpy(char *dst, const char *src, size_t size)
|
|
{
|
|
if(size == 0)
|
|
{ return; }
|
|
size_t i;
|
|
for(i = 0; i < size - 1 && src[i] != '\0'; i++)
|
|
{
|
|
dst[i] = src[i];
|
|
}
|
|
dst[i] = '\0';
|
|
}
|
|
|
|
static char *wiki_dir = "../wiki/pages/configuration";
|
|
static char *is_defined_file = "is_defined.txt";
|
|
static char *output_wiki_c = "pages_wiki.c";
|
|
static char *output_wiki_h = "pages_wiki.h";
|
|
|
|
struct wiki_entry
|
|
{
|
|
char param[MAX_PARAM_LEN]; /* parameter name, e.g. "serverip" */
|
|
char config[MAX_CONFIG_LEN]; /* config file, e.g. "conf", "server", "user" */
|
|
char section[MAX_SECTION_LEN]; /* section name, e.g. "dvbapi", "anticasc" */
|
|
char text[MAX_TEXT_LEN]; /* help text */
|
|
int text_len;
|
|
int8_t status; /* 0=ok, 1=review, 2=missing */
|
|
#ifdef USE_COMPRESSION
|
|
uint32_t param_ofs;
|
|
uint32_t config_ofs;
|
|
uint32_t section_ofs;
|
|
uint32_t text_ofs;
|
|
#endif
|
|
};
|
|
|
|
struct wiki_data
|
|
{
|
|
unsigned int num;
|
|
struct wiki_entry entries[MAX_WIKI_ENTRIES];
|
|
};
|
|
|
|
static struct wiki_data wiki;
|
|
static FILE *output_file;
|
|
|
|
/* Statistics */
|
|
static unsigned int stats_total = 0;
|
|
static unsigned int stats_included = 0;
|
|
static unsigned int stats_skipped = 0;
|
|
|
|
__attribute__ ((noreturn)) static void die(const char *s, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, s);
|
|
fprintf(stderr, "ERROR: ");
|
|
vfprintf(stderr, s, args);
|
|
if(s[strlen(s) - 1] != '\n')
|
|
{ fprintf(stderr, "\n"); }
|
|
va_end(args);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
static FILE *xfopen(char *filename, char *mode)
|
|
{
|
|
FILE *fh = fopen(filename, mode);
|
|
if(!fh)
|
|
{ die("fopen(%s, %s): %s\n", filename, mode, strerror(errno)); }
|
|
return fh;
|
|
}
|
|
|
|
/* Read file into memory */
|
|
static void readfile(const char *filename, char **data, size_t *data_len)
|
|
{
|
|
FILE *f = fopen(filename, "rb");
|
|
if(!f)
|
|
{
|
|
*data = NULL;
|
|
*data_len = 0;
|
|
return;
|
|
}
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
*data_len = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
*data = malloc(*data_len + 1);
|
|
if(!*data)
|
|
{
|
|
fclose(f);
|
|
*data_len = 0;
|
|
return;
|
|
}
|
|
|
|
if(fread(*data, 1, *data_len, f) != *data_len)
|
|
{
|
|
free(*data);
|
|
*data = NULL;
|
|
*data_len = 0;
|
|
fclose(f);
|
|
return;
|
|
}
|
|
|
|
(*data)[*data_len] = '\0';
|
|
fclose(f);
|
|
}
|
|
|
|
/* Check if a flag is defined in is_defined.txt */
|
|
static bool is_flag_defined(const char *flag)
|
|
{
|
|
if(!is_defined_content || !flag || !flag[0])
|
|
{ return false; }
|
|
|
|
/* Search for flag as whole word (line by line) */
|
|
const char *p = is_defined_content;
|
|
size_t flag_len = strlen(flag);
|
|
|
|
while(*p)
|
|
{
|
|
/* Skip leading whitespace */
|
|
while(*p && isspace((unsigned char)*p))
|
|
{ p++; }
|
|
|
|
if(!*p)
|
|
{ break; }
|
|
|
|
/* Find end of line */
|
|
const char *line_start = p;
|
|
while(*p && *p != '\n' && *p != '\r')
|
|
{ p++; }
|
|
|
|
size_t line_len = p - line_start;
|
|
|
|
/* Compare (trimmed) */
|
|
while(line_len > 0 && isspace((unsigned char)line_start[line_len - 1]))
|
|
{ line_len--; }
|
|
|
|
if(line_len == flag_len && strncmp(line_start, flag, flag_len) == 0)
|
|
{ return true; }
|
|
|
|
/* Skip newline */
|
|
while(*p && (*p == '\n' || *p == '\r'))
|
|
{ p++; }
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Check if entry should be included based on flag and is_defined.txt */
|
|
static bool should_include_entry(const char *flag)
|
|
{
|
|
/* No flag = always include */
|
|
if(!flag || !flag[0])
|
|
{ return true; }
|
|
|
|
/* No is_defined.txt = include all (no filtering) */
|
|
if(!is_defined_content)
|
|
{ return true; }
|
|
|
|
/* Check if flag is defined */
|
|
return is_flag_defined(flag);
|
|
}
|
|
|
|
/* Extract config name from filename, e.g. "oscam.conf.md" -> "conf" */
|
|
static void extract_config_name(const char *filename, char *config, size_t config_size)
|
|
{
|
|
const char *base = filename;
|
|
const char *slash = strrchr(filename, '/');
|
|
if(slash)
|
|
{ base = slash + 1; }
|
|
|
|
/* Skip "oscam." prefix */
|
|
if(strncmp(base, "oscam.", 6) == 0)
|
|
{ base += 6; }
|
|
|
|
/* Copy until ".md" */
|
|
size_t i = 0;
|
|
while(*base && *base != '.' && i < config_size - 1)
|
|
{
|
|
config[i++] = *base++;
|
|
}
|
|
config[i] = '\0';
|
|
}
|
|
|
|
/* Trim whitespace from both ends of string */
|
|
static char *trim(char *str)
|
|
{
|
|
char *end;
|
|
|
|
/* Trim leading space */
|
|
while(isspace((unsigned char)*str))
|
|
{ str++; }
|
|
|
|
if(*str == 0)
|
|
{ return str; }
|
|
|
|
/* Trim trailing space */
|
|
end = str + strlen(str) - 1;
|
|
while(end > str && isspace((unsigned char)*end))
|
|
{ end--; }
|
|
|
|
end[1] = '\0';
|
|
return str;
|
|
}
|
|
|
|
/* Escape string for C string literal */
|
|
static void escape_for_c(const char *src, char *dst, size_t dst_size)
|
|
{
|
|
size_t j = 0;
|
|
for(size_t i = 0; src[i] && j < dst_size - 2; i++)
|
|
{
|
|
switch(src[i])
|
|
{
|
|
case '\n':
|
|
if(j < dst_size - 3) { dst[j++] = '\\'; dst[j++] = 'n'; }
|
|
break;
|
|
case '\r':
|
|
/* Skip CR */
|
|
break;
|
|
case '\t':
|
|
if(j < dst_size - 3) { dst[j++] = '\\'; dst[j++] = 't'; }
|
|
break;
|
|
case '\\':
|
|
if(j < dst_size - 3) { dst[j++] = '\\'; dst[j++] = '\\'; }
|
|
break;
|
|
case '"':
|
|
if(j < dst_size - 3) { dst[j++] = '\\'; dst[j++] = '"'; }
|
|
break;
|
|
default:
|
|
dst[j++] = src[i];
|
|
break;
|
|
}
|
|
}
|
|
dst[j] = '\0';
|
|
}
|
|
|
|
/*
|
|
* Check if line is a parameter heading (### param_name)
|
|
* Returns true if it's a parameter heading, extracts param_name
|
|
*/
|
|
static bool is_param_heading(const char *line, char *param_name, size_t param_size)
|
|
{
|
|
param_name[0] = '\0';
|
|
|
|
/* Skip leading whitespace */
|
|
while(isspace((unsigned char)*line))
|
|
{ line++; }
|
|
|
|
/* Check for ### */
|
|
if(strncmp(line, "###", 3) != 0)
|
|
{ return false; }
|
|
|
|
line += 3;
|
|
|
|
/* Skip whitespace after ### */
|
|
while(isspace((unsigned char)*line))
|
|
{ line++; }
|
|
|
|
/* Extract parameter name (alphanumeric and underscore) */
|
|
size_t i = 0;
|
|
while((isalnum((unsigned char)*line) || *line == '_' || *line == '-') && i < param_size - 1)
|
|
{
|
|
param_name[i++] = *line++;
|
|
}
|
|
param_name[i] = '\0';
|
|
|
|
if(i == 0)
|
|
{ return false; }
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Extract flag from text if it contains "**(requires `FLAG` compilation flag)**"
|
|
* Returns true if flag was found, extracts the flag name
|
|
*/
|
|
static bool extract_flag_from_text(const char *text, char *flag, size_t flag_size)
|
|
{
|
|
flag[0] = '\0';
|
|
|
|
/* Search for "(requires `" pattern */
|
|
const char *pattern = "(requires `";
|
|
const char *p = strstr(text, pattern);
|
|
if(!p)
|
|
{ return false; }
|
|
|
|
p += strlen(pattern);
|
|
|
|
/* Extract flag name until closing backtick */
|
|
size_t i = 0;
|
|
while(*p && *p != '`' && i < flag_size - 1)
|
|
{
|
|
flag[i++] = *p++;
|
|
}
|
|
flag[i] = '\0';
|
|
|
|
return (i > 0 && *p == '`');
|
|
}
|
|
|
|
/*
|
|
* Determine documentation status from text content
|
|
* Returns: 0=ok, 1=review, 2=missing
|
|
*/
|
|
static int8_t get_doc_status(const char *text)
|
|
{
|
|
if(strstr(text, "NEEDS REVIEW"))
|
|
{ return 1; }
|
|
if(strstr(text, "MISSING DOCUMENTATION") || strstr(text, "Missing documentation"))
|
|
{ return 2; }
|
|
return 0;
|
|
}
|
|
|
|
/* Check if line is a section separator (---) */
|
|
static bool is_separator(const char *line)
|
|
{
|
|
/* Skip leading whitespace */
|
|
while(isspace((unsigned char)*line))
|
|
{ line++; }
|
|
|
|
/* Check for --- (at least 3 dashes) */
|
|
int dashes = 0;
|
|
while(*line == '-')
|
|
{
|
|
dashes++;
|
|
line++;
|
|
}
|
|
|
|
/* Rest should be whitespace or end of line */
|
|
while(*line)
|
|
{
|
|
if(!isspace((unsigned char)*line))
|
|
{ return false; }
|
|
line++;
|
|
}
|
|
|
|
return dashes >= 3;
|
|
}
|
|
|
|
/* Check if line is a section heading (## [section] Section) - not parameter
|
|
* If it is, extract the section name (e.g. "dvbapi" from "## [dvbapi] Section")
|
|
* Returns true if it's a section heading
|
|
*/
|
|
static bool is_section_heading(const char *line, char *section_name, size_t section_size)
|
|
{
|
|
if(section_name && section_size > 0)
|
|
{ section_name[0] = '\0'; }
|
|
|
|
/* Skip leading whitespace */
|
|
while(isspace((unsigned char)*line))
|
|
{ line++; }
|
|
|
|
/* ## but not ### */
|
|
if(!(line[0] == '#' && line[1] == '#' && line[2] != '#'))
|
|
{ return false; }
|
|
|
|
/* Skip ## and whitespace */
|
|
line += 2;
|
|
while(isspace((unsigned char)*line))
|
|
{ line++; }
|
|
|
|
/* Check for [section] pattern */
|
|
if(*line == '[' && section_name && section_size > 0)
|
|
{
|
|
line++;
|
|
size_t i = 0;
|
|
while(*line && *line != ']' && i < section_size - 1)
|
|
{
|
|
section_name[i++] = *line++;
|
|
}
|
|
section_name[i] = '\0';
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Add wiki entry if it should be included */
|
|
static void add_wiki_entry(const char *param, const char *config, const char *section, const char *text)
|
|
{
|
|
char flag[MAX_FLAG_LEN];
|
|
|
|
stats_total++;
|
|
|
|
/* Extract flag from text content */
|
|
extract_flag_from_text(text, flag, sizeof(flag));
|
|
|
|
if(!should_include_entry(flag))
|
|
{
|
|
stats_skipped++;
|
|
return;
|
|
}
|
|
|
|
if(wiki.num >= MAX_WIKI_ENTRIES)
|
|
{
|
|
fprintf(stderr, "Warning: Too many wiki entries, skipping %s.%s.%s\n", config, section, param);
|
|
return;
|
|
}
|
|
|
|
struct wiki_entry *e = &wiki.entries[wiki.num];
|
|
safe_strncpy(e->param, param, MAX_PARAM_LEN);
|
|
safe_strncpy(e->config, config, MAX_CONFIG_LEN);
|
|
safe_strncpy(e->section, section ? section : "", MAX_SECTION_LEN);
|
|
e->status = get_doc_status(text);
|
|
|
|
char *text_copy = strdup(text);
|
|
if(text_copy)
|
|
{
|
|
char *trimmed = trim(text_copy);
|
|
escape_for_c(trimmed, e->text, MAX_TEXT_LEN);
|
|
free(text_copy);
|
|
}
|
|
else
|
|
{
|
|
e->text[0] = '\0';
|
|
}
|
|
|
|
e->text_len = strlen(e->text);
|
|
wiki.num++;
|
|
stats_included++;
|
|
}
|
|
|
|
/* Parse a single markdown file */
|
|
static void parse_wiki_file(const char *filepath)
|
|
{
|
|
FILE *f = fopen(filepath, "r");
|
|
if(!f)
|
|
{
|
|
fprintf(stderr, "Warning: Cannot open %s: %s\n", filepath, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
char config[MAX_CONFIG_LEN];
|
|
extract_config_name(filepath, config, sizeof(config));
|
|
|
|
char line[MAX_LINE_LEN];
|
|
char current_param[MAX_PARAM_LEN] = "";
|
|
char current_section[MAX_SECTION_LEN] = "";
|
|
char current_text[MAX_TEXT_LEN] = "";
|
|
int text_pos = 0;
|
|
bool in_param = false;
|
|
|
|
while(fgets(line, sizeof(line), f))
|
|
{
|
|
char param_name[MAX_PARAM_LEN];
|
|
char section_name[MAX_SECTION_LEN];
|
|
|
|
if(is_param_heading(line, param_name, sizeof(param_name)))
|
|
{
|
|
/* Save previous parameter if exists */
|
|
if(in_param && current_param[0] && text_pos > 0)
|
|
{
|
|
current_text[text_pos] = '\0';
|
|
add_wiki_entry(current_param, config, current_section, current_text);
|
|
}
|
|
|
|
/* Start new parameter */
|
|
safe_strncpy(current_param, param_name, MAX_PARAM_LEN);
|
|
text_pos = 0;
|
|
current_text[0] = '\0';
|
|
in_param = true;
|
|
}
|
|
else if(is_section_heading(line, section_name, sizeof(section_name)))
|
|
{
|
|
/* Save current parameter before section change */
|
|
if(in_param && current_param[0] && text_pos > 0)
|
|
{
|
|
current_text[text_pos] = '\0';
|
|
add_wiki_entry(current_param, config, current_section, current_text);
|
|
}
|
|
in_param = false;
|
|
current_param[0] = '\0';
|
|
text_pos = 0;
|
|
|
|
/* Update current section if extracted */
|
|
if(section_name[0])
|
|
{
|
|
safe_strncpy(current_section, section_name, MAX_SECTION_LEN);
|
|
}
|
|
}
|
|
else if(in_param)
|
|
{
|
|
/* Check for end of parameter section (separator) */
|
|
if(is_separator(line))
|
|
{
|
|
/* Save current parameter */
|
|
if(current_param[0] && text_pos > 0)
|
|
{
|
|
current_text[text_pos] = '\0';
|
|
add_wiki_entry(current_param, config, current_section, current_text);
|
|
}
|
|
in_param = false;
|
|
current_param[0] = '\0';
|
|
text_pos = 0;
|
|
}
|
|
else
|
|
{
|
|
/* Append line to current text */
|
|
int line_len = strlen(line);
|
|
if(text_pos + line_len < MAX_TEXT_LEN - 1)
|
|
{
|
|
memcpy(current_text + text_pos, line, line_len);
|
|
text_pos += line_len;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Save last parameter if file ends without separator */
|
|
if(in_param && current_param[0] && text_pos > 0)
|
|
{
|
|
current_text[text_pos] = '\0';
|
|
add_wiki_entry(current_param, config, current_section, current_text);
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
/* Scan wiki directory for markdown files */
|
|
static void scan_wiki_directory(const char *dirpath)
|
|
{
|
|
DIR *dir = opendir(dirpath);
|
|
if(!dir)
|
|
{
|
|
fprintf(stderr, "Warning: Cannot open wiki directory %s: %s\n", dirpath, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
unsigned int file_count = 0;
|
|
struct dirent *entry;
|
|
while((entry = readdir(dir)) != NULL)
|
|
{
|
|
/* Skip hidden files and directories */
|
|
if(entry->d_name[0] == '.')
|
|
{ continue; }
|
|
|
|
/* Check for .md extension */
|
|
size_t len = strlen(entry->d_name);
|
|
if(len < 4 || strcmp(entry->d_name + len - 3, ".md") != 0)
|
|
{ continue; }
|
|
|
|
/* Build full path */
|
|
char filepath[512];
|
|
snprintf(filepath, sizeof(filepath), "%s/%s", dirpath, entry->d_name);
|
|
|
|
parse_wiki_file(filepath);
|
|
file_count++;
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
printf("WIKI\tParsed %u markdown files in %s\n", file_count, dirpath);
|
|
}
|
|
|
|
/* Generate pages_wiki.h */
|
|
static void generate_header(void)
|
|
{
|
|
output_file = xfopen(output_wiki_h, "w");
|
|
|
|
fprintf(output_file, "/*\n");
|
|
fprintf(output_file, " * OSCam WebIf Wiki data - AUTO GENERATED, DO NOT EDIT!\n");
|
|
fprintf(output_file, " * Generated by wiki_gen from wiki markdown files\n");
|
|
fprintf(output_file, " */\n");
|
|
fprintf(output_file, "#ifndef WEBIF_PAGES_WIKI_H_\n");
|
|
fprintf(output_file, "#define WEBIF_PAGES_WIKI_H_\n");
|
|
fprintf(output_file, "\n");
|
|
fprintf(output_file, "#ifdef WEBIF_WIKI\n");
|
|
fprintf(output_file, "\n");
|
|
#ifdef USE_COMPRESSION
|
|
fprintf(output_file, "#define COMPRESSED_WIKI 1\n\n");
|
|
fprintf(output_file, "struct wiki_entry {\n");
|
|
fprintf(output_file, "\tuint32_t param_ofs;\n");
|
|
fprintf(output_file, "\tuint32_t config_ofs;\n");
|
|
fprintf(output_file, "\tuint32_t section_ofs;\n");
|
|
fprintf(output_file, "\tuint32_t text_ofs;\n");
|
|
fprintf(output_file, "\tint8_t status;\n");
|
|
fprintf(output_file, "};\n");
|
|
fprintf(output_file, "\n");
|
|
fprintf(output_file, "int32_t wiki_count(void);\n");
|
|
fprintf(output_file, "const struct wiki_entry *wiki_get_entries(void);\n");
|
|
fprintf(output_file, "const char *wiki_get_help(const char *config, const char *section, const char *param);\n");
|
|
fprintf(output_file, "int8_t wiki_get_status(const char *config, const char *section, const char *param);\n");
|
|
fprintf(output_file, "void wiki_get_data(const char **data, size_t *data_len, size_t *data_olen);\n");
|
|
fprintf(output_file, "char *wiki_get_decompressed_data(void);\n");
|
|
fprintf(output_file, "void webif_wiki_prepare(void);\n");
|
|
fprintf(output_file, "void webif_wiki_free(void);\n");
|
|
#else
|
|
fprintf(output_file, "struct wiki_entry {\n");
|
|
fprintf(output_file, "\tconst char *param;\n");
|
|
fprintf(output_file, "\tconst char *config;\n");
|
|
fprintf(output_file, "\tconst char *section;\n");
|
|
fprintf(output_file, "\tconst char *text;\n");
|
|
fprintf(output_file, "\tint8_t status;\n");
|
|
fprintf(output_file, "};\n");
|
|
fprintf(output_file, "\n");
|
|
fprintf(output_file, "int32_t wiki_count(void);\n");
|
|
fprintf(output_file, "const struct wiki_entry *wiki_get_entries(void);\n");
|
|
fprintf(output_file, "const char *wiki_get_help(const char *config, const char *section, const char *param);\n");
|
|
fprintf(output_file, "int8_t wiki_get_status(const char *config, const char *section, const char *param);\n");
|
|
fprintf(output_file, "void webif_wiki_prepare(void);\n");
|
|
fprintf(output_file, "void webif_wiki_free(void);\n");
|
|
#endif
|
|
fprintf(output_file, "\n");
|
|
fprintf(output_file, "#endif /* WEBIF_WIKI */\n");
|
|
fprintf(output_file, "\n");
|
|
fprintf(output_file, "#endif /* WEBIF_PAGES_WIKI_H_ */\n");
|
|
|
|
fclose(output_file);
|
|
}
|
|
|
|
#ifdef USE_COMPRESSION
|
|
#define HEAP_ALLOC(var, size) \
|
|
lzo_align_t __LZO_MMODEL var [ ((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t) ]
|
|
|
|
static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);
|
|
|
|
static void dump_cbinary(char *var_name, uint8_t *buf, size_t buf_len, size_t obuf_len)
|
|
{
|
|
fprintf(output_file, "static const char *%s = \"", var_name);
|
|
size_t i;
|
|
for(i = 0; i < buf_len; i++)
|
|
{
|
|
fprintf(output_file, "\\x%02x", buf[i]);
|
|
}
|
|
fprintf(output_file, "\";\n");
|
|
fprintf(output_file, "static const size_t %s_len = %zu;\n" , var_name, buf_len);
|
|
fprintf(output_file, "static const size_t %s_olen = %zu;\n\n", var_name, obuf_len);
|
|
}
|
|
#endif
|
|
|
|
/* Generate pages_wiki.c */
|
|
static void generate_source(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
output_file = xfopen(output_wiki_c, "w");
|
|
|
|
fprintf(output_file, "/*\n");
|
|
fprintf(output_file, " * OSCam WebIf Wiki data - AUTO GENERATED, DO NOT EDIT!\n");
|
|
fprintf(output_file, " * Generated by wiki_gen from wiki markdown files\n");
|
|
fprintf(output_file, " */\n");
|
|
fprintf(output_file, "#define MODULE_LOG_PREFIX \"webif\"\n");
|
|
fprintf(output_file, "#include \"../globals.h\"\n");
|
|
fprintf(output_file, "\n");
|
|
fprintf(output_file, "#if defined(WEBIF) && defined(WEBIF_WIKI)\n");
|
|
fprintf(output_file, "\n");
|
|
fprintf(output_file, "#include \"pages_wiki.h\"\n");
|
|
fprintf(output_file, "#include <string.h>\n");
|
|
#ifdef USE_COMPRESSION
|
|
fprintf(output_file, "#ifdef WITH_COMPRESS_WEBIF\n");
|
|
fprintf(output_file, "#include \"../minilzo/minilzo.h\"\n");
|
|
fprintf(output_file, "#endif\n");
|
|
#endif
|
|
fprintf(output_file, "\n");
|
|
|
|
#ifdef USE_COMPRESSION
|
|
/* Calculate positions */
|
|
uint32_t cur_pos = 0;
|
|
#define align_up(val, align) (val += (align - val % align))
|
|
for(i = 0; i < wiki.num; i++)
|
|
{
|
|
struct wiki_entry *e = &wiki.entries[i];
|
|
e->param_ofs = cur_pos;
|
|
cur_pos += strlen(e->param) + 1;
|
|
align_up(cur_pos, sizeof(void *));
|
|
e->config_ofs = cur_pos;
|
|
cur_pos += strlen(e->config) + 1;
|
|
align_up(cur_pos, sizeof(void *));
|
|
e->section_ofs = cur_pos;
|
|
cur_pos += strlen(e->section) + 1;
|
|
align_up(cur_pos, sizeof(void *));
|
|
e->text_ofs = cur_pos;
|
|
cur_pos += strlen(e->text) + 1;
|
|
align_up(cur_pos, sizeof(void *));
|
|
}
|
|
|
|
/* Allocate and populate data buffer */
|
|
uint32_t data_len = cur_pos;
|
|
if(!data_len)
|
|
{ die("No wiki entries"); }
|
|
uint8_t *data = calloc(1, data_len);
|
|
if(!data)
|
|
{ die("Can't alloc %u bytes", data_len); }
|
|
for(i = 0; i < wiki.num; i++)
|
|
{
|
|
struct wiki_entry *e = &wiki.entries[i];
|
|
memcpy(data + e->param_ofs, e->param, strlen(e->param));
|
|
memcpy(data + e->config_ofs, e->config, strlen(e->config));
|
|
memcpy(data + e->section_ofs, e->section, strlen(e->section));
|
|
memcpy(data + e->text_ofs, e->text, strlen(e->text));
|
|
}
|
|
|
|
/* Compress data */
|
|
lzo_uint in_len = data_len;
|
|
lzo_uint out_len = data_len + data_len / 16 + 64 + 3;
|
|
uint8_t *out = malloc(out_len);
|
|
if(!out)
|
|
{ die("Can't alloc %zu bytes", out_len); }
|
|
|
|
if(lzo_init() != LZO_E_OK)
|
|
{
|
|
fprintf(stderr, "internal error - lzo_init() failed!\n");
|
|
free(out);
|
|
free(data);
|
|
exit(3);
|
|
}
|
|
|
|
int r = lzo1x_1_compress(data, in_len, out, &out_len, wrkmem);
|
|
if(r == LZO_E_OK)
|
|
{
|
|
printf("GEN\tCompressed %lu wiki entry bytes into %lu bytes. %ld saved bytes (%.1f%%).\n",
|
|
(unsigned long)in_len, (unsigned long)out_len,
|
|
(long)in_len - (long)out_len, 100.0 - ((float)out_len / in_len) * 100);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "compression failed: %d\n", r);
|
|
free(out);
|
|
free(data);
|
|
exit(2);
|
|
}
|
|
|
|
dump_cbinary("wiki_data", out, out_len, data_len);
|
|
free(out);
|
|
free(data);
|
|
#endif
|
|
|
|
/* Generate wiki entries array - all entries are unconditional (filtering done at gen time) */
|
|
fprintf(output_file, "static const struct wiki_entry wiki_entries[] = {\n");
|
|
for(i = 0; i < wiki.num; i++)
|
|
{
|
|
struct wiki_entry *e = &wiki.entries[i];
|
|
#ifdef USE_COMPRESSION
|
|
fprintf(output_file, "\t{ .param_ofs=%5u, .config_ofs=%5u, .section_ofs=%5u, .text_ofs=%5u, .status=%d }, /* %s.%s.%s */\n",
|
|
e->param_ofs, e->config_ofs, e->section_ofs, e->text_ofs, e->status, e->config, e->section, e->param);
|
|
#else
|
|
fprintf(output_file, "\t{ .param = \"%s\", .config = \"%s\", .section = \"%s\", .text = \"%s\", .status = %d },\n",
|
|
e->param, e->config, e->section, e->text, e->status);
|
|
#endif
|
|
}
|
|
fprintf(output_file, "};\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
/* Generate accessor functions */
|
|
fprintf(output_file, "int32_t wiki_count(void)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\treturn sizeof(wiki_entries) / sizeof(struct wiki_entry);\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
fprintf(output_file, "const struct wiki_entry *wiki_get_entries(void)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\treturn wiki_entries;\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
#ifdef USE_COMPRESSION
|
|
fprintf(output_file, "static char *wiki_data_decompressed = NULL;\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
fprintf(output_file, "void wiki_get_data(const char **data, size_t *data_len, size_t *data_olen)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\t*data = wiki_data;\n");
|
|
fprintf(output_file, "\t*data_len = wiki_data_len;\n");
|
|
fprintf(output_file, "\t*data_olen = wiki_data_olen;\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
fprintf(output_file, "void webif_wiki_prepare(void)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\tif(wiki_data_decompressed) return;\n");
|
|
fprintf(output_file, "\tconst char *data;\n");
|
|
fprintf(output_file, "\tsize_t data_len, data_olen;\n");
|
|
fprintf(output_file, "\twiki_get_data(&data, &data_len, &data_olen);\n");
|
|
fprintf(output_file, "\twiki_data_decompressed = malloc(data_olen);\n");
|
|
fprintf(output_file, "\tif(!wiki_data_decompressed)\n");
|
|
fprintf(output_file, "\t{\n");
|
|
fprintf(output_file, "\t\tcs_log(\"wiki: cannot allocate %%zu bytes for decompression\", data_olen);\n");
|
|
fprintf(output_file, "\t\treturn;\n");
|
|
fprintf(output_file, "\t}\n");
|
|
fprintf(output_file, "\tlzo_uint new_len = data_olen;\n");
|
|
fprintf(output_file, "\tint r = lzo1x_decompress_safe((uint8_t *)data, data_len, (uint8_t *)wiki_data_decompressed, &new_len, NULL);\n");
|
|
fprintf(output_file, "\tif(r == LZO_E_OK && new_len == data_olen)\n");
|
|
fprintf(output_file, "\t{\n");
|
|
fprintf(output_file, "\t\tcs_log(\"wiki: decompressed %%zu bytes back into %%zu bytes\", data_len, data_olen);\n");
|
|
fprintf(output_file, "\t}\n");
|
|
fprintf(output_file, "\telse\n");
|
|
fprintf(output_file, "\t{\n");
|
|
fprintf(output_file, "\t\tcs_log(\"wiki: decompression failed: %%d\", r);\n");
|
|
fprintf(output_file, "\t\tfree(wiki_data_decompressed);\n");
|
|
fprintf(output_file, "\t\twiki_data_decompressed = NULL;\n");
|
|
fprintf(output_file, "\t}\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
fprintf(output_file, "void webif_wiki_free(void)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\tif(wiki_data_decompressed)\n");
|
|
fprintf(output_file, "\t{\n");
|
|
fprintf(output_file, "\t\tfree(wiki_data_decompressed);\n");
|
|
fprintf(output_file, "\t\twiki_data_decompressed = NULL;\n");
|
|
fprintf(output_file, "\t}\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
fprintf(output_file, "char *wiki_get_decompressed_data(void)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\treturn wiki_data_decompressed;\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
fprintf(output_file, "const char *wiki_get_help(const char *config, const char *section, const char *param)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\tif(!wiki_data_decompressed) return NULL;\n");
|
|
fprintf(output_file, "\tint32_t i, count = wiki_count();\n");
|
|
fprintf(output_file, "\tconst char *fallback = NULL;\n");
|
|
fprintf(output_file, "\tfor(i = 0; i < count; i++)\n");
|
|
fprintf(output_file, "\t{\n");
|
|
fprintf(output_file, "\t\tconst char *e_config = wiki_data_decompressed + wiki_entries[i].config_ofs;\n");
|
|
fprintf(output_file, "\t\tconst char *e_section = wiki_data_decompressed + wiki_entries[i].section_ofs;\n");
|
|
fprintf(output_file, "\t\tconst char *e_param = wiki_data_decompressed + wiki_entries[i].param_ofs;\n");
|
|
fprintf(output_file, "\t\tif(strcmp(e_config, config) == 0 && strcmp(e_param, param) == 0)\n");
|
|
fprintf(output_file, "\t\t{\n");
|
|
fprintf(output_file, "\t\t\tif(section && section[0] && strcmp(e_section, section) == 0)\n");
|
|
fprintf(output_file, "\t\t\t\treturn wiki_data_decompressed + wiki_entries[i].text_ofs;\n");
|
|
fprintf(output_file, "\t\t\tif(!fallback)\n");
|
|
fprintf(output_file, "\t\t\t\tfallback = wiki_data_decompressed + wiki_entries[i].text_ofs;\n");
|
|
fprintf(output_file, "\t\t}\n");
|
|
fprintf(output_file, "\t}\n");
|
|
fprintf(output_file, "\treturn fallback;\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
/* Generate wiki_get_status function */
|
|
fprintf(output_file, "int8_t wiki_get_status(const char *config, const char *section, const char *param)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\tif(!wiki_data_decompressed) return -1;\n");
|
|
fprintf(output_file, "\tint32_t i, count = wiki_count();\n");
|
|
fprintf(output_file, "\tint8_t fallback = -1;\n");
|
|
fprintf(output_file, "\tfor(i = 0; i < count; i++)\n");
|
|
fprintf(output_file, "\t{\n");
|
|
fprintf(output_file, "\t\tconst char *e_config = wiki_data_decompressed + wiki_entries[i].config_ofs;\n");
|
|
fprintf(output_file, "\t\tconst char *e_section = wiki_data_decompressed + wiki_entries[i].section_ofs;\n");
|
|
fprintf(output_file, "\t\tconst char *e_param = wiki_data_decompressed + wiki_entries[i].param_ofs;\n");
|
|
fprintf(output_file, "\t\tif(strcmp(e_config, config) == 0 && strcmp(e_param, param) == 0)\n");
|
|
fprintf(output_file, "\t\t{\n");
|
|
fprintf(output_file, "\t\t\tif(section && section[0] && strcmp(e_section, section) == 0)\n");
|
|
fprintf(output_file, "\t\t\t\treturn wiki_entries[i].status;\n");
|
|
fprintf(output_file, "\t\t\tif(fallback < 0)\n");
|
|
fprintf(output_file, "\t\t\t\tfallback = wiki_entries[i].status;\n");
|
|
fprintf(output_file, "\t\t}\n");
|
|
fprintf(output_file, "\t}\n");
|
|
fprintf(output_file, "\treturn fallback;\n");
|
|
fprintf(output_file, "}\n");
|
|
#else
|
|
fprintf(output_file, "void webif_wiki_prepare(void)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\tcs_log(\"wiki: %%d help entries loaded\", wiki_count());\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
fprintf(output_file, "void webif_wiki_free(void)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\t/* nothing to free in uncompressed mode */\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
fprintf(output_file, "const char *wiki_get_help(const char *config, const char *section, const char *param)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\tint32_t i;\n");
|
|
fprintf(output_file, "\tint32_t count = wiki_count();\n");
|
|
fprintf(output_file, "\tconst char *fallback = NULL;\n");
|
|
fprintf(output_file, "\tfor(i = 0; i < count; i++)\n");
|
|
fprintf(output_file, "\t{\n");
|
|
fprintf(output_file, "\t\tif(strcmp(wiki_entries[i].config, config) == 0 &&\n");
|
|
fprintf(output_file, "\t\t strcmp(wiki_entries[i].param, param) == 0)\n");
|
|
fprintf(output_file, "\t\t{\n");
|
|
fprintf(output_file, "\t\t\tif(section && section[0] && strcmp(wiki_entries[i].section, section) == 0)\n");
|
|
fprintf(output_file, "\t\t\t\treturn wiki_entries[i].text;\n");
|
|
fprintf(output_file, "\t\t\tif(!fallback)\n");
|
|
fprintf(output_file, "\t\t\t\tfallback = wiki_entries[i].text;\n");
|
|
fprintf(output_file, "\t\t}\n");
|
|
fprintf(output_file, "\t}\n");
|
|
fprintf(output_file, "\treturn fallback;\n");
|
|
fprintf(output_file, "}\n");
|
|
fprintf(output_file, "\n");
|
|
|
|
/* Generate wiki_get_status function */
|
|
fprintf(output_file, "int8_t wiki_get_status(const char *config, const char *section, const char *param)\n");
|
|
fprintf(output_file, "{\n");
|
|
fprintf(output_file, "\tint32_t i;\n");
|
|
fprintf(output_file, "\tint32_t count = wiki_count();\n");
|
|
fprintf(output_file, "\tint8_t fallback = -1;\n");
|
|
fprintf(output_file, "\tfor(i = 0; i < count; i++)\n");
|
|
fprintf(output_file, "\t{\n");
|
|
fprintf(output_file, "\t\tif(strcmp(wiki_entries[i].config, config) == 0 &&\n");
|
|
fprintf(output_file, "\t\t strcmp(wiki_entries[i].param, param) == 0)\n");
|
|
fprintf(output_file, "\t\t{\n");
|
|
fprintf(output_file, "\t\t\tif(section && section[0] && strcmp(wiki_entries[i].section, section) == 0)\n");
|
|
fprintf(output_file, "\t\t\t\treturn wiki_entries[i].status;\n");
|
|
fprintf(output_file, "\t\t\tif(fallback < 0)\n");
|
|
fprintf(output_file, "\t\t\t\tfallback = wiki_entries[i].status;\n");
|
|
fprintf(output_file, "\t\t}\n");
|
|
fprintf(output_file, "\t}\n");
|
|
fprintf(output_file, "\treturn fallback;\n");
|
|
fprintf(output_file, "}\n");
|
|
#endif
|
|
|
|
fprintf(output_file, "\n");
|
|
fprintf(output_file, "#endif /* WEBIF && WEBIF_WIKI */\n");
|
|
|
|
fclose(output_file);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
/* Allow overriding wiki directory via command line */
|
|
if(argc > 1)
|
|
{ wiki_dir = argv[1]; }
|
|
|
|
/* Load is_defined.txt for filtering */
|
|
readfile(is_defined_file, &is_defined_content, &is_defined_len);
|
|
|
|
scan_wiki_directory(wiki_dir);
|
|
|
|
if(wiki.num == 0)
|
|
{
|
|
fprintf(stderr, "Warning: No wiki entries found in %s\n", wiki_dir);
|
|
}
|
|
else
|
|
{
|
|
printf("WIKI\tProcessed %u entries: %u included, %u skipped (disabled in config)\n",
|
|
stats_total, stats_included, stats_skipped);
|
|
}
|
|
|
|
generate_header();
|
|
|
|
generate_source();
|
|
|
|
if(is_defined_content)
|
|
{ free(is_defined_content); }
|
|
|
|
return 0;
|
|
}
|