Split lines with many copyright years
[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, 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()), line);
532         links_.addLink("[REF]" + line + "[ref]", formatString(":ref:`%s`", line.c_str()), line);
533     }
534     linksFile.close();
535     initProgramLinks(&links_, helpModule);
536 }
537
538 void HelpExportReStructuredText::startModuleExport()
539 {
540     indexFile_ = std::make_unique<TextWriter>(
541             outputRedirector_->openTextOutputFile("fragments/byname.rst"));
542     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s", binaryName_.c_str(),
543                                        binaryName_.c_str(), RootHelpText::title));
544     manPagesFile_ =
545             std::make_unique<TextWriter>(outputRedirector_->openTextOutputFile("conf-man.py"));
546     manPagesFile_->writeLine("man_pages = [");
547 }
548
549 void HelpExportReStructuredText::exportModuleHelp(const ICommandLineModule& module,
550                                                   const std::string&        tag,
551                                                   const std::string&        displayName)
552 {
553     TextOutputStreamPointer file =
554             outputRedirector_->openTextOutputFile("onlinehelp/" + tag + ".rst");
555     TextWriter writer(file);
556     writer.writeLine(formatString(".. _%s:", displayName.c_str()));
557     if (displayName == binaryName_ + " mdrun")
558     {
559         // Make an extra link target for the convenience of
560         // MPI-specific documentation
561         writer.writeLine(".. _mdrun_mpi:");
562     }
563     writer.ensureEmptyLine();
564
565     CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
566     context.enterSubSection(displayName);
567     context.setModuleDisplayName(displayName);
568     module.writeHelp(context);
569
570     writer.ensureEmptyLine();
571     writer.writeLine(".. only:: man");
572     writer.writeLine();
573     writer.writeLine("   See also");
574     writer.writeLine("   --------");
575     writer.writeLine();
576     writer.writeLine(formatString("   :manpage:`%s(1)`", binaryName_.c_str()));
577     writer.writeLine();
578     writer.writeLine(
579             "   More information about |Gromacs| is available at <http://www.gromacs.org/>.");
580     file->close();
581
582     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s", displayName.c_str(),
583                                        tag.c_str(), module.shortDescription()));
584     manPagesFile_->writeLine(formatString("    ('onlinehelp/%s', '%s', \"%s\", '', 1),",
585                                           tag.c_str(), tag.c_str(), module.shortDescription()));
586 }
587
588 void HelpExportReStructuredText::finishModuleExport()
589 {
590     indexFile_->close();
591     indexFile_.reset();
592     // TODO: Generalize.
593     manPagesFile_->writeLine(formatString("    ('onlinehelp/%s', '%s', '%s', '', 1)",
594                                           binaryName_.c_str(), binaryName_.c_str(), 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(":doc:`%s </onlinehelp/%s>`\n  %s", displayName.c_str(),
629                                            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