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