/*=========================================================================

  Program:   CMake - Cross-Platform Makefile Generator
  Module:    $RCSfile: cmDocumentation.cxx,v $
  Language:  C++
  Date:      $Date: 2006/10/27 20:01:47 $
  Version:   $Revision: 1.31.2.3 $

  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#include "cmDocumentation.h"

#include "cmSystemTools.h"
#include "cmVersion.h"
#include <cmsys/Directory.hxx>

//----------------------------------------------------------------------------
static const cmDocumentationEntry cmDocumentationStandardOptions[] =
{
  {"--copyright [file]", "Print the CMake copyright and exit.",
   "If a file is specified, the copyright is written into it."},
  {"--help", "Print usage information and exit.",
   "Usage describes the basic command line interface and its options."},
  {"--help-full [file]", "Print full help and exit.",
   "Full help displays most of the documentation provided by the UNIX "
   "man page.  It is provided for use on non-UNIX platforms, but is "
   "also convenient if the man page is not installed.  If a file is "
   "specified, the help is written into it."},
  {"--help-html [file]", "Print full help in HTML format.",
   "This option is used by CMake authors to help produce web pages.  "
   "If a file is specified, the help is written into it."},
  {"--help-man [file]", "Print a UNIX man page and exit.",
   "This option is used by the cmake build to generate the UNIX man page.  "
   "If a file is specified, the help is written into it."},
  {"--version [file]", "Show program name/version banner and exit.",
   "If a file is specified, the version is written into it."},
  {0,0,0}
};

//----------------------------------------------------------------------------
static const cmDocumentationEntry cmDocumentationCommandsHeader[] =
{
  {0,
   "The following commands are available in CMakeLists.txt code:", 0},
  {0,0,0}
};

//----------------------------------------------------------------------------
static const cmDocumentationEntry cmDocumentationModulesHeader[] =
{
  {0,
   "The following modules are provided with CMake. "
   "They can be used with INCLUDE(ModuleName).", 0},
  {0,0,0}
};

//----------------------------------------------------------------------------
static const cmDocumentationEntry cmDocumentationGeneratorsHeader[] =
{
  {0,
   "The following generators are available on this platform:", 0},
  {0,0,0}
};

//----------------------------------------------------------------------------
static const cmDocumentationEntry cmDocumentationStandardSeeAlso[] =
{
  {0,
   "The following resources are available to get help using CMake:", 0},
  {"Home Page",
   "http://www.cmake.org",
   "The primary starting point for learning about CMake."},
  {"Frequently Asked Questions",
   "http://www.cmake.org/Wiki/CMake_FAQ",
   "A Wiki is provided containing answers to frequently asked questions. "},
  {"Online Documentation",
   "http://www.cmake.org/HTML/Documentation.html",
   "Links to available documentation may be found on this web page."},
  {"Mailing List",
   "http://www.cmake.org/HTML/MailingLists.html",
   "For help and discussion about using cmake, a mailing list is provided at "
   "cmake@cmake.org. "
   "The list is member-post-only but one may sign up on the CMake web page. "
   "Please first read the full documentation at "
   "http://www.cmake.org before posting questions to the list."},
  {0,
   "Summary of helpful links:\n"
   "  Home: http://www.cmake.org\n"
   "  Docs: http://www.cmake.org/HTML/Documentation.html\n"
   "  Mail: http://www.cmake.org/HTML/MailingLists.html\n"
   "  FAQ:  http://www.cmake.org/Wiki/CMake_FAQ\n"
   , 0},
  {0,0,0}
};

//----------------------------------------------------------------------------
const cmDocumentationEntry cmDocumentationAuthor[] =
{
  {0,
   "This manual page was generated by the \"--help-man\" option.", 0},
  {0,0,0}
};

//----------------------------------------------------------------------------
const cmDocumentationEntry cmDocumentationCopyright[] =
{
  {0,
   "Copyright (c) 2002 Kitware, Inc., Insight Consortium.  "
   "All rights reserved.", 0},
  {0,
   "Redistribution and use in source and binary forms, with or without "
   "modification, are permitted provided that the following conditions are "
   "met:", 0},
  {"",
   "Redistributions of source code must retain the above copyright notice, "
   "this list of conditions and the following disclaimer.", 0},
  {"",
   "Redistributions in binary form must reproduce the above copyright "
   "notice, this list of conditions and the following disclaimer in the "
   "documentation and/or other materials provided with the distribution.",
   0},
  {"",
   "The names of Kitware, Inc., the Insight Consortium, or the names of "
   "any consortium members, or of any contributors, may not be used to "
   "endorse or promote products derived from this software without "
   "specific prior written permission.", 0},
  {"",
   "Modified source versions must be plainly marked as such, and must "
   "not be misrepresented as being the original software.", 0},
  {0,
   "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "
   "``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT "
   "LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR "
   "A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR "
   "CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, "
   "EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, "
   "PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR "
   "PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF "
   "LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING "
   "NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS "
   "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.", 0},
  {0, 0, 0}
};

//----------------------------------------------------------------------------
cmDocumentation::cmDocumentation()
{
  this->CurrentForm = TextForm;
  this->TextIndent = "";
  this->TextWidth = 77;
}

//----------------------------------------------------------------------------
cmDocumentation::~cmDocumentation()
{
  for(std::vector< char* >::iterator i = this->ModuleStrings.begin();
      i != this->ModuleStrings.end(); ++i)
    {
    delete [] *i;
    }
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintCopyright(std::ostream& os)
{
  for(const cmDocumentationEntry* op = cmDocumentationCopyright;
      op->brief; ++op)
    {
    if(op->name)
      {
      os << " * ";
      this->TextIndent = "    ";
      this->PrintColumn(os, op->brief);
      }
    else
      {
      this->TextIndent = "";
      this->PrintColumn(os, op->brief);
      }
    os << "\n";
    }
  return true;
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintVersion(std::ostream& os)
{
  os << this->GetNameString() << " version " 
     << cmVersion::GetCMakeVersion() << "\n";
  return true;
}

//----------------------------------------------------------------------------
void cmDocumentation::AddSection(const char* name,
                                 const cmDocumentationEntry* d)
{
  this->Names.push_back(name);
  this->Sections.push_back(d);
}

//----------------------------------------------------------------------------
void cmDocumentation::ClearSections()
{
  this->Names.erase(this->Names.begin(), this->Names.end());
  this->Sections.erase(this->Sections.begin(), this->Sections.end());
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintDocumentation(Type ht, std::ostream& os)
{
  if ( ht != cmDocumentation::HTML &&
    ht != cmDocumentation::Man )
    {
    this->PrintVersion(os);
    }
  switch (ht)
    {
    case cmDocumentation::Usage:     return this->PrintDocumentationUsage(os);
    case cmDocumentation::Single:    
      return this->PrintDocumentationSingle(os);
    case cmDocumentation::SingleModule:  
      return this->PrintDocumentationSingleModule(os);
    case cmDocumentation::List:      return this->PrintDocumentationList(os);
    case cmDocumentation::ModuleList: return this->PrintModuleList(os);
    case cmDocumentation::Full:      return this->PrintDocumentationFull(os);
    case cmDocumentation::HTML:      return this->PrintDocumentationHTML(os);
    case cmDocumentation::Man:       return this->PrintDocumentationMan(os);
    case cmDocumentation::Copyright: return this->PrintCopyright(os);
    case cmDocumentation::Version:   return true;
    default: return false;
    }
}

//----------------------------------------------------------------------------
bool cmDocumentation::CreateModulesSection()
{
  this->ModulesSection.push_back(cmDocumentationModulesHeader[0]);
  std::string cmakeModules = this->CMakeRoot;
  cmakeModules += "/Modules";
  cmsys::Directory dir;
  dir.Load(cmakeModules.c_str());
  for(unsigned int i = 0; i < dir.GetNumberOfFiles(); ++i)
    {
    std::string fname = dir.GetFile(i);
    if(fname.length() > 6)
      {
      if(fname.substr(fname.length()-6, 6) == ".cmake")
        {
        std::string moduleName = fname.substr(0, fname.length()-6);
        std::string path = cmakeModules;
        path += "/";
        path += fname;
        this->CreateSingleModule(path.c_str(), moduleName.c_str());
        }
      }
    } 
  cmDocumentationEntry e = { 0, 0, 0 };
  this->ModulesSection.push_back(e);
  return true;
}

//----------------------------------------------------------------------------
bool cmDocumentation::CreateSingleModule(const char* fname, 
                                         const char* moduleName)
{
  std::ifstream fin(fname);
  if(!fin)
    {
    std::cerr << "Internal error: can not open module." << fname << std::endl;
    return false;
    } 
  std::string line;
  std::string text;
  std::string brief;
  brief = " ";
  bool newParagraph = true;
  while ( fin && cmSystemTools::GetLineFromStream(fin, line) )
    {
    if(line.size() && line[0] == '#')
      {
      // blank line
      if(line.size() <= 2)
        {
        text += "\n"; 
        newParagraph = true;
        }
      else if(line[2] == '-') 
        {
        brief = line.c_str()+4;
        }
      else 
        {
        // two spaces
        if(line[1] == ' ' && line[2] == ' ')
          {
          if(!newParagraph)
            {
            text += "\n";
            newParagraph = true;
            }
          // Skip #, and leave space for preformatted
          text += line.c_str()+1;
          text += "\n";
          }
        else if(line[1] == ' ')
          {
          if(!newParagraph)
            {
            text += " ";
            }
          newParagraph = false;
          // skip # and space
          text += line.c_str()+2;
          }
        else
          {
          if(!newParagraph)
            {
            text += " ";
            }
          newParagraph = false;
          // skip #
          text += line.c_str()+1;
          }
        }
      }
    else
      {
      if(text.length() < 2 && brief.length() == 1)
        {
        return false;
        }
      char* pname = strcpy(new char[strlen(moduleName)+1], moduleName);
      char* ptext = strcpy(new char[text.length()+1], text.c_str());
      this->ModuleStrings.push_back(pname);
      this->ModuleStrings.push_back(ptext);
      char* pbrief = strcpy(new char[brief.length()+1], brief.c_str());
      this->ModuleStrings.push_back(pbrief);
      cmDocumentationEntry e = { pname, pbrief, ptext };
      this->ModulesSection.push_back(e);
      return true;
      }
    }
  return true;
}


//----------------------------------------------------------------------------
bool cmDocumentation::PrintRequestedDocumentation(std::ostream& os)
{
  bool result = true;
  
  // Loop over requested documentation types.
  for(RequestedMapType::const_iterator i = this->RequestedMap.begin();
      i != this->RequestedMap.end(); ++i)
    {
    // Special case for printing help for a single command.
    if(i->first == cmDocumentation::Usage && i->second.length() > 0 &&
       !this->CommandsSection.empty())
      {
      // Check if the argument to the usage request was a command.
      for(cmDocumentationEntry* entry = &this->CommandsSection[0];
          entry->brief; ++entry)
        {
        if(entry->name && (strcmp(entry->name, i->second.c_str()) == 0))
          {
          this->PrintDocumentationCommand(os, entry);
          return true;
          }
        }
      
      // Argument was not a command.  Complain.
      os << "Help argument \"" << i->second.c_str()
         << "\" is not a CMake command.  "
         << "Use --help-command-list to see all commands.\n";
      return false;
      }
    
    // If a file name was given, use it.  Otherwise, default to the
    // given stream.
    std::ofstream* fout = 0;
    std::ostream* s = &os;
    if(i->second.length() > 0)
      {
      fout = new std::ofstream(i->second.c_str(), std::ios::out);
      if(fout)
        {
        s = fout;
        }
      else
        {
        result = false;
        }
      }
    
    // Print this documentation type to the stream.
    if(!this->PrintDocumentation(i->first, *s) || !*s)
      {
      result = false;
      }
    
    // Close the file if we wrote one.
    if(fout)
      {
      delete fout;
      }
    }
  return result;
}

//----------------------------------------------------------------------------
bool cmDocumentation::CheckOptions(int argc, const char* const* argv)
{
  // Providing zero arguments gives usage information.
  if(argc == 1)
    {
    this->RequestedMap[cmDocumentation::Usage] = "";
    return true;
    }
  
  // Search for supported help options.
  bool result = false;
  for(int i=1; i < argc; ++i)
    {
    // Check if this is a supported help option.
    Type type = cmDocumentation::None;
    if((strcmp(argv[i], "-help") == 0) ||
       (strcmp(argv[i], "--help") == 0) ||
       (strcmp(argv[i], "/?") == 0) ||
       (strcmp(argv[i], "-usage") == 0) ||
       (strcmp(argv[i], "-h") == 0) ||
       (strcmp(argv[i], "-H") == 0))
      {
      type = cmDocumentation::Usage;
      }
    else if(strcmp(argv[i], "--help-full") == 0)
      {
      type = cmDocumentation::Full;
      }
    else if(strcmp(argv[i], "--help-html") == 0)
      {
      type = cmDocumentation::HTML;
      }
    else if(strcmp(argv[i], "--help-man") == 0)
      {
      type = cmDocumentation::Man;
      }
    else if(strcmp(argv[i], "--help-command") == 0)
      {
      type = cmDocumentation::Single;
      if((i+1 < argc) && !this->IsOption(argv[i+1]))
        {
        this->SingleCommand = argv[i+1];
        this->SingleCommand = cmSystemTools::UpperCase(this->SingleCommand);
        i = i+1;
        }
      }
    else if(strcmp(argv[i], "--help-module") == 0)
      {
      type = cmDocumentation::SingleModule;
      if((i+1 < argc) && !this->IsOption(argv[i+1]))
        {
        this->SingleModuleName = argv[i+1];
        i = i+1;
        }
      }
    else if(strcmp(argv[i], "--help-command-list") == 0)
      {
      type = cmDocumentation::List;
      }
    else if(strcmp(argv[i], "--help-module-list") == 0)
      {
      type = cmDocumentation::ModuleList;
      }
    else if(strcmp(argv[i], "--copyright") == 0)
      {
      type = cmDocumentation::Copyright;
      }
    else if((strcmp(argv[i], "--version") == 0) || 
            (strcmp(argv[i], "-version") == 0) || 
            (strcmp(argv[i], "/V") == 0))
      {
      type = cmDocumentation::Version;
      }
    if(type)
      {
      // This is a help option.  See if there is a file name given.
      result = true;
      if((i+1 < argc) && !this->IsOption(argv[i+1]))
        {
        this->RequestedMap[type] = argv[i+1];
        i = i+1;
        }
      else
        {
        this->RequestedMap[type] = "";
        }
      }
    }
  return result;
}

//----------------------------------------------------------------------------
void cmDocumentation::Print(Form f, std::ostream& os)
{
  this->CurrentForm = f;
  for(unsigned int i=0; i < this->Sections.size(); ++i)
    {
    this->PrintSection(os, this->Sections[i], this->Names[i]);
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::SetName(const char* name)
{
  this->NameString = name?name:"";
}

//----------------------------------------------------------------------------
void cmDocumentation::SetNameSection(const cmDocumentationEntry* section)
{
  this->SetSection(0, section, 0, this->NameSection);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetUsageSection(const cmDocumentationEntry* section)
{
  this->SetSection(0, section, 0, this->UsageSection);
}

//----------------------------------------------------------------------------
void cmDocumentation
::SetDescriptionSection(const cmDocumentationEntry* section)
{
  this->SetSection(0, section, 0, this->DescriptionSection);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetOptionsSection(const cmDocumentationEntry* section)
{
  this->SetSection(0, section, cmDocumentationStandardOptions,
                   this->OptionsSection);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetCommandsSection(const cmDocumentationEntry* section)
{
  this->SetSection(cmDocumentationCommandsHeader, section, 0,
                   this->CommandsSection);
}

//----------------------------------------------------------------------------
void cmDocumentation
::SetGeneratorsSection(const cmDocumentationEntry* section)
{
  this->SetSection(cmDocumentationGeneratorsHeader, section, 0,
                   this->GeneratorsSection);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetSeeAlsoList(const cmDocumentationEntry* also)
{
  this->SeeAlsoSection.clear();
  this->SeeAlsoString = ".B ";
  for(const cmDocumentationEntry* i = also; i->brief; ++i)
    {
    this->SeeAlsoString += i->brief;
    this->SeeAlsoString += (i+1)->brief? "(1), ":"(1)";    
    }
  cmDocumentationEntry e = {0, 0, 0};
  e.brief = this->SeeAlsoString.c_str();
  this->SeeAlsoSection.push_back(e);
  for(const cmDocumentationEntry* i = cmDocumentationStandardSeeAlso;
      i->brief; ++i)
    {
    this->SeeAlsoSection.push_back(*i);
    }
  e.brief = 0;
  this->SeeAlsoSection.push_back(e);  
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSection(std::ostream& os,
                                   const cmDocumentationEntry* section,
                                   const char* name)
{
  switch (this->CurrentForm)
    {
    case TextForm: this->PrintSectionText(os, section, name); break;
    case HTMLForm: this->PrintSectionHTML(os, section, name); break;
    case ManForm: this->PrintSectionMan(os, section, name); break;
    case UsageForm: this->PrintSectionUsage(os, section, name); break;
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSectionText(std::ostream& os,
                                       const cmDocumentationEntry* section,
                                       const char* name)
{
  if(name)
    {
    os <<
      "---------------------------------------"
      "---------------------------------------\n";
    os << name << "\n\n";
    }
  if(!section) { return; }
  for(const cmDocumentationEntry* op = section; op->brief; ++op)
    {
    if(op->name)
      {
      if(op->name[0])
        {
        os << "  " << op->name << "\n";
        }
      this->TextIndent = "       ";
      this->PrintFormatted(os, op->brief);
      if(op->full)
        {
        os << "\n";
        this->PrintFormatted(os, op->full);
        }
      }
    else
      {
      this->TextIndent = "";
      this->PrintFormatted(os, op->brief);
      }
    os << "\n";
    }  
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSectionHTML(std::ostream& os,
                                       const cmDocumentationEntry* section,
                                       const char* name)
{
  if(name)
    {
    os << "<h2>" << name << "</h2>\n";
    }
  if(!section) { return; }
  for(const cmDocumentationEntry* op = section; op->brief;)
    {
    if(op->name)
      {
      os << "<ul>\n";
      for(;op->name;++op)
        {
        os << "  <li>\n";
        if(op->name[0])
          {
          os << "    <b><code>";
          this->PrintHTMLEscapes(os, op->name);
          os << "</code></b>: ";
          }
        this->PrintHTMLEscapes(os, op->brief);
        if(op->full)
          {
          os << "<br>\n    ";
          this->PrintFormatted(os, op->full);
          }
        os << "\n";
        os << "  </li>\n";
        }
      os << "</ul>\n";
      }
    else
      {
      this->PrintFormatted(os, op->brief);
      os << "\n";
      ++op;
      }
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSectionMan(std::ostream& os,
                                      const cmDocumentationEntry* section,
                                      const char* name)
{
  if(name)
    {
    os << ".SH " << name << "\n";
    }
  if(!section) { return; }
  for(const cmDocumentationEntry* op = section; op->brief; ++op)
    {
    if(op->name)
      {
      os << ".TP\n"
         << ".B " << (op->name[0]?op->name:"*") << "\n";
      this->PrintFormatted(os, op->brief);
      this->PrintFormatted(os, op->full);
      }
    else
      {
      os << ".PP\n";
      this->PrintFormatted(os, op->brief);
      }
    }  
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSectionUsage(std::ostream& os,
                                        const cmDocumentationEntry* section,
                                        const char* name)
{
  if(name)
    {
    os << name << "\n";
    }
  if(!section) { return; }
  for(const cmDocumentationEntry* op = section; op->brief; ++op)
    {
    if(op->name)
      {
      os << "  " << op->name;
      this->TextIndent = "                                ";
      int align = static_cast<int>(strlen(this->TextIndent))-4;
      for(int i = static_cast<int>(strlen(op->name)); i < align; ++i)
        {
        os << " ";
        }
      if ( strlen(op->name) > strlen(this->TextIndent)-4 )
        {
        os << "\n";
        os.write(this->TextIndent, strlen(this->TextIndent)-2);
        }
      os << "= ";
      this->PrintColumn(os, op->brief);
      os << "\n";
      }
    else
      {
      os << "\n";
      this->TextIndent = "";
      this->PrintFormatted(os, op->brief);
      }
    }
  os << "\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintFormatted(std::ostream& os, const char* text)
{
  if(!text)
    {
    return;
    }
  const char* ptr = text;
  while(*ptr)
    {
    // Any ptrs starting in a space are treated as preformatted text.
    std::string preformatted;
    while(*ptr == ' ')
      {
      for(char ch = *ptr; ch && ch != '\n'; ++ptr, ch = *ptr)
        {
        preformatted.append(1, ch);
        }
      if(*ptr)
        {
        ++ptr;
        preformatted.append(1, '\n');
        }
      }
    if(preformatted.length())
      {
      this->PrintPreformatted(os, preformatted.c_str());
      }
    
    // Other ptrs are treated as paragraphs.
    std::string paragraph;
    for(char ch = *ptr; ch && ch != '\n'; ++ptr, ch = *ptr)
      {
      paragraph.append(1, ch);
      }
    if(*ptr)
      {
      ++ptr;
      paragraph.append(1, '\n');
      }
    if(paragraph.length())
      {
      this->PrintParagraph(os, paragraph.c_str());
      }
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintPreformatted(std::ostream& os, const char* text)
{
  switch (this->CurrentForm)
    {
    case TextForm: this->PrintPreformattedText(os, text); break;
    case HTMLForm: this->PrintPreformattedHTML(os, text); break;
    case ManForm: this->PrintPreformattedMan(os, text); break;
    case UsageForm: this->PrintPreformattedText(os, text); break;
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintParagraph(std::ostream& os, const char* text)
{
  switch (this->CurrentForm)
    {
    case TextForm: this->PrintParagraphText(os, text); break;
    case HTMLForm: this->PrintParagraphHTML(os, text); break;
    case ManForm: this->PrintParagraphMan(os, text); break;
    case UsageForm: this->PrintParagraphText(os, text); break;
    }
}

//----------------------------------------------------------------------------
void cmDocumentation
::PrintPreformattedText(std::ostream& os, const char* text)
{
  bool newline = true;
  for(const char* ptr = text; *ptr; ++ptr)
    {
    if(newline)
      {
      os << this->TextIndent;
      newline = false;
      }
    os << *ptr;
    if(*ptr == '\n')
      {
      newline = true;
      }
    }
  os << "\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintParagraphText(std::ostream& os, const char* text)
{
  os << this->TextIndent;
  this->PrintColumn(os, text);
  os << "\n";
}

//----------------------------------------------------------------------------
void cmDocumentation
::PrintPreformattedHTML(std::ostream& os, const char* text)
{
  os << "<pre>";
  this->PrintHTMLEscapes(os, text);
  os << "</pre>\n    ";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintParagraphHTML(std::ostream& os, const char* text)
{
  os << "<p>";
  this->PrintHTMLEscapes(os, text);
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintPreformattedMan(std::ostream& os, const char* text)
{
  std::string man_text = text;
  cmSystemTools::ReplaceString(man_text, "\\", "\\\\");
  os << man_text << "\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintParagraphMan(std::ostream& os, const char* text)
{
  std::string man_text = text;
  cmSystemTools::ReplaceString(man_text, "\\", "\\\\");
  os << man_text << "\n\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintColumn(std::ostream& os, const char* text)
{
  // Print text arranged in an indented column of fixed witdh.
  const char* l = text;
  int column = 0;
  bool newSentence = false;
  bool firstLine = true;
  int width = this->TextWidth - static_cast<int>(strlen(this->TextIndent));
  
  // Loop until the end of the text.
  while(*l)
    {
    // Parse the next word.
    const char* r = l;
    while(*r && (*r != '\n') && (*r != ' ')) { ++r; }
    
    // Does it fit on this line?
    if(r-l < (width-column-(newSentence?1:0)))
      {
      // Word fits on this line.
      if(r > l)
        {
        if(column)
          {
          // Not first word on line.  Separate from the previous word
          // by a space, or two if this is a new sentence.
          if(newSentence)
            {
            os << "  ";
            column += 2;
            }
          else
            {
            os << " ";
            column += 1;
            }
          }
        else
          {
          // First word on line.  Print indentation unless this is the
          // first line.
          os << (firstLine?"":this->TextIndent);
          }
        
        // Print the word.
        os.write(l, static_cast<long>(r-l));
        newSentence = (*(r-1) == '.');
        }
      
      if(*r == '\n')
        {
        // Text provided a newline.  Start a new line.
        os << "\n";
        ++r;
        column = 0;
        firstLine = false;
        }
      else
        {
        // No provided newline.  Continue this line.
        column += static_cast<long>(r-l);
        }
      }
    else
      {
      // Word does not fit on this line.  Start a new line.
      os << "\n";
      firstLine = false;
      if(r > l)
        {
        os << this->TextIndent;
        os.write(l, static_cast<long>(r-l));
        column = static_cast<long>(r-l);
        newSentence = (*(r-1) == '.');
        }
      else
        {
        column = 0;
        }
      }
    
    // Move to beginning of next word.  Skip over whitespace.
    l = r;
    while(*l && (*l == ' ')) { ++l; }    
    }
}

//----------------------------------------------------------------------------
static bool cmDocumentationIsHyperlinkChar(char c)
{
  // This is not a complete list but works for CMake documentation.
  return ((c >= 'A' && c <= 'Z') ||
          (c >= 'a' && c <= 'z') ||
          (c >= '0' && c <= '9') ||
          c == '-' || c == '.' || c == '/' || c == '~' || c == '@' ||
          c == ':' || c == '_' || c == '&' || c == '?' || c == '=');
}

//----------------------------------------------------------------------------
static void cmDocumentationPrintHTMLChar(std::ostream& os, char c)
{
  // Use an escape sequence if necessary.
  static cmDocumentationEntry escapes[] =
  {
    {"<", "&lt;", 0},
    {">", "&gt;", 0},
    {"&", "&amp;", 0},
    {"\n", "<br>", 0},
    {0,0,0}
  };
  for(const cmDocumentationEntry* op = escapes; op->name; ++op)
    {
    if(op->name[0] == c)
        {
        os << op->brief;
      return;
      }
    }

  // No escape sequence is needed.
  os << c;
}

//----------------------------------------------------------------------------
const char* cmDocumentationPrintHTMLLink(std::ostream& os, const char* begin)
{
  // Look for the end of the link.
  const char* end = begin;
  while(cmDocumentationIsHyperlinkChar(*end))
    {
    ++end;
    }

  // Print the hyperlink itself.
  os << "<a href=\"";
  for(const char* c = begin; c != end; ++c)
    {
    cmDocumentationPrintHTMLChar(os, *c);
    }
  os << "\">";

  // The name of the hyperlink is the text itself.
  for(const char* c = begin; c != end; ++c)
    {
    cmDocumentationPrintHTMLChar(os, *c);
    }
  os << "</a>";

  // Return the position at which to continue scanning the input
  // string.
  return end;
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintHTMLEscapes(std::ostream& os, const char* text)
{
  // Hyperlink prefixes.
  static const char* hyperlinks[] = {"http://", "ftp://", "mailto:", 0};

  // Print each character.
  for(const char* p = text; *p;)
    {
    // Handle hyperlinks specially to make them active.
    bool found_hyperlink = false;
    for(const char** h = hyperlinks; !found_hyperlink && *h; ++h)
      {
      if(strncmp(p, *h, strlen(*h)) == 0)
        {
        p = cmDocumentationPrintHTMLLink(os, p);
        found_hyperlink = true;
        }
      }

    // Print other characters normally.
    if(!found_hyperlink)
      {
      cmDocumentationPrintHTMLChar(os, *p++);
      }
    }
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintDocumentationSingle(std::ostream& os)
{
  if(this->CommandsSection.empty())
    {
    os << "Internal error: commands list is empty." << std::endl;
    return false;
    }
  if(this->SingleCommand.length() == 0)
    {
    os << "Argument --help-command needs a command name.\n";
    return false;
    }
  for(cmDocumentationEntry* entry = &this->CommandsSection[0];
      entry->brief; ++entry)
    {
    if(entry->name && this->SingleCommand == entry->name)
      {
      this->PrintDocumentationCommand(os, entry);
      return true;
      }
    }
  // Argument was not a command.  Complain.
  os << "Argument \"" << this->SingleCommand.c_str()
     << "\" to --help-command is not a CMake command.  "
     << "Use --help-command-list to see all commands.\n";
  return false;
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintDocumentationSingleModule(std::ostream& os)
{
  if(this->SingleModuleName.length() == 0)
    {
    os << "Argument --help-module needs a module name.\n";
    return false;
    }
  std::string cmakeModules = this->CMakeRoot;
  cmakeModules += "/Modules/";
  cmakeModules += this->SingleModuleName;
  cmakeModules += ".cmake";
  if(cmSystemTools::FileExists(cmakeModules.c_str())
     && this->CreateSingleModule(cmakeModules.c_str(), 
                                 this->SingleModuleName.c_str()))
    {
    this->PrintDocumentationCommand(os, &this->ModulesSection[0]);
    os <<  "\n       Defined in: ";
    os << cmakeModules << "\n";
    return true;
    }
  // Argument was not a module.  Complain.
  os << "Argument \"" << this->SingleModuleName.c_str()
     << "\" to --help-module is not a CMake module.";
  return false;
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintDocumentationList(std::ostream& os)
{
  if(this->CommandsSection.empty())
    {
    os << "Internal error: commands list is empty." << std::endl;
    return false;
    }
  for(cmDocumentationEntry* entry = &this->CommandsSection[0];
      entry->brief; ++entry)
    {
    if(entry->name)
      {
      os << entry->name << std::endl;
      }
    }
  return true;
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintModuleList(std::ostream& os)
{
  this->CreateModulesSection();
  if(this->ModulesSection.empty())
    {
    os << "Internal error: modules list is empty." << std::endl;
    return false;
    }
  for(cmDocumentationEntry* entry = &this->ModulesSection[0];
      entry->brief; ++entry)
    {
    if(entry->name)
      {
      os << entry->name << std::endl;
      }
    }
  return true;
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintDocumentationUsage(std::ostream& os)
{
  this->CreateUsageDocumentation();
  this->Print(UsageForm, os);
  return true;
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintDocumentationFull(std::ostream& os)
{
  this->CreateFullDocumentation();
  this->Print(TextForm, os);
  return true;
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintDocumentationHTML(std::ostream& os)
{
  this->CreateFullDocumentation();
  os << "<html><body>\n";
  this->Print(HTMLForm, os);
  os << "</body></html>\n";
  return true;
}

//----------------------------------------------------------------------------
bool cmDocumentation::PrintDocumentationMan(std::ostream& os)
{
  this->CreateManDocumentation();
  os << ".TH " << this->GetNameString() << " 1 \""
     << cmSystemTools::GetCurrentDateTime("%B %d, %Y").c_str()
     << "\" \"" << this->GetNameString() 
     << " " << cmVersion::GetCMakeVersion()
     << "\"\n";
  this->Print(ManForm, os);
  return true;
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintDocumentationCommand(std::ostream& os,
                                                cmDocumentationEntry* entry)
{
  cmDocumentationEntry singleCommandSection[3] =
    {
      {entry->name, entry->brief, entry->full},
      {0,0,0}
    };
  this->ClearSections();
  this->AddSection(0, &singleCommandSection[0]);
  this->Print(TextForm, os);
}

//----------------------------------------------------------------------------
void cmDocumentation::CreateUsageDocumentation()
{
  this->ClearSections();
  if(!this->UsageSection.empty())
    {
    this->AddSection("Usage", &this->UsageSection[0]);
    }
  if(!this->OptionsSection.empty())
    {
    this->AddSection("Command-Line Options", &this->OptionsSection[0]);
    }
  if(!this->GeneratorsSection.empty())
    {
    this->AddSection("Generators", &this->GeneratorsSection[0]);
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::CreateFullDocumentation()
{
  this->ClearSections();
  if(!this->NameSection.empty())
    {
    this->AddSection("Name", &this->NameSection[0]);
    }
  if(!this->UsageSection.empty())
    {
    this->AddSection("Usage", &this->UsageSection[0]);
    }
  if(!this->DescriptionSection.empty())
    {
    this->AddSection(0, &this->DescriptionSection[0]);
    }
  if(!this->OptionsSection.empty())
    {
    this->AddSection("Command-Line Options", &this->OptionsSection[0]);
    }
  if(!this->GeneratorsSection.empty())
    {
    this->AddSection("Generators", &this->GeneratorsSection[0]);
    }
  if(!this->CommandsSection.empty())
    {
    this->AddSection("Listfile Commands", &this->CommandsSection[0]);
    }
  this->CreateModulesSection();
  if(!this->ModulesSection.empty())
    {
    this->AddSection("Standard CMake Modules", &this->ModulesSection[0]);
    }
  this->AddSection("Copyright", cmDocumentationCopyright);
  this->AddSection("See Also", cmDocumentationStandardSeeAlso);
}

//----------------------------------------------------------------------------
void cmDocumentation::CreateManDocumentation()
{
  this->ClearSections();
  if(!this->NameSection.empty())
    {
    this->AddSection("NAME", &this->NameSection[0]);
    }
  if(!this->UsageSection.empty())
    {
    this->AddSection("SYNOPSIS", &this->UsageSection[0]);
    }
  if(!this->DescriptionSection.empty())
    {
    this->AddSection("DESCRIPTION", &this->DescriptionSection[0]);
    }
  if(!this->OptionsSection.empty())
    {
    this->AddSection("OPTIONS", &this->OptionsSection[0]);
    }
  if(!this->GeneratorsSection.empty())
    {
    this->AddSection("GENERATORS", &this->GeneratorsSection[0]);
    }
  if(!this->CommandsSection.empty())
    {
    this->AddSection("COMMANDS", &this->CommandsSection[0]);
    }
  this->CreateModulesSection();
  if(!this->ModulesSection.empty())
    {
    this->AddSection("MODULES", &this->ModulesSection[0]);
    }

  this->AddSection("COPYRIGHT", cmDocumentationCopyright);
  if(!this->SeeAlsoSection.empty())
    {
    this->AddSection("SEE ALSO", &this->SeeAlsoSection[0]);
    }
  this->AddSection("AUTHOR", cmDocumentationAuthor);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetSection(const cmDocumentationEntry* header,
                                 const cmDocumentationEntry* section,
                                 const cmDocumentationEntry* footer,
                                 std::vector<cmDocumentationEntry>& vec)
{
  vec.erase(vec.begin(), vec.end());
  if(header)
    {
    for(const cmDocumentationEntry* op = header; op->brief; ++op)
      {
      vec.push_back(*op);
      }
    }
  if(section)
    {
    for(const cmDocumentationEntry* op = section; op->brief; ++op)
      {
      vec.push_back(*op);
      }
    }
  if(footer)
    {
    for(const cmDocumentationEntry* op = footer; op->brief; ++op)
      {
      vec.push_back(*op);
      }
    }
  cmDocumentationEntry empty = {0,0,0};
  vec.push_back(empty);  
}

//----------------------------------------------------------------------------
const char* cmDocumentation::GetNameString()
{
  if(this->NameString.length() > 0)
    {
    return this->NameString.c_str();
    }
  else
    {
    return "CMake";
    }
}

//----------------------------------------------------------------------------
bool cmDocumentation::IsOption(const char* arg)
{
  return ((arg[0] == '-') || (strcmp(arg, "/V") == 0) || 
          (strcmp(arg, "/?") == 0));
}
