/*
* Copyright (C) 1999-2007 Lorenzo Bettini, 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"
#endif
#include "startapp.h"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <assert.h>
#include <string.h>
#include "colors.h"
#include "cmdline.h"
#include "fileutil.h"
#include "utils.h"
#include "messages.h"
#include "copyright.h"
#include "reportbugs.h"
#include "parsestyles.h"
#include "generatorfactory.h"
#include "srcuntabifier.h"
#include "chartranslator.h"
#include "langdefloader.h"
#include "outputgenerator.h"
#include "langmap.h"
#include "regexpengine.h"
#include "regexpenginedebug.h"
#include "docgenerator.h"
#include "textstyles.h"
#include "outlangdefparserfun.h"
#include "fileinfo.h"
#include "stopwatch.h"
#include "textformatter.h"
#include "languageinfer.h"
// for command line arguments
#include "cmdlineargs.h"
using namespace std;
#ifdef BUILD_AS_CGI
#include "envmapper.h"
#endif // BUILD_AS_CGI
gengetopt_args_info args_info; // command line structure
static void print_cgi_header();
static void run_ctags(const string &cmd);
/**
* Print progress status information (provided --quiet is not specified)
* @param message
*/
static void progressInfo(const string &message) {
if (args_info.quiet_given)
return;
cerr << message;
}
StartApp::StartApp() :
docgenerator(0), preformatter(0), langmap(new LangMap),
outlangmap(new LangMap), styledefaults(new LangMap),
generator_factory(0), entire_doc(0),
verbose(0), cssUrl(0), use_css(0), is_cgi(0), gen_version(true),
generate_line_num(false), generate_ref(false) {
}
StartApp::~StartApp() {
// cout << "destroying StartApp..." << endl;
cmdline_parser_free(&args_info);
if (preformatter)
delete preformatter;
if (docgenerator)
delete docgenerator;
if (generator_factory)
delete generator_factory;
}
int StartApp::start(int argc, char * argv[]) {
char *docTitle;
char *docHeader; // the buffer with the header
char *docFooter; // the buffer with the footer
const char *header_fileName = 0;
const char *footer_fileName = 0;
unsigned i;
int v;
int tabSpaces = 0;
#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 ((v = 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();
return EXIT_SUCCESS;
}
if (args_info.help_given) {
cout << "GNU ";
cmdline_parser_print_help();
print_reportbugs();
return EXIT_SUCCESS;
}
gen_version = (args_info.gen_version_flag != 0);
/* initialization of global symbols */
inputFileName = outputFileName = 0;
docTitle = 0;
docHeader = 0;
docFooter = 0;
docTitle = args_info.title_arg;
header_fileName = args_info.header_arg;
footer_fileName = args_info.footer_arg;
verbose = args_info.verbose_given;
const string style_file = args_info.style_file_arg;
if (args_info.tab_given > 0)
tabSpaces = args_info.tab_arg;
if (header_fileName)
docHeader = read_file(header_fileName);
if (footer_fileName)
docFooter = read_file(footer_fileName);
cssUrl = args_info.css_arg;
use_css = ( cssUrl != 0 );
entire_doc =(! args_info.no_doc_given) &&( args_info.doc_given || (docTitle != 0) || use_css );
string inputFileName;
if (args_info.input_given)
inputFileName = args_info.input_arg;
string outputFileName;
if (inputFileName.size()&& ! is_cgi && args_info.output_given)
outputFileName = args_info.output_arg;
bool generate_to_stdout =(args_info.output_arg &&
strcmp (args_info.output_arg, "STDOUT") == 0);
if (verbose)
setMessager(new DefaultMessages);
printMessage( PACKAGE);
printMessage( VERSION);
printMessage(argv[0]);
if (verbose) {
printMessage("command line arguments: ");
for (int i = 0; i < argc; ++i) {
printMessage(argv[i]);
}
}
/*
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 = ABSOLUTEDATADIR;
if (args_info.data_dir_given)
data_dir = args_info.data_dir_arg;
if (args_info.show_regex_given) {
if (LangDefLoader::show_regex(data_dir, args_info.show_regex_arg)) {
return (EXIT_SUCCESS);
}
return (EXIT_FAILURE);
}
if (args_info.check_lang_given) {
cout << "checking " << args_info.check_lang_arg << "... ";
if (LangDefLoader::check_lang_def(data_dir, args_info.check_lang_arg)) {
cout << "OK" << endl;
return (EXIT_SUCCESS);
}
return (EXIT_FAILURE);
}
if (args_info.check_outlang_given) {
cout << "checking " << args_info.check_outlang_arg << "... ";
textstyles = parse_outlang_def(data_dir.c_str(),
args_info.check_outlang_arg);
cout << "OK" << endl;
return (EXIT_SUCCESS);
}
if (args_info.show_lang_elements_given) {
// we simply printe all the language elements defined in the
// language definition file
if (LangDefLoader::show_lang_elements(data_dir,
args_info.show_lang_elements_arg))
return EXIT_SUCCESS;
return EXIT_FAILURE;
}
string lang_map = args_info.lang_map_arg;
assert(lang_map.size());
if (! args_info.lang_def_given)
langmap = LangMapPtr(new LangMap(data_dir, lang_map));
string outlang_map = args_info.outlang_map_arg;
assert(outlang_map.size());
if (! args_info.outlang_def_given)
outlangmap = LangMapPtr(new LangMap(data_dir, outlang_map));
string defaults_map = args_info.style_defaults_arg;
assert(defaults_map.size());
styledefaults = LangMapPtr(new LangMap(data_dir, defaults_map));
if (args_info.lang_list_given) {
cout << "Supported languages (file extensions)\nand associated language definition files\n\n";
langmap->print();
return (EXIT_SUCCESS);
}
if (args_info.outlang_list_given) {
cout << "Supported output languages\nand associated language definition files\n\n";
outlangmap->print();
return (EXIT_SUCCESS);
}
// when debugging, always flush the output
outputbuffer.setAlwaysFlush(args_info.debug_langdef_given);
string title;
string doc_header;
string doc_footer;
string css_url;
if (docTitle)
title = docTitle;
if ((! docTitle) && inputFileName.size())
title = inputFileName;
if (docHeader)
doc_header = docHeader;
if (docFooter)
doc_footer = docFooter;
if (cssUrl)
css_url = cssUrl;
if (args_info.line_number_ref_given)
args_info.line_number_given = args_info.line_number_ref_given;
string outlangfile;
if (! args_info.outlang_def_given) {
string out_format = args_info.out_format_arg;
if (use_css)
out_format += "-css";
if (entire_doc)
out_format += "-doc";
outlangfile = outlangmap->get_file(out_format);
if (! outlangfile.size()) {
cerr << PACKAGE << ": ";
cerr << "output language " << out_format<< " not handled" << endl;
return EXIT_FAILURE;
}
} else {
outlangfile = args_info.outlang_def_arg;
}
textstyles = parse_outlang_def(data_dir.c_str(), outlangfile.c_str());
if (! textstyles->file_extension.size() && ! outputFileName.size()) {
cerr << PACKAGE << ": ";
cerr << "empty file extension in output language file " <<outlangfile << endl;
return EXIT_FAILURE;
}
const string ext = "." + textstyles->file_extension;
RefPosition refposition;
if (strcmp(args_info.gen_references_arg, "inline")==0)
refposition = INLINE;
else if (strcmp(args_info.gen_references_arg, "postline")==0)
refposition = POSTLINE;
else if (strcmp(args_info.gen_references_arg, "postdoc")==0)
refposition = POSTDOC;
else {
cerr << PACKAGE << ": ";
cerr << "Bug: unhandled reference position " <<args_info.gen_references_arg << endl;
return EXIT_FAILURE;
}
if (args_info.gen_references_given && strlen(args_info.ctags_arg)> 0) {
string ctags_cmd = args_info.ctags_arg;
if (inputFileName.size()) {
ctags_cmd += " ";
ctags_cmd += inputFileName;
} else if (args_info.inputs_num) {
for (i = 0; i < (args_info.inputs_num); ++i) {
ctags_cmd += " ";
ctags_cmd += args_info.inputs[i];
}
}
run_ctags(ctags_cmd);
}
if (tabSpaces)
preformatter = new Untabifier (tabSpaces);
else if (args_info.line_number_given)
preformatter = new Untabifier(8);
else
preformatter = new PreFormatter();
PreFormatterPtr chartranslator(textstyles->charTranslator);
preformatter->setFormatter(chartranslator);
string background_color;
generator_factory =new GeneratorFactory(textstyles, preformatter, &outputbuffer,
args_info.gen_references_given,
args_info.ctags_file_arg,
refposition, args_info.debug_langdef_given);
if (args_info.style_css_file_given) {
parseCssStyles(data_dir, args_info.style_css_file_arg,
generator_factory, background_color);
} else {
parseStyles(data_dir, style_file, generator_factory, background_color);
}
generator_factory->addDefaultGenerator();
setStylesFromDefaults();
if (background_color != "")
background_color = generator_factory->preprocessColor(background_color);
docgenerator = new DocGenerator(title, inputFileName,
doc_header, doc_footer,
css_url, background_color, entire_doc,
textstyles->docTemplate.toStringBegin(),
textstyles->docTemplate.toStringEnd());;
if (is_cgi)
print_cgi_header();
// let's start the translation :-)
generate_line_num =(args_info.line_number_given || args_info.line_number_ref_given);
generate_ref = args_info.line_number_ref_given;
if (args_info.lang_def_arg)
lang_file = args_info.lang_def_arg;
int result= EXIT_SUCCESS;
if (args_info.src_lang_given)
source_language = args_info.src_lang_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);
// first the --input file
if (! args_info.inputs_num) {
result = processFile(inputFileName, (generate_to_stdout ? "" : outputFileName), ext);
}
// let's process other files, if there are any
if (args_info.inputs_num && !is_cgi) {
for (i = 0; i < (args_info.inputs_num); ++i) {
progressInfo(string("Processing ")+ args_info.inputs[i] + " ... ");
const string &outputFileName = createOutputFileName(
args_info.inputs[i], args_info.output_dir_arg, ext);
result = processFile(args_info.inputs[i], (generate_to_stdout ? "" : outputFileName), ext);
if (result == EXIT_FAILURE)
break;
progressInfo("created " + outputFileName + "\n");
}
}
return (result);
}
void StartApp::setStylesFromDefaults() {
for (LangMap::const_iterator it = styledefaults->begin(); it != styledefaults->end(); ++it) {
if (generator_factory->createMissingGenerator(it->first, it->second))
printMessage("style for " + it->first + " defaults to style for " + it->second);
}
}
void StartApp::print_copyright() {
int i;
for (i = 1; i <= copyright_text_length; ++i)
cout << copyright_text[i] << endl;
;
}
void StartApp::print_reportbugs() {
int i;
for (i = 1; i <= reportbugs_text_length; ++i)
cout << reportbugs_text[i] << endl;
}
void StartApp::print_version() {
cout << "GNU " << PACKAGE << " " << VERSION << endl;
}
int process_file(const char *file, TextFormatter *pre, const string &path,
const string &lang_file, FileInfo *fileinfo, bool verbose) {
RegExpStatePtr initial_state = LangDefLoader::get_lang_def(path, lang_file);
try {
printMessage("Processing " + string((file ? file : "standard input")) + " with regex");
printMessage("Using language definition " + lang_file);
RegExpEnginePtr engine;
if (args_info.debug_langdef_given) {
RegExpEngineDebug *debugEngine = new RegExpEngineDebug(initial_state, pre, fileinfo);
debugEngine->setInteractive( strcmp(args_info.debug_langdef_arg, "interactive" ) == 0);
engine = RegExpEnginePtr(debugEngine);
} else {
engine = RegExpEnginePtr(new RegExpEngine(initial_state, pre, fileinfo));
}
engine->process_file(file);
}
catch(...)
{
exitError("error during regex processing");
}
return 0;
}
string StartApp::inferLang(const string &inputFileName) {
printMessage("inferring input language...", cerr);
if (!inputFileName.size()) {
cerr << PACKAGE << ": ";
cerr << "missing feature: language inference requires input file" << endl;
return "";
}
LanguageInfer languageInfer;
const string &result = languageInfer.infer(inputFileName);
if (result.size()) {
printMessage("inferred input language: " + result, cerr);
// OK now map it into a .lang file
string mapped_lang = langmap->get_file(result);
if (!mapped_lang.size()) {
// try the lower version
mapped_lang = langmap->get_file(Utils::tolower(result));
}
if (mapped_lang.size()) {
return mapped_lang;
}
} else {
printMessage("couldn't infer input language", cerr);
}
return "";
}
int StartApp::processFile(const string &inputFileName,
const string &outputFileName, const string &file_extension) {
FILE *in = 0;
bool deleteOStream = false;
bool langSpecFound = false;
ostream* sout = 0;
/// num of digits to represent line number
unsigned int line_num_digit;
/// character to use for padding the line number
char line_num_padding;
if (outputFileName.size()) {
sout = new ofstream(outputFileName.c_str());
if (! (*sout)) {
cerr << "Error in creating " << outputFileName << " for output" << endl;
return EXIT_FAILURE;
}
deleteOStream = true;
printMessage("output file: " + inputFileName);
}
if (inputFileName.size()) {
unsigned int lines = get_line_count(inputFileName);
printMessage("input file: " + inputFileName);
line_num_digit = 0;
while (lines) {
++line_num_digit;
lines /= 10;
}
} else
line_num_digit = 5;
// if we read from stdin, we can't read the file in advance and
// check how many lines of code it contains. In this case set
// the number of digit for the line number to 5.
/*
* Use default values for any options not provided
*/
if (sout == 0) {
sout = &cout;
}
if (in == 0) {
; /* Well stdin already points to stdin so, .... */
}
OutputGenerator *outputgenerator = 0;
if (generate_line_num) {
// Read the padding character to use for line numbers
line_num_padding = args_info.line_number_arg[0];
outputgenerator =new OutputGenerator(*sout, generator_factory->getTextFormatter()->getGenerator("linenum"),
&(textstyles->refstyle.anchor), generate_ref,
(args_info.line_number_ref_given ? args_info.line_number_ref_arg : ""),
textstyles->line_prefix);
outputgenerator->setLineNumDigit(line_num_digit);
outputgenerator->setLineNumPadding(line_num_padding);
}
else
outputgenerator = new OutputGenerator(*sout, textstyles->line_prefix);
// when debugging, always flush the output
outputgenerator->setAlwaysFlush(args_info.debug_langdef_given);
outputbuffer.setOutputGenerator(outputgenerator);
docgenerator->set_gen_version(gen_version);
printMessage("translating source code... ", cerr);
string langfile = lang_file;
if (args_info.infer_lang_given) {
langfile = inferLang(inputFileName);
if (langfile.size())
langSpecFound = true;
}
// language inference has the precedence (if it succeeds)
if (!langfile.size() && !langSpecFound) {
// find the language definition file associated to a language
if (source_language.size()) {
langfile = langmap->get_file(source_language);
if (! langfile.size()) {
if (! args_info.failsafe_given) {
cerr << PACKAGE << ": ";
cerr << "source language " << source_language<< " not handled" << endl;
return EXIT_FAILURE;
}
} else
langSpecFound = true;
} else {
if (! inputFileName.size()) {
if (! args_info.failsafe_given) {
cerr << PACKAGE << ": ";
cerr << "when using stdin, please specify a source language"<< endl;
return EXIT_FAILURE;
}
}
string file_ext = get_file_extension(inputFileName);
if (file_ext != "")
langfile = langmap->get_file(file_ext);
if (langfile.size())
langSpecFound = true;
}
} else
langSpecFound = true;
// language inference is always performed, if the other attempts failed
// if --infer-lang was specified at command line, then the inference
// has already been performed, otherwise we perform it now
if (!langSpecFound && !args_info.infer_lang_given) {
langfile = inferLang(inputFileName);
if (langfile.size())
langSpecFound = true;
}
if (!langSpecFound && args_info.failsafe_given) {
// OK we use default.lang
langfile = "default.lang";
langSpecFound = true;
}
if (langSpecFound) {
docgenerator->generate_start_doc(sout);
const string &i_file_name = get_input_file_name(inputFileName);
const char *input_file_name = (i_file_name.size() ? i_file_name.c_str() : 0);
FileInfo fileinfo(i_file_name, outputFileName);
process_file(input_file_name, generator_factory->getTextFormatter(),
data_dir, langfile, &fileinfo, verbose);
outputbuffer.flush();
docgenerator->generate_end_doc(sout);
printMessage("done !", cerr);
} else {
cerr << PACKAGE << ": ";
cerr << "unknown input language for "<< (inputFileName.size() ? inputFileName : "(stdin)") << endl;
return EXIT_FAILURE;
}
/*
else // we're in failsafe mode so we simply copy the file to the output
{
istream *input;
if(! inputFileName.size())
input = &cin;
else
input = open_file_istream_or_error(inputFileName);
*sout << input->rdbuf();
if (input != &cin)
delete input;
}
*/
sout->flush();
if (deleteOStream)
delete sout;
delete outputgenerator;
return EXIT_SUCCESS;
}
void run_ctags(const string &cmd) {
printMessage("Running ctags: " + cmd);
int res = system(cmd.c_str());
if (res != 0) {
exitError("error running ctags");
}
}
void print_cgi_header() {
printf("Content-type: text/html\n");
printf("\n");
}