Add listing of programs by topic to HTML output.
[alexxy/gromacs.git] / src / gromacs / commandline / cmdlinemodulemanager.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013, by the GROMACS development team, led by
5  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
6  * others, as listed in the AUTHORS file in the top-level source
7  * 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::CommandLineModuleManager.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_commandline
41  */
42 #include "cmdlinemodulemanager.h"
43
44 #include <cstdio>
45
46 #include <algorithm>
47 #include <map>
48 #include <string>
49 #include <utility>
50
51 #include <boost/scoped_ptr.hpp>
52
53 #include "gromacs/legacyheaders/copyrite.h"
54 #include "gromacs/legacyheaders/network.h"
55 #include "gromacs/legacyheaders/smalloc.h"
56
57 #include "gromacs/commandline/cmdlinehelpcontext.h"
58 #include "gromacs/commandline/cmdlinemodule.h"
59 #include "gromacs/commandline/cmdlineparser.h"
60 #include "gromacs/fileio/futil.h"
61 #include "gromacs/onlinehelp/helpformat.h"
62 #include "gromacs/onlinehelp/helpmanager.h"
63 #include "gromacs/onlinehelp/helptopic.h"
64 #include "gromacs/onlinehelp/helpwritercontext.h"
65 #include "gromacs/options/basicoptions.h"
66 #include "gromacs/options/options.h"
67 #include "gromacs/utility/exceptions.h"
68 #include "gromacs/utility/file.h"
69 #include "gromacs/utility/gmxassert.h"
70 #include "gromacs/utility/init.h"
71 #include "gromacs/utility/programinfo.h"
72 #include "gromacs/utility/stringutil.h"
73 #include "gromacs/utility/uniqueptr.h"
74
75 namespace gmx
76 {
77
78 //! Container type for mapping module names to module objects.
79 typedef std::map<std::string, CommandLineModulePointer> CommandLineModuleMap;
80 //! Smart pointer type for managing a CommandLineModuleGroup.
81 typedef gmx_unique_ptr<internal::CommandLineModuleGroupData>::type
82     CommandLineModuleGroupDataPointer;
83 //! Container type for keeping a list of module groups.
84 typedef std::vector<CommandLineModuleGroupDataPointer>
85     CommandLineModuleGroupList;
86
87 class CommandLineHelpModule;
88
89 namespace internal
90 {
91
92 /*! \internal
93  * \brief
94  * Internal data for a CommandLineModuleManager module group.
95  *
96  * This class contains the state of a module group.  CommandLineModuleGroup
97  * provides the public interface to construct/alter the state, and
98  * CommandLineModuleManager and its associated classes use it for help output.
99  *
100  * \ingroup module_commandline
101  */
102 class CommandLineModuleGroupData
103 {
104     public:
105         /*! \brief
106          * Shorthand for a list of modules contained in the group.
107          *
108          * The first element in the contained pair contains the tag
109          * (gmx-something) of the module, and the second element contains the
110          * description.  The second element is never NULL.
111          */
112         typedef std::vector<std::pair<std::string, const char *> > ModuleList;
113
114         /*! \brief
115          * Constructs an empty module group.
116          *
117          * \param[in] modules  List of all modules
118          *     (used for checking and default descriptions).
119          * \param[in] title    Title of the group.
120          *
121          * Does not throw.
122          */
123         CommandLineModuleGroupData(const CommandLineModuleMap &modules,
124                                    const char                 *title)
125             : allModules_(modules), title_(title)
126         {
127         }
128
129         //! Returns the title for the group.
130         const char *title() const { return title_; }
131         //! Returns the list of modules in the group.
132         const ModuleList &modules() const { return modules_; }
133
134         /*! \brief
135          * Adds a module to the group.
136          *
137          * \param[in] name        Name of the module.
138          * \param[in] description Description of the module in this group.
139          * \throws    std::bad_alloc if out of memory.
140          *
141          * If \p description is NULL, the description returned by the module is
142          * used.
143          */
144         void addModule(const char *name, const char *description)
145         {
146             CommandLineModuleMap::const_iterator moduleIter = allModules_.find(name);
147             GMX_RELEASE_ASSERT(moduleIter != allModules_.end(),
148                                "Non-existent module added to a group");
149             if (description == NULL)
150             {
151                 description = moduleIter->second->shortDescription();
152                 GMX_RELEASE_ASSERT(description != NULL,
153                                    "Module without a description added to a group");
154             }
155             const char *const program =
156                 ProgramInfo::getInstance().invariantProgramName().c_str();
157             std::string       tag(formatString("%s-%s", program, name));
158             modules_.push_back(std::make_pair(tag, description));
159         }
160
161     private:
162         const CommandLineModuleMap &allModules_;
163         const char                 *title_;
164         ModuleList                  modules_;
165
166         GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineModuleGroupData);
167 };
168
169 }   // namespace internal
170
171 namespace
172 {
173
174 /********************************************************************
175  * RootHelpTopic
176  */
177
178 struct RootHelpText
179 {
180     static const char        name[];
181     static const char        title[];
182     static const char *const text[];
183 };
184
185 // The first two are not used.
186 const char        RootHelpText::name[]  = "";
187 const char        RootHelpText::title[] = "";
188 const char *const RootHelpText::text[]  = {
189     "Usage: [PROGRAM] <command> [<args>]",
190 };
191
192 /*! \brief
193  * Help topic that forms the root of the help tree for the help subcommand.
194  *
195  * \ingroup module_commandline
196  */
197 class RootHelpTopic : public CompositeHelpTopic<RootHelpText>
198 {
199     public:
200         /*! \brief
201          * Creates a root help topic.
202          *
203          * \param[in] modules  List of modules for to use for module listings.
204          *
205          * Does not throw.
206          */
207         explicit RootHelpTopic(const CommandLineModuleMap &modules)
208             : modules_(modules)
209         {
210         }
211
212         virtual void writeHelp(const HelpWriterContext &context) const;
213
214     private:
215         void printModuleList(const HelpWriterContext &context) const;
216
217         const CommandLineModuleMap &modules_;
218
219         GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
220 };
221
222 void RootHelpTopic::writeHelp(const HelpWriterContext &context) const
223 {
224     if (context.outputFormat() != eHelpOutputFormat_Console)
225     {
226         // TODO: Implement once the situation with Redmine issue #969 is more
227         // clear.
228         GMX_THROW(NotImplementedError(
229                           "Root help is not implemented for this output format"));
230     }
231     writeBasicHelpTopic(context, *this, helpText());
232     // TODO: If/when this list becomes long, it may be better to only print
233     // "common" commands here, and have a separate topic (e.g.,
234     // "help commands") that prints the full list.
235     printModuleList(context);
236     context.writeTextBlock(
237             "For additional help on a command, use '[PROGRAM] help <command>'");
238     writeSubTopicList(context,
239                       "\nAdditional help is available on the following topics:");
240     context.writeTextBlock(
241             "To access the help, use '[PROGRAM] help <topic>'.");
242 }
243
244 void RootHelpTopic::printModuleList(const HelpWriterContext &context) const
245 {
246     if (context.outputFormat() != eHelpOutputFormat_Console)
247     {
248         // TODO: Implement once the situation with Redmine issue #969 is more
249         // clear.
250         GMX_THROW(NotImplementedError(
251                           "Module list is not implemented for this output format"));
252     }
253     int maxNameLength = 0;
254     CommandLineModuleMap::const_iterator module;
255     for (module = modules_.begin(); module != modules_.end(); ++module)
256     {
257         int nameLength = static_cast<int>(module->first.length());
258         if (module->second->shortDescription() != NULL
259             && nameLength > maxNameLength)
260         {
261             maxNameLength = nameLength;
262         }
263     }
264     File              &file = context.outputFile();
265     TextTableFormatter formatter;
266     formatter.addColumn(NULL, maxNameLength + 1, false);
267     formatter.addColumn(NULL, 72 - maxNameLength, true);
268     formatter.setFirstColumnIndent(4);
269     file.writeLine();
270     file.writeLine("Available commands:");
271     for (module = modules_.begin(); module != modules_.end(); ++module)
272     {
273         const char *name        = module->first.c_str();
274         const char *description = module->second->shortDescription();
275         if (description != NULL)
276         {
277             formatter.clear();
278             formatter.addColumnLine(0, name);
279             formatter.addColumnLine(1, description);
280             file.writeString(formatter.formatRow());
281         }
282     }
283 }
284
285 /********************************************************************
286  * ModuleHelpTopic declaration
287  */
288
289 /*! \brief
290  * Help topic wrapper for a command-line module.
291  *
292  * This class implements HelpTopicInterface such that it wraps a
293  * CommandLineModuleInterface, allowing subcommand "help <command>"
294  * to produce the help for "<command>".
295  *
296  * \ingroup module_commandline
297  */
298 class ModuleHelpTopic : public HelpTopicInterface
299 {
300     public:
301         //! Constructs a help topic for a specific module.
302         ModuleHelpTopic(const CommandLineModuleInterface &module,
303                         const CommandLineHelpModule      &helpModule)
304             : module_(module), helpModule_(helpModule)
305         {
306         }
307
308         virtual const char *name() const { return module_.name(); }
309         virtual const char *title() const { return NULL; }
310         virtual bool hasSubTopics() const { return false; }
311         virtual const HelpTopicInterface *findSubTopic(const char * /*name*/) const
312         {
313             return NULL;
314         }
315         virtual void writeHelp(const HelpWriterContext &context) const;
316
317     private:
318         const CommandLineModuleInterface &module_;
319         const CommandLineHelpModule      &helpModule_;
320
321         GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
322 };
323
324 /********************************************************************
325  * HelpExportInterface
326  */
327
328 /*! \brief
329  * Callbacks for exporting help information for command-line modules.
330  *
331  * \ingroup module_commandline
332  */
333 class HelpExportInterface
334 {
335     public:
336         //! Shorthand for a list of modules contained in a group.
337         typedef internal::CommandLineModuleGroupData::ModuleList
338             ModuleGroupContents;
339
340         virtual ~HelpExportInterface() {};
341
342         /*! \brief
343          * Called once before exporting individual modules.
344          *
345          * Can, e.g., open shared output files (e.g., if the output is written
346          * into a single file, or if a separate index is required) and write
347          * headers into them.
348          */
349         virtual void startModuleExport() = 0;
350         /*! \brief
351          * Called to export the help for each module.
352          *
353          * \param[in] tag     Unique tag for the module (gmx-something).
354          * \param[in] module  Module for which the help should be exported.
355          */
356         virtual void exportModuleHelp(const std::string                &tag,
357                                       const CommandLineModuleInterface &module) = 0;
358         /*! \brief
359          * Called after all modules have been exported.
360          *
361          * Can close files opened in startModuleExport(), write footers to them
362          * etc.
363          */
364         virtual void finishModuleExport() = 0;
365
366         /*! \brief
367          * Called once before exporting module groups.
368          *
369          * Can, e.g., open a single output file for listing all the groups.
370          */
371         virtual void startModuleGroupExport() = 0;
372         /*! \brief
373          * Called to export the help for each module group.
374          *
375          * \param[in] title    Title for the group.
376          * \param[in] modules  List of modules in the group.
377          */
378         virtual void exportModuleGroup(const char                *title,
379                                        const ModuleGroupContents &modules) = 0;
380         /*! \brief
381          * Called after all module groups have been exported.
382          *
383          * Can close files opened in startModuleGroupExport(), write footers to them
384          * etc.
385          */
386         virtual void finishModuleGroupExport() = 0;
387 };
388
389 /********************************************************************
390  * HelpExportMan
391  */
392
393 /*! \internal \brief
394  * Implements export for man pages.
395  *
396  * \ingroup module_commandline
397  */
398 class HelpExportMan : public HelpExportInterface
399 {
400     public:
401         virtual void startModuleExport() {}
402         virtual void exportModuleHelp(const std::string                &tag,
403                                       const CommandLineModuleInterface &module);
404         virtual void finishModuleExport() {}
405
406         virtual void startModuleGroupExport() {}
407         virtual void exportModuleGroup(const char                * /*title*/,
408                                        const ModuleGroupContents & /*modules*/) {}
409         virtual void finishModuleGroupExport() {}
410
411     private:
412         boost::scoped_ptr<File> indexFile_;
413 };
414
415 void HelpExportMan::exportModuleHelp(const std::string                &tag,
416                                      const CommandLineModuleInterface &module)
417 {
418     File file("man1/" + tag + ".1", "w");
419
420     // TODO: It would be nice to remove the VERSION prefix from the version
421     // string to make it shorter.
422     file.writeLine(formatString(".TH %s 1 \"\" \"%s\" \"GROMACS Manual\"\n",
423                                 tag.c_str(),
424                                 GromacsVersion()));
425     file.writeLine(".SH NAME");
426     file.writeLine(formatString("%s - %s", tag.c_str(),
427                                 module.shortDescription()));
428     file.writeLine();
429
430     CommandLineHelpContext context(&file, eHelpOutputFormat_Man);
431     std::string            displayName(tag);
432     std::replace(displayName.begin(), displayName.end(), '-', ' ');
433     context.setModuleDisplayName(displayName);
434     module.writeHelp(context);
435
436     file.writeLine(".SH SEE ALSO");
437     file.writeLine(".BR gromacs(7)");
438     file.writeLine();
439     file.writeLine("More information about \\fBGROMACS\\fR is available at <\\fIhttp://www.gromacs.org/\\fR>.");
440 }
441
442 /********************************************************************
443  * HelpExportHtml
444  */
445
446 /*! \internal \brief
447  * Implements export for HTML help.
448  *
449  * \ingroup module_commandline
450  */
451 class HelpExportHtml : public HelpExportInterface
452 {
453     public:
454         HelpExportHtml();
455
456         virtual void startModuleExport();
457         virtual void exportModuleHelp(const std::string                &tag,
458                                       const CommandLineModuleInterface &module);
459         virtual void finishModuleExport();
460
461         virtual void startModuleGroupExport();
462         virtual void exportModuleGroup(const char                *title,
463                                        const ModuleGroupContents &modules);
464         virtual void finishModuleGroupExport();
465
466     private:
467         void writeHtmlHeader(File *file, const std::string &title) const;
468         void writeHtmlFooter(File *file) const;
469
470         boost::scoped_ptr<File>  indexFile_;
471         HelpLinks                links_;
472 };
473
474 HelpExportHtml::HelpExportHtml()
475 {
476     char *linksFilename = low_gmxlibfn("links.dat", FALSE, FALSE);
477     if (linksFilename != NULL)
478     {
479         scoped_ptr_sfree guard(linksFilename);
480         File             linksFile(linksFilename, "r");
481         std::string      line;
482         while (linksFile.readLine(&line))
483         {
484             links_.addLink(line, "../online/" + line);
485         }
486     }
487 }
488
489 void HelpExportHtml::startModuleExport()
490 {
491     indexFile_.reset(new File("byname.html", "w"));
492     writeHtmlHeader(indexFile_.get(), "GROMACS Programs by Name");
493     indexFile_->writeLine("<H3>GROMACS Programs Alphabetically</H3>");
494 }
495
496 void HelpExportHtml::exportModuleHelp(const std::string                &tag,
497                                       const CommandLineModuleInterface &module)
498 {
499     File file(tag + ".html", "w");
500     writeHtmlHeader(&file, tag);
501
502     CommandLineHelpContext context(&file, eHelpOutputFormat_Html);
503     std::string            displayName(tag);
504     std::replace(displayName.begin(), displayName.end(), '-', ' ');
505     context.setModuleDisplayName(displayName);
506     context.setLinks(links_);
507     module.writeHelp(context);
508
509     writeHtmlFooter(&file);
510     file.close();
511
512     indexFile_->writeLine(formatString("<a href=\"%s.html\">%s</a> - %s<br>",
513                                        tag.c_str(), displayName.c_str(),
514                                        module.shortDescription()));
515 }
516
517 void HelpExportHtml::finishModuleExport()
518 {
519     writeHtmlFooter(indexFile_.get());
520     indexFile_->close();
521 }
522
523 void HelpExportHtml::startModuleGroupExport()
524 {
525     indexFile_.reset(new File("bytopic.html", "w"));
526     writeHtmlHeader(indexFile_.get(), "GROMACS Programs by Topic");
527     indexFile_->writeLine("<H3>GROMACS Programs by Topic</H3>");
528 }
529
530 void HelpExportHtml::exportModuleGroup(const char                *title,
531                                        const ModuleGroupContents &modules)
532 {
533     indexFile_->writeLine(formatString("<H4>%s</H4>", title));
534
535     ModuleGroupContents::const_iterator module;
536     for (module = modules.begin(); module != modules.end(); ++module)
537     {
538         const std::string     &tag(module->first);
539         std::string            displayName(tag);
540         std::replace(displayName.begin(), displayName.end(), '-', ' ');
541         indexFile_->writeLine(formatString("<a href=\"%s.html\">%s</a> - %s<br>",
542                                            tag.c_str(), displayName.c_str(),
543                                            module->second));
544     }
545 }
546
547 void HelpExportHtml::finishModuleGroupExport()
548 {
549     writeHtmlFooter(indexFile_.get());
550     indexFile_->close();
551 }
552
553 void HelpExportHtml::writeHtmlHeader(File *file, const std::string &title) const
554 {
555     file->writeLine("<HTML>");
556     file->writeLine("<HEAD>");
557     file->writeLine(formatString("<TITLE>%s</TITLE>", title.c_str()));
558     file->writeLine("<LINK rel=stylesheet href=\"../online/style.css\" type=\"text/css\">");
559     file->writeLine("<BODY text=\"#000000\" bgcolor=\"#FFFFFF\" link=\"#0000FF\" vlink=\"#990000\" alink=\"#FF0000\">");
560     file->writeLine("<TABLE WIDTH=\"98%%\" NOBORDER><TR>");
561     file->writeLine("<TD WIDTH=400><TABLE WIDTH=400 NOBORDER>");
562     file->writeLine("<TD WIDTH=116>");
563     file->writeLine("<A HREF=\"http://www.gromacs.org/\">"
564                     "<IMG SRC=\"../images/gmxlogo_small.jpg\" BORDER=0>"
565                     "</A>");
566     file->writeLine("</TD>");
567     file->writeLine(formatString("<TD ALIGN=LEFT VALIGN=TOP WIDTH=280>"
568                                  "<BR><H2>%s</H2>", title.c_str()));
569     file->writeLine("<FONT SIZE=-1><A HREF=\"../online.html\">Main Table of Contents</A></FONT>");
570     file->writeLine("</TD>");
571     file->writeLine("</TABLE></TD>");
572     file->writeLine("<TD WIDTH=\"*\" ALIGN=RIGHT VALIGN=BOTTOM>");
573     file->writeLine(formatString("<P><B>%s</B>", GromacsVersion()));
574     file->writeLine("</TD>");
575     file->writeLine("</TR></TABLE>");
576     file->writeLine("<HR>");
577 }
578
579 void HelpExportHtml::writeHtmlFooter(File *file) const
580 {
581     file->writeLine("<P>");
582     file->writeLine("<HR>");
583     file->writeLine("<DIV ALIGN=RIGHT><FONT SIZE=\"-1\">");
584     file->writeLine("<A HREF=\"http://www.gromacs.org\">http://www.gromacs.org</A><BR>");
585     file->writeLine("</FONT></DIV>");
586     file->writeLine("</BODY>");
587     file->writeLine("</HTML>");
588 }
589
590 }   // namespace
591
592 /********************************************************************
593  * CommandLineHelpModule
594  */
595
596 /*! \internal
597  * \brief
598  * Command-line module for producing help.
599  *
600  * This module implements the 'help' subcommand that is automatically added by
601  * CommandLineModuleManager.
602  *
603  * \ingroup module_commandline
604  */
605 class CommandLineHelpModule : public CommandLineModuleInterface
606 {
607     public:
608         /*! \brief
609          * Creates a command-line help module.
610          *
611          * \param[in] modules  List of modules for to use for module listings.
612          * \param[in] groups   List of module groups.
613          * \throws    std::bad_alloc if out of memory.
614          */
615         CommandLineHelpModule(const CommandLineModuleMap       &modules,
616                               const CommandLineModuleGroupList &groups);
617
618         /*! \brief
619          * Adds a top-level help topic.
620          *
621          * \param[in] topic  Help topic to add.
622          * \throws    std::bad_alloc if out of memory.
623          */
624         void addTopic(HelpTopicPointer topic);
625         //! Sets whether hidden options will be shown in help.
626         void setShowHidden(bool bHidden) { bHidden_ = bHidden; }
627         /*! \brief
628          * Sets an override to show the help for the given module.
629          *
630          * If called, the help module directly prints the help for the given
631          * module when called, skipping any other processing.
632          */
633         void setModuleOverride(const CommandLineModuleInterface &module)
634         {
635             moduleOverride_ = &module;
636         }
637
638         //! Returns the context object for help output.
639         const CommandLineHelpContext &context() const
640         {
641             return *context_;
642         }
643
644         virtual const char *name() const { return "help"; }
645         virtual const char *shortDescription() const
646         {
647             return "Print help information";
648         }
649
650         virtual int run(int argc, char *argv[]);
651         virtual void writeHelp(const CommandLineHelpContext &context) const;
652
653     private:
654         void exportHelp(HelpExportInterface *exporter) const;
655
656         boost::scoped_ptr<RootHelpTopic>  rootTopic_;
657         const CommandLineModuleMap       &modules_;
658         const CommandLineModuleGroupList &groups_;
659
660         CommandLineHelpContext           *context_;
661         const CommandLineModuleInterface *moduleOverride_;
662         bool                              bHidden_;
663
664         GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModule);
665 };
666
667 CommandLineHelpModule::CommandLineHelpModule(
668         const CommandLineModuleMap       &modules,
669         const CommandLineModuleGroupList &groups)
670     : rootTopic_(new RootHelpTopic(modules)), modules_(modules), groups_(groups),
671       context_(NULL), moduleOverride_(NULL), bHidden_(false)
672 {
673 }
674
675 void CommandLineHelpModule::addTopic(HelpTopicPointer topic)
676 {
677     rootTopic_->addSubTopic(move(topic));
678 }
679
680 int CommandLineHelpModule::run(int argc, char *argv[])
681 {
682     const char *const exportFormats[] = { "man", "html", "completion" };
683     std::string       exportFormat;
684     Options           options(NULL, NULL);
685     options.addOption(StringOption("export").store(&exportFormat)
686                           .enumValue(exportFormats));
687     CommandLineParser(&options).parse(&argc, argv);
688     if (!exportFormat.empty())
689     {
690         boost::scoped_ptr<HelpExportInterface> exporter;
691         if (exportFormat == "man")
692         {
693             exporter.reset(new HelpExportMan);
694         }
695         else if (exportFormat == "html")
696         {
697             exporter.reset(new HelpExportHtml);
698         }
699         else
700         {
701             GMX_THROW(NotImplementedError("This help format is not implemented"));
702         }
703         exportHelp(exporter.get());
704         return 0;
705     }
706
707     boost::scoped_ptr<CommandLineHelpContext> context(
708             new CommandLineHelpContext(&File::standardOutput(),
709                                        eHelpOutputFormat_Console));
710     context->setShowHidden(bHidden_);
711     context_ = context.get();
712     if (moduleOverride_ != NULL)
713     {
714         ModuleHelpTopic(*moduleOverride_, *this).writeHelp(context->writerContext());
715         return 0;
716     }
717
718     HelpManager       helpManager(*rootTopic_, context->writerContext());
719     try
720     {
721         for (int i = 1; i < argc; ++i)
722         {
723             helpManager.enterTopic(argv[i]);
724         }
725     }
726     catch (const InvalidInputError &ex)
727     {
728         fprintf(stderr, "%s\n", ex.what());
729         return 2;
730     }
731     helpManager.writeCurrentTopic();
732     return 0;
733 }
734
735 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext &context) const
736 {
737     const HelpWriterContext &writerContext = context.writerContext();
738     // TODO: Implement.
739     if (writerContext.outputFormat() != eHelpOutputFormat_Console)
740     {
741         return;
742     }
743     writerContext.writeTextBlock(
744             "Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
745     // TODO: More information.
746 }
747
748 void CommandLineHelpModule::exportHelp(HelpExportInterface *exporter) const
749 {
750     // TODO: Would be nicer to have the file names supplied by the build system
751     // and/or export a list of files from here.
752     const char *const program =
753         ProgramInfo::getInstance().invariantProgramName().c_str();
754
755     exporter->startModuleExport();
756     CommandLineModuleMap::const_iterator module;
757     for (module = modules_.begin(); module != modules_.end(); ++module)
758     {
759         if (module->second->shortDescription() != NULL)
760         {
761             const char *const moduleName = module->first.c_str();
762             std::string       tag(formatString("%s-%s", program, moduleName));
763             exporter->exportModuleHelp(tag, *module->second);
764         }
765     }
766     exporter->finishModuleExport();
767
768     exporter->startModuleGroupExport();
769     CommandLineModuleGroupList::const_iterator group;
770     for (group = groups_.begin(); group != groups_.end(); ++group)
771     {
772         exporter->exportModuleGroup((*group)->title(), (*group)->modules());
773     }
774     exporter->finishModuleGroupExport();
775 }
776
777 namespace
778 {
779
780 /********************************************************************
781  * ModuleHelpTopic implementation
782  */
783
784 void ModuleHelpTopic::writeHelp(const HelpWriterContext & /*context*/) const
785 {
786     module_.writeHelp(helpModule_.context());
787 }
788
789 /********************************************************************
790  * CMainCommandLineModule
791  */
792
793 /*! \internal \brief
794  * Implements a CommandLineModuleInterface, given a function with C/C++ main()
795  * signature.
796  *
797  * \ingroup module_commandline
798  */
799 class CMainCommandLineModule : public CommandLineModuleInterface
800 {
801     public:
802         //! \copydoc gmx::CommandLineModuleManager::CMainFunction
803         typedef CommandLineModuleManager::CMainFunction CMainFunction;
804
805         /*! \brief
806          * Creates a wrapper module for the given main function.
807          *
808          * \param[in] name             Name for the module.
809          * \param[in] shortDescription One-line description for the module.
810          * \param[in] mainFunction     Main function to wrap.
811          *
812          * Does not throw.  This is essential for correct implementation of
813          * CommandLineModuleManager::runAsMainCMain().
814          */
815         CMainCommandLineModule(const char *name, const char *shortDescription,
816                                CMainFunction mainFunction)
817             : name_(name), shortDescription_(shortDescription),
818               mainFunction_(mainFunction)
819         {
820         }
821
822         virtual const char *name() const
823         {
824             return name_;
825         }
826         virtual const char *shortDescription() const
827         {
828             return shortDescription_;
829         }
830
831         virtual int run(int argc, char *argv[])
832         {
833             return mainFunction_(argc, argv);
834         }
835         virtual void writeHelp(const CommandLineHelpContext &context) const
836         {
837             const HelpOutputFormat format = context.writerContext().outputFormat();
838             const char            *type;
839             switch (format)
840             {
841                 case eHelpOutputFormat_Console:
842                     type = "help";
843                     break;
844                 case eHelpOutputFormat_Man:
845                     type = "nroff";
846                     break;
847                 case eHelpOutputFormat_Html:
848                     type = "html";
849                     break;
850                 default:
851                     GMX_THROW(NotImplementedError(
852                                       "Command-line help is not implemented for this output format"));
853             }
854             char *argv[4];
855             int   argc = 3;
856             // TODO: The constness should not be cast away.
857             argv[0] = const_cast<char *>(name_);
858             argv[1] = const_cast<char *>("-man");
859             argv[2] = const_cast<char *>(type);
860             argv[3] = NULL;
861             GlobalCommandLineHelpContext global(context);
862             mainFunction_(argc, argv);
863         }
864
865     private:
866         const char             *name_;
867         const char             *shortDescription_;
868         CMainFunction           mainFunction_;
869
870 };
871
872 }   // namespace
873
874 /********************************************************************
875  * CommandLineModuleManager::Impl
876  */
877
878 /*! \internal \brief
879  * Private implementation class for CommandLineModuleManager.
880  *
881  * \ingroup module_commandline
882  */
883 class CommandLineModuleManager::Impl
884 {
885     public:
886         /*! \brief
887          * Initializes the implementation class.
888          *
889          * \param     programInfo  Program information for the running binary.
890          */
891         explicit Impl(ProgramInfo *programInfo);
892
893         /*! \brief
894          * Helper method that adds a given module to the module manager.
895          *
896          * \throws    std::bad_alloc if out of memory.
897          */
898         void addModule(CommandLineModulePointer module);
899         /*! \brief
900          * Creates the help module if it does not yet exist.
901          *
902          * \throws    std::bad_alloc if out of memory.
903          *
904          * This method should be called before accessing \a helpModule_.
905          */
906         void ensureHelpModuleExists();
907
908         /*! \brief
909          * Finds a module that matches a name.
910          *
911          * \param[in] name  Module name to find.
912          * \returns   Iterator to the found module, or
913          *      \c modules_.end() if not found.
914          *
915          * Does not throw.
916          */
917         CommandLineModuleMap::const_iterator
918         findModuleByName(const std::string &name) const;
919         /*! \brief
920          * Finds a module that the name of the binary.
921          *
922          * \param[in] programInfo  Program information object to use.
923          * \throws    std::bad_alloc if out of memory.
924          * \returns   Iterator to the found module, or
925          *      \c modules_.end() if not found.
926          *
927          * Checks whether the program is invoked through a symlink whose name
928          * is different from ProgramInfo::realBinaryName(), and if so, checks
929          * if a module name matches the name of the symlink.
930          *
931          * Note that the \p programInfo parameter is currently not necessary
932          * (as the program info object is also contained as a member), but it
933          * clarifies the control flow.
934          */
935         CommandLineModuleMap::const_iterator
936         findModuleFromBinaryName(const ProgramInfo &programInfo) const;
937
938         /*! \brief
939          * Processes command-line options for the wrapper binary.
940          *
941          * \param[in,out] argc On input, argc passed to run().
942          *     On output, argc to be passed to the module.
943          * \param[in,out] argv On input, argv passed to run().
944          *     On output, argv to be passed to the module.
945          * \throws    InvalidInputError if there are invalid options.
946          * \returns   The module that should be run.
947          *
948          * Handles command-line options that affect the wrapper binary
949          * (potentially changing the members of \c this in response to the
950          * options).  Also finds the module that should be run and the
951          * arguments that should be passed to it.
952          */
953         CommandLineModuleInterface *
954         processCommonOptions(int *argc, char ***argv);
955
956         /*! \brief
957          * Maps module names to module objects.
958          *
959          * Owns the contained modules.
960          */
961         CommandLineModuleMap         modules_;
962         /*! \brief
963          * List of groupings for modules for help output.
964          *
965          * Owns the contained module group data objects.
966          * CommandLineModuleGroup objects point to the data objects contained
967          * here.
968          */
969         CommandLineModuleGroupList   moduleGroups_;
970         //! Information about the currently running program.
971         ProgramInfo                 &programInfo_;
972         /*! \brief
973          * Module that implements help for the binary.
974          *
975          * The pointed module is owned by the \a modules_ container.
976          */
977         CommandLineHelpModule       *helpModule_;
978         //! Settings for what to write in the startup header.
979         BinaryInformationSettings    binaryInfoSettings_;
980         //! If non-NULL, run this module in single-module mode.
981         CommandLineModuleInterface  *singleModule_;
982         //! Whether all stderr output should be suppressed.
983         bool                         bQuiet_;
984         //! Whether to write the startup information to stdout iso stderr.
985         bool                         bStdOutInfo_;
986
987     private:
988         GMX_DISALLOW_COPY_AND_ASSIGN(Impl);
989 };
990
991 CommandLineModuleManager::Impl::Impl(ProgramInfo *programInfo)
992     : programInfo_(*programInfo), helpModule_(NULL), singleModule_(NULL),
993       bQuiet_(false), bStdOutInfo_(false)
994 {
995     binaryInfoSettings_.copyright(true);
996 }
997
998 void CommandLineModuleManager::Impl::addModule(CommandLineModulePointer module)
999 {
1000     GMX_ASSERT(modules_.find(module->name()) == modules_.end(),
1001                "Attempted to register a duplicate module name");
1002     ensureHelpModuleExists();
1003     HelpTopicPointer helpTopic(new ModuleHelpTopic(*module, *helpModule_));
1004     modules_.insert(std::make_pair(std::string(module->name()),
1005                                    move(module)));
1006     helpModule_->addTopic(move(helpTopic));
1007 }
1008
1009 void CommandLineModuleManager::Impl::ensureHelpModuleExists()
1010 {
1011     if (helpModule_ == NULL)
1012     {
1013         helpModule_ = new CommandLineHelpModule(modules_, moduleGroups_);
1014         addModule(CommandLineModulePointer(helpModule_));
1015     }
1016 }
1017
1018 CommandLineModuleMap::const_iterator
1019 CommandLineModuleManager::Impl::findModuleByName(const std::string &name) const
1020 {
1021     // TODO: Accept unambiguous prefixes?
1022     return modules_.find(name);
1023 }
1024
1025 CommandLineModuleMap::const_iterator
1026 CommandLineModuleManager::Impl::findModuleFromBinaryName(
1027         const ProgramInfo &programInfo) const
1028 {
1029     std::string binaryName = programInfo.invariantProgramName();
1030     if (binaryName == programInfo.realBinaryName())
1031     {
1032         return modules_.end();
1033     }
1034     if (binaryName.compare(0, 2, "g_") == 0)
1035     {
1036         binaryName.erase(0, 2);
1037     }
1038     if (binaryName.compare(0, 3, "gmx") == 0)
1039     {
1040         binaryName.erase(0, 3);
1041     }
1042     return findModuleByName(binaryName);
1043 }
1044
1045 CommandLineModuleInterface *
1046 CommandLineModuleManager::Impl::processCommonOptions(int *argc, char ***argv)
1047 {
1048     // Check if we are directly invoking a certain module.
1049     CommandLineModuleInterface *module = singleModule_;
1050     if (module == NULL)
1051     {
1052         // Also check for invokation through named symlinks.
1053         CommandLineModuleMap::const_iterator moduleIter
1054             = findModuleFromBinaryName(programInfo_);
1055         if (moduleIter != modules_.end())
1056         {
1057             module = moduleIter->second.get();
1058         }
1059     }
1060
1061     bool bHelp      = false;
1062     bool bHidden    = false;
1063     bool bVersion   = false;
1064     bool bCopyright = true;
1065     // TODO: Print the common options into the help.
1066     // TODO: It would be nice to propagate at least the -quiet option to
1067     // the modules so that they can also be quiet in response to this.
1068     Options options(NULL, NULL);
1069     options.addOption(BooleanOption("h").store(&bHelp));
1070     options.addOption(BooleanOption("hidden").store(&bHidden));
1071     options.addOption(BooleanOption("quiet").store(&bQuiet_));
1072     options.addOption(BooleanOption("version").store(&bVersion));
1073     options.addOption(BooleanOption("copyright").store(&bCopyright));
1074
1075     if (module == NULL)
1076     {
1077         // If not in single-module mode, process options to the wrapper binary.
1078         // TODO: Ideally, this could be done by CommandLineParser.
1079         int argcForWrapper = 1;
1080         while (argcForWrapper < *argc && (*argv)[argcForWrapper][0] == '-')
1081         {
1082             ++argcForWrapper;
1083         }
1084         if (argcForWrapper > 1)
1085         {
1086             CommandLineParser(&options).parse(&argcForWrapper, *argv);
1087         }
1088         // If no action requested and there is a module specified, process it.
1089         if (argcForWrapper < *argc && !bHelp && !bVersion)
1090         {
1091             const char *moduleName = (*argv)[argcForWrapper];
1092             CommandLineModuleMap::const_iterator moduleIter
1093                 = findModuleByName(moduleName);
1094             if (moduleIter == modules_.end())
1095             {
1096                 std::string message =
1097                     formatString("'%s' is not a GROMACS command.", moduleName);
1098                 GMX_THROW(InvalidInputError(message));
1099             }
1100             module = moduleIter->second.get();
1101             programInfo_.setDisplayName(
1102                     programInfo_.realBinaryName() + "-" + moduleIter->first);
1103             *argc -= argcForWrapper;
1104             *argv += argcForWrapper;
1105             // After this point, argc and argv are the same independent of
1106             // which path is taken: (*argv)[0] is the module name.
1107         }
1108     }
1109     if (module != NULL)
1110     {
1111         // Recognize the common options also after the module name.
1112         // TODO: It could be nicer to only recognize -h/-hidden if module is not
1113         // null.
1114         CommandLineParser(&options).skipUnknown(true).parse(argc, *argv);
1115     }
1116     options.finish();
1117     binaryInfoSettings_.extendedInfo(bVersion);
1118     binaryInfoSettings_.copyright(bCopyright);
1119     if (bVersion)
1120     {
1121         bQuiet_      = false;
1122         bStdOutInfo_ = true;
1123         return NULL;
1124     }
1125     // If no module specified and no other action, show the help.
1126     // Also explicitly specifying -h for the wrapper binary goes here.
1127     if (module == NULL || bHelp)
1128     {
1129         ensureHelpModuleExists();
1130         if (module != NULL)
1131         {
1132             helpModule_->setModuleOverride(*module);
1133         }
1134         *argc  = 1;
1135         module = helpModule_;
1136     }
1137     if (module == helpModule_)
1138     {
1139         helpModule_->setShowHidden(bHidden);
1140     }
1141     return module;
1142 }
1143
1144 /********************************************************************
1145  * CommandLineModuleManager
1146  */
1147
1148 CommandLineModuleManager::CommandLineModuleManager(ProgramInfo *programInfo)
1149     : impl_(new Impl(programInfo))
1150 {
1151 }
1152
1153 CommandLineModuleManager::~CommandLineModuleManager()
1154 {
1155 }
1156
1157 void CommandLineModuleManager::setQuiet(bool bQuiet)
1158 {
1159     impl_->bQuiet_ = bQuiet;
1160 }
1161
1162 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
1163 {
1164     impl_->addModule(move(module));
1165 }
1166
1167 void CommandLineModuleManager::addModuleCMain(
1168         const char *name, const char *shortDescription,
1169         CMainFunction mainFunction)
1170 {
1171     CommandLineModulePointer module(
1172             new CMainCommandLineModule(name, shortDescription, mainFunction));
1173     addModule(move(module));
1174 }
1175
1176 CommandLineModuleGroup CommandLineModuleManager::addModuleGroup(
1177         const char *title)
1178 {
1179     CommandLineModuleGroupDataPointer group(
1180             new internal::CommandLineModuleGroupData(impl_->modules_, title));
1181     impl_->moduleGroups_.push_back(move(group));
1182     return CommandLineModuleGroup(impl_->moduleGroups_.back().get());
1183 }
1184
1185 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
1186 {
1187     impl_->ensureHelpModuleExists();
1188     impl_->helpModule_->addTopic(move(topic));
1189 }
1190
1191 int CommandLineModuleManager::run(int argc, char *argv[])
1192 {
1193     CommandLineModuleInterface *module;
1194     const bool                  bMaster = (!gmx_mpi_initialized() || gmx_node_rank() == 0);
1195     try
1196     {
1197         module = impl_->processCommonOptions(&argc, &argv);
1198     }
1199     catch (const std::exception &)
1200     {
1201         if (bMaster && !impl_->bQuiet_)
1202         {
1203             printBinaryInformation(stderr, impl_->programInfo_,
1204                                    impl_->binaryInfoSettings_);
1205         }
1206         throw;
1207     }
1208     if (!bMaster)
1209     {
1210         impl_->bQuiet_ = true;
1211     }
1212     if (!impl_->bQuiet_)
1213     {
1214         FILE *out = (impl_->bStdOutInfo_ ? stdout : stderr);
1215         printBinaryInformation(out, impl_->programInfo_,
1216                                impl_->binaryInfoSettings_);
1217         fprintf(out, "\n");
1218     }
1219     if (module == NULL)
1220     {
1221         return 0;
1222     }
1223     int rc = module->run(argc, argv);
1224     if (!impl_->bQuiet_)
1225     {
1226         gmx_thanx(stderr);
1227     }
1228     return rc;
1229 }
1230
1231 // static
1232 int CommandLineModuleManager::runAsMainSingleModule(
1233         int argc, char *argv[], CommandLineModuleInterface *module)
1234 {
1235     ProgramInfo &programInfo = gmx::init(&argc, &argv);
1236     try
1237     {
1238         CommandLineModuleManager manager(&programInfo);
1239         manager.impl_->singleModule_ = module;
1240         int rc = manager.run(argc, argv);
1241         gmx::finalize();
1242         return rc;
1243     }
1244     catch (const std::exception &ex)
1245     {
1246         printFatalErrorMessage(stderr, ex);
1247         return processExceptionAtExit(ex);
1248     }
1249 }
1250
1251 // static
1252 int CommandLineModuleManager::runAsMainCMain(
1253         int argc, char *argv[], CMainFunction mainFunction)
1254 {
1255     CMainCommandLineModule module(argv[0], NULL, mainFunction);
1256     return runAsMainSingleModule(argc, argv, &module);
1257 }
1258
1259 /********************************************************************
1260  * CommandLineModuleGroup
1261  */
1262
1263 void CommandLineModuleGroup::addModule(const char *name)
1264 {
1265     impl_->addModule(name, NULL);
1266 }
1267
1268 void CommandLineModuleGroup::addModuleWithDescription(const char *name,
1269                                                       const char *description)
1270 {
1271     impl_->addModule(name, description);
1272 }
1273
1274 } // namespace gmx