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