2015-02-25 18:54:10 +01:00
|
|
|
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice appear in all copies.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
|
|
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
|
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
|
|
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
|
// PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
|
|
// Produce System Messages Manual
|
|
|
|
//
|
2015-02-28 01:05:45 +01:00
|
|
|
// This tool reads all the message files given on the command line.
|
|
|
|
// It pulls all the messages and description out, sorts them by
|
|
|
|
// message ID, and writes them out as a single (formatted) file.
|
2015-02-25 18:54:10 +01:00
|
|
|
//
|
|
|
|
// Invocation:
|
|
|
|
// The code is invoked using the command line:
|
|
|
|
//
|
2015-02-28 01:05:45 +01:00
|
|
|
// system_messages [-o <output-file>] <files>
|
2015-02-25 18:54:10 +01:00
|
|
|
//
|
|
|
|
// If no output file is specified, output is written to stdout.
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <fstream>
|
|
|
|
#include <iostream>
|
|
|
|
#include <map>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2015-02-28 01:05:45 +01:00
|
|
|
#ifdef USE_BOOST_FILESYSTEM
|
2015-02-25 18:54:10 +01:00
|
|
|
#include <boost/filesystem.hpp>
|
2015-02-28 01:05:45 +01:00
|
|
|
#endif
|
2015-02-25 18:54:10 +01:00
|
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
|
|
|
|
typedef std::vector<std::string> lines_type;
|
|
|
|
|
|
|
|
/// dictionary values
|
|
|
|
struct Details {
|
|
|
|
std::string text;
|
|
|
|
lines_type description;
|
2015-02-27 16:47:08 +01:00
|
|
|
std::string sname;
|
2015-02-25 18:54:10 +01:00
|
|
|
std::string filename;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Main dictionary holding all the messages. The messages are
|
|
|
|
/// accumulated here before being printed in alphabetical order.
|
|
|
|
typedef std::map<const std::string, Details> dictionary_type;
|
|
|
|
dictionary_type dictionary;
|
|
|
|
|
|
|
|
// The structure of the output page is:
|
|
|
|
//
|
|
|
|
// header
|
2015-02-27 15:24:03 +01:00
|
|
|
// section header
|
|
|
|
// message
|
|
|
|
// separator
|
|
|
|
// message
|
|
|
|
// separator
|
|
|
|
// :
|
|
|
|
// separator
|
|
|
|
// message
|
|
|
|
// section trailer
|
|
|
|
// separator
|
|
|
|
// section header
|
|
|
|
// :
|
|
|
|
// section trailer
|
2015-02-25 18:54:10 +01:00
|
|
|
// trailer
|
|
|
|
//
|
|
|
|
// (Indentation is not relevant - it has only been added to the above
|
|
|
|
// illustration to make the structure clearer.) The text of these section is:
|
|
|
|
|
|
|
|
// Header - this is output before anything else.
|
2015-02-27 15:24:03 +01:00
|
|
|
const std::string FILE_HEADER =
|
2015-02-25 18:54:10 +01:00
|
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
|
|
|
<!DOCTYPE book PUBLIC \"-//OASIS//DTD DocBook XML V4.2//EN\"\n\
|
|
|
|
\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\" [\n\
|
|
|
|
<!ENTITY mdash \"—\" >\n\
|
|
|
|
<!ENTITY % version SYSTEM \"version.ent\">\n\
|
|
|
|
%version;\n\
|
|
|
|
]>\n\
|
|
|
|
<!--\n\
|
|
|
|
This XML document is generated using the system_messages.py tool\n\
|
|
|
|
based on the .mes message files.\n\
|
|
|
|
\n\
|
|
|
|
Do not edit this file.\n\
|
|
|
|
-->\n\
|
|
|
|
<book>\n\
|
|
|
|
<?xml-stylesheet href=\"kea-guide.css\" type=\"text/css\"?>\n\
|
|
|
|
\n\
|
|
|
|
<bookinfo>\n\
|
|
|
|
<title>Kea Messages Manual</title>\n\
|
|
|
|
\n\
|
|
|
|
<copyright>\n\
|
|
|
|
<year>2011-2014</year><holder>Internet Systems Consortium, Inc.</holder>\n\
|
|
|
|
</copyright>\n\
|
|
|
|
\n\
|
|
|
|
<abstract>\n\
|
|
|
|
<para>\n\
|
|
|
|
This is the messages manual for Kea version &__VERSION__;.\n\
|
2015-02-27 15:24:03 +01:00
|
|
|
The most up-to-date version of this document, along with\n\
|
|
|
|
other documents for Kea, can be found at\n\
|
2015-02-25 18:54:10 +01:00
|
|
|
<ulink url=\"http://kea.isc.org/docs\"/>.\n\
|
|
|
|
</para>\n\
|
|
|
|
</abstract>\n\
|
|
|
|
\n\
|
|
|
|
<releaseinfo>This is the messages manual for Kea version\n\
|
|
|
|
&__VERSION__;.</releaseinfo>\n\
|
|
|
|
</bookinfo>\n\
|
|
|
|
\n\
|
|
|
|
<chapter id=\"intro\">\n\
|
|
|
|
<title>Introduction</title>\n\
|
|
|
|
<para>\n\
|
|
|
|
This document lists each message that can be logged by the\n\
|
|
|
|
programs in the Kea package. Each entry in this manual\n\
|
|
|
|
is of the form:\n\
|
|
|
|
<screen>IDENTIFICATION message-text</screen>\n\
|
|
|
|
... where \"IDENTIFICATION\" is the message identification included\n\
|
|
|
|
in each message logged and \"message-text\" is the accompanying\n\
|
|
|
|
message text. The \"message-text\" may include placeholders of the\n\
|
|
|
|
form \"%1\", \"%2\" etc.; these parameters are replaced by relevant\n\
|
|
|
|
values when the message is logged.\n\
|
|
|
|
</para>\n\
|
|
|
|
<para>\n\
|
|
|
|
Each entry is also accompanied by a description giving more\n\
|
|
|
|
information about the circumstances that result in the message\n\
|
|
|
|
being logged.\n\
|
|
|
|
</para>\n\
|
|
|
|
<para>\n\
|
|
|
|
For information on configuring and using Kea logging,\n\
|
|
|
|
refer to the <ulink url=\"kea-guide.html\">Kea Guide</ulink>.\n\
|
|
|
|
</para>\n\
|
|
|
|
</chapter>\n\
|
|
|
|
\n\
|
|
|
|
<chapter id=\"messages\">\n\
|
2015-02-27 16:47:08 +01:00
|
|
|
<title>Kea Log Messages</title>\n";
|
|
|
|
|
|
|
|
// This is output one for each module. $M substitution token is the name.
|
|
|
|
const std::string SECTION_HEADER = " <section id=\"$M\">\n\
|
|
|
|
<title>$M Module</title>\n\
|
2015-02-25 18:54:10 +01:00
|
|
|
<para>\n\
|
|
|
|
<variablelist>\n";
|
|
|
|
|
|
|
|
// This is output once for each message. The string contains
|
|
|
|
// substitution tokens: $I is replaced by the message identification,
|
|
|
|
// $T by the message text, and $D by the message description.
|
2015-02-27 15:24:03 +01:00
|
|
|
const std::string ID_MESSAGE =
|
2015-02-25 18:54:10 +01:00
|
|
|
"<varlistentry id=\"$I\">\n\
|
|
|
|
<term>$I $T</term>\n\
|
|
|
|
<listitem><para>\n\
|
|
|
|
$D</para></listitem>\n\
|
|
|
|
</varlistentry>";
|
|
|
|
|
|
|
|
// A description may contain blank lines intended to separate
|
|
|
|
// paragraphs. If so, each blank line is replaced by the following.
|
2015-02-27 16:47:08 +01:00
|
|
|
const std::string BLANK = "</para><para>";
|
2015-02-25 18:54:10 +01:00
|
|
|
|
|
|
|
// The separator is copied to the output verbatim after each message except
|
|
|
|
// the last.
|
2015-02-27 15:24:03 +01:00
|
|
|
const std::string SEPARATOR = "";
|
2015-02-25 18:54:10 +01:00
|
|
|
|
|
|
|
// The trailier is copied to the output verbatim after the last message.
|
2015-02-27 16:47:08 +01:00
|
|
|
const std::string SECTION_TRAILER =
|
2015-02-25 18:54:10 +01:00
|
|
|
" </variablelist>\n\
|
|
|
|
</para>\n\
|
2015-02-27 16:47:08 +01:00
|
|
|
</section>";
|
|
|
|
|
|
|
|
// The trailier is copied to the output verbatim after the last section.
|
|
|
|
const std::string FILE_TRAILER =
|
|
|
|
" </chapter>\n\
|
2015-02-25 18:54:10 +01:00
|
|
|
</book>";
|
|
|
|
|
2015-02-27 16:47:08 +01:00
|
|
|
|
2015-02-25 18:54:10 +01:00
|
|
|
/// Report an error and exit
|
|
|
|
void reportError(const std::string& filename, const std::string& what)
|
|
|
|
{
|
|
|
|
std::cerr << "*** ERROR in " << filename << "\n";
|
|
|
|
std::cerr << "*** REASON: " << what << "\n";
|
|
|
|
std::cerr << "*** System message generator terminating" << "\n";
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Replaces the '<' and '>' in text about to be inserted into the template
|
|
|
|
/// sections above with < and > to avoid problems with message text
|
|
|
|
/// being interpreted as XML text.
|
|
|
|
std::string replaceTag(const std::string& src)
|
|
|
|
{
|
|
|
|
std::string result;
|
|
|
|
for (std::string::const_iterator it = src.begin(); it != src.end(); ++it) {
|
|
|
|
if (*it == '<') {
|
|
|
|
result.append("<");
|
|
|
|
} else if (*it == '>') {
|
|
|
|
result.append(">");
|
|
|
|
} else {
|
|
|
|
result.push_back(*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Replace $c in a string
|
|
|
|
std::string replaceShell(const std::string& src, char c,
|
|
|
|
const std::string& val)
|
|
|
|
{
|
|
|
|
std::string result;
|
|
|
|
bool shell = false;
|
|
|
|
for (std::string::const_iterator it = src.begin(); it != src.end(); ++it) {
|
|
|
|
if (shell) {
|
|
|
|
if (*it == c) {
|
|
|
|
result.append(val);
|
|
|
|
} else {
|
|
|
|
result.push_back('$');
|
|
|
|
result.push_back(*it);
|
|
|
|
}
|
|
|
|
shell = false;
|
|
|
|
} else if (*it == '$') {
|
|
|
|
shell = true;
|
|
|
|
} else {
|
|
|
|
result.push_back(*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Replaces blank lines in an array with the contents of the 'blank' section.
|
|
|
|
lines_type replaceBlankLines(const lines_type lines)
|
|
|
|
{
|
|
|
|
lines_type result;
|
|
|
|
for (lines_type::const_iterator l = lines.begin(); l != lines.end(); ++l) {
|
|
|
|
if (l->empty()) {
|
2015-02-27 16:47:08 +01:00
|
|
|
result.push_back(BLANK);
|
2015-02-25 18:54:10 +01:00
|
|
|
} else {
|
|
|
|
result.push_back(*l);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Printing functions
|
|
|
|
void printHeader() {
|
2015-02-27 15:24:03 +01:00
|
|
|
std::cout << FILE_HEADER << "\n";
|
2015-02-25 18:54:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void printSeparator() {
|
2015-02-27 15:24:03 +01:00
|
|
|
std::cout << SEPARATOR << "\n";
|
2015-02-25 18:54:10 +01:00
|
|
|
}
|
|
|
|
|
2015-02-27 16:47:08 +01:00
|
|
|
void printSectionHeader(const std::string& sname)
|
|
|
|
{
|
|
|
|
// In the section name, replace "<" and ">" with XML-safe versions and
|
|
|
|
// substitute into the data.
|
|
|
|
std::cout << replaceShell(SECTION_HEADER, 'M', replaceTag(sname));
|
|
|
|
}
|
|
|
|
|
2015-02-25 18:54:10 +01:00
|
|
|
void printMessage(const std::string& msgid)
|
|
|
|
{
|
2015-02-27 16:47:08 +01:00
|
|
|
// In the message ID, replace "<" and ">" with XML-safe versions and
|
2015-02-25 18:54:10 +01:00
|
|
|
// substitute into the data.
|
2015-02-27 15:24:03 +01:00
|
|
|
const std::string m0 = ID_MESSAGE;
|
2015-02-25 18:54:10 +01:00
|
|
|
const std::string m1 = replaceShell(m0, 'I', replaceTag(msgid));
|
|
|
|
|
|
|
|
// Do the same for the message text.
|
|
|
|
std::string m2 = replaceShell(m1, 'T',
|
|
|
|
replaceTag(dictionary[msgid].text));
|
|
|
|
|
|
|
|
// Do the same for the description then replace blank lines with the
|
|
|
|
// specified separator. (We do this in that order to avoid replacing
|
|
|
|
// the "<" and ">" in the XML tags in the separator.)
|
|
|
|
lines_type desc0 = dictionary[msgid].description;
|
|
|
|
lines_type desc1;
|
|
|
|
for (lines_type::iterator l = desc0.begin(); l != desc0.end(); ++l) {
|
|
|
|
desc1.push_back(replaceTag(*l));
|
|
|
|
}
|
|
|
|
lines_type desc2 = replaceBlankLines(desc1);
|
|
|
|
|
|
|
|
// Join the lines together to form a single string and insert into
|
|
|
|
// current text.
|
|
|
|
std::string m3;
|
|
|
|
for (lines_type::iterator l = desc2.begin(); l != desc2.end(); ++l) {
|
|
|
|
m3.append(*l);
|
|
|
|
m3.push_back('\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
std::cout << replaceShell(m2, 'D', m3) << "\n";
|
|
|
|
}
|
|
|
|
|
2015-02-27 16:47:08 +01:00
|
|
|
void printSectionTrailer() {
|
|
|
|
std::cout << SECTION_TRAILER << "\n";
|
|
|
|
}
|
|
|
|
|
2015-02-25 18:54:10 +01:00
|
|
|
void printTrailer() {
|
2015-02-27 15:24:03 +01:00
|
|
|
std::cout << FILE_TRAILER << "\n";
|
2015-02-25 18:54:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Removes leading and trailing empty lines.
|
|
|
|
///
|
|
|
|
/// A list of strings is passed as argument, some of which may be empty.
|
|
|
|
/// This function removes from the start and end of list a contiguous
|
|
|
|
/// sequence of empty lines and returns the result. Embedded sequence of
|
|
|
|
/// empty lines are not touched.
|
|
|
|
///
|
|
|
|
/// Parameters:
|
|
|
|
/// lines List of strings to be modified.
|
|
|
|
///
|
|
|
|
/// Return:
|
|
|
|
/// Input list of strings with leading/trailing blank line sequences
|
|
|
|
/// removed.
|
|
|
|
lines_type removeEmptyLeadingTrailing(lines_type lines)
|
|
|
|
{
|
|
|
|
lines_type retlines = lines;
|
|
|
|
|
|
|
|
// Dispose of degenerate case of empty array
|
|
|
|
if (retlines.empty()) {
|
|
|
|
return retlines;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search for first non-blank line
|
|
|
|
for (;;) {
|
|
|
|
lines_type::iterator start = retlines.begin();
|
|
|
|
if (start == retlines.end()) {
|
|
|
|
return retlines;
|
|
|
|
}
|
|
|
|
if (start->empty()) {
|
|
|
|
retlines.erase(start);
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search for last non-blank line
|
|
|
|
for (;;) {
|
|
|
|
lines_type::reverse_iterator finish = retlines.rbegin();
|
|
|
|
if (finish == retlines.rend()) {
|
|
|
|
return retlines;
|
|
|
|
}
|
|
|
|
if (finish->empty()) {
|
|
|
|
retlines.erase(retlines.end() - 1);
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return retlines;
|
|
|
|
}
|
|
|
|
|
2015-02-27 16:47:08 +01:00
|
|
|
|
2015-02-25 18:54:10 +01:00
|
|
|
/// Add the current message ID and associated information to the global
|
|
|
|
/// dictionary. If a message with that ID already exists, loop appending
|
|
|
|
/// suffixes of the form "(n)" to it until one is found that doesn't.
|
|
|
|
///
|
|
|
|
/// Parameters:
|
|
|
|
/// msgid Message ID
|
|
|
|
/// msgtext Message text
|
|
|
|
/// desc Message description
|
|
|
|
/// filename File from which the message came. Currently this is
|
|
|
|
/// not used, but a future enhancement may wish to include the
|
|
|
|
/// name of the message file in the messages manual.
|
|
|
|
void addToDictionary(const std::string& msgid,
|
|
|
|
const std::string& msgtext,
|
|
|
|
const lines_type& desc,
|
|
|
|
const std::string& filename)
|
|
|
|
{
|
|
|
|
// If the ID is in the dictionary, append a "(n)" to the name - this will
|
|
|
|
// flag that there are multiple instances. (However, this is an error -
|
|
|
|
// each ID should be unique in the code.)
|
|
|
|
std::string key = msgid;
|
|
|
|
if (dictionary.count(key) > 0) {
|
|
|
|
int i = 1;
|
|
|
|
std::string s = boost::lexical_cast<std::string>(i);
|
|
|
|
key = msgid + " (" + s + ")";
|
|
|
|
while (dictionary.count(key) > 0) {
|
|
|
|
i = i + 1;
|
|
|
|
s = boost::lexical_cast<std::string>(i);
|
|
|
|
key = msgid + " (" + s + ")";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove leading and trailing blank lines in the description, then
|
|
|
|
// add everything into a subdictionary which is then added to the main
|
|
|
|
// one.
|
|
|
|
Details details;
|
|
|
|
details.text = msgtext;
|
|
|
|
details.description = removeEmptyLeadingTrailing(desc);
|
2015-02-27 16:47:08 +01:00
|
|
|
size_t underscore = msgid.find_first_of('_');
|
|
|
|
details.sname = msgid.substr(0, underscore);
|
2015-02-25 18:54:10 +01:00
|
|
|
details.filename = filename;
|
|
|
|
dictionary.insert(std::pair<const std::string, Details>(key, details));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Processes file content. Messages and descriptions are identified and
|
|
|
|
/// added to a dictionary (keyed by message ID). If the key already exists,
|
|
|
|
/// a numeric suffix is added to it.
|
|
|
|
///
|
|
|
|
/// Parameters:
|
|
|
|
/// filename Name of the message file being processed
|
|
|
|
/// lines Lines read from the file
|
|
|
|
void processFileContent(const std::string& filename,
|
|
|
|
const lines_type& lines)
|
|
|
|
{
|
|
|
|
std::string prefix; // Last prefix encountered
|
|
|
|
std::string msgid; // Last message ID encountered
|
|
|
|
std::string msgtext; // Text of the message
|
|
|
|
lines_type description; // Description
|
|
|
|
|
|
|
|
for (lines_type::const_iterator l = lines.begin(); l != lines.end(); ++l) {
|
|
|
|
if (l->empty()) {
|
|
|
|
description.push_back(*l);
|
|
|
|
} else if (l->at(0) == '$') {
|
|
|
|
// Starts with "$". Ignore anything other than $PREFIX
|
|
|
|
char* line = new char [l->size() + 1];
|
|
|
|
std::strcpy(line, l->c_str());
|
|
|
|
char* word0 = strtok(line, " \t\r\n\t\v");
|
|
|
|
if (strcasecmp(word0, "$PREFIX") == 0) {
|
|
|
|
char* word1 = strtok(NULL, " \t\r\n\t\v");
|
|
|
|
prefix = word1;
|
|
|
|
}
|
|
|
|
} else if (l->at(0) == '%') {
|
|
|
|
// Start of a message. Add the message we were processing to the
|
|
|
|
// dictionary and clear everything apart from the file name.
|
|
|
|
if (!msgid.empty()) {
|
|
|
|
addToDictionary(msgid, msgtext, description, filename);
|
|
|
|
}
|
|
|
|
msgid.clear();
|
|
|
|
msgtext.clear();
|
|
|
|
description.clear();
|
|
|
|
// Start of a message
|
|
|
|
char* line = new char [l->size() + 1];
|
|
|
|
std::strcpy(line, l->c_str());
|
|
|
|
// Remove "%" and trim leading spaces
|
|
|
|
size_t start = l->find_first_not_of(" \t\r\n\t\v", 1);
|
|
|
|
if (start == std::string::npos) {
|
|
|
|
reportError(filename, "Line with single % found");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Split into words. The first word is the message ID
|
|
|
|
char* word0 = strtok(line + start, " \t\r\n\t\v");
|
|
|
|
msgid = prefix;
|
|
|
|
msgid.append(word0);
|
|
|
|
std::transform(msgid.begin(), msgid.end(),
|
|
|
|
msgid.begin(), toupper);
|
|
|
|
char* word1 = strtok(NULL, " \t\r\n\t\v");
|
|
|
|
start = word1 - line;
|
|
|
|
size_t finish = l->find_last_not_of(" \t\r\n\t\v");
|
|
|
|
msgtext = l->substr(start, finish + 1 - start);
|
|
|
|
} else {
|
|
|
|
// Part of a description, so add to the current description array
|
|
|
|
description.push_back(*l);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// All done, add the last message to the global dictionaty.
|
|
|
|
if (!msgid.empty()) {
|
|
|
|
addToDictionary(msgid, msgtext, description, filename);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Processes a file by reading it in and stripping out all comments and
|
|
|
|
/// and directives. Leading and trailing blank lines in the file are removed
|
|
|
|
/// and the remainder passed for message processing.
|
|
|
|
///
|
|
|
|
/// Parameters:
|
|
|
|
/// filename Name of the message file to process
|
|
|
|
void processFile(const std::string& filename)
|
|
|
|
{
|
|
|
|
std::ifstream cin;
|
|
|
|
cin.open(filename, std::ios::in);
|
|
|
|
lines_type lines0;
|
|
|
|
while (!cin.eof()) {
|
|
|
|
std::string line;
|
|
|
|
getline(cin, line);
|
|
|
|
lines0.push_back(line);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trim leading and trailing spaces from each line, and remove comments.
|
|
|
|
lines_type lines1;
|
|
|
|
for (lines_type::iterator l = lines0.begin(); l != lines0.end(); ++l) {
|
|
|
|
std::string line = *l;
|
|
|
|
if (line.empty()) {
|
|
|
|
lines1.push_back(line);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
size_t start = line.find_first_not_of(" \t\r\n\t\v");
|
|
|
|
if (start != 0) {
|
|
|
|
line.erase(0, start);
|
|
|
|
}
|
|
|
|
if (line.empty()) {
|
|
|
|
lines1.push_back(line);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
size_t finish = line.find_last_not_of(" \t\r\n\t\v");
|
|
|
|
if ((finish != std::string::npos) &&
|
|
|
|
(finish + 1 != line.size())) {
|
|
|
|
line.erase(finish + 1);
|
|
|
|
}
|
|
|
|
if (line.empty()) {
|
|
|
|
lines1.push_back(line);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (line[0] != '#') {
|
|
|
|
lines1.push_back(line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove leading/trailing empty line sequences from the result
|
|
|
|
lines_type lines2 = removeEmptyLeadingTrailing(lines1);
|
|
|
|
|
|
|
|
// Interpret content
|
|
|
|
processFileContent(filename, lines2);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Iterates through all files in the tree starting at the given root and
|
|
|
|
/// calls processFile for all .mes files found.
|
|
|
|
///
|
|
|
|
/// Parameters:
|
|
|
|
/// root Directory that is the root of the source tree
|
2015-02-28 01:05:45 +01:00
|
|
|
#ifdef USE_BOOST_FILESYSTEM
|
2015-02-25 18:54:10 +01:00
|
|
|
void processAllFiles(const boost::filesystem::path& root)
|
|
|
|
{
|
|
|
|
boost::filesystem::directory_iterator endd;
|
|
|
|
for (boost::filesystem::directory_iterator file(root);
|
|
|
|
file != endd;
|
|
|
|
++file) {
|
|
|
|
boost::filesystem::path path = file->path();
|
|
|
|
if (boost::filesystem::is_directory(path)) {
|
|
|
|
processAllFiles(path);
|
|
|
|
} else if (boost::filesystem::is_regular_file(path)) {
|
|
|
|
boost::filesystem::path extension = path.extension();
|
|
|
|
// Identify message files
|
|
|
|
if (extension == ".mes") {
|
|
|
|
processFile(path.native());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-28 01:05:45 +01:00
|
|
|
#endif
|
2015-02-27 16:47:08 +01:00
|
|
|
|
2015-02-25 18:54:10 +01:00
|
|
|
void usage(char* progname)
|
|
|
|
{
|
2015-02-28 01:05:45 +01:00
|
|
|
#ifdef USE_BOOST_FILESYSTEM
|
|
|
|
std::cerr << "Usage: " << progname <<
|
|
|
|
" [--help | options] root|files\n";
|
|
|
|
#else
|
2015-02-25 18:54:10 +01:00
|
|
|
std::cerr << "Usage: " << progname <<
|
2015-02-28 01:05:45 +01:00
|
|
|
" [--help | options] files\n";
|
|
|
|
#endif
|
2015-02-25 18:54:10 +01:00
|
|
|
std::cerr << " options: --output file: " <<
|
|
|
|
"output file name (default to stdout)\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char* argv[])
|
|
|
|
{
|
|
|
|
char* progname = argv[0];
|
|
|
|
std::ofstream fout;
|
|
|
|
while (argc > 1) {
|
|
|
|
--argc;
|
|
|
|
++argv;
|
|
|
|
if (strcmp(argv[0], "--help") == 0) {
|
|
|
|
usage(progname);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
// Redirect output if specified (errors are written to stderr)
|
|
|
|
if ((strcmp(argv[0], "-o") == 0) ||
|
|
|
|
(strcmp(argv[0], "--output") == 0)) {
|
|
|
|
--argc;
|
|
|
|
++argv;
|
|
|
|
if (argc == 0) {
|
|
|
|
usage(progname);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
fout.open(argv[0], std::ofstream::out | std::ofstream::trunc);
|
|
|
|
std::cout.rdbuf(fout.rdbuf());
|
2015-02-27 16:47:08 +01:00
|
|
|
--argc;
|
|
|
|
++argv;
|
2015-02-25 18:54:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (argc == 0) {
|
|
|
|
usage(progname);
|
|
|
|
exit(-1);
|
|
|
|
}
|
2015-02-28 01:05:45 +01:00
|
|
|
#ifdef USE_BOOST_FILESYSTEM
|
2015-02-25 18:54:10 +01:00
|
|
|
if (argc > 1) {
|
|
|
|
for (int i = 0; i < argc; ++i) {
|
|
|
|
processFile(argv[i]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
boost::filesystem::path root(argv[0]);
|
|
|
|
if (boost::filesystem::is_directory(root)) {
|
|
|
|
processAllFiles(root);
|
|
|
|
} else {
|
|
|
|
processFile(argv[0]);
|
|
|
|
}
|
|
|
|
}
|
2015-02-28 01:05:45 +01:00
|
|
|
#else
|
|
|
|
for (int i = 0; i < argc; ++i) {
|
|
|
|
processFile(argv[i]);
|
|
|
|
}
|
|
|
|
#endif
|
2015-02-25 18:54:10 +01:00
|
|
|
|
|
|
|
// Now just print out everything we've read (in alphabetical order).
|
|
|
|
bool first = true;
|
2015-02-27 16:47:08 +01:00
|
|
|
std::string sname;
|
2015-02-25 18:54:10 +01:00
|
|
|
printHeader();
|
|
|
|
for (dictionary_type::iterator it = dictionary.begin();
|
|
|
|
it != dictionary.end();
|
|
|
|
++it) {
|
2015-02-27 16:47:08 +01:00
|
|
|
if (sname.compare(it->second.sname) != 0) {
|
|
|
|
if (!sname.empty()) {
|
|
|
|
printSectionTrailer();
|
|
|
|
printSeparator();
|
|
|
|
}
|
|
|
|
sname = it->second.sname;
|
|
|
|
printSectionHeader(sname);
|
|
|
|
first = true;
|
|
|
|
}
|
|
|
|
if (!first) {
|
|
|
|
printSeparator();
|
|
|
|
}
|
|
|
|
first = false;
|
|
|
|
printMessage(it->first);
|
|
|
|
}
|
|
|
|
if (!sname.empty()) {
|
|
|
|
printSectionTrailer();
|
2015-02-25 18:54:10 +01:00
|
|
|
}
|
|
|
|
printTrailer();
|
|
|
|
exit(0);
|
|
|
|
}
|