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