Browse Source

[3516] Addressed old comments

Francis Dupont 9 years ago
parent
commit
a0a929cc69
2 changed files with 144 additions and 587 deletions
  1. 144 145
      tools/system_messages.cc
  2. 0 442
      tools/system_messages.py

+ 144 - 145
tools/system_messages.cc

@@ -24,6 +24,7 @@
 // system_messages [-o <output-file>] <files>
 //
 // If no output file is specified, output is written to stdout.
+// The produced format is docbook XML.
 
 #include <algorithm>
 #include <fstream>
@@ -35,48 +36,50 @@
 #include <stdlib.h>
 #include <string.h>
 
-#ifdef USE_BOOST_FILESYSTEM
-#include <boost/filesystem.hpp>
-#endif
 #include <boost/lexical_cast.hpp>
 
-typedef std::vector<std::string> lines_type;
+typedef std::vector<std::string> LinesType;
 
-/// dictionary values
+/// @brief dictionary values
 struct Details {
     std::string text;
-    lines_type description;
+    LinesType description;
     std::string sname;
     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;
+/// @brief Main dictionary holding all the messages.
+/// The messages are accumulated here before being printed in
+/// alphabetical order.
+typedef std::map<const std::string, Details> dictionaryType;
+dictionaryType dictionary;
 
-// The structure of the output page is:
+/// @brief The structure of the output page
 //
-//        header
-//           section header
-//                 message
-//              separator
-//                 message
-//              separator
-//                :
-//              separator
-//                 message
-//           section trailer
-//           separator
-//           section header
-//             :
-//           section trailer
-//        trailer
+///        header
+///           section header
+///                 message
+///              separator
+///                 message
+///              separator
+///                :
+///              separator
+///                 message
+///           section trailer
+///           separator
+///           section header
+///             :
+///           section trailer
+///        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:
+/// (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.
+/// @name Constants for the output page
+//@{
+
+/// @brief File header
+/// this is output before anything else.
 const std::string FILE_HEADER =
 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
 <!DOCTYPE book PUBLIC \"-//OASIS//DTD DocBook XML V4.2//EN\"\n\
@@ -98,7 +101,7 @@ const std::string FILE_HEADER =
     <title>Kea Messages Manual</title>\n\
 \n\
     <copyright>\n\
-      <year>2011-2014</year><holder>Internet Systems Consortium, Inc.</holder>\n\
+      <year>2011-2015</year><holder>Internet Systems Consortium, Inc.</holder>\n\
     </copyright>\n\
 \n\
     <abstract>\n\
@@ -141,15 +144,17 @@ const std::string FILE_HEADER =
   <chapter id=\"messages\">\n\
     <title>Kea Log Messages</title>\n";
 
-// This is output one for each module. $M substitution token is the name.
+/// @brief Section header
+/// 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\
     <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.
+/// @brief message ID
+/// 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.
 const std::string ID_MESSAGE =
 "<varlistentry id=\"$I\">\n\
 <term>$I $T</term>\n\
@@ -157,27 +162,35 @@ const std::string ID_MESSAGE =
 $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.
+/// @brief Blank line
+/// A description may contain blank lines intended to separate
+/// paragraphs.  If so, each blank line is replaced by the following.
 const std::string BLANK = "</para><para>";
 
-// The separator is copied to the output verbatim after each message except
-// the last.
+/// @brief Separator
+/// The separator is copied to the output verbatim after each message except
+/// the last.
 const std::string SEPARATOR = "";
 
-// The trailier is copied to the output verbatim after the last message.
+/// @brief Section trailer
+/// The trailer is copied to the output verbatim after the last message.
 const std::string SECTION_TRAILER =
 "      </variablelist>\n\
     </para>\n\
   </section>";
 
-// The trailier is copied to the output verbatim after the last section.
+/// @brief File trailer
+/// The trailier is copied to the output verbatim after the last section.
 const std::string FILE_TRAILER =
 "  </chapter>\n\
 </book>";
 
+//@}
+
+/// @name Utility routines
+//@{
 
-/// Report an error and exit
+/// @brief Report an error and exit
 void reportError(const std::string& filename, const std::string& what)
 {
     std::cerr << "*** ERROR in " << filename << "\n";
@@ -186,7 +199,7 @@ void reportError(const std::string& filename, const std::string& what)
     exit(1);
 }
 
-
+/// @brief Replace tag
 /// Replaces the '<' and '>' in text about to be inserted into the template
 /// sections above with &lt; and &gt; to avoid problems with message text
 /// being interpreted as XML text.
@@ -202,10 +215,20 @@ std::string replaceTag(const std::string& src)
            result.push_back(*it);
         }
     }
