SYCL: Avoid using no_init read accessor in rocFFT
[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), 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), binaryName_(helpModule.binaryName_), links_(eHelpOutputFormat_Rst)
522 {
523     TextReader  linksFile("links.dat");
524     std::string line;
525     linksFile.setTrimTrailingWhiteSpace(true);
526     while (linksFile.readLine(&line))
527     {
528         links_.addLink("[REF]." + line + "[ref]",
529                        formatString(":ref:`.%s <%s>`", line.c_str(), line.c_str()),
530                        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(
542             "* :doc:`%s </onlinehelp/%s>` - %s", binaryName_.c_str(), 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     writer.ensureEmptyLine();
557
558     CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
559     context.enterSubSection(displayName);
560     context.setModuleDisplayName(displayName);
561     module.writeHelp(context);
562
563     writer.ensureEmptyLine();
564     writer.writeLine(".. only:: man");
565     writer.writeLine();
566     writer.writeLine("   See also");
567     writer.writeLine("   --------");
568     writer.writeLine();
569     writer.writeLine(formatString("   :manpage:`%s(1)`", binaryName_.c_str()));
570     writer.writeLine();
571     writer.writeLine(
572             "   More information about |Gromacs| is available at <http://www.gromacs.org/>.");
573     file->close();
574
575     indexFile_->writeLine(formatString(
576             "* :doc:`%s </onlinehelp/%s>` - %s", displayName.c_str(), tag.c_str(), module.shortDescription()));
577     manPagesFile_->writeLine(formatString("    ('onlinehelp/%s', '%s', \"%s\", '', 1),",
578                                           tag.c_str(),
579                                           tag.c_str(),
580                                           module.shortDescription()));
581 }
582
583 void HelpExportReStructuredText::finishModuleExport()
584 {
585     indexFile_->close();
586     indexFile_.reset();
587     // TODO: Generalize.
588     manPagesFile_->writeLine(formatString("    ('onlinehelp/%s', '%s', '%s', '', 1)",
589                                           binaryName_.c_str(),
590                                           binaryName_.c_str(),
591                                           RootHelpText::title));
592     manPagesFile_->writeLine("]");
593     manPagesFile_->close();
594     manPagesFile_.reset();
595 }
596
597 void HelpExportReStructuredText::startModuleGroupExport()
598 {
599     indexFile_ = std::make_unique<TextWriter>(
600             outputRedirector_->openTextOutputFile("fragments/bytopic.rst"));
601     manPagesFile_ = std::make_unique<TextWriter>(
602             outputRedirector_->openTextOutputFile("fragments/bytopic-man.rst"));
603 }
604
605 void HelpExportReStructuredText::exportModuleGroup(const char* title, const ModuleGroupContents& modules)
606 {
607     indexFile_->ensureEmptyLine();
608     indexFile_->writeLine(title);
609     indexFile_->writeLine(std::string(std::strlen(title), '^'));
610     manPagesFile_->ensureEmptyLine();
611     manPagesFile_->writeLine(title);
612     manPagesFile_->writeLine(std::string(std::strlen(title), '^'));
613
614     ModuleGroupContents::const_iterator module;
615     for (module = modules.begin(); module != modules.end(); ++module)
616     {
617         const std::string& tag(module->first);
618         std::string        displayName(tag);
619         // TODO: This does not work if the binary name would contain a dash,
620         // but that is not currently the case.
621         const size_t dashPos = displayName.find('-');
622         GMX_RELEASE_ASSERT(dashPos != std::string::npos,
623                            "There should always be at least one dash in the tag");
624         displayName[dashPos] = ' ';
625         indexFile_->writeLine(formatString(
626                 ":doc:`%s </onlinehelp/%s>`\n  %s", displayName.c_str(), tag.c_str(), module->second));
627         manPagesFile_->writeLine(formatString(":manpage:`%s(1)`\n  %s", tag.c_str(), module->second));
628     }
629 }
630
631 void HelpExportReStructuredText::finishModuleGroupExport()
632 {
633     indexFile_->close();
634     indexFile_.reset();
635     manPagesFile_->close();
636     manPagesFile_.reset();
637 }
638
639 void HelpExportReStructuredText::exportTopic(const IHelpTopic& topic)
640 {
641     const std::string      path("onlinehelp/" + std::string(topic.name()) + ".rst");
642     TextWriter             writer(outputRedirector_->openTextOutputFile(path));
643     CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
644     HelpManager            manager(topic, context.writerContext());
645     manager.writeCurrentTopic();
646     writer.close();
647 }
648
649 /********************************************************************
650  * HelpExportCompletion
651  */
652
653 /*! \internal \brief
654  * Implements export for command-line completion.
655  *
656  * \ingroup module_commandline
657  */
658 class HelpExportCompletion : public IHelpExport
659 {
660 public:
661     //! Initializes completion exporter.
662     explicit HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule);
663
664     void startModuleExport() override;
665     void exportModuleHelp(const ICommandLineModule& module,
666                           const std::string&        tag,
667                           const std::string&        displayName) override;
668     void finishModuleExport() override;
669
670     void startModuleGroupExport() override {}
671     void exportModuleGroup(const char* /*title*/, const ModuleGroupContents& /*modules*/) override
672     {
673     }
674     void finishModuleGroupExport() override {}
675
676     void exportTopic(const IHelpTopic& /*topic*/) override {}
677
678 private:
679     ShellCompletionWriter    bashWriter_;
680     std::vector<std::string> modules_;
681 };
682
683 HelpExportCompletion::HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule) :
684     bashWriter_(helpModule.binaryName_, eShellCompletionFormat_Bash)
685 {
686 }
687
688 void HelpExportCompletion::startModuleExport()
689 {
690     bashWriter_.startCompletions();
691 }
692
693 void HelpExportCompletion::exportModuleHelp(const ICommandLineModule& module,
694                                             const std::string& /*tag*/,
695                                             const std::string& /*displayName*/)
696 {
697     modules_.emplace_back(module.name());
698     {
699         CommandLineHelpContext context(&bashWriter_);
700         // We use the display name to pass the name of the module to the
701         // completion writer.
702         context.setModuleDisplayName(module.name());
703         module.writeHelp(context);
704     }
705 }
706
707 void HelpExportCompletion::finishModuleExport()
708 {
709     CommandLineCommonOptionsHolder optionsHolder;
710     optionsHolder.initOptions();
711     bashWriter_.writeWrapperCompletions(modules_, *optionsHolder.options());
712     bashWriter_.finishCompletions();
713 }
714
715 } // namespace
716
717 /********************************************************************
718  * CommandLineHelpModuleImpl implementation
719  */
720
721 CommandLineHelpModuleImpl::CommandLineHelpModuleImpl(const IProgramContext&      programContext,
722                                                      const std::string&          binaryName,
723                                                      const CommandLineModuleMap& modules,
724                                                      const CommandLineModuleGroupList& groups) :
725     rootTopic_(*this),
726     programContext_(programContext),
727     binaryName_(binaryName),
728     modules_(modules),
729     groups_(groups),
730     context_(nullptr),
731     moduleOverride_(nullptr),
732     bHidden_(false),
733     outputRedirector_(&defaultFileOutputRedirector())
734 {
735 }
736
737 std::unique_ptr<IHelpExport> CommandLineHelpModuleImpl::createExporter(const std::string& format,
738                                                                        IFileOutputRedirector* redirector)
739 {
740     if (format == "rst")
741     {
742         return std::unique_ptr<IHelpExport>(new HelpExportReStructuredText(*this, redirector));
743     }
744     else if (format == "completion")
745     {
746         return std::unique_ptr<IHelpExport>(new HelpExportCompletion(*this));
747     }
748     GMX_THROW(NotImplementedError("This help format is not implemented"));
749 }
750
751 void CommandLineHelpModuleImpl::exportHelp(IHelpExport* exporter)
752 {
753     // TODO: Would be nicer to have the file names supplied by the build system
754     // and/or export a list of files from here.
755     const char* const program = binaryName_.c_str();
756
757     exporter->startModuleExport();
758     CommandLineModuleMap::const_iterator module;
759     for (module = modules_.begin(); module != modules_.end(); ++module)
760     {
761         if (module->second->shortDescription() != nullptr)
762         {
763             const char* const moduleName = module->first.c_str();
764             std::string       tag(formatString("%s-%s", program, moduleName));
765             std::string       displayName(formatString("%s %s", program, moduleName));
766             exporter->exportModuleHelp(*module->second, tag, displayName);
767         }
768     }
769     exporter->finishModuleExport();
770
771     exporter->startModuleGroupExport();
772     CommandLineModuleGroupList::const_iterator group;
773     for (group = groups_.begin(); group != groups_.end(); ++group)
774     {
775         exporter->exportModuleGroup((*group)->title(), (*group)->modules());
776     }
777     exporter->finishModuleGroupExport();
778
779     rootTopic_.exportHelp(exporter);
780 }
781
782 namespace
783 {
784
785 /********************************************************************
786  * ModificationCheckingFileOutputStream
787  */
788
789 class ModificationCheckingFileOutputStream : public TextOutputStream
790 {
791 public:
792     ModificationCheckingFileOutputStream(const char* path, IFileOutputRedirector* redirector) :
793         path_(path), redirector_(redirector)
794     {
795     }
796
797     void write(const char* str) override { contents_.write(str); }
798     void close() override
799     {
800         const std::string& newContents = contents_.toString();
801         // TODO: Redirect these for unit tests.
802         if (File::exists(path_, File::returnFalseOnError))
803         {
804             const std::string originalContents_ = TextReader::readFileToString(path_);
805             if (originalContents_ == newContents)
806             {
807                 return;
808             }
809         }
810         TextWriter writer(redirector_->openTextOutputFile(path_));
811         writer.writeString(newContents);
812     }
813
814 private:
815     std::string            path_;
816     StringOutputStream     contents_;
817     IFileOutputRedirector* redirector_;
818 };
819
820 /********************************************************************
821  * ModificationCheckingFileOutputRedirector
822  */
823
824 class ModificationCheckingFileOutputRedirector : public IFileOutputRedirector
825 {
826 public:
827     explicit ModificationCheckingFileOutputRedirector(IFileOutputRedirector* redirector) :
828         redirector_(redirector)
829     {
830     }
831
832     TextOutputStream&       standardOutput() override { return redirector_->standardOutput(); }
833     TextOutputStreamPointer openTextOutputFile(const char* filename) override
834     {
835         return TextOutputStreamPointer(new ModificationCheckingFileOutputStream(filename, redirector_));
836     }
837
838 private:
839     IFileOutputRedirector* redirector_;
840 };
841
842 } // namespace
843
844 /********************************************************************
845  * CommandLineHelpModule
846  */
847
848 CommandLineHelpModule::CommandLineHelpModule(const IProgramContext&            programContext,
849                                              const std::string&                binaryName,
850                                              const CommandLineModuleMap&       modules,
851                                              const CommandLineModuleGroupList& groups) :
852     impl_(new Impl(programContext, binaryName, modules, groups))
853 {
854 }
855
856 CommandLineHelpModule::~CommandLineHelpModule() {}
857
858 HelpTopicPointer CommandLineHelpModule::createModuleHelpTopic(const ICommandLineModule& module) const
859 {
860     return HelpTopicPointer(new ModuleHelpTopic(module, *impl_));
861 }
862
863 void CommandLineHelpModule::addTopic(HelpTopicPointer topic, bool bExported)
864 {
865     impl_->rootTopic_.addTopic(std::move(topic), bExported);
866 }
867
868 void CommandLineHelpModule::setShowHidden(bool bHidden)
869 {
870     impl_->bHidden_ = bHidden;
871 }
872
873 void CommandLineHelpModule::setModuleOverride(const ICommandLineModule& module)
874 {
875     impl_->moduleOverride_ = &module;
876 }
877
878 void CommandLineHelpModule::setOutputRedirector(IFileOutputRedirector* output)
879 {
880     impl_->outputRedirector_ = output;
881 }
882
883 int CommandLineHelpModule::run(int argc, char* argv[])
884 {
885     // Add internal topics lazily here.
886     addTopic(HelpTopicPointer(new CommandsHelpTopic(*impl_)), false);
887
888     const char* const exportFormats[] = { "rst", "completion" };
889     std::string       exportFormat;
890     Options           options;
891     options.addOption(StringOption("export").store(&exportFormat).enumValue(exportFormats));
892     CommandLineParser(&options).allowPositionalArguments(true).parse(&argc, argv);
893     if (!exportFormat.empty())
894     {
895         ModificationCheckingFileOutputRedirector redirector(impl_->outputRedirector_);
896         const std::unique_ptr<IHelpExport> exporter(impl_->createExporter(exportFormat, &redirector));
897         impl_->exportHelp(exporter.get());
898         return 0;
899     }
900
901     TextOutputStream& outputFile = impl_->outputRedirector_->standardOutput();
902     TextWriter        writer(&outputFile);
903     HelpLinks         links(eHelpOutputFormat_Console);
904     initProgramLinks(&links, *impl_);
905     CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, &links, impl_->binaryName_);
906     context.setShowHidden(impl_->bHidden_);
907     if (impl_->moduleOverride_ != nullptr)
908     {
909         context.setModuleDisplayName(impl_->programContext_.displayName());
910         impl_->moduleOverride_->writeHelp(context);
911         return 0;
912     }
913     impl_->context_ = &context;
914
915     HelpManager helpManager(impl_->rootTopic_, context.writerContext());
916     try
917     {
918         for (int i = 1; i < argc; ++i)
919         {
920             helpManager.enterTopic(argv[i]);
921         }
922     }
923     catch (const InvalidInputError& ex)
924     {
925         fprintf(stderr, "%s\n", ex.what());
926         return 2;
927     }
928     helpManager.writeCurrentTopic();
929     return 0;
930 }
931
932 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext& context) const
933 {
934     const HelpWriterContext& writerContext = context.writerContext();
935     // TODO: Implement.
936     if (writerContext.outputFormat() != eHelpOutputFormat_Console)
937     {
938         return;
939     }
940     writerContext.writeTextBlock("Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
941     // TODO: More information.
942 }
943
944 } // namespace gmx