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