Change naming convention for C++ interfaces
[alexxy/gromacs.git] / src / gromacs / commandline / cmdlinehelpmodule.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2015, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::CommandLineHelpModule.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_commandline
41  */
42 #include "gmxpre.h"
43
44 #include "cmdlinehelpmodule.h"
45
46 #include <string>
47 #include <vector>
48
49 #include <boost/scoped_ptr.hpp>
50
51 #include "gromacs/commandline/cmdlinehelpcontext.h"
52 #include "gromacs/commandline/cmdlinehelpwriter.h"
53 #include "gromacs/commandline/cmdlineparser.h"
54 #include "gromacs/onlinehelp/helpformat.h"
55 #include "gromacs/onlinehelp/helpmanager.h"
56 #include "gromacs/onlinehelp/helptopic.h"
57 #include "gromacs/onlinehelp/helpwritercontext.h"
58 #include "gromacs/options/basicoptions.h"
59 #include "gromacs/options/options.h"
60 #include "gromacs/utility/arrayref.h"
61 #include "gromacs/utility/baseversion.h"
62 #include "gromacs/utility/exceptions.h"
63 #include "gromacs/utility/fileredirector.h"
64 #include "gromacs/utility/gmxassert.h"
65 #include "gromacs/utility/path.h"
66 #include "gromacs/utility/programcontext.h"
67 #include "gromacs/utility/stringstream.h"
68 #include "gromacs/utility/stringutil.h"
69 #include "gromacs/utility/textreader.h"
70 #include "gromacs/utility/textstream.h"
71 #include "gromacs/utility/textwriter.h"
72
73 #include "shellcompletions.h"
74
75 namespace gmx
76 {
77
78 namespace
79 {
80 class IHelpExport;
81 class RootHelpTopic;
82 }   // namespace
83
84 /********************************************************************
85  * CommandLineHelpModuleImpl declaration
86  */
87
88 class CommandLineHelpModuleImpl
89 {
90     public:
91         CommandLineHelpModuleImpl(const IProgramContext            &programContext,
92                                   const std::string                &binaryName,
93                                   const CommandLineModuleMap       &modules,
94                                   const CommandLineModuleGroupList &groups);
95
96         void exportHelp(IHelpExport *exporter);
97
98         boost::scoped_ptr<RootHelpTopic>  rootTopic_;
99         const IProgramContext            &programContext_;
100         std::string                       binaryName_;
101         const CommandLineModuleMap       &modules_;
102         const CommandLineModuleGroupList &groups_;
103
104         CommandLineHelpContext           *context_;
105         const ICommandLineModule         *moduleOverride_;
106         bool                              bHidden_;
107
108         IFileOutputRedirector            *outputRedirector_;
109
110         GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModuleImpl);
111 };
112
113 namespace
114 {
115
116 /********************************************************************
117  * IHelpExport
118  */
119
120 /*! \brief
121  * Callbacks for exporting help information for command-line modules.
122  *
123  * \ingroup module_commandline
124  */
125 class IHelpExport
126 {
127     public:
128         //! Shorthand for a list of modules contained in a group.
129         typedef CommandLineModuleGroupData::ModuleList ModuleGroupContents;
130
131         virtual ~IHelpExport() {};
132
133         /*! \brief
134          * Called once before exporting individual modules.
135          *
136          * Can, e.g., open shared output files (e.g., if the output is written
137          * into a single file, or if a separate index is required) and write
138          * headers into them.
139          */
140         virtual void startModuleExport() = 0;
141         /*! \brief
142          * Called to export the help for each module.
143          *
144          * \param[in] module      Module for which the help should be exported.
145          * \param[in] tag         Unique tag for the module (gmx-something).
146          * \param[in] displayName Display name for the module (gmx something).
147          */
148         virtual void exportModuleHelp(
149             const ICommandLineModule         &module,
150             const std::string                &tag,
151             const std::string                &displayName) = 0;
152         /*! \brief
153          * Called after all modules have been exported.
154          *
155          * Can close files opened in startModuleExport(), write footers to them
156          * etc.
157          */
158         virtual void finishModuleExport() = 0;
159
160         /*! \brief
161          * Called once before exporting module groups.
162          *
163          * Can, e.g., open a single output file for listing all the groups.
164          */
165         virtual void startModuleGroupExport() = 0;
166         /*! \brief
167          * Called to export the help for each module group.
168          *
169          * \param[in] title    Title for the group.
170          * \param[in] modules  List of modules in the group.
171          */
172         virtual void exportModuleGroup(const char                *title,
173                                        const ModuleGroupContents &modules) = 0;
174         /*! \brief
175          * Called after all module groups have been exported.
176          *
177          * Can close files opened in startModuleGroupExport(), write footers to them
178          * etc.
179          */
180         virtual void finishModuleGroupExport() = 0;
181
182         /*! \brief
183          * Called to export the help for a top-level topic.
184          *
185          * \param[in] topic   Topic to export.
186          */
187         virtual void exportTopic(const IHelpTopic &topic) = 0;
188 };
189
190 /********************************************************************
191  * RootHelpTopic
192  */
193
194 struct RootHelpText
195 {
196     static const char        title[];
197     static const char *const text[];
198 };
199
200 // These are used for the gmx.1 man page.
201 // TODO: Do not hardcode them here, but pass them from the outside to make this
202 // code more generic.
203 const char        RootHelpText::title[] = "molecular dynamics simulation suite";
204 const char *const RootHelpText::text[]  = {
205     "|Gromacs| is a full-featured suite of programs to perform molecular",
206     "dynamics simulations, i.e., to simulate the behavior of systems with",
207     "hundreds to millions of particles using Newtonian equations of motion.",
208     "It is primarily used for research on proteins, lipids, and polymers, but",
209     "can be applied to a wide variety of chemical and biological research",
210     "questions.",
211 };
212
213 /*! \brief
214  * Help topic that forms the root of the help tree for the help subcommand.
215  *
216  * \ingroup module_commandline
217  */
218 class RootHelpTopic : public AbstractCompositeHelpTopic
219 {
220     public:
221         /*! \brief
222          * Creates a root help topic.
223          *
224          * Does not throw.
225          */
226         explicit RootHelpTopic(const CommandLineHelpModuleImpl &helpModule)
227             : helpModule_(helpModule)
228         {
229         }
230
231         virtual const char *name() const { return helpModule_.binaryName_.c_str(); }
232         virtual const char *title() const { return title_.c_str(); }
233
234         //! Adds a top-level topic and optionally marks it as exported.
235         void addTopic(HelpTopicPointer topic, bool bExported)
236         {
237             if (bExported)
238             {
239                 exportedTopics_.push_back(topic->name());
240             }
241             addSubTopic(move(topic));
242         }
243         //! Exports all the top-level topics with the given exporter.
244         void exportHelp(IHelpExport *exporter);
245
246         virtual void writeHelp(const HelpWriterContext &context) const;
247
248     private:
249         // unused because of the writeHelp() override
250         virtual std::string helpText() const { return ""; }
251
252         const CommandLineHelpModuleImpl  &helpModule_;
253         std::string                       title_;
254         std::vector<std::string>          exportedTopics_;
255
256         GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
257 };
258
259 void RootHelpTopic::exportHelp(IHelpExport *exporter)
260 {
261     std::vector<std::string>::const_iterator topicName;
262     for (topicName = exportedTopics_.begin();
263          topicName != exportedTopics_.end();
264          ++topicName)
265     {
266         const IHelpTopic *topic = findSubTopic(topicName->c_str());
267         GMX_RELEASE_ASSERT(topic != NULL, "Exported help topic no longer found");
268         exporter->exportTopic(*topic);
269     }
270     // For now, the title is only set for the export to make it not appear in
271     // console output, which makes things consistent for 'gmx help' and
272     // 'gmx help <command>'.
273     title_ = RootHelpText::title;
274     exporter->exportTopic(*this);
275 }
276
277 void RootHelpTopic::writeHelp(const HelpWriterContext &context) const
278 {
279     {
280         CommandLineCommonOptionsHolder            optionsHolder;
281         boost::scoped_ptr<CommandLineHelpContext> cmdlineContext;
282         if (helpModule_.context_ != NULL)
283         {
284             cmdlineContext.reset(new CommandLineHelpContext(*helpModule_.context_));
285         }
286         else
287         {
288             cmdlineContext.reset(new CommandLineHelpContext(context));
289         }
290         cmdlineContext->setModuleDisplayName(helpModule_.binaryName_);
291         optionsHolder.initOptions();
292         Options &options = *optionsHolder.options();
293         options.setDescription(RootHelpText::text);
294         // TODO: Add <command> [<args>] into the synopsis.
295         CommandLineHelpWriter(options)
296             .setShowDescriptions(context.outputFormat() != eHelpOutputFormat_Console)
297             .writeHelp(*cmdlineContext);
298     }
299     if (context.outputFormat() == eHelpOutputFormat_Console)
300     {
301         // TODO: Consider printing a list of "core" commands. Would require someone
302         // to determine such a set...
303         writeSubTopicList(context,
304                           "Additional help is available on the following topics:");
305         context.writeTextBlock("To access the help, use '[PROGRAM] help <topic>'.");
306         context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
307     }
308     else
309     {
310         // TODO: This should not really end up on the HTML page.
311         context.writeTitle(formatString("%s commands", helpModule_.binaryName_.c_str()));
312         context.writeTextBlock(
313                 "The following commands are available. Please refer to their "
314                 "individual man pages or [TT][PROGRAM] help <command>[tt] "
315                 "for further details.");
316         context.writeTextBlock("");
317         context.writeTextBlock(".. include:: /fragments/bytopic-man.rst");
318     }
319 }
320
321 /********************************************************************
322  * CommandsHelpTopic
323  */
324
325 /*! \brief
326  * Help topic for listing the commands.
327  *
328  * \ingroup module_commandline
329  */
330 class CommandsHelpTopic : public IHelpTopic
331 {
332     public:
333         /*! \brief
334          * Creates a command list help topic.
335          *
336          * \param[in]     helpModule Help module to get module information from.
337          *
338          * Does not throw.
339          */
340         explicit CommandsHelpTopic(const CommandLineHelpModuleImpl &helpModule)
341             : helpModule_(helpModule)
342         {
343         }
344
345         virtual const char *name() const { return "commands"; }
346         virtual const char *title() const { return "List of available commands"; }
347         virtual bool hasSubTopics() const { return false; }
348         virtual const IHelpTopic *findSubTopic(const char * /*name*/) const
349         {
350             return NULL;
351         }
352
353         virtual void writeHelp(const HelpWriterContext &context) const;
354
355     private:
356         const CommandLineHelpModuleImpl &helpModule_;
357
358         GMX_DISALLOW_COPY_AND_ASSIGN(CommandsHelpTopic);
359 };
360
361 void CommandsHelpTopic::writeHelp(const HelpWriterContext &context) const
362 {
363     if (context.outputFormat() != eHelpOutputFormat_Console)
364     {
365         GMX_THROW(NotImplementedError(
366                           "Module list is not implemented for this output format"));
367     }
368     int maxNameLength = 0;
369     const CommandLineModuleMap           &modules = helpModule_.modules_;
370     CommandLineModuleMap::const_iterator  module;
371     for (module = modules.begin(); module != modules.end(); ++module)
372     {
373         int nameLength = static_cast<int>(module->first.length());
374         if (module->second->shortDescription() != NULL
375             && nameLength > maxNameLength)
376         {
377             maxNameLength = nameLength;
378         }
379     }
380     context.writeTextBlock(
381             "Usage: [PROGRAM] [<options>] <command> [<args>][PAR]"
382             "Available commands:");
383     TextWriter        &file = context.outputFile();
384     TextTableFormatter formatter;
385     formatter.addColumn(NULL, maxNameLength + 1, false);
386     formatter.addColumn(NULL, 72 - maxNameLength, true);
387     formatter.setFirstColumnIndent(4);
388     for (module = modules.begin(); module != modules.end(); ++module)
389     {
390         const char *name        = module->first.c_str();
391         const char *description = module->second->shortDescription();
392         if (description != NULL)
393         {
394             formatter.clear();
395             formatter.addColumnLine(0, name);
396             formatter.addColumnLine(1, description);
397             file.writeString(formatter.formatRow());
398         }
399     }
400     context.writeTextBlock(
401             "For help on a command, use '[PROGRAM] help <command>'.");
402 }
403
404 /********************************************************************
405  * ModuleHelpTopic
406  */
407
408 /*! \brief
409  * Help topic wrapper for a command-line module.
410  *
411  * This class implements IHelpTopic such that it wraps a
412  * ICommandLineModule, allowing subcommand "help <command>"
413  * to produce the help for "<command>".
414  *
415  * \ingroup module_commandline
416  */
417 class ModuleHelpTopic : public IHelpTopic
418 {
419     public:
420         //! Constructs a help topic for a specific module.
421         ModuleHelpTopic(const ICommandLineModule         &module,
422                         const CommandLineHelpModuleImpl  &helpModule)
423             : module_(module), helpModule_(helpModule)
424         {
425         }
426
427         virtual const char *name() const { return module_.name(); }
428         virtual const char *title() const { return NULL; }
429         virtual bool hasSubTopics() const { return false; }
430         virtual const IHelpTopic *findSubTopic(const char * /*name*/) const
431         {
432             return NULL;
433         }
434         virtual void writeHelp(const HelpWriterContext &context) const;
435
436     private:
437         const ICommandLineModule         &module_;
438         const CommandLineHelpModuleImpl  &helpModule_;
439
440         GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
441 };
442
443 void ModuleHelpTopic::writeHelp(const HelpWriterContext & /*context*/) const
444 {
445     CommandLineHelpContext context(*helpModule_.context_);
446     const char *const      program = helpModule_.binaryName_.c_str();
447     context.setModuleDisplayName(formatString("%s %s", program, module_.name()));
448     module_.writeHelp(context);
449 }
450
451 /********************************************************************
452  * HelpExportReStructuredText
453  */
454
455 /*! \internal \brief
456  * Adds hyperlinks to modules within this binary.
457  *
458  * \param[in,out] links      Links are added here.
459  * \param[in]     helpModule Help module to get module information from.
460  * \throws        std::bad_alloc if out of memory.
461  *
462  * Initializes a HelpLinks object with links to modules defined in
463  * \p helpModule.
464  *
465  * \ingroup module_commandline
466  */
467 void initProgramLinks(HelpLinks *links, const CommandLineHelpModuleImpl &helpModule)
468 {
469     const char *const                    program = helpModule.binaryName_.c_str();
470     CommandLineModuleMap::const_iterator module;
471     for (module = helpModule.modules_.begin();
472          module != helpModule.modules_.end();
473          ++module)
474     {
475         if (module->second->shortDescription() != NULL)
476         {
477             std::string linkName("[gmx-" + module->first + "]");
478             const char *name = module->first.c_str();
479             std::string reference(
480                     formatString(":doc:`%s %s <%s-%s>`", program, name, program, name));
481             std::string displayName(
482                     formatString("[TT]%s %s[tt]", program, name));
483             links->addLink(linkName, reference, displayName);
484         }
485     }
486 }
487
488 /*! \internal \brief
489  * Implements export for web pages as reStructuredText.
490  *
491  * \ingroup module_commandline
492  */
493 class HelpExportReStructuredText : public IHelpExport
494 {
495     public:
496         //! Initializes reST exporter.
497         HelpExportReStructuredText(
498             const CommandLineHelpModuleImpl &helpModule,
499             IFileOutputRedirector           *outputRedirector);
500
501         virtual void startModuleExport();
502         virtual void exportModuleHelp(
503             const ICommandLineModule         &module,
504             const std::string                &tag,
505             const std::string                &displayName);
506         virtual void finishModuleExport();
507
508         virtual void startModuleGroupExport();
509         virtual void exportModuleGroup(const char                *title,
510                                        const ModuleGroupContents &modules);
511         virtual void finishModuleGroupExport();
512
513         virtual void exportTopic(const IHelpTopic &topic);
514
515     private:
516         IFileOutputRedirector          *outputRedirector_;
517         const std::string              &binaryName_;
518         HelpLinks                       links_;
519         boost::scoped_ptr<TextWriter>   indexFile_;
520         boost::scoped_ptr<TextWriter>   manPagesFile_;
521 };
522
523 HelpExportReStructuredText::HelpExportReStructuredText(
524         const CommandLineHelpModuleImpl &helpModule,
525         IFileOutputRedirector           *outputRedirector)
526     : outputRedirector_(outputRedirector),
527       binaryName_(helpModule.binaryName_),
528       links_(eHelpOutputFormat_Rst)
529 {
530     TextReader   linksFile("links.dat");
531     std::string  line;
532     while (linksFile.readLineTrimmed(&line))
533     {
534         links_.addLink("[REF]." + line + "[ref]",
535                        formatString(":ref:`.%s <%s>`", line.c_str(), line.c_str()),
536                        line);
537         links_.addLink("[REF]" + line + "[ref]", formatString(":ref:`%s`", line.c_str()), line);
538     }
539     linksFile.close();
540     initProgramLinks(&links_, helpModule);
541 }
542
543 void HelpExportReStructuredText::startModuleExport()
544 {
545     indexFile_.reset(
546             new TextWriter(
547                     outputRedirector_->openTextOutputFile("fragments/byname.rst")));
548     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s",
549                                        binaryName_.c_str(), binaryName_.c_str(),
550                                        RootHelpText::title));
551     manPagesFile_.reset(
552             new TextWriter(
553                     outputRedirector_->openTextOutputFile("conf-man.py")));
554     manPagesFile_->writeLine("man_pages = [");
555 }
556
557 void HelpExportReStructuredText::exportModuleHelp(
558         const ICommandLineModule         &module,
559         const std::string                &tag,
560         const std::string                &displayName)
561 {
562     TextOutputStreamPointer file
563         = outputRedirector_->openTextOutputFile("onlinehelp/" + tag + ".rst");
564     TextWriter              writer(file);
565     writer.writeLine(formatString(".. _%s:", displayName.c_str()));
566     if (0 == displayName.compare(binaryName_ + " mdrun"))
567     {
568         // Make an extra link target for the convenience of
569         // MPI-specific documentation
570         writer.writeLine(".. _mdrun_mpi:");
571     }
572     writer.writeLine();
573
574     CommandLineHelpContext context(file.get(), eHelpOutputFormat_Rst, &links_, binaryName_);
575     context.enterSubSection(displayName);
576     context.setModuleDisplayName(displayName);
577     module.writeHelp(context);
578
579     writer.writeLine();
580     writer.writeLine(".. only:: man");
581     writer.writeLine();
582     writer.writeLine("   See also");
583     writer.writeLine("   --------");
584     writer.writeLine();
585     writer.writeLine(formatString("   :manpage:`%s(1)`", binaryName_.c_str()));
586     writer.writeLine();
587     writer.writeLine("   More information about |Gromacs| is available at <http://www.gromacs.org/>.");
588     file->close();
589
590     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s",
591                                        displayName.c_str(), tag.c_str(),
592                                        module.shortDescription()));
593     manPagesFile_->writeLine(
594             formatString("    ('onlinehelp/%s', '%s', \"%s\", '', 1),",
595                          tag.c_str(), tag.c_str(), module.shortDescription()));
596 }
597
598 void HelpExportReStructuredText::finishModuleExport()
599 {
600     indexFile_->close();
601     indexFile_.reset();
602     // TODO: Generalize.
603     manPagesFile_->writeLine(
604             formatString("    ('onlinehelp/%s', '%s', '%s', '', 1)",
605                          binaryName_.c_str(), binaryName_.c_str(),
606                          RootHelpText::title));
607     manPagesFile_->writeLine("]");
608     manPagesFile_->close();
609     manPagesFile_.reset();
610 }
611
612 void HelpExportReStructuredText::startModuleGroupExport()
613 {
614     indexFile_.reset(
615             new TextWriter(
616                     outputRedirector_->openTextOutputFile("fragments/bytopic.rst")));
617     manPagesFile_.reset(
618             new TextWriter(
619                     outputRedirector_->openTextOutputFile("fragments/bytopic-man.rst")));
620 }
621
622 void HelpExportReStructuredText::exportModuleGroup(
623         const char                *title,
624         const ModuleGroupContents &modules)
625 {
626     indexFile_->writeLine(title);
627     indexFile_->writeLine(std::string(std::strlen(title), '^'));
628     manPagesFile_->writeLine(title);
629     manPagesFile_->writeLine(std::string(std::strlen(title), '^'));
630
631     ModuleGroupContents::const_iterator module;
632     for (module = modules.begin(); module != modules.end(); ++module)
633     {
634         const std::string     &tag(module->first);
635         std::string            displayName(tag);
636         // TODO: This does not work if the binary name would contain a dash,
637         // but that is not currently the case.
638         const size_t           dashPos = displayName.find('-');
639         GMX_RELEASE_ASSERT(dashPos != std::string::npos,
640                            "There should always be at least one dash in the tag");
641         displayName[dashPos] = ' ';
642         indexFile_->writeLine(formatString(":doc:`%s </onlinehelp/%s>`\n  %s",
643                                            displayName.c_str(), tag.c_str(),
644                                            module->second));
645         manPagesFile_->writeLine(formatString(":manpage:`%s(1)`\n  %s",
646                                               tag.c_str(),
647                                               module->second));
648     }
649     indexFile_->writeLine();
650     manPagesFile_->writeLine();
651 }
652
653 void HelpExportReStructuredText::finishModuleGroupExport()
654 {
655     indexFile_->close();
656     indexFile_.reset();
657     manPagesFile_->close();
658     manPagesFile_.reset();
659 }
660
661 void HelpExportReStructuredText::exportTopic(const IHelpTopic &topic)
662 {
663     const std::string       path("onlinehelp/" + std::string(topic.name()) + ".rst");
664     TextOutputStreamPointer file(outputRedirector_->openTextOutputFile(path));
665     CommandLineHelpContext  context(file.get(), eHelpOutputFormat_Rst, &links_,
666                                     binaryName_);
667     HelpManager             manager(topic, context.writerContext());
668     manager.writeCurrentTopic();
669     file->close();
670 }
671
672 /********************************************************************
673  * HelpExportCompletion
674  */
675
676 /*! \internal \brief
677  * Implements export for command-line completion.
678  *
679  * \ingroup module_commandline
680  */
681 class HelpExportCompletion : public IHelpExport
682 {
683     public:
684         //! Initializes completion exporter.
685         explicit HelpExportCompletion(const CommandLineHelpModuleImpl &helpModule);
686
687         virtual void startModuleExport();
688         virtual void exportModuleHelp(
689             const ICommandLineModule         &module,
690             const std::string                &tag,
691             const std::string                &displayName);
692         virtual void finishModuleExport();
693
694         virtual void startModuleGroupExport() {}
695         virtual void exportModuleGroup(const char                * /*title*/,
696                                        const ModuleGroupContents & /*modules*/) {}
697         virtual void finishModuleGroupExport() {}
698
699         virtual void exportTopic(const IHelpTopic & /*topic*/) {}
700
701     private:
702         ShellCompletionWriter    bashWriter_;
703         std::vector<std::string> modules_;
704 };
705
706 HelpExportCompletion::HelpExportCompletion(
707         const CommandLineHelpModuleImpl &helpModule)
708     : bashWriter_(helpModule.binaryName_, eShellCompletionFormat_Bash)
709 {
710 }
711
712 void HelpExportCompletion::startModuleExport()
713 {
714     bashWriter_.startCompletions();
715 }
716
717 void HelpExportCompletion::exportModuleHelp(
718         const ICommandLineModule         &module,
719         const std::string                 & /*tag*/,
720         const std::string                 & /*displayName*/)
721 {
722     modules_.push_back(module.name());
723     {
724         CommandLineHelpContext context(&bashWriter_);
725         // We use the display name to pass the name of the module to the
726         // completion writer.
727         context.setModuleDisplayName(module.name());
728         module.writeHelp(context);
729     }
730 }
731
732 void HelpExportCompletion::finishModuleExport()
733 {
734     CommandLineCommonOptionsHolder optionsHolder;
735     optionsHolder.initOptions();
736     bashWriter_.writeWrapperCompletions(modules_, *optionsHolder.options());
737     bashWriter_.finishCompletions();
738 }
739
740 }   // namespace
741
742 /********************************************************************
743  * CommandLineHelpModuleImpl implementation
744  */
745
746 CommandLineHelpModuleImpl::CommandLineHelpModuleImpl(
747         const IProgramContext            &programContext,
748         const std::string                &binaryName,
749         const CommandLineModuleMap       &modules,
750         const CommandLineModuleGroupList &groups)
751     : rootTopic_(new RootHelpTopic(*this)), programContext_(programContext),
752       binaryName_(binaryName), modules_(modules), groups_(groups),
753       context_(NULL), moduleOverride_(NULL), bHidden_(false),
754       outputRedirector_(&defaultFileOutputRedirector())
755 {
756 }
757
758 void CommandLineHelpModuleImpl::exportHelp(IHelpExport *exporter)
759 {
760     // TODO: Would be nicer to have the file names supplied by the build system
761     // and/or export a list of files from here.
762     const char *const program = binaryName_.c_str();
763
764     exporter->startModuleExport();
765     CommandLineModuleMap::const_iterator module;
766     for (module = modules_.begin(); module != modules_.end(); ++module)
767     {
768         if (module->second->shortDescription() != NULL)
769         {
770             const char *const moduleName = module->first.c_str();
771             std::string       tag(formatString("%s-%s", program, moduleName));
772             std::string       displayName(formatString("%s %s", program, moduleName));
773             exporter->exportModuleHelp(*module->second, tag, displayName);
774         }
775     }
776     exporter->finishModuleExport();
777
778     exporter->startModuleGroupExport();
779     CommandLineModuleGroupList::const_iterator group;
780     for (group = groups_.begin(); group != groups_.end(); ++group)
781     {
782         exporter->exportModuleGroup((*group)->title(), (*group)->modules());
783     }
784     exporter->finishModuleGroupExport();
785
786     rootTopic_->exportHelp(exporter);
787 }
788
789 namespace
790 {
791
792 /********************************************************************
793  * ModificationCheckingFileOutputStream
794  */
795
796 class ModificationCheckingFileOutputStream : public TextOutputStream
797 {
798     public:
799         ModificationCheckingFileOutputStream(
800             const char                    *path,
801             IFileOutputRedirector         *redirector)
802             : path_(path), redirector_(redirector)
803         {
804         }
805
806         virtual void write(const char *str) { contents_.write(str); }
807         virtual void close()
808         {
809             const std::string &newContents = contents_.toString();
810             // TODO: Redirect these for unit tests.
811             if (File::exists(path_))
812             {
813                 const std::string originalContents_
814                     = TextReader::readFileToString(path_);
815                 if (originalContents_ == newContents)
816                 {
817                     return;
818                 }
819             }
820             TextWriter writer(redirector_->openTextOutputFile(path_));
821             writer.writeString(newContents);
822         }
823
824     private:
825         std::string                     path_;
826         StringOutputStream              contents_;
827         IFileOutputRedirector          *redirector_;
828 };
829
830 /********************************************************************
831  * ModificationCheckingFileOutputRedirector
832  */
833
834 class ModificationCheckingFileOutputRedirector : public IFileOutputRedirector
835 {
836     public:
837         explicit ModificationCheckingFileOutputRedirector(
838             IFileOutputRedirector *redirector)
839             : redirector_(redirector)
840         {
841         }
842
843         virtual TextOutputStream &standardOutput()
844         {
845             return redirector_->standardOutput();
846         }
847         virtual TextOutputStreamPointer openTextOutputFile(const char *filename)
848         {
849             return TextOutputStreamPointer(
850                     new ModificationCheckingFileOutputStream(filename, redirector_));
851         }
852
853     private:
854         IFileOutputRedirector  *redirector_;
855 };
856
857 }   // namespace
858
859 /********************************************************************
860  * CommandLineHelpModule
861  */
862
863 CommandLineHelpModule::CommandLineHelpModule(
864         const IProgramContext            &programContext,
865         const std::string                &binaryName,
866         const CommandLineModuleMap       &modules,
867         const CommandLineModuleGroupList &groups)
868     : impl_(new Impl(programContext, binaryName, modules, groups))
869 {
870 }
871
872 CommandLineHelpModule::~CommandLineHelpModule()
873 {
874 }
875
876 HelpTopicPointer CommandLineHelpModule::createModuleHelpTopic(
877         const ICommandLineModule &module) const
878 {
879     return HelpTopicPointer(new ModuleHelpTopic(module, *impl_));
880 }
881
882 void CommandLineHelpModule::addTopic(HelpTopicPointer topic, bool bExported)
883 {
884     impl_->rootTopic_->addTopic(move(topic), bExported);
885 }
886
887 void CommandLineHelpModule::setShowHidden(bool bHidden)
888 {
889     impl_->bHidden_ = bHidden;
890 }
891
892 void CommandLineHelpModule::setModuleOverride(
893         const ICommandLineModule &module)
894 {
895     impl_->moduleOverride_ = &module;
896 }
897
898 void CommandLineHelpModule::setOutputRedirector(
899         IFileOutputRedirector *output)
900 {
901     impl_->outputRedirector_ = output;
902 }
903
904 int CommandLineHelpModule::run(int argc, char *argv[])
905 {
906     // Add internal topics lazily here.
907     addTopic(HelpTopicPointer(new CommandsHelpTopic(*impl_)), false);
908
909     const char *const exportFormats[] = { "rst", "completion" };
910     std::string       exportFormat;
911     Options           options(NULL, NULL);
912     options.addOption(StringOption("export").store(&exportFormat)
913                           .enumValue(exportFormats));
914     CommandLineParser(&options).parse(&argc, argv);
915     if (!exportFormat.empty())
916     {
917         ModificationCheckingFileOutputRedirector redirector(impl_->outputRedirector_);
918         boost::scoped_ptr<IHelpExport>           exporter;
919         if (exportFormat == "rst")
920         {
921             exporter.reset(new HelpExportReStructuredText(*impl_, &redirector));
922         }
923         else if (exportFormat == "completion")
924         {
925             exporter.reset(new HelpExportCompletion(*impl_));
926         }
927         else
928         {
929             GMX_THROW(NotImplementedError("This help format is not implemented"));
930         }
931         impl_->exportHelp(exporter.get());
932         return 0;
933     }
934
935     TextOutputStream      &outputFile = impl_->outputRedirector_->standardOutput();
936     HelpLinks              links(eHelpOutputFormat_Console);
937     initProgramLinks(&links, *impl_);
938     CommandLineHelpContext context(&outputFile, eHelpOutputFormat_Console, &links,
939                                    impl_->binaryName_);
940     context.setShowHidden(impl_->bHidden_);
941     if (impl_->moduleOverride_ != NULL)
942     {
943         context.setModuleDisplayName(impl_->programContext_.displayName());
944         impl_->moduleOverride_->writeHelp(context);
945         return 0;
946     }
947     impl_->context_ = &context;
948
949     HelpManager helpManager(*impl_->rootTopic_, context.writerContext());
950     try
951     {
952         for (int i = 1; i < argc; ++i)
953         {
954             helpManager.enterTopic(argv[i]);
955         }
956     }
957     catch (const InvalidInputError &ex)
958     {
959         fprintf(stderr, "%s\n", ex.what());
960         return 2;
961     }
962     helpManager.writeCurrentTopic();
963     return 0;
964 }
965
966 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext &context) const
967 {
968     const HelpWriterContext &writerContext = context.writerContext();
969     // TODO: Implement.
970     if (writerContext.outputFormat() != eHelpOutputFormat_Console)
971     {
972         return;
973     }
974     writerContext.writeTextBlock(
975             "Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
976     // TODO: More information.
977 }
978
979 } // namespace gmx