mirror of
https://github.com/KingDuckZ/kamokan.git
synced 2024-11-23 00:33:44 +00:00
Add source-highlight to docs.
It's useful as reference.
This commit is contained in:
parent
d449781c40
commit
0323fb0ba6
1 changed files with 594 additions and 0 deletions
594
docs/source-highlight.cc
Normal file
594
docs/source-highlight.cc
Normal file
|
@ -0,0 +1,594 @@
|
|||
/*
|
||||
* Copyright (C) 1999-2009 Lorenzo Bettini <http://www.lorenzobettini.it>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
|
||||
// this is glib related so don't use it when compiling with qmake
|
||||
#include "progname.h"
|
||||
|
||||
#else
|
||||
|
||||
#define set_program_name(x) ;
|
||||
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "srchilite/sourcehighlight.h"
|
||||
|
||||
#include "srchilite/fileutil.h"
|
||||
#include "srchilite/verbosity.h"
|
||||
#include "srchilite/langmap.h"
|
||||
#include "srchilite/languageinfer.h"
|
||||
#include "srchilite/utils.h"
|
||||
#include "srchilite/highlightbuilderexception.h"
|
||||
#include "srchilite/parserexception.h"
|
||||
#include "srchilite/debuglistener.h"
|
||||
#include "srchilite/ctagsmanager.h"
|
||||
#include "srchilite/stopwatch.h"
|
||||
#include "srchilite/lineranges.h"
|
||||
#include "srchilite/regexranges.h"
|
||||
#include "srchilite/versions.h"
|
||||
#include "srchilite/settings.h"
|
||||
|
||||
#include "cmdline.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace srchilite;
|
||||
|
||||
/**
|
||||
* how language inference should be made: NOINFERENCE = no inference at all,
|
||||
* INFERFIRST = inference has priority (e.g., if --infer-lang is specified),
|
||||
* INFERATLAST = try inference as the last chance
|
||||
*/
|
||||
enum InferPolicy {
|
||||
NOINFERENCE = 0, INFERFIRST, INFERATLAST
|
||||
};
|
||||
|
||||
static void print_copyright();
|
||||
static void print_reportbugs();
|
||||
static void print_version();
|
||||
static string inferLang(const string &inputFileName);
|
||||
static string getMappedLang(const string &s, LangMap &langmap);
|
||||
static void printError(const string &s);
|
||||
static void print_cgi_header();
|
||||
|
||||
/**
|
||||
* Exits for a generic error (i.e., file not found)
|
||||
*/
|
||||
static void exitError(const string &s);
|
||||
|
||||
static string getLangFileName(InferPolicy infer, const string &inputFileName,
|
||||
const string &langFileName, const string &langName, LangMap &langMap);
|
||||
|
||||
static bool verbose = false;
|
||||
static bool failsafe = false;
|
||||
|
||||
#ifdef BUILD_AS_CGI
|
||||
#include "envmapper.h"
|
||||
#endif // BUILD_AS_CGI
|
||||
/**
|
||||
* Print progress status information (provided --quiet is not specified)
|
||||
* @param message
|
||||
*/
|
||||
#define PROGRESSINFO(message) if (!args_info.quiet_given) cerr << message;
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
set_program_name(argv[0]);
|
||||
gengetopt_args_info args_info; // command line structure
|
||||
bool is_cgi = false;
|
||||
|
||||
#ifdef BUILD_AS_CGI
|
||||
// map environment to parameters if used as CGI
|
||||
char **temp_argv;
|
||||
temp_argv = map_environment(&argc, argv);
|
||||
is_cgi = temp_argv != argv;
|
||||
argv = temp_argv;
|
||||
#endif // BUILD_AS_CGI
|
||||
if (cmdline_parser(argc, argv, &args_info) != 0) {
|
||||
// calls cmdline parser. The user gived bag args if it doesn't return -1
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (args_info.version_given) {
|
||||
print_version();
|
||||
print_copyright();
|
||||
cmdline_parser_free(&args_info);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (args_info.help_given || args_info.detailed_help_given) {
|
||||
cout << "GNU ";
|
||||
if (args_info.detailed_help_given) {
|
||||
cmdline_parser_print_detailed_help();
|
||||
} else {
|
||||
cmdline_parser_print_help();
|
||||
}
|
||||
print_reportbugs();
|
||||
cmdline_parser_free(&args_info);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// set possible line ranges
|
||||
boost::shared_ptr<LineRanges> lineRanges;
|
||||
if (args_info.line_range_given) {
|
||||
lineRanges = boost::shared_ptr<LineRanges>(new LineRanges);
|
||||
for (unsigned int i = 0; i < args_info.line_range_given; ++i) {
|
||||
if (lineRanges->addRange(args_info.line_range_arg[i]) != NO_ERROR) {
|
||||
string invalid_range = args_info.line_range_arg[i];
|
||||
cmdline_parser_free(&args_info);
|
||||
exitError("invalid line range: " + invalid_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set possible regex ranges
|
||||
boost::shared_ptr<RegexRanges> regexRanges;
|
||||
if (args_info.regex_range_given) {
|
||||
regexRanges = boost::shared_ptr<RegexRanges>(new RegexRanges);
|
||||
for (unsigned int i = 0; i < args_info.regex_range_given; ++i) {
|
||||
if (!regexRanges->addRegexRange(args_info.regex_range_arg[i])) {
|
||||
string invalid_range = args_info.regex_range_arg[i];
|
||||
cmdline_parser_free(&args_info);
|
||||
exitError("invalid regex range: " + invalid_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args_info.range_context_given) {
|
||||
lineRanges->setContextLines(args_info.range_context_arg);
|
||||
}
|
||||
|
||||
// if a stopwatch is created, when it is deleted (automatically
|
||||
// since we're using a shared pointer, it will print the
|
||||
// elapsed seconds.
|
||||
boost::shared_ptr<StopWatch> stopwatch;
|
||||
if (args_info.statistics_given) {
|
||||
stopwatch = boost::shared_ptr<StopWatch>(new StopWatch);
|
||||
}
|
||||
|
||||
verbose = args_info.verbose_given;
|
||||
failsafe = args_info.failsafe_given;
|
||||
|
||||
string inputFile;
|
||||
string outputFile;
|
||||
string srcLang;
|
||||
string outputFormat;
|
||||
string langFile;
|
||||
string outLangFile;
|
||||
string dataDir;
|
||||
string outputDir;
|
||||
|
||||
Verbosity::setVerbosity(verbose);
|
||||
|
||||
if (args_info.input_given)
|
||||
inputFile = args_info.input_arg;
|
||||
|
||||
if (args_info.output_given)
|
||||
outputFile = args_info.output_arg;
|
||||
|
||||
// for cgi force output to standard output
|
||||
if (is_cgi)
|
||||
outputFile = "STDOUT";
|
||||
|
||||
if (args_info.src_lang_given)
|
||||
srcLang = args_info.src_lang_arg;
|
||||
|
||||
if (args_info.lang_def_given)
|
||||
langFile = args_info.lang_def_arg;
|
||||
|
||||
if (args_info.outlang_def_given)
|
||||
outLangFile = args_info.outlang_def_arg;
|
||||
|
||||
// the default for output format is html
|
||||
outputFormat = args_info.out_format_arg;
|
||||
|
||||
if (args_info.data_dir_given) {
|
||||
dataDir = args_info.data_dir_arg;
|
||||
}
|
||||
|
||||
if (args_info.output_dir_given) {
|
||||
outputDir = args_info.output_dir_arg;
|
||||
}
|
||||
|
||||
/*
|
||||
the starting default path to search for files is computed at
|
||||
run-time: it is
|
||||
the path of the binary + ".." + RELATIVEDATADIR
|
||||
this should make the package relocable (i.e., not stuck
|
||||
with a fixed installation directory).
|
||||
Of course, the GNU standards for installation directories
|
||||
should be followed, but this is not a problem if you use
|
||||
configure and make install features.
|
||||
If no path is specified in the running program we go back to
|
||||
the absolute datadir.
|
||||
*/
|
||||
// this is defined in fileutil.cc
|
||||
string prefix_dir = get_file_path(argv[0]);
|
||||
if (prefix_dir.size())
|
||||
start_path = get_file_path(argv[0]) + RELATIVEDATADIR;
|
||||
else
|
||||
start_path = Settings::retrieveDataDir();
|
||||
|
||||
// if datadir is not specified, we rely on start_path?
|
||||
|
||||
try {
|
||||
// initialize map files
|
||||
LangMap langmap(dataDir, args_info.lang_map_arg);
|
||||
|
||||
// just print the input language list and associations
|
||||
if (args_info.lang_list_given) {
|
||||
langmap.open();
|
||||
langmap.print();
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
LangMap outlangmap(dataDir, args_info.outlang_map_arg);
|
||||
|
||||
// just print the input language list and associations
|
||||
if (args_info.outlang_list_given) {
|
||||
outlangmap.open();
|
||||
outlangmap.print();
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
string cssUrl;
|
||||
if (args_info.css_given)
|
||||
cssUrl = args_info.css_arg;
|
||||
|
||||
string docTitle;
|
||||
if (args_info.title_given)
|
||||
docTitle = args_info.title_arg;
|
||||
|
||||
if (args_info.check_lang_given || args_info.check_outlang_given
|
||||
|| args_info.show_regex_given
|
||||
|| args_info.show_lang_elements_given
|
||||
|| args_info.lang_list_given || args_info.outlang_list_given) {
|
||||
// FIXME just a bogus outlang file specification not to search in outlang.map
|
||||
outLangFile = "html.outlang";
|
||||
}
|
||||
|
||||
// initialize the main source highlight object
|
||||
SourceHighlight sourcehighlight(getLangFileName(NOINFERENCE,
|
||||
outputFile, outLangFile, outputFormat
|
||||
+ (cssUrl.size() ? +"-css" : ""), outlangmap));
|
||||
|
||||
// and sets all its properties according to the command line args
|
||||
sourcehighlight.setDataDir(dataDir);
|
||||
|
||||
boost::shared_ptr<DebugListener> debugListener;
|
||||
|
||||
// if a simple check is required...
|
||||
if (args_info.check_lang_given) {
|
||||
cout << "checking " << args_info.check_lang_arg << "... ";
|
||||
sourcehighlight.checkLangDef(args_info.check_lang_arg);
|
||||
// if we're here, everything went fine!
|
||||
cout << "OK" << endl;
|
||||
// otherwise an exception has already been thrown
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
if (args_info.check_outlang_given) {
|
||||
cout << "checking " << args_info.check_outlang_arg << "... ";
|
||||
sourcehighlight.checkOutLangDef(args_info.check_outlang_arg);
|
||||
// if we're here, everything went fine!
|
||||
cout << "OK" << endl;
|
||||
// otherwise an exception has already been thrown
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
// just print the highlightstate if show-regex is specified
|
||||
if (args_info.show_regex_given) {
|
||||
sourcehighlight.printHighlightState(args_info.show_regex_arg, cout);
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
// just print the lang elems if show-lang-elements is specified
|
||||
if (args_info.show_lang_elements_given) {
|
||||
sourcehighlight.printLangElems(args_info.show_lang_elements_arg,
|
||||
cout);
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
sourcehighlight.setGenerateEntireDoc((!args_info.no_doc_given)
|
||||
&& (args_info.doc_given || (docTitle.size()) || cssUrl.size()));
|
||||
sourcehighlight.setStyleDefaultFile(args_info.style_defaults_arg);
|
||||
sourcehighlight.setStyleFile(args_info.style_file_arg);
|
||||
if (args_info.style_css_file_given)
|
||||
sourcehighlight.setStyleCssFile(args_info.style_css_file_arg);
|
||||
sourcehighlight.setGenerateVersion(args_info.gen_version_flag != 0);
|
||||
sourcehighlight.setCss(cssUrl);
|
||||
sourcehighlight.setTitle(docTitle);
|
||||
sourcehighlight.setOutputDir(outputDir);
|
||||
sourcehighlight.setLineRanges(lineRanges.get());
|
||||
sourcehighlight.setRegexRanges(regexRanges.get());
|
||||
sourcehighlight.setBinaryOutput(args_info.binary_output_given);
|
||||
|
||||
if (args_info.debug_langdef_given) {
|
||||
debugListener = boost::shared_ptr<DebugListener>(new DebugListener);
|
||||
const string debugType = args_info.debug_langdef_arg;
|
||||
if (debugType == "interactive")
|
||||
debugListener->setInteractive(true);
|
||||
sourcehighlight.setHighlightEventListener(debugListener.get());
|
||||
// during debugging disable optimizations
|
||||
sourcehighlight.setOptimize(false);
|
||||
}
|
||||
|
||||
int tabs = 0;
|
||||
if (args_info.tab_given) {
|
||||
tabs = args_info.tab_arg;
|
||||
}
|
||||
|
||||
if (args_info.header_given)
|
||||
sourcehighlight.setHeaderFileName(args_info.header_arg);
|
||||
|
||||
if (args_info.footer_given)
|
||||
sourcehighlight.setFooterFileName(args_info.footer_arg);
|
||||
|
||||
bool generateLineNumbers = args_info.line_number_given
|
||||
|| args_info.line_number_ref_given;
|
||||
sourcehighlight.setGenerateLineNumbers(generateLineNumbers);
|
||||
|
||||
if (args_info.line_number_given) {
|
||||
// set the line number padding char (default is always '0')
|
||||
sourcehighlight.setLineNumberPad(args_info.line_number_arg[0]);
|
||||
}
|
||||
|
||||
if (args_info.line_number_ref_given) {
|
||||
sourcehighlight.setGenerateLineNumberRefs(true);
|
||||
sourcehighlight.setLineNumberAnchorPrefix(
|
||||
args_info.line_number_ref_arg);
|
||||
}
|
||||
|
||||
if (generateLineNumbers) {
|
||||
// when generating line numbers tabs must be translated
|
||||
// otherwise the output line will not be aligned
|
||||
// due to the presence of line numbers
|
||||
sourcehighlight.setTabSpaces(tabs ? tabs : 8);
|
||||
}
|
||||
|
||||
if (tabs) {
|
||||
sourcehighlight.setTabSpaces(tabs);
|
||||
}
|
||||
|
||||
// whether to give precedence to language inference
|
||||
InferPolicy inferPolicy = (args_info.infer_lang_given ? INFERFIRST
|
||||
: INFERATLAST);
|
||||
|
||||
RefPosition refposition = INLINE;
|
||||
string gen_references_arg = args_info.gen_references_arg;
|
||||
if (gen_references_arg == "inline")
|
||||
refposition = INLINE;
|
||||
else if (gen_references_arg == "postline")
|
||||
refposition = POSTLINE;
|
||||
else if (gen_references_arg == "postdoc")
|
||||
refposition = POSTDOC;
|
||||
|
||||
boost::shared_ptr<CTagsManager> ctagsManager;
|
||||
if (args_info.gen_references_given) {
|
||||
string ctags = args_info.ctags_arg;
|
||||
string ctags_file = args_info.ctags_file_arg;
|
||||
|
||||
// build the additional arguments for the ctags command
|
||||
if (args_info.gen_references_given && ctags != "") {
|
||||
if (inputFile.size()) {
|
||||
ctags += " ";
|
||||
ctags += inputFile;
|
||||
} else if (args_info.inputs_num) {
|
||||
for (unsigned int i = 0; i < (args_info.inputs_num); ++i) {
|
||||
ctags += " ";
|
||||
ctags += args_info.inputs[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the ctags command must be executed if --ctags is specified with an empty string
|
||||
ctagsManager = boost::shared_ptr<CTagsManager>(new CTagsManager(
|
||||
ctags_file, ctags, ctags != "", refposition));
|
||||
sourcehighlight.setCTagsManager(ctagsManager.get());
|
||||
}
|
||||
|
||||
if (args_info.range_separator_given) {
|
||||
sourcehighlight.setRangeSeparator(args_info.range_separator_arg);
|
||||
}
|
||||
|
||||
// OK, let's highlight!
|
||||
|
||||
if (args_info.inputs_num && (args_info.input_given
|
||||
|| args_info.output_given)) {
|
||||
// do not mix command line invocation modes
|
||||
cerr << "Please, use one of the two syntaxes for invocation: "
|
||||
<< endl;
|
||||
cerr
|
||||
<< "source-highlight [OPTIONS]... -i input_file -o output_file"
|
||||
<< endl;
|
||||
cerr << "source-highlight [OPTIONS]... [FILES]..." << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// for cgi we can only process one file specified with --input
|
||||
if (args_info.inputs_num && !is_cgi) {
|
||||
// the output file name is empty, but we don't want to generate to stdout
|
||||
sourcehighlight.setCanUseStdOut(false);
|
||||
// in case multiple input files were specified (without --input)
|
||||
for (unsigned int i = 0; i < (args_info.inputs_num); ++i) {
|
||||
PROGRESSINFO(string("Processing ")+ args_info.inputs[i] + " ... ");
|
||||
sourcehighlight.highlight(args_info.inputs[i], "",
|
||||
getLangFileName(inferPolicy, args_info.inputs[i],
|
||||
langFile, srcLang, langmap));
|
||||
PROGRESSINFO("created " + sourcehighlight.createOutputFileName(args_info.inputs[i]) + "\n");
|
||||
}
|
||||
} else {
|
||||
if (is_cgi)
|
||||
print_cgi_header();
|
||||
|
||||
// in case only one input file was specified with --input
|
||||
sourcehighlight.highlight(inputFile, outputFile, getLangFileName(
|
||||
inferPolicy, inputFile, langFile, srcLang, langmap));
|
||||
}
|
||||
} catch (const HighlightBuilderException &e) {
|
||||
cerr << e << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
} catch (const ParserException &e) {
|
||||
cerr << e << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
} catch (const exception &e) {
|
||||
exitError(e.what());
|
||||
}
|
||||
|
||||
cmdline_parser_free(&args_info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void print_copyright() {
|
||||
int i;
|
||||
int copyright_text_length = 5;
|
||||
const char *copyright_text[] = {
|
||||
"copyright.text",
|
||||
"Copyright (C) 1999-2008 Lorenzo Bettini <http://www.lorenzobettini.it>",
|
||||
"This program comes with ABSOLUTELY NO WARRANTY.",
|
||||
"This is free software; you may redistribute copies of the program",
|
||||
"under the terms of the GNU General Public License.",
|
||||
"For more information about these matters, see the file named COPYING.",
|
||||
0 };
|
||||
|
||||
for (i = 1; i <= copyright_text_length; ++i)
|
||||
cout << copyright_text[i] << endl;;
|
||||
}
|
||||
|
||||
void print_reportbugs() {
|
||||
int i;
|
||||
int reportbugs_text_length = 3;
|
||||
const char *reportbugs_text[] = {
|
||||
"reportbugs.text",
|
||||
"",
|
||||
"Maintained by Lorenzo Bettini <http://www.lorenzobettini.it>",
|
||||
"Report bugs to <bug-source-highlight at gnu.org>",
|
||||
0 };
|
||||
|
||||
for (i = 1; i <= reportbugs_text_length; ++i)
|
||||
cout << reportbugs_text[i] << endl;
|
||||
}
|
||||
|
||||
void print_version() {
|
||||
cout << Versions::getCompleteVersion() << endl;
|
||||
}
|
||||
|
||||
string inferLang(const string &inputFileName) {
|
||||
VERBOSELN("inferring input language...");
|
||||
if (!inputFileName.size()) {
|
||||
printError("missing feature: language inference requires input file");
|
||||
return "";
|
||||
}
|
||||
|
||||
LanguageInfer languageInfer;
|
||||
|
||||
const string &result = languageInfer.infer(inputFileName);
|
||||
if (result.size()) {
|
||||
VERBOSELN("inferred input language: " + result);
|
||||
} else {
|
||||
VERBOSELN("couldn't infer input language");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
string getMappedLang(const string &s, LangMap &langmap) {
|
||||
// OK now map it into a .lang file
|
||||
string mapped_lang = langmap.getMappedFileName(s);
|
||||
|
||||
if (!mapped_lang.size()) {
|
||||
// try the lower version
|
||||
mapped_lang = langmap.getFileName(Utils::tolower(s));
|
||||
}
|
||||
|
||||
return mapped_lang;
|
||||
}
|
||||
|
||||
string getLangFileName(InferPolicy infer, const string &inputFileName,
|
||||
const string &langFileName, const string &langName, LangMap &langMap) {
|
||||
string langFile;
|
||||
|
||||
// language inference has the precedence
|
||||
if (infer == INFERFIRST) {
|
||||
langFile = inferLang(inputFileName);
|
||||
langFile = getMappedLang(langFile, langMap);
|
||||
if (langFile.size())
|
||||
return langFile;
|
||||
}
|
||||
|
||||
// then if a lang file name was specified we're done
|
||||
if (langFileName.size())
|
||||
return langFileName;
|
||||
|
||||
// now try with the langName
|
||||
langFile = getMappedLang(langName, langMap);
|
||||
if (langFile.size())
|
||||
return langFile;
|
||||
|
||||
// otherwise try with the inputFileName (its file extension
|
||||
// and its name)
|
||||
langFile = langMap.getMappedFileNameFromFileName(inputFileName);
|
||||
if (langFile.size())
|
||||
return langFile;
|
||||
|
||||
// OK, as a last chance let's try with language infer
|
||||
if (infer == INFERATLAST) {
|
||||
langFile = getMappedLang(inferLang(inputFileName), langMap);
|
||||
if (langFile.size())
|
||||
return langFile;
|
||||
}
|
||||
|
||||
// if we're here we failed all checks
|
||||
// if failsafe is specified we default to default.lang
|
||||
if (failsafe)
|
||||
langFile = "default.lang";
|
||||
|
||||
if (!langFile.size()) {
|
||||
// if we're here we must exit with failure
|
||||
if (langName.size())
|
||||
exitError("could not find a language definition for " + langName);
|
||||
else
|
||||
exitError("could not find a language definition for input file "
|
||||
+ inputFileName);
|
||||
}
|
||||
|
||||
return langFile;
|
||||
}
|
||||
|
||||
void printError(const string &s) {
|
||||
cerr << PACKAGE << ": " << s << endl;
|
||||
}
|
||||
|
||||
void exitError(const string &s) {
|
||||
printError(s);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void print_cgi_header() {
|
||||
cout << "Content-type: text/html\n";
|
||||
cout << "\n";
|
||||
}
|
||||
|
||||
|
Loading…
Reference in a new issue