0a2ceac2ec1e64441020e7ff76bb2caba326739e
[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, 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 != NULL, "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         ConstArrayRef<const char *> 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_ != NULL)
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 NULL;
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() != NULL
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(NULL, maxNameLength + 1, false);
409     formatter.addColumn(NULL, 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 != NULL)
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 NULL; }
452         virtual bool hasSubTopics() const { return false; }
453         virtual const IHelpTopic *findSubTopic(const char * /*name*/) const
454         {
455             return NULL;
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() != NULL)
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     while (linksFile.readLineTrimmed(&line))
557     {
558         links_.addLink("[REF]." + line + "[ref]",
559                        formatString(":ref:`.%s <%s>`", line.c_str(), line.c_str()),
560                        line);
561         links_.addLink("[REF]" + line + "[ref]", formatString(":ref:`%s`", line.c_str()), line);
562     }
563     linksFile.close();
564     initProgramLinks(&links_, helpModule);
565 }
566
567 void HelpExportReStructuredText::startModuleExport()
568 {
569     indexFile_.reset(
570             new TextWriter(
571                     outputRedirector_->openTextOutputFile("fragments/byname.rst")));
572     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s",
573                                        binaryName_.c_str(), binaryName_.c_str(),
574                                        RootHelpText::title));
575     manPagesFile_.reset(
576             new 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 (0 == displayName.compare(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_.reset(
639             new TextWriter(
640                     outputRedirector_->openTextOutputFile("fragments/bytopic.rst")));
641     manPagesFile_.reset(
642             new TextWriter(
643                     outputRedirector_->openTextOutputFile("fragments/bytopic-man.rst")));
644 }
645
646 void HelpExportReStructuredText::exportModuleGroup(
647         const char                *title,
648         const ModuleGroupContents &modules)
649 {
650     indexFile_->ensureEmptyLine();
651     indexFile_->writeLine(title);
652     indexFile_->writeLine(std::string(std::strlen(title), '^'));
653     manPagesFile_->ensureEmptyLine();
654     manPagesFile_->writeLine(title);
655     manPagesFile_->writeLine(std::string(std::strlen(title), '^'));
656
657     ModuleGroupContents::const_iterator module;
658     for (module = modules.begin(); module != modules.end(); ++module)
659     {
660         const std::string     &tag(module->first);
661         std::string            displayName(tag);
662         // TODO: This does not work if the binary name would contain a dash,
663         // but that is not currently the case.
664         const size_t           dashPos = displayName.find('-');
665         GMX_RELEASE_ASSERT(dashPos != std::string::npos,
666                            "There should always be at least one dash in the tag");
667         displayName[dashPos] = ' ';
668         indexFile_->writeLine(formatString(":doc:`%s </onlinehelp/%s>`\n  %s",
669                                            displayName.c_str(), tag.c_str(),
670                                            module->second));
671         manPagesFile_->writeLine(formatString(":manpage:`%s(1)`\n  %s",
672                                               tag.c_str(),
673                                               module->second));
674     }
675 }
676
677 void HelpExportReStructuredText::finishModuleGroupExport()
678 {
679     indexFile_->close();
680     indexFile_.reset();
681     manPagesFile_->close();
682     manPagesFile_.reset();
683 }
684
685 void HelpExportReStructuredText::exportTopic(const IHelpTopic &topic)
686 {
687     const std::string       path("onlinehelp/" + std::string(topic.name()) + ".rst");
688     TextWriter              writer(outputRedirector_->openTextOutputFile(path));
689     CommandLineHelpContext  context(&writer, eHelpOutputFormat_Rst, &links_,
690                                     binaryName_);
691     HelpManager             manager(topic, context.writerContext());
692     manager.writeCurrentTopic();
693     writer.close();
694 }
695
696 /********************************************************************
697  * HelpExportCompletion
698  */
699
700 /*! \internal \brief
701  * Implements export for command-line completion.
702  *
703  * \ingroup module_commandline
704  */
705 class HelpExportCompletion : public IHelpExport
706 {
707     public:
708         //! Initializes completion exporter.
709         explicit HelpExportCompletion(const CommandLineHelpModuleImpl &helpModule);
710
711         virtual void startModuleExport();
712         virtual void exportModuleHelp(
713             const ICommandLineModule         &module,
714             const std::string                &tag,
715             const std::string                &displayName);
716         virtual void finishModuleExport();
717
718         virtual void startModuleGroupExport() {}
719         virtual void exportModuleGroup(const char                * /*title*/,
720                                        const ModuleGroupContents & /*modules*/) {}
721         virtual void finishModuleGroupExport() {}
722
723         virtual void exportTopic(const IHelpTopic & /*topic*/) {}
724
725     private:
726         ShellCompletionWriter    bashWriter_;
727         std::vector<std::string> modules_;
728 };
729
730 HelpExportCompletion::HelpExportCompletion(
731         const CommandLineHelpModuleImpl &helpModule)
732     : bashWriter_(helpModule.binaryName_, eShellCompletionFormat_Bash)
733 {
734 }
735
736 void HelpExportCompletion::startModuleExport()
737 {
738     bashWriter_.startCompletions();
739 }
740
741 void HelpExportCompletion::exportModuleHelp(
742         const ICommandLineModule         &module,
743         const std::string                 & /*tag*/,
744         const std::string                 & /*displayName*/)
745 {
746     modules_.emplace_back(module.name());
747     {
748         CommandLineHelpContext context(&bashWriter_);
749         // We use the display name to pass the name of the module to the
750         // completion writer.
751         context.setModuleDisplayName(module.name());
752         module.writeHelp(context);
753     }
754 }
755
756 void HelpExportCompletion::finishModuleExport()
757 {
758     CommandLineCommonOptionsHolder optionsHolder;
759     optionsHolder.initOptions();
760     bashWriter_.writeWrapperCompletions(modules_, *optionsHolder.options());
761     bashWriter_.finishCompletions();
762 }
763
764 }   // namespace
765
766 /********************************************************************
767  * CommandLineHelpModuleImpl implementation
768  */
769
770 CommandLineHelpModuleImpl::CommandLineHelpModuleImpl(
771         const IProgramContext            &programContext,
772         const std::string                &binaryName,
773         const CommandLineModuleMap       &modules,
774         const CommandLineModuleGroupList &groups)
775     : rootTopic_(*this), programContext_(programContext),
776       binaryName_(binaryName), modules_(modules), groups_(groups),
777       context_(NULL), moduleOverride_(NULL), bHidden_(false),
778       outputRedirector_(&defaultFileOutputRedirector())
779 {
780 }
781
782 std::unique_ptr<IHelpExport>
783 CommandLineHelpModuleImpl::createExporter(const std::string     &format,
784                                           IFileOutputRedirector *redirector)
785 {
786     if (format == "rst")
787     {
788         return std::unique_ptr<IHelpExport>(
789                 new HelpExportReStructuredText(*this, redirector));
790     }
791     else if (format == "completion")
792     {
793         return std::unique_ptr<IHelpExport>(
794                 new HelpExportCompletion(*this));
795     }
796     GMX_THROW(NotImplementedError("This help format is not implemented"));
797 }
798
799 void CommandLineHelpModuleImpl::exportHelp(IHelpExport *exporter)
800 {
801     // TODO: Would be nicer to have the file names supplied by the build system
802     // and/or export a list of files from here.
803     const char *const program = binaryName_.c_str();
804
805     exporter->startModuleExport();
806     CommandLineModuleMap::const_iterator module;
807     for (module = modules_.begin(); module != modules_.end(); ++module)
808     {
809         if (module->second->shortDescription() != NULL)
810         {
811             const char *const moduleName = module->first.c_str();
812             std::string       tag(formatString("%s-%s", program, moduleName));
813             std::string       displayName(formatString("%s %s", program, moduleName));
814             exporter->exportModuleHelp(*module->second, tag, displayName);
815         }
816     }
817     exporter->finishModuleExport();
818
819     exporter->startModuleGroupExport();
820     CommandLineModuleGroupList::const_iterator group;
821     for (group = groups_.begin(); group != groups_.end(); ++group)
822     {
823         exporter->exportModuleGroup((*group)->title(), (*group)->modules());
824     }
825     exporter->finishModuleGroupExport();
826
827     rootTopic_.exportHelp(exporter);
828 }
829
830 namespace
831 {
832
833 /********************************************************************
834  * ModificationCheckingFileOutputStream
835  */
836
837 class ModificationCheckingFileOutputStream : public TextOutputStream
838 {
839     public:
840         ModificationCheckingFileOutputStream(
841             const char                    *path,
842             IFileOutputRedirector         *redirector)
843             : path_(path), redirector_(redirector)
844         {
845         }
846
847         virtual void write(const char *str) { contents_.write(str); }
848         virtual void close()
849         {
850             const std::string &newContents = contents_.toString();
851             // TODO: Redirect these for unit tests.
852             if (File::exists(path_, File::returnFalseOnError))
853             {
854                 const std::string originalContents_
855                     = TextReader::readFileToString(path_);
856                 if (originalContents_ == newContents)
857                 {
858                     return;
859                 }
860             }
861             TextWriter writer(redirector_->openTextOutputFile(path_));
862             writer.writeString(newContents);
863         }
864
865     private:
866         std::string                     path_;
867         StringOutputStream              contents_;
868         IFileOutputRedirector          *redirector_;
869 };
870
871 /********************************************************************
872  * ModificationCheckingFileOutputRedirector
873  */
874
875 class ModificationCheckingFileOutputRedirector : public IFileOutputRedirector
876 {
877     public:
878         explicit ModificationCheckingFileOutputRedirector(
879             IFileOutputRedirector *redirector)
880             : redirector_(redirector)
881         {
882         }
883
884         virtual TextOutputStream &standardOutput()
885         {
886             return redirector_->standardOutput();
887         }
888         virtual TextOutputStreamPointer openTextOutputFile(const char *filename)
889         {
890             return TextOutputStreamPointer(
891                     new ModificationCheckingFileOutputStream(filename, redirector_));
892         }
893
894     private:
895         IFileOutputRedirector  *redirector_;
896 };
897
898 }   // namespace
899
900 /********************************************************************
901  * CommandLineHelpModule
902  */
903
904 CommandLineHelpModule::CommandLineHelpModule(
905         const IProgramContext            &programContext,
906         const std::string                &binaryName,
907         const CommandLineModuleMap       &modules,
908         const CommandLineModuleGroupList &groups)
909     : impl_(new Impl(programContext, binaryName, modules, groups))
910 {
911 }
912
913 CommandLineHelpModule::~CommandLineHelpModule()
914 {
915 }
916
917 HelpTopicPointer CommandLineHelpModule::createModuleHelpTopic(
918         const ICommandLineModule &module) const
919 {
920     return HelpTopicPointer(new ModuleHelpTopic(module, *impl_));
921 }
922
923 void CommandLineHelpModule::addTopic(HelpTopicPointer topic, bool bExported)
924 {
925     impl_->rootTopic_.addTopic(std::move(topic), bExported);
926 }
927
928 void CommandLineHelpModule::setShowHidden(bool bHidden)
929 {
930     impl_->bHidden_ = bHidden;
931 }
932
933 void CommandLineHelpModule::setModuleOverride(
934         const ICommandLineModule &module)
935 {
936     impl_->moduleOverride_ = &module;
937 }
938
939 void CommandLineHelpModule::setOutputRedirector(
940         IFileOutputRedirector *output)
941 {
942     impl_->outputRedirector_ = output;
943 }
944
945 int CommandLineHelpModule::run(int argc, char *argv[])
946 {
947     // Add internal topics lazily here.
948     addTopic(HelpTopicPointer(new CommandsHelpTopic(*impl_)), false);
949
950     const char *const exportFormats[] = { "rst", "completion" };
951     std::string       exportFormat;
952     Options           options;
953     options.addOption(StringOption("export").store(&exportFormat)
954                           .enumValue(exportFormats));
955     CommandLineParser(&options).parse(&argc, argv);
956     if (!exportFormat.empty())
957     {
958         ModificationCheckingFileOutputRedirector redirector(impl_->outputRedirector_);
959         const std::unique_ptr<IHelpExport>       exporter(
960                 impl_->createExporter(exportFormat, &redirector));
961         impl_->exportHelp(exporter.get());
962         return 0;
963     }
964
965     TextOutputStream      &outputFile = impl_->outputRedirector_->standardOutput();
966     TextWriter             writer(&outputFile);
967     HelpLinks              links(eHelpOutputFormat_Console);
968     initProgramLinks(&links, *impl_);
969     CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, &links,
970                                    impl_->binaryName_);
971     context.setShowHidden(impl_->bHidden_);
972     if (impl_->moduleOverride_ != NULL)
973     {
974         context.setModuleDisplayName(impl_->programContext_.displayName());
975         impl_->moduleOverride_->writeHelp(context);
976         return 0;
977     }
978     impl_->context_ = &context;
979
980     HelpManager helpManager(impl_->rootTopic_, context.writerContext());
981     try
982     {
983         for (int i = 1; i < argc; ++i)
984         {
985             helpManager.enterTopic(argv[i]);
986         }
987     }
988     catch (const InvalidInputError &ex)
989     {
990         fprintf(stderr, "%s\n", ex.what());
991         return 2;
992     }
993     helpManager.writeCurrentTopic();
994     return 0;
995 }
996
997 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext &context) const
998 {
999     const HelpWriterContext &writerContext = context.writerContext();
1000     // TODO: Implement.
1001     if (writerContext.outputFormat() != eHelpOutputFormat_Console)
1002     {
1003         return;
1004     }
1005     writerContext.writeTextBlock(
1006             "Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
1007     // TODO: More information.
1008 }
1009
1010 } // namespace gmx