-    return result;
+    return (result);
 }
 
-/// Replace $c in a string
+/// @brief Replace shell
+/// Replace $c in a string (or with other words performs macro expansion
+/// with '$' for introducing a macro followed by a character selecting
+/// a specific macro.
+///
+/// @param src  source string
+/// @param c    character selecting a macro when it follows '$'
+/// @param val  value which
+///
+/// @return     the source string where all occurrences of $c were
+///             replaced by val
 std::string replaceShell(const std::string& src, char c,
                          const std::string& val)
 {
@@ -226,33 +249,40 @@ std::string replaceShell(const std::string& src, char c,
             result.push_back(*it);
         }
     }
-    return result;
+    return (result);
 }
 
+/// @brief Replace blank lines
 /// Replaces blank lines in an array with the contents of the 'blank' section.
-lines_type replaceBlankLines(const lines_type lines)
+LinesType replaceBlankLines(const LinesType lines)
 {
-    lines_type result;
-    for (lines_type::const_iterator l = lines.begin(); l != lines.end(); ++l) {
+    LinesType result;
+    for (LinesType::const_iterator l = lines.begin(); l != lines.end(); ++l) {
         if (l->empty()) {
             result.push_back(BLANK);
         } else {
             result.push_back(*l);
         }
     }
-    return result;
+    return (result);
 }
 
+//@}
+
+/// @name Printing functions
+//@{
 
-/// Printing functions
+/// @brief Print file header
 void printHeader() {
     std::cout << FILE_HEADER << "\n";
 }
 
+/// @brief Print separator
 void printSeparator() {
     std::cout << SEPARATOR << "\n";
 }
 
+/// @brief Print section header
 void printSectionHeader(const std::string& sname)
 {
     // In the section name, replace "<" and ">" with XML-safe versions and
@@ -260,6 +290,7 @@ void printSectionHeader(const std::string& sname)
     std::cout << replaceShell(SECTION_HEADER, 'M', replaceTag(sname));
 }
 
+/// @brief print message id
 void printMessage(const std::string& msgid)
 {
     // In the message ID, replace "<" and ">" with XML-safe versions and
@@ -274,17 +305,17 @@ void printMessage(const std::string& msgid)
     // 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) {
+    LinesType desc0 = dictionary[msgid].description;
+    LinesType desc1;
+    for (LinesType::iterator l = desc0.begin(); l != desc0.end(); ++l) {
         desc1.push_back(replaceTag(*l));
     }
-    lines_type desc2 = replaceBlankLines(desc1);
+    LinesType 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) {
+    for (LinesType::iterator l = desc2.begin(); l != desc2.end(); ++l) {
         m3.append(*l);
         m3.push_back('\n');
     }
@@ -292,42 +323,43 @@ void printMessage(const std::string& msgid)
     std::cout << replaceShell(m2, 'D', m3) << "\n";
 }
 
+/// @brief print section trailer
 void printSectionTrailer() {
     std::cout << SECTION_TRAILER << "\n";
 }
 
+/// @brief print file trailer
 void printTrailer() {
     std::cout << FILE_TRAILER << "\n";
 }
 
+//@}
 
-/// Removes leading and trailing empty lines.
+/// @brief 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.
+/// @param 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)
+/// @return Input list of strings with leading/trailing blank line
+/// sequences removed.
+LinesType removeEmptyLeadingTrailing(const LinesType& lines)
 {
-    lines_type retlines = lines;
+    LinesType retlines = lines;
 
     // Dispose of degenerate case of empty array
     if (retlines.empty()) {
-        return retlines;
+        return (retlines);
     }
 
     // Search for first non-blank line
     for (;;) {
-        lines_type::iterator start = retlines.begin();
+        LinesType::iterator start = retlines.begin();
         if (start == retlines.end()) {
-            return retlines;
+            return (retlines);
         }
         if (start->empty()) {
             retlines.erase(start);
@@ -338,9 +370,9 @@ lines_type removeEmptyLeadingTrailing(lines_type lines)
 
     // Search for last non-blank line
     for (;;) {
-        lines_type::reverse_iterator finish = retlines.rbegin();
+        LinesType::reverse_iterator finish = retlines.rbegin();
         if (finish == retlines.rend()) {
-            return retlines;
+            return (retlines);
         }
         if (finish->empty()) {
             retlines.erase(retlines.end() - 1);
@@ -349,24 +381,25 @@ lines_type removeEmptyLeadingTrailing(lines_type lines)
         }
     }
 
-    return retlines;
+    return (retlines);
 }
 
 
-/// 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.
+/// @brief 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.
+///  @param msgid        Message ID
+///  @param msgtext      Message text
+///  @param desc         Message description
+///  @param 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 LinesType& desc,
                      const std::string& filename)
 {
     // If the ID is in the dictionary, append a "(n)" to the name - this will
@@ -397,22 +430,24 @@ void addToDictionary(const std::string& msgid,
 }
 
 
-/// 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.
+/// @brief 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.
+
+/// The format of .mes files is fully described in src/lib/log/logging.dox
 ///
-/// Parameters:
-///  filename     Name of the message file being processed
-///  lines        Lines read from the file
+/// @param filename     Name of the message file being processed
+/// @param lines        Lines read from the file
 void processFileContent(const std::string& filename,
-                        const lines_type& lines)
+                        const LinesType& 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
+    LinesType description;      // Description
 
-    for (lines_type::const_iterator l = lines.begin(); l != lines.end(); ++l) {
+    for (LinesType::const_iterator l = lines.begin(); l != lines.end(); ++l) {
         if (l->empty()) {
             description.push_back(*l);
         } else if (l->at(0) == '$') {
@@ -464,26 +499,30 @@ void processFileContent(const std::string& 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.
+/// @brief Process a file
+/// Read it in and strip 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
+/// @param 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;
+    if (!cin.is_open()) {
+        reportError(filename, "open for read failure");
+    }
+    LinesType lines0;
     while (!cin.eof()) {
         std::string line;
         getline(cin, line);
         lines0.push_back(line);
     }
+    cin.close();
 
     // 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) {
+    LinesType lines1;
+    for (LinesType::iterator l = lines0.begin(); l != lines0.end(); ++l) {
         std::string line = *l;
         if (line.empty()) {
             lines1.push_back(line);
@@ -512,51 +551,22 @@ void processFile(const std::string& filename)
     }  
 
     // Remove leading/trailing empty line sequences from the result
-    lines_type lines2 = removeEmptyLeadingTrailing(lines1);
+    LinesType 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
-#ifdef USE_BOOST_FILESYSTEM
-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());
-            }
-        }
-    }
-}
-#endif
-
+/// @brief Usage error routine
 void usage(char* progname)
 {
-#ifdef USE_BOOST_FILESYSTEM
-    std::cerr << "Usage: " << progname <<
-        " [--help | options] root|files\n";
-#else
     std::cerr << "Usage: " << progname <<
         " [--help | options] files\n";
-#endif
     std::cerr << " options: --output file: " <<
         "output file name (default to stdout)\n";
 }
 
+/// @brief Main (entry point)
 int main(int argc, char* argv[])
 {
     char* progname = argv[0];
@@ -578,9 +588,13 @@ int main(int argc, char* argv[])
                 exit(-1);
             }
             fout.open(argv[0], std::ofstream::out | std::ofstream::trunc);
+            if (!fout.is_open()) {
+                reportError(argv[0], "open for write failure");
+            }
             std::cout.rdbuf(fout.rdbuf());
             --argc;
             ++argv;
+            break;
         }
     }
 
@@ -588,30 +602,15 @@ int main(int argc, char* argv[])
         usage(progname);
         exit(-1);
     }
