Merge branch release-5-1
[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         ConstArrayRef<const char *> helpText;
294         if (context.outputFormat() != eHelpOutputFormat_Console)
295         {
296             helpText = RootHelpText::text;
297         }
298         // TODO: Add <command> [<args>] into the synopsis.
299         CommandLineHelpWriter(options)
300             .setHelpText(helpText)
301             .writeHelp(*cmdlineContext);
302     }
303     if (context.outputFormat() == eHelpOutputFormat_Console)
304     {
305         // TODO: Consider printing a list of "core" commands. Would require someone
306         // to determine such a set...
307         writeSubTopicList(context,
308                           "Additional help is available on the following topics:");
309         context.writeTextBlock("To access the help, use '[PROGRAM] help <topic>'.");
310         context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
311     }
312     else
313     {
314         // TODO: This should not really end up on the HTML page.
315         context.writeTitle(formatString("%s commands", helpModule_.binaryName_.c_str()));
316         context.writeTextBlock(
317                 "The following commands are available. Please refer to their "
318                 "individual man pages or [TT][PROGRAM] help <command>[tt] "
319                 "for further details.");
320         context.writeTextBlock("");
321         context.writeTextBlock(".. include:: /fragments/bytopic-man.rst");
322     }
323 }
324
325 /********************************************************************
326  * CommandsHelpTopic
327  */
328
329 /*! \brief
330  * Help topic for listing the commands.
331  *
332  * \ingroup module_commandline
333  */
334 class CommandsHelpTopic : public IHelpTopic
335 {
336     public:
337         /*! \brief
338          * Creates a command list help topic.
339          *
340          * \param[in]     helpModule Help module to get module information from.
341          *
342          * Does not throw.
343          */
344         explicit CommandsHelpTopic(const CommandLineHelpModuleImpl &helpModule)
345             : helpModule_(helpModule)
346         {
347         }
348
349         virtual const char *name() const { return "commands"; }
350         virtual const char *title() const { return "List of available commands"; }
351         virtual bool hasSubTopics() const { return false; }
352         virtual const IHelpTopic *findSubTopic(const char * /*name*/) const
353         {
354             return NULL;
355         }
356
357         virtual void writeHelp(const HelpWriterContext &context) const;
358
359     private:
360         const CommandLineHelpModuleImpl &helpModule_;
361
362         GMX_DISALLOW_COPY_AND_ASSIGN(CommandsHelpTopic);
363 };
364
365 void CommandsHelpTopic::writeHelp(const HelpWriterContext &context) const
366 {
367     if (context.outputFormat() != eHelpOutputFormat_Console)
368     {
369         GMX_THROW(NotImplementedError(
370                           "Module list is not implemented for this output format"));
371     }
372     int maxNameLength = 0;
373     const CommandLineModuleMap           &modules = helpModule_.modules_;
374     CommandLineModuleMap::const_iterator  module;
375     for (module = modules.begin(); module != modules.end(); ++module)
376     {
377         int nameLength = static_cast<int>(module->first.length());
378         if (module->second->shortDescription() != NULL
379             && nameLength > maxNameLength)
380         {
381             maxNameLength = nameLength;
382         }
383     }
384     context.writeTextBlock(
385             "Usage: [PROGRAM] [<options>] <command> [<args>][PAR]"
386             "Available commands:");
387     TextWriter        &file = context.outputFile();
388     TextTableFormatter formatter;
389     formatter.addColumn(NULL, maxNameLength + 1, false);
390     formatter.addColumn(NULL, 72 - maxNameLength, true);
391     formatter.setFirstColumnIndent(4);
392     for (module = modules.begin(); module != modules.end(); ++module)
393     {
394         const char *name        = module->first.c_str();
395         const char *description = module->second->shortDescription();
396         if (description != NULL)
397         {
398             formatter.clear();
399             formatter.addColumnLine(0, name);
400             formatter.addColumnLine(1, description);
401             file.writeString(formatter.formatRow());
402         }
403     }
404     context.writeTextBlock(
405             "For help on a command, use '[PROGRAM] help <command>'.");
406 }
407
408 /********************************************************************
409  * ModuleHelpTopic
410  */
411
412 /*! \brief
413  * Help topic wrapper for a command-line module.
414  *
415  * This class implements IHelpTopic such that it wraps a
416  * ICommandLineModule, allowing subcommand "help <command>"
417  * to produce the help for "<command>".
418  *
419  * \ingroup module_commandline
420  */
421 class ModuleHelpTopic : public IHelpTopic
422 {
423     public:
424         //! Constructs a help topic for a specific module.
425         ModuleHelpTopic(const ICommandLineModule         &module,
426                         const CommandLineHelpModuleImpl  &helpModule)
427             : module_(module), helpModule_(helpModule)
428         {
429         }
430
431         virtual const char *name() const { return module_.name(); }
432         virtual const char *title() const { return NULL; }
433         virtual bool hasSubTopics() const { return false; }
434         virtual const IHelpTopic *findSubTopic(const char * /*name*/) const
435         {
436             return NULL;
437         }
438         virtual void writeHelp(const HelpWriterContext &context) const;
439
440     private:
441         const ICommandLineModule         &module_;
442         const CommandLineHelpModuleImpl  &helpModule_;
443
444         GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
445 };
446
447 void ModuleHelpTopic::writeHelp(const HelpWriterContext & /*context*/) const
448 {
449     CommandLineHelpContext context(*helpModule_.context_);
450     const char *const      program = helpModule_.binaryName_.c_str();
451     context.setModuleDisplayName(formatString("%s %s", program, module_.name()));
452     module_.writeHelp(context);
453 }
454
455 /********************************************************************
456  * HelpExportReStructuredText
457  */
458
459 /*! \internal \brief
460  * Adds hyperlinks to modules within this binary.
461  *
462  * \param[in,out] links      Links are added here.
463  * \param[in]     helpModule Help module to get module information from.
464  * \throws        std::bad_alloc if out of memory.
465  *
466  * Initializes a HelpLinks object with links to modules defined in
467  * \p helpModule.
468  *
469  * \ingroup module_commandline
470  */
471 void initProgramLinks(HelpLinks *links, const CommandLineHelpModuleImpl &helpModule)
472 {
473     const char *const                    program = helpModule.binaryName_.c_str();
474     CommandLineModuleMap::const_iterator module;
475     for (module = helpModule.modules_.begin();
476          module != helpModule.modules_.end();
477          ++module)
478     {
479         if (module->second->shortDescription() != NULL)
480         {
481             std::string linkName("[gmx-" + module->first + "]");
482             const char *name = module->first.c_str();
483             std::string reference(
484                     formatString(":doc:`%s %s <%s-%s>`", program, name, program, name));
485             std::string displayName(
486                     formatString("[TT]%s %s[tt]", program, name));
487             links->addLink(linkName, reference, displayName);
488         }
489     }
490 }
491
492 /*! \internal \brief
493  * Implements export for web pages as reStructuredText.
494  *
495  * \ingroup module_commandline
496  */
497 class HelpExportReStructuredText : public IHelpExport
498 {
499     public:
500         //! Initializes reST exporter.
501         HelpExportReStructuredText(
502             const CommandLineHelpModuleImpl &helpModule,
503             IFileOutputRedirector           *outputRedirector);
504
505         virtual void startModuleExport();
506         virtual void exportModuleHelp(
507             const ICommandLineModule         &module,
508             const std::string                &tag,
509             const std::string                &displayName);
510         virtual void finishModuleExport();
511
512         virtual void startModuleGroupExport();
513         virtual void exportModuleGroup(const char                *title,
514                                        const ModuleGroupContents &modules);
515         virtual void finishModuleGroupExport();
516
517         virtual void exportTopic(const IHelpTopic &topic);
518
519     private:
520         IFileOutputRedirector          *outputRedirector_;
521         const std::string              &binaryName_;
522         HelpLinks                       links_;
523         boost::scoped_ptr<TextWriter>   indexFile_;
524         boost::scoped_ptr<TextWriter>   manPagesFile_;
525 };
526
527 HelpExportReStructuredText::HelpExportReStructuredText(
528         const CommandLineHelpModuleImpl &helpModule,
529         IFileOutputRedirector           *outputRedirector)
530     : outputRedirector_(outputRedirector),
531       binaryName_(helpModule.binaryName_),
532       links_(eHelpOutputFormat_Rst)
533 {
534     TextReader   linksFile("links.dat");
535     std::string  line;
536     while (linksFile.readLineTrimmed(&line))
537     {
538         links_.addLink("[REF]." + line + "[ref]",
539                        formatString(":ref:`.%s <%s>`", line.c_str(), line.c_str()),
540                        line);
541         links_.addLink("[REF]" + line + "[ref]", formatString(":ref:`%s`", line.c_str()), line);
542     }
543     linksFile.close();
544     initProgramLinks(&links_, helpModule);
545 }
546
547 void HelpExportReStructuredText::startModuleExport()
548 {
549     indexFile_.reset(
550             new TextWriter(
551                     outputRedirector_->openTextOutputFile("fragments/byname.rst")));
552     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s",
553                                        binaryName_.c_str(), binaryName_.c_str(),
554                                        RootHelpText::title));
555     manPagesFile_.reset(
556             new TextWriter(
557                     outputRedirector_->openTextOutputFile("conf-man.py")));
558     manPagesFile_->writeLine("man_pages = [");
559 }
560
561 void HelpExportReStructuredText::exportModuleHelp(
562         const ICommandLineModule         &module,
563         const std::string                &tag,
564         const std::string                &displayName)
565 {
566     TextOutputStreamPointer file
567         = outputRedirector_->openTextOutputFile("onlinehelp/" + tag + ".rst");
568     TextWriter              writer(file);
569     writer.writeLine(formatString(".. _%s:", displayName.c_str()));
570     if (0 == displayName.compare(binaryName_ + " mdrun"))
571     {
572         // Make an extra link target for the convenience of
573         // MPI-specific documentation
574         writer.writeLine(".. _mdrun_mpi:");
575     }
576     writer.writeLine();
577
578     CommandLineHelpContext context(file.get(), eHelpOutputFormat_Rst, &links_, binaryName_);
579     context.enterSubSection(displayName);
580     context.setModuleDisplayName(displayName);
581     module.writeHelp(context);
582
583     writer.writeLine();
584     writer.writeLine(".. only:: man");
585     writer.writeLine();
586     writer.writeLine("   See also");
587     writer.writeLine("   --------");
588     writer.writeLine();
589     writer.writeLine(formatString("   :manpage:`%s(1)`", binaryName_.c_str()));
590     writer.writeLine();
591     writer.writeLine("   More information about |Gromacs| is available at <http://www.gromacs.org/>.");
592     file->close();
593
594     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s",
595                                        displayName.c_str(), tag.c_str(),
596                                        module.shortDescription()));
597     manPagesFile_->writeLine(
598             formatString("    ('onlinehelp/%s', '%s', \"%s\", '', 1),",
599                          tag.c_str(), tag.c_str(), module.shortDescription()));
600 }
601
602 void HelpExportReStructuredText::finishModuleExport()
603 {
604     indexFile_->close();
605     indexFile_.reset();
606     // TODO: Generalize.
607     manPagesFile_->writeLine(
608             formatString("    ('onlinehelp/%s', '%s', '%s', '', 1)",
609                          binaryName_.c_str(), binaryName_.c_str(),
610                          RootHelpText::title));
611     manPagesFile_->writeLine("]");
612     manPagesFile_->close();
613     manPagesFile_.reset();
614 }
615
616 void HelpExportReStructuredText::startModuleGroupExport()
617 {
618     indexFile_.reset(
619             new TextWriter(
620                     outputRedirector_->openTextOutputFile("fragments/bytopic.rst")));
621     manPagesFile_.reset(
622             new TextWriter(
623                     outputRedirector_->openTextOutputFile("fragments/bytopic-man.rst")));
624 }
625
626 void HelpExportReStructuredText::exportModuleGroup(
627         const char                *title,
628         const ModuleGroupContents &modules)
629 {
630     indexFile_->writeLine(title);
631     indexFile_->writeLine(std::string(std::strlen(title), '^'));
632     manPagesFile_->writeLine(title);
633     manPagesFile_->writeLine(std::string(std::strlen(title), '^'));
634
635     ModuleGroupContents::const_iterator module;
636     for (module = modules.begin(); module != modules.end(); ++module)
637     {
638         const std::string     &tag(module->first);
639         std::string            displayName(tag);
640         // TODO: This does not work if the binary name would contain a dash,
641         // but that is not currently the case.
642         const size_t           dashPos = displayName.find('-');
643         GMX_RELEASE_ASSERT(dashPos != std::string::npos,
644                            "There should always be at least one dash in the tag");
645         displayName[dashPos] = ' ';
646         indexFile_->writeLine(formatString(":doc:`%s </onlinehelp/%s>`\n  %s",
647                                            displayName.c_str(), tag.c_str(),
648                                            module->second));
649         manPagesFile_->writeLine(formatString(":manpage:`%s(1)`\n  %s",
650                                               tag.c_str(),
651                                               module->second));
652     }
653     indexFile_->writeLine();
654     manPagesFile_->writeLine();
655 }
656
657 void HelpExportReStructuredText::finishModuleGroupExport()
658 {
659     indexFile_->close();
660     indexFile_.reset();
661     manPagesFile_->close();
662     manPagesFile_.reset();
663 }
664
665 void HelpExportReStructuredText::exportTopic(const IHelpTopic &topic)
666 {
667     const std::string       path("onlinehelp/" + std::string(topic.name()) + ".rst");
668     TextOutputStreamPointer file(outputRedirector_->openTextOutputFile(path));
669     CommandLineHelpContext  context(file.get(), eHelpOutputFormat_Rst, &links_,
670                                     binaryName_);
671     HelpManager             manager(topic, context.writerContext());
672     manager.writeCurrentTopic();
673     file->close();
674 }
675
676 /********************************************************************
677  * HelpExportCompletion
678  */
679
680 /*! \internal \brief
681  * Implements export for command-line completion.
682  *
683  * \ingroup module_commandline
684  */
685 class HelpExportCompletion : public IHelpExport
686 {
687     public:
688         //! Initializes completion exporter.
689         explicit HelpExportCompletion(const CommandLineHelpModuleImpl &helpModule);
690
691         virtual void startModuleExport();
692         virtual void exportModuleHelp(
693             const ICommandLineModule         &module,
694             const std::string                &tag,
695             const std::string                &displayName);
696         virtual void finishModuleExport();
697
698         virtual void startModuleGroupExport() {}
699         virtual void exportModuleGroup(const char                * /*title*/,
700                                        const ModuleGroupContents & /*modules*/) {}
701         virtual void finishModuleGroupExport() {}
702
703         virtual void exportTopic(const IHelpTopic & /*topic*/) {}
704
705     private:
706         ShellCompletionWriter    bashWriter_;
707         std::vector<std::string> modules_;
708 };
709
710 HelpExportCompletion::HelpExportCompletion(
711         const CommandLineHelpModuleImpl &helpModule)
712     : bashWriter_(helpModule.binaryName_, eShellCompletionFormat_Bash)
713 {
714 }
715
716 void HelpExportCompletion::startModuleExport()
717 {
718     bashWriter_.startCompletions();
719 }
720
721 void HelpExportCompletion::exportModuleHelp(
722         const ICommandLineModule         &module,
723         const std::string                 & /*tag*/,
724         const std::string                 & /*displayName*/)
725 {
726     modules_.push_back(module.name());
727     {
728         CommandLineHelpContext context(&bashWriter_);
729         // We use the display name to pass the name of the module to the
730         // completion writer.
731         context.setModuleDisplayName(module.name());
732         module.writeHelp(context);
733     }
734 }
735
736 void HelpExportCompletion::finishModuleExport()
737 {
738     CommandLineCommonOptionsHolder optionsHolder;
739     optionsHolder.initOptions();
740     bashWriter_.writeWrapperCompletions(modules_, *optionsHolder.options());
741     bashWriter_.finishCompletions();
742 }
743
744 }   // namespace
745
746 /********************************************************************
747  * CommandLineHelpModuleImpl implementation
748  */
749
750 CommandLineHelpModuleImpl::CommandLineHelpModuleImpl(
751         const IProgramContext            &programContext,
752         const std::string                &binaryName,
753         const CommandLineModuleMap       &modules,
754         const CommandLineModuleGroupList &groups)
755     : rootTopic_(new RootHelpTopic(*this)), programContext_(programContext),
756       binaryName_(binaryName), modules_(modules), groups_(groups),
757       context_(NULL), moduleOverride_(NULL), bHidden_(false),
758       outputRedirector_(&defaultFileOutputRedirector())
759 {
760 }
761
762 void CommandLineHelpModuleImpl::exportHelp(IHelpExport *exporter)
763 {
764     // TODO: Would be nicer to have the file names supplied by the build system
765     // and/or export a list of files from here.
766     const char *const program = binaryName_.c_str();
767
768     exporter->startModuleExport();
769     CommandLineModuleMap::const_iterator module;
770     for (module = modules_.begin(); module != modules_.end(); ++module)
771     {
772         if (module->second->shortDescription() != NULL)
773         {
774             const char *const moduleName = module->first.c_str();
775             std::string       tag(formatString("%s-%s", program, moduleName));
776             std::string       displayName(formatString("%s %s", program, moduleName));
777             exporter->exportModuleHelp(*module->second, tag, displayName);
778         }
779     }
780     exporter->finishModuleExport();
781
782     exporter->startModuleGroupExport();
783     CommandLineModuleGroupList::const_iterator group;
784     for (group = groups_.begin(); group != groups_.end(); ++group)
785     {
786         exporter->exportModuleGroup((*group)->title(), (*group)->modules());
787     }
788     exporter->finishModuleGroupExport();
789
790     rootTopic_->exportHelp(exporter);
791 }
792
793 namespace
794 {
795
796 /********************************************************************
797  * ModificationCheckingFileOutputStream
798  */
799
800 class ModificationCheckingFileOutputStream : public TextOutputStream
801 {
802     public:
803         ModificationCheckingFileOutputStream(
804             const char                    *path,
805             IFileOutputRedirector         *redirector)
806             : path_(path), redirector_(redirector)
807         {
808         }
809
810         virtual void write(const char *str) { contents_.write(str); }
811         virtual void close()
812         {
813             const std::string &newContents = contents_.toString();
814             // TODO: Redirect these for unit tests.
815             if (File::exists(path_))
816             {
817                 const std::string originalContents_
818                     = TextReader::readFileToString(path_);
819                 if (originalContents_ == newContents)
820                 {
821                     return;
822                 }
823             }
824             TextWriter writer(redirector_->openTextOutputFile(path_));
825             writer.writeString(newContents);
826         }
827
828     private:
829         std::string                     path_;
830         StringOutputStream              contents_;
831         IFileOutputRedirector          *redirector_;
832 };
833
834 /********************************************************************
835  * ModificationCheckingFileOutputRedirector
836  */
837
838 class ModificationCheckingFileOutputRedirector : public IFileOutputRedirector
839 {
840     public:
841         explicit ModificationCheckingFileOutputRedirector(
842             IFileOutputRedirector *redirector)
843             : redirector_(redirector)
844         {
845         }
846
847         virtual TextOutputStream &standardOutput()
848         {
849             return redirector_->standardOutput();
850         }
851         virtual TextOutputStreamPointer openTextOutputFile(const char *filename)
852         {
853             return TextOutputStreamPointer(
854                     new ModificationCheckingFileOutputStream(filename, redirector_));
855         }
856
857     private:
858         IFileOutputRedirector  *redirector_;
859 };
860
861 }   // namespace
862
863 /********************************************************************
864  * CommandLineHelpModule
865  */
866
867 CommandLineHelpModule::CommandLineHelpModule(
868         const IProgramContext            &programContext,
869         const std::string                &binaryName,
870         const CommandLineModuleMap       &modules,
871         const CommandLineModuleGroupList &groups)
872     : impl_(new Impl(programContext, binaryName, modules, groups))
873 {
874 }
875
876 CommandLineHelpModule::~CommandLineHelpModule()
877 {
878 }
879
880 HelpTopicPointer CommandLineHelpModule::createModuleHelpTopic(
881         const ICommandLineModule &module) const
882 {
883     return HelpTopicPointer(new ModuleHelpTopic(module, *impl_));
884 }
885
886 void CommandLineHelpModule::addTopic(HelpTopicPointer topic, bool bExported)
887 {
888     impl_->rootTopic_->addTopic(move(topic), bExported);
889 }
890
891 void CommandLineHelpModule::setShowHidden(bool bHidden)
892 {
893     impl_->bHidden_ = bHidden;
894 }
895
896 void CommandLineHelpModule::setModuleOverride(
897         const ICommandLineModule &module)
898 {
899     impl_->moduleOverride_ = &module;
900 }
901
902 void CommandLineHelpModule::setOutputRedirector(
903         IFileOutputRedirector *output)
904 {
905     impl_->outputRedirector_ = output;
906 }
907
908 int CommandLineHelpModule::run(int argc, char *argv[])
909 {
910     // Add internal topics lazily here.
911     addTopic(HelpTopicPointer(new CommandsHelpTopic(*impl_)), false);
912
913     const char *const exportFormats[] = { "rst", "completion" };
914     std::string       exportFormat;
915     Options           options(NULL, NULL);
916     options.addOption(StringOption("export").store(&exportFormat)
917                           .enumValue(exportFormats));
918     CommandLineParser(&options).parse(&argc, argv);
919     if (!exportFormat.empty())
920     {
921         ModificationCheckingFileOutputRedirector redirector(impl_->outputRedirector_);
922         boost::scoped_ptr<IHelpExport>           exporter;
923         if (exportFormat == "rst")
924         {
925             exporter.reset(new HelpExportReStructuredText(*impl_, &redirector));
926         }
927         else if (exportFormat == "completion")
928         {
929             exporter.reset(new HelpExportCompletion(*impl_));
930         }
931         else
932         {
933             GMX_THROW(NotImplementedError("This help format is not implemented"));
934         }
935         impl_->exportHelp(exporter.get());
936         return 0;
937     }
938
939     TextOutputStream      &outputFile = impl_->outputRedirector_->standardOutput();
940     HelpLinks              links(eHelpOutputFormat_Console);
941     initProgramLinks(&links, *impl_);
942     CommandLineHelpContext context(&outputFile, eHelpOutputFormat_Console, &links,
943                                    impl_->binaryName_);
944     context.setShowHidden(impl_->bHidden_);
945     if (impl_->moduleOverride_ != NULL)
946     {
947         context.setModuleDisplayName(impl_->programContext_.displayName());
948         impl_->moduleOverride_->writeHelp(context);
949         return 0;
950     }
951     impl_->context_ = &context;
952
953     HelpManager helpManager(*impl_->rootTopic_, context.writerContext());
954     try
955     {
956         for (int i = 1; i < argc; ++i)
957         {
958             helpManager.enterTopic(argv[i]);
959         }
960     }
961     catch (const InvalidInputError &ex)
962     {
963         fprintf(stderr, "%s\n", ex.what());
964         return 2;
965     }
966     helpManager.writeCurrentTopic();
967     return 0;
968 }
969
970 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext &context) const
971 {
972     const HelpWriterContext &writerContext = context.writerContext();
973     // TODO: Implement.
974     if (writerContext.outputFormat() != eHelpOutputFormat_Console)
975     {
976         return;
977     }
978     writerContext.writeTextBlock(
979             "Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
980     // TODO: More information.
981 }
982
983 } // namespace gmx