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