Apply clang-format to source tree
[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,2017,2018,2019, 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 <memory>
47 #include <string>
48 #include <vector>
49
50 #include "gromacs/commandline/cmdlinehelpcontext.h"
51 #include "gromacs/commandline/cmdlinehelpwriter.h"
52 #include "gromacs/commandline/cmdlineparser.h"
53 #include "gromacs/onlinehelp/helpformat.h"
54 #include "gromacs/onlinehelp/helpmanager.h"
55 #include "gromacs/onlinehelp/helptopic.h"
56 #include "gromacs/onlinehelp/helpwritercontext.h"
57 #include "gromacs/options/basicoptions.h"
58 #include "gromacs/options/options.h"
59 #include "gromacs/utility/arrayref.h"
60 #include "gromacs/utility/baseversion.h"
61 #include "gromacs/utility/exceptions.h"
62 #include "gromacs/utility/fileredirector.h"
63 #include "gromacs/utility/gmxassert.h"
64 #include "gromacs/utility/path.h"
65 #include "gromacs/utility/programcontext.h"
66 #include "gromacs/utility/stringstream.h"
67 #include "gromacs/utility/stringutil.h"
68 #include "gromacs/utility/textreader.h"
69 #include "gromacs/utility/textstream.h"
70 #include "gromacs/utility/textwriter.h"
71
72 #include "shellcompletions.h"
73
74 namespace gmx
75 {
76
77 namespace
78 {
79 class IHelpExport;
80
81 /********************************************************************
82  * RootHelpTopic declaration
83  */
84
85 /*! \brief
86  * Help topic that forms the root of the help tree for the help subcommand.
87  *
88  * \ingroup module_commandline
89  */
90 class RootHelpTopic : public AbstractCompositeHelpTopic
91 {
92 public:
93     /*! \brief
94      * Creates a root help topic.
95      *
96      * Does not throw.
97      */
98     explicit RootHelpTopic(const CommandLineHelpModuleImpl& helpModule) : helpModule_(helpModule) {}
99
100     const char* name() const override;
101     const char* title() const override { return title_.c_str(); }
102
103     //! Adds a top-level topic and optionally marks it as exported.
104     void addTopic(HelpTopicPointer topic, bool bExported)
105     {
106         if (bExported)
107         {
108             exportedTopics_.emplace_back(topic->name());
109         }
110         addSubTopic(std::move(topic));
111     }
112     //! Exports all the top-level topics with the given exporter.
113     void exportHelp(IHelpExport* exporter);
114
115     void writeHelp(const HelpWriterContext& context) const override;
116
117 private:
118     // unused because of the writeHelp() override
119     std::string helpText() const override { return ""; }
120
121     CommandLineHelpContext createContext(const HelpWriterContext& context) const;
122
123     const CommandLineHelpModuleImpl& helpModule_;
124     std::string                      title_;
125     std::vector<std::string>         exportedTopics_;
126
127     GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
128 };
129
130 } // namespace
131
132 /********************************************************************
133  * CommandLineHelpModuleImpl declaration
134  */
135
136 class CommandLineHelpModuleImpl
137 {
138 public:
139     CommandLineHelpModuleImpl(const IProgramContext&            programContext,
140                               const std::string&                binaryName,
141                               const CommandLineModuleMap&       modules,
142                               const CommandLineModuleGroupList& groups);
143
144     std::unique_ptr<IHelpExport> createExporter(const std::string& format, IFileOutputRedirector* redirector);
145     void                         exportHelp(IHelpExport* exporter);
146
147     RootHelpTopic                     rootTopic_;
148     const IProgramContext&            programContext_;
149     std::string                       binaryName_;
150     const CommandLineModuleMap&       modules_;
151     const CommandLineModuleGroupList& groups_;
152
153     CommandLineHelpContext*   context_;
154     const ICommandLineModule* moduleOverride_;
155     bool                      bHidden_;
156
157     IFileOutputRedirector* outputRedirector_;
158
159     GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModuleImpl);
160 };
161
162 namespace
163 {
164
165 /********************************************************************
166  * IHelpExport
167  */
168
169 /*! \brief
170  * Callbacks for exporting help information for command-line modules.
171  *
172  * \ingroup module_commandline
173  */
174 class IHelpExport
175 {
176 public:
177     //! Shorthand for a list of modules contained in a group.
178     typedef CommandLineModuleGroupData::ModuleList ModuleGroupContents;
179
180     virtual ~IHelpExport() {}
181
182     /*! \brief
183      * Called once before exporting individual modules.
184      *
185      * Can, e.g., open shared output files (e.g., if the output is written
186      * into a single file, or if a separate index is required) and write
187      * headers into them.
188      */
189     virtual void startModuleExport() = 0;
190     /*! \brief
191      * Called to export the help for each module.
192      *
193      * \param[in] module      Module for which the help should be exported.
194      * \param[in] tag         Unique tag for the module (gmx-something).
195      * \param[in] displayName Display name for the module (gmx something).
196      */
197     virtual void exportModuleHelp(const ICommandLineModule& module,
198                                   const std::string&        tag,
199                                   const std::string&        displayName) = 0;
200     /*! \brief
201      * Called after all modules have been exported.
202      *
203      * Can close files opened in startModuleExport(), write footers to them
204      * etc.
205      */
206     virtual void finishModuleExport() = 0;
207
208     /*! \brief
209      * Called once before exporting module groups.
210      *
211      * Can, e.g., open a single output file for listing all the groups.
212      */
213     virtual void startModuleGroupExport() = 0;
214     /*! \brief
215      * Called to export the help for each module group.
216      *
217      * \param[in] title    Title for the group.
218      * \param[in] modules  List of modules in the group.
219      */
220     virtual void exportModuleGroup(const char* title, const ModuleGroupContents& modules) = 0;
221     /*! \brief
222      * Called after all module groups have been exported.
223      *
224      * Can close files opened in startModuleGroupExport(), write footers to them
225      * etc.
226      */
227     virtual void finishModuleGroupExport() = 0;
228
229     /*! \brief
230      * Called to export the help for a top-level topic.
231      *
232      * \param[in] topic   Topic to export.
233      */
234     virtual void exportTopic(const IHelpTopic& topic) = 0;
235 };
236
237 /********************************************************************
238  * RootHelpTopic implementation
239  */
240
241 struct RootHelpText
242 {
243     static const char        title[];
244     static const char* const text[];
245 };
246
247 // These are used for the gmx.1 man page.
248 // TODO: Do not hardcode them here, but pass them from the outside to make this
249 // code more generic.
250 const char        RootHelpText::title[] = "molecular dynamics simulation suite";
251 const char* const RootHelpText::text[]  = {
252     "|Gromacs| is a full-featured suite of programs to perform molecular",
253     "dynamics simulations, i.e., to simulate the behavior of systems with",
254     "hundreds to millions of particles using Newtonian equations of motion.",
255     "It is primarily used for research on proteins, lipids, and polymers, but",
256     "can be applied to a wide variety of chemical and biological research",
257     "questions.",
258 };
259
260 const char* RootHelpTopic::name() const
261 {
262     return helpModule_.binaryName_.c_str();
263 }
264
265 void RootHelpTopic::exportHelp(IHelpExport* exporter)
266 {
267     std::vector<std::string>::const_iterator topicName;
268     for (topicName = exportedTopics_.begin(); topicName != exportedTopics_.end(); ++topicName)
269     {
270         const IHelpTopic* topic = findSubTopic(topicName->c_str());
271         GMX_RELEASE_ASSERT(topic != nullptr, "Exported help topic no longer found");
272         exporter->exportTopic(*topic);
273     }
274     // For now, the title is only set for the export to make it not appear in
275     // console output, which makes things consistent for 'gmx help' and
276     // 'gmx help <command>'.
277     title_ = RootHelpText::title;
278     exporter->exportTopic(*this);
279 }
280
281 void RootHelpTopic::writeHelp(const HelpWriterContext& context) const
282 {
283     {
284         CommandLineCommonOptionsHolder optionsHolder;
285         CommandLineHelpContext         cmdlineContext(createContext(context));
286         cmdlineContext.setModuleDisplayName(helpModule_.binaryName_);
287         optionsHolder.initOptions();
288         Options&                    options = *optionsHolder.options();
289         ArrayRef<const char* const> helpText;
290         if (context.outputFormat() != eHelpOutputFormat_Console)
291         {
292             helpText = RootHelpText::text;
293         }
294         // TODO: Add <command> [<args>] into the synopsis.
295         CommandLineHelpWriter(options).setHelpText(helpText).writeHelp(cmdlineContext);
296     }
297     if (context.outputFormat() == eHelpOutputFormat_Console)
298     {
299         // TODO: Consider printing a list of "core" commands. Would require someone
300         // to determine such a set...
301         context.paragraphBreak();
302         writeSubTopicList(context, "Additional help is available on the following topics:");
303         context.writeTextBlock("To access the help, use '[PROGRAM] help <topic>'.");
304         context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
305     }
306     else
307     {
308         // TODO: This should not really end up on the HTML page.
309         context.writeTitle(formatString("%s commands", helpModule_.binaryName_.c_str()));
310         context.writeTextBlock(
311                 "The following commands are available. Please refer to their "
312                 "individual man pages or [TT][PROGRAM] help <command>[tt] "
313                 "for further details.");
314         context.writeTextBlock("");
315         context.writeTextBlock(".. include:: /fragments/bytopic-man.rst");
316     }
317 }
318
319 CommandLineHelpContext RootHelpTopic::createContext(const HelpWriterContext& context) const
320 {
321     if (helpModule_.context_ != nullptr)
322     {
323         return CommandLineHelpContext(*helpModule_.context_);
324     }
325     else
326     {
327         return CommandLineHelpContext(context);
328     }
329 }
330
331 /********************************************************************
332  * CommandsHelpTopic
333  */
334
335 /*! \brief
336  * Help topic for listing the commands.
337  *
338  * \ingroup module_commandline
339  */
340 class CommandsHelpTopic : public IHelpTopic
341 {
342 public:
343     /*! \brief
344      * Creates a command list help topic.
345      *
346      * \param[in]     helpModule Help module to get module information from.
347      *
348      * Does not throw.
349      */
350     explicit CommandsHelpTopic(const CommandLineHelpModuleImpl& helpModule) :
351         helpModule_(helpModule)
352     {
353     }
354
355     const char*       name() const override { return "commands"; }
356     const char*       title() const override { return "List of available commands"; }
357     bool              hasSubTopics() const override { return false; }
358     const IHelpTopic* findSubTopic(const char* /*name*/) const override { return nullptr; }
359
360     void writeHelp(const HelpWriterContext& context) const override;
361
362 private:
363     const CommandLineHelpModuleImpl& helpModule_;
364
365     GMX_DISALLOW_COPY_AND_ASSIGN(CommandsHelpTopic);
366 };
367
368 void CommandsHelpTopic::writeHelp(const HelpWriterContext& context) const
369 {
370     if (context.outputFormat() != eHelpOutputFormat_Console)
371     {
372         GMX_THROW(NotImplementedError("Module list is not implemented for this output format"));
373     }
374     int                                  maxNameLength = 0;
375     const CommandLineModuleMap&          modules       = helpModule_.modules_;
376     CommandLineModuleMap::const_iterator module;
377     for (module = modules.begin(); module != modules.end(); ++module)
378     {
379         int nameLength = static_cast<int>(module->first.length());
380         if (module->second->shortDescription() != nullptr && nameLength > maxNameLength)
381         {
382             maxNameLength = nameLength;
383         }
384     }
385     context.writeTextBlock(
386             "Usage: [PROGRAM] [<options>] <command> [<args>][PAR]"
387             "Available commands:");
388     TextWriter&        file = context.outputFile();
389     TextTableFormatter formatter;
390     formatter.addColumn(nullptr, maxNameLength + 1, false);
391     formatter.addColumn(nullptr, 72 - maxNameLength, true);
392     formatter.setFirstColumnIndent(4);
393     for (module = modules.begin(); module != modules.end(); ++module)
394     {
395         const char* name        = module->first.c_str();
396         const char* description = module->second->shortDescription();
397         if (description != nullptr)
398         {
399             formatter.clear();
400             formatter.addColumnLine(0, name);
401             formatter.addColumnLine(1, description);
402             file.writeString(formatter.formatRow());
403         }
404     }
405     context.writeTextBlock("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, const CommandLineHelpModuleImpl& helpModule) :
426         module_(module),
427         helpModule_(helpModule)
428     {
429     }
430
431     const char*       name() const override { return module_.name(); }
432     const char*       title() const override { return nullptr; }
433     bool              hasSubTopics() const override { return false; }
434     const IHelpTopic* findSubTopic(const char* /*name*/) const override { return nullptr; }
435     void              writeHelp(const HelpWriterContext& context) const override;
436
437 private:
438     const ICommandLineModule&        module_;
439     const CommandLineHelpModuleImpl& helpModule_;
440
441     GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
442 };
443
444 void ModuleHelpTopic::writeHelp(const HelpWriterContext& /*context*/) const
445 {
446     CommandLineHelpContext context(*helpModule_.context_);
447     const char* const      program = helpModule_.binaryName_.c_str();
448     context.setModuleDisplayName(formatString("%s %s", program, module_.name()));
449     module_.writeHelp(context);
450 }
451
452 /********************************************************************
453  * HelpExportReStructuredText
454  */
455
456 /*! \internal \brief
457  * Adds hyperlinks to modules within this binary.
458  *
459  * \param[in,out] links      Links are added here.
460  * \param[in]     helpModule Help module to get module information from.
461  * \throws        std::bad_alloc if out of memory.
462  *
463  * Initializes a HelpLinks object with links to modules defined in
464  * \p helpModule.
465  *
466  * \ingroup module_commandline
467  */
468 void initProgramLinks(HelpLinks* links, const CommandLineHelpModuleImpl& helpModule)
469 {
470     const char* const                    program = helpModule.binaryName_.c_str();
471     CommandLineModuleMap::const_iterator module;
472     for (module = helpModule.modules_.begin(); module != helpModule.modules_.end(); ++module)
473     {
474         if (module->second->shortDescription() != nullptr)
475         {
476             std::string linkName("[gmx-" + module->first + "]");
477             const char* name = module->first.c_str();
478             std::string reference(formatString(":doc:`%s %s <%s-%s>`", program, name, program, name));
479             std::string displayName(formatString("[TT]%s %s[tt]", program, name));
480             links->addLink(linkName, reference, displayName);
481         }
482     }
483 }
484
485 /*! \internal \brief
486  * Implements export for web pages as reStructuredText.
487  *
488  * \ingroup module_commandline
489  */
490 class HelpExportReStructuredText : public IHelpExport
491 {
492 public:
493     //! Initializes reST exporter.
494     HelpExportReStructuredText(const CommandLineHelpModuleImpl& helpModule,
495                                IFileOutputRedirector*           outputRedirector);
496
497     void startModuleExport() override;
498     void exportModuleHelp(const ICommandLineModule& module,
499                           const std::string&        tag,
500                           const std::string&        displayName) override;
501     void finishModuleExport() override;
502
503     void startModuleGroupExport() override;
504     void exportModuleGroup(const char* title, const ModuleGroupContents& modules) override;
505     void finishModuleGroupExport() override;
506
507     void exportTopic(const IHelpTopic& topic) override;
508
509 private:
510     IFileOutputRedirector* outputRedirector_;
511     const std::string&     binaryName_; //NOLINT(google-runtime-member-string-references)
512     HelpLinks              links_;
513     // These never release ownership.
514     std::unique_ptr<TextWriter> indexFile_;
515     std::unique_ptr<TextWriter> manPagesFile_;
516 };
517
518 HelpExportReStructuredText::HelpExportReStructuredText(const CommandLineHelpModuleImpl& helpModule,
519                                                        IFileOutputRedirector* outputRedirector) :
520     outputRedirector_(outputRedirector),
521     binaryName_(helpModule.binaryName_),
522     links_(eHelpOutputFormat_Rst)
523 {
524     TextReader  linksFile("links.dat");
525     std::string line;
526     linksFile.setTrimTrailingWhiteSpace(true);
527     while (linksFile.readLine(&line))
528     {
529         links_.addLink("[REF]." + line + "[ref]",
530                        formatString(":ref:`.%s <%s>`", line.c_str(), line.c_str()), line);
531         links_.addLink("[REF]" + line + "[ref]", formatString(":ref:`%s`", line.c_str()), line);
532     }
533     linksFile.close();
534     initProgramLinks(&links_, helpModule);
535 }
536
537 void HelpExportReStructuredText::startModuleExport()
538 {
539     indexFile_ = std::make_unique<TextWriter>(
540             outputRedirector_->openTextOutputFile("fragments/byname.rst"));
541     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s", binaryName_.c_str(),
542                                        binaryName_.c_str(), RootHelpText::title));
543     manPagesFile_ =
544             std::make_unique<TextWriter>(outputRedirector_->openTextOutputFile("conf-man.py"));
545     manPagesFile_->writeLine("man_pages = [");
546 }
547
548 void HelpExportReStructuredText::exportModuleHelp(const ICommandLineModule& module,
549                                                   const std::string&        tag,
550                                                   const std::string&        displayName)
551 {
552     TextOutputStreamPointer file =
553             outputRedirector_->openTextOutputFile("onlinehelp/" + tag + ".rst");
554     TextWriter writer(file);
555     writer.writeLine(formatString(".. _%s:", displayName.c_str()));
556     if (displayName == binaryName_ + " mdrun")
557     {
558         // Make an extra link target for the convenience of
559         // MPI-specific documentation
560         writer.writeLine(".. _mdrun_mpi:");
561     }
562     writer.ensureEmptyLine();
563
564     CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
565     context.enterSubSection(displayName);
566     context.setModuleDisplayName(displayName);
567     module.writeHelp(context);
568
569     writer.ensureEmptyLine();
570     writer.writeLine(".. only:: man");
571     writer.writeLine();
572     writer.writeLine("   See also");
573     writer.writeLine("   --------");
574     writer.writeLine();
575     writer.writeLine(formatString("   :manpage:`%s(1)`", binaryName_.c_str()));
576     writer.writeLine();
577     writer.writeLine(
578             "   More information about |Gromacs| is available at <http://www.gromacs.org/>.");
579     file->close();
580
581     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s", displayName.c_str(),
582                                        tag.c_str(), module.shortDescription()));
583     manPagesFile_->writeLine(formatString("    ('onlinehelp/%s', '%s', \"%s\", '', 1),",
584                                           tag.c_str(), tag.c_str(), module.shortDescription()));
585 }
586
587 void HelpExportReStructuredText::finishModuleExport()
588 {
589     indexFile_->close();
590     indexFile_.reset();
591     // TODO: Generalize.
592     manPagesFile_->writeLine(formatString("    ('onlinehelp/%s', '%s', '%s', '', 1)",
593                                           binaryName_.c_str(), binaryName_.c_str(), 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(":doc:`%s </onlinehelp/%s>`\n  %s", displayName.c_str(),
628                                            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