-#ifdef USE_BOOST_FILESYSTEM
-    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]);
-        }
-    }
-#else
     for (int i = 0; i < argc; ++i) {
         processFile(argv[i]);
     }
-#endif
 
     // Now just print out everything we've read (in alphabetical order).
     bool first = true;
     std::string sname;
     printHeader();
-    for (dictionary_type::iterator it = dictionary.begin();
+    for (dictionaryType::iterator it = dictionary.begin();
          it != dictionary.end();
          ++it) {
         if (sname.compare(it->second.sname) != 0) {

+ 0 - 442
tools/system_messages.py

@@ -1,442 +0,0 @@
-# Copyright (C) 2011, 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
-#
-# This tool reads all the .mes files in the directory tree whose root is given
-# on the command line and interprets them as message files.  It pulls
-# all the messages and description out, sorts them by message ID, and writes
-# them out as a single (formatted) file.
-#
-# Invocation:
-# The code is invoked using the command line:
-#
-# python system_messages.py [-o <output-file>] <top-source-directory>|<files>
-#
-# If no output file is specified, output is written to stdout.
-
-import re
-import os
-import sys
-from optparse import OptionParser
-
-# Main dictionary holding all the messages.  The messages are accumulated here
-# before being printed in alphabetical order.
-dictionary = {}
-
-# The structure of the output page is:
-#
-#        header
-#           section header
-#                 message
-#              separator
-#                 message
-#              separator
-#                :
-#              separator
-#                 message
-#           section trailer
-#           separator
-#           section header
-#             :
-#           section trailer
-#        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.
-FILE_HEADER = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
-"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
-<!ENTITY mdash  "&#x2014;" >
-<!ENTITY % version SYSTEM "version.ent">
-%version;
-]>
-<!--
-     This XML document is generated using the system_messages.py tool
-     based on the .mes message files.
-
-     Do not edit this file.
--->
-<book>
-  <?xml-stylesheet href="kea-guide.css" type="text/css"?>
-
-  <bookinfo>
-    <title>Kea Messages Manual</title>
-
-    <copyright>
-      <year>2011-2014</year><holder>Internet Systems Consortium, Inc.</holder>
-    </copyright>
-
-    <abstract>
-      <para>
-        This is the messages manual for Kea version &__VERSION__;.
-            The most up-to-date version of this document, along with
-            other documents for Kea, can be found at
-        <ulink url="http://kea.isc.org/docs"/>.
-      </para>
-    </abstract>
-
-    <releaseinfo>This is the messages manual for Kea version
-        &__VERSION__;.</releaseinfo>
-  </bookinfo>
-
-  <chapter id="intro">
-    <title>Introduction</title>
-    <para>
-      This document lists each message that can be logged by the
-      programs in the Kea package.  Each entry in this manual
-      is of the form:
-      <screen>IDENTIFICATION message-text</screen>
-      ... where "IDENTIFICATION" is the message identification included
-      in each message logged and "message-text" is the accompanying
-      message text.  The "message-text" may include placeholders of the
-      form "%1", "%2" etc.; these parameters are replaced by relevant
-      values when the message is logged.
-    </para>
-    <para>
-      Each entry is also accompanied by a description giving more
-      information about the circumstances that result in the message
-      being logged.
-    </para>
-    <para>
-      For information on configuring and using Kea logging,
-      refer to the <ulink url="kea-guide.html">Kea Guide</ulink>.
-    </para>
-  </chapter>
-
-  <chapter id="messages">
-    <title>Kea Log Messages</title>
-"""
-
-# This is output one for each module. $M substitution token is the name.
-SECTION_HEADER = """  <section id="$M">
-    <title>$M Module</title>
-    <para>
-      <variablelist>"""
-
-# 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.
-ID_MESSAGE = """<varlistentry id="$I">
-<term>$I $T</term>
-<listitem><para>
-$D
-</para></listitem>
-</varlistentry>"""
-
-# A description may contain blank lines intended to separate
-# paragraphs.  If so, each blank line is replaced by the following.
-BLANK = "</para><para>"
-
-# The separator is copied to the output verbatim after each message or
-# section except the last.
-SEPARATOR = ""
-
-# The trailer is copied to the output verbatim after the last message.
-SECTION_TRAILER = """      </variablelist>
-    </para>
-  </section>"""
-
-# The trailer is copied to the output verbatim after the last section.
-FILE_TRAILER = """  </chapter>
-</book>"""
-
-
-def reportError(filename, what):
-    """Report an error and exit"""
-    print("*** ERROR in ", filename, file=sys.stderr)
-    print("*** REASON: ", what, file=sys.stderr)
-    print("*** System message generator terminating", file=sys.stderr)
-    sys.exit(1)
-
-
-def replaceTag(string):
-    """Replaces the '<' and '>' in text about to be inserted into the template
-       sections above with &lt; and &gt; to avoid problems with message text
-       being interpreted as XML text.
-    """
-    string1 = string.replace("<", "&lt;")
-    string2 = string1.replace(">", "&gt;")
-    return string2
-
-def replaceBlankLines(lines):
-    """Replaces blank lines in an array with the contents of the 'blank'
-       section.
-    """
-    result = []
-    for l in lines:
-        if len(l) == 0:
-            result.append(BLANK)
-        else:
-            result.append(l)
-
-    return result
-
-
-# Printing functions
-def printHeader():
-    print(FILE_HEADER)
-
-def printSeparator():
-    print(SEPARATOR)
-
-def printSectionHeader(sname):
-    # In the section name, replace "<" and ">" with XML-safe versions and
-    # substitute into the data.
-    m = SECTION_HEADER.replace("$M", replaceTag(sname));
-
-    print(m)
-
-def printMessage(msgid):
-    # In the message ID, replace "<" and ">" with XML-safe versions and
-    # substitute into the data.
-    m1 = ID_MESSAGE.replace("$I", replaceTag(msgid))
-
-    # Do the same for the message text.
-    m2 = m1.replace("$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.)
-    desc1 = [replaceTag(l) for l in dictionary[msgid]['description']]
-    desc2 = replaceBlankLines(desc1)
-
-    # Join the lines together to form a single string and insert into
-    # current text.
-    m3 = m2.replace("$D", "\n".join(desc2))
-
-    print(m3)
-
-def printSectionTrailer():
-    print(SECTION_TRAILER)
-
-def printTrailer():
-    print(FILE_TRAILER)
-
-
-def removeEmptyLeadingTrailing(lines):
-    """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.
-    """
-
-    retlines = []
-
-    # Dispose of degenerate case of empty array
-    if len(lines) == 0:
-        return retlines
-
-    # Search for first non-blank line
-    start = 0
-    while start < len(lines):
-        if len(lines[start]) > 0:
-            break
-        start = start + 1
-
-    # Handle case when entire list is empty
-    if start >= len(lines):
-        return retlines
-
-    # Search for last non-blank line
-    finish = len(lines) - 1
-    while finish >= 0:
-        if len(lines[finish]) > 0:
-            break
-        finish = finish - 1
-
-    retlines = lines[start:finish + 1]
-    return retlines
-
-
-def addToDictionary(msgid, msgtext, desc, filename):
-    """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
-       sname        Section name (part before the first _ of the ID)
-       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.
-    """
-
-    # 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.)
-    if msgid in dictionary:
-        i = 1
-        while msgid + " (" + str(i) + ")" in dictionary:
-            i = i + 1
-        msgid = msgid + " (" + str(i) + ")"
-
-    # Remove leading and trailing blank lines in the description, then
-    # add everything into a subdictionary which is then added to the main
-    # one.
-    details = {}
-    words = re.split("_", msgid)
-    details['text'] = msgtext
-    details['description'] = removeEmptyLeadingTrailing(desc)
-    details['sname'] = words[0]
-    details['filename'] = filename
-    dictionary[msgid] = details
-
-
-def processFileContent(filename, lines):
-    """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
-    """
-
-    prefix = ""         # Last prefix encountered
-    msgid = ""          # Last message ID encountered
-    msgtext = ""        # Text of the message
-    description = []    # Description
-
-    for l in lines:
-        if l.startswith("$"):
-            # Starts with "$".  Ignore anything other than $PREFIX
-            words = re.split("\s+", l)
-            if words[0].upper() == "$PREFIX":
-                if len(words) == 1:
-                    prefix = ""
-                else:
-                    prefix = words[1]
-
-        elif l.startswith("%"):
-            # Start of a message.  Add the message we were processing to the
-            # dictionary and clear everything apart from the file name.
-            if msgid != "":
-                addToDictionary(msgid, msgtext, description, filename)
-
-            msgid = ""
-            msgtext = ""
-            description = []
-
-            # Start of a message
-            l = l[1:].strip()       # Remove "%" and trim leading spaces
-            if len(l) == 0:
-                printError(filename, "Line with single % found")
-                next
-
-            # Split into words.  The first word is the message ID
-            words = re.split("\s+", l)
-            msgid = (prefix + words[0]).upper()
-            msgtext = l[len(words[0]):].strip()
-
-        else:
-            # Part of a description, so add to the current description array
-            description.append(l)
-
-    # All done, add the last message to the global dictionaty.
-    if msgid != "":
-        addToDictionary(msgid, msgtext, description, filename)
-
-
-def processFile(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
-    """
-    lines = open(filename).readlines();
-
-    # Trim leading and trailing spaces from each line, and remove comments.
-    lines = [l.strip() for l in lines]
-    lines = [l for l in lines if not l.startswith("#")]
-
-    # Remove leading/trailing empty line sequences from the result
-    lines = removeEmptyLeadingTrailing(lines)
-
-    # Interpret content
-    processFileContent(filename, lines)
-
-
-def processAllFiles(root):
-    """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
-    """
-    for (path, dirs, files) in os.walk(root):
-
-        # Identify message files
-        mes_files = [f for f in files if f.endswith(".mes")]
-
-        # ... and process each file in the list
-        for m in mes_files:
-            processFile(path + os.sep + m)
-
-
-# Main program
-if __name__ == "__main__":
-    parser = OptionParser(usage="Usage: %prog [--help | options] root|files")
-    parser.add_option("-o", "--output", dest="output", default=None,
-                      metavar="FILE", 
-                      help="output file name (default to stdout)")
-    (options, args) = parser.parse_args()
-
-    if len(args) == 0:
-        parser.error("Must supply directory at which to begin search or files")
-
-    # Redirect output if specified (errors are written to stderr)
-    if options.output is not None:
-        sys.stdout = open(options.output, 'w')
-
-    # Read the files and load the data
-    if (len(args) > 1) or (not os.path.isdir(args[0])):
-        for file in args:
-            processFile(file)
-    else:
-        processAllFiles(args[0])
-
-    # Now just print out everything we've read (in alphabetical order).
-    sname = ""
-    printHeader()
-    for msgid in sorted(dictionary):
-        if dictionary[msgid]['sname'] != sname:
-            if sname != "":
-               printSectionTrailer()
-               printSeparator()
-            sname = dictionary[msgid]['sname']
-            printSectionHeader(sname)
-            count = 1
-        if count > 1:
-            printSeparator()
-        count = count + 1
-        printMessage(msgid)
-    if sname !="":
-        printSectionTrailer()
-    printTrailer()