[RFC] Really uniform startup path for all binaries.
[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 <map>
47 #include <string>
48 #include <utility>
49
50 #include "gromacs/legacyheaders/copyrite.h"
51 #include "gromacs/legacyheaders/network.h"
52
53 #include "gromacs/commandline/cmdlinemodule.h"
54 #include "gromacs/commandline/cmdlineparser.h"
55 #include "gromacs/onlinehelp/helpformat.h"
56 #include "gromacs/onlinehelp/helpmanager.h"
57 #include "gromacs/onlinehelp/helptopic.h"
58 #include "gromacs/onlinehelp/helpwritercontext.h"
59 #include "gromacs/options/basicoptions.h"
60 #include "gromacs/options/options.h"
61 #include "gromacs/utility/exceptions.h"
62 #include "gromacs/utility/file.h"
63 #include "gromacs/utility/gmxassert.h"
64 #include "gromacs/utility/init.h"
65 #include "gromacs/utility/programinfo.h"
66 #include "gromacs/utility/stringutil.h"
67
68 namespace gmx
69 {
70
71 //! Container type for mapping module names to module objects.
72 typedef std::map<std::string, CommandLineModulePointer> CommandLineModuleMap;
73
74 namespace
75 {
76
77 /********************************************************************
78  * RootHelpTopic
79  */
80
81 struct RootHelpText
82 {
83     static const char        name[];
84     static const char        title[];
85     static const char *const text[];
86 };
87
88 // The first two are not used.
89 const char        RootHelpText::name[]  = "";
90 const char        RootHelpText::title[] = "";
91 const char *const RootHelpText::text[]  = {
92     "Usage: [PROGRAM] <command> [<args>]",
93 };
94
95 /*! \internal \brief
96  * Help topic that forms the root of the help tree for the help subcommand.
97  *
98  * \ingroup module_commandline
99  */
100 class RootHelpTopic : public CompositeHelpTopic<RootHelpText>
101 {
102     public:
103         /*! \brief
104          * Creates a root help topic.
105          *
106          * \param[in] modules  List of modules for to use for module listings.
107          *
108          * Does not throw.
109          */
110         explicit RootHelpTopic(const CommandLineModuleMap &modules)
111             : modules_(modules)
112         {
113         }
114
115         virtual void writeHelp(const HelpWriterContext &context) const;
116
117     private:
118         void printModuleList(const HelpWriterContext &context) const;
119
120         const CommandLineModuleMap &modules_;
121
122         GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
123 };
124
125 void RootHelpTopic::writeHelp(const HelpWriterContext &context) const
126 {
127     if (context.outputFormat() != eHelpOutputFormat_Console)
128     {
129         // TODO: Implement once the situation with Redmine issue #969 is more
130         // clear.
131         GMX_THROW(NotImplementedError(
132                           "Root help is not implemented for this output format"));
133     }
134     writeBasicHelpTopic(context, *this, helpText());
135     // TODO: If/when this list becomes long, it may be better to only print
136     // "common" commands here, and have a separate topic (e.g.,
137     // "help commands") that prints the full list.
138     printModuleList(context);
139     context.writeTextBlock(
140             "For additional help on a command, use '[PROGRAM] help <command>'");
141     writeSubTopicList(context,
142                       "\nAdditional help is available on the following topics:");
143     context.writeTextBlock(
144             "To access the help, use '[PROGRAM] help <topic>'.");
145 }
146
147 void RootHelpTopic::printModuleList(const HelpWriterContext &context) const
148 {
149     if (context.outputFormat() != eHelpOutputFormat_Console)
150     {
151         // TODO: Implement once the situation with Redmine issue #969 is more
152         // clear.
153         GMX_THROW(NotImplementedError(
154                           "Module list is not implemented for this output format"));
155     }
156     int maxNameLength = 0;
157     CommandLineModuleMap::const_iterator module;
158     for (module = modules_.begin(); module != modules_.end(); ++module)
159     {
160         int nameLength = static_cast<int>(module->first.length());
161         if (module->second->shortDescription() != NULL
162             && nameLength > maxNameLength)
163         {
164             maxNameLength = nameLength;
165         }
166     }
167     File              &file = context.outputFile();
168     TextTableFormatter formatter;
169     formatter.addColumn(NULL, maxNameLength + 1, false);
170     formatter.addColumn(NULL, 72 - maxNameLength, true);
171     formatter.setFirstColumnIndent(4);
172     file.writeLine();
173     file.writeLine("Available commands:");
174     for (module = modules_.begin(); module != modules_.end(); ++module)
175     {
176         const char *name        = module->first.c_str();
177         const char *description = module->second->shortDescription();
178         if (description != NULL)
179         {
180             formatter.clear();
181             formatter.addColumnLine(0, name);
182             formatter.addColumnLine(1, description);
183             file.writeString(formatter.formatRow());
184         }
185     }
186 }
187
188 /********************************************************************
189  * ModuleHelpTopic
190  */
191
192 /*! \internal \brief
193  * Help topic wrapper for a command-line module.
194  *
195  * This class implements HelpTopicInterface such that it wraps a
196  * CommandLineModuleInterface, allowing subcommand "help <command>"
197  * to produce the help for "<command>".
198  *
199  * \ingroup module_commandline
200  */
201 class ModuleHelpTopic : public HelpTopicInterface
202 {
203     public:
204         //! Constructs a help topic for a specific module.
205         explicit ModuleHelpTopic(const CommandLineModuleInterface &module)
206             : module_(module)
207         {
208         }
209
210         virtual const char *name() const { return module_.name(); }
211         virtual const char *title() const { return NULL; }
212         virtual bool hasSubTopics() const { return false; }
213         virtual const HelpTopicInterface *findSubTopic(const char * /*name*/) const
214         {
215             return NULL;
216         }
217         virtual void writeHelp(const HelpWriterContext &context) const;
218
219     private:
220         const CommandLineModuleInterface &module_;
221
222         GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
223 };
224
225 void ModuleHelpTopic::writeHelp(const HelpWriterContext &context) const
226 {
227     module_.writeHelp(context);
228 }
229
230 }   // namespace
231
232 /********************************************************************
233  * CommandLineHelpModule
234  */
235
236 /*! \internal \brief
237  * Command-line module for producing help.
238  *
239  * This module implements the 'help' subcommand that is automatically added by
240  * CommandLineModuleManager.
241  *
242  * \ingroup module_commandline
243  */
244 class CommandLineHelpModule : public CommandLineModuleInterface
245 {
246     public:
247         /*! \brief
248          * Creates a command-line help module.
249          *
250          * \param[in] modules  List of modules for to use for module listings.
251          * \throws    std::bad_alloc if out of memory.
252          */
253         explicit CommandLineHelpModule(const CommandLineModuleMap &modules);
254
255         /*! \brief
256          * Adds a top-level help topic.
257          *
258          * \param[in] topic  Help topic to add.
259          * \throws    std::bad_alloc if out of memory.
260          */
261         void addTopic(HelpTopicPointer topic);
262
263         virtual const char *name() const { return "help"; }
264         virtual const char *shortDescription() const
265         {
266             return "Print help information";
267         }
268
269         virtual int run(int argc, char *argv[]);
270         virtual void writeHelp(const HelpWriterContext &context) const;
271
272     private:
273         CompositeHelpTopicPointer   rootTopic_;
274
275         GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModule);
276 };
277
278 CommandLineHelpModule::CommandLineHelpModule(const CommandLineModuleMap &modules)
279     : rootTopic_(new RootHelpTopic(modules))
280 {
281 }
282
283 void CommandLineHelpModule::addTopic(HelpTopicPointer topic)
284 {
285     rootTopic_->addSubTopic(move(topic));
286 }
287
288 int CommandLineHelpModule::run(int argc, char *argv[])
289 {
290     HelpWriterContext context(&File::standardOutput(),
291                               eHelpOutputFormat_Console);
292     HelpManager       helpManager(*rootTopic_, context);
293     try
294     {
295         for (int i = 1; i < argc; ++i)
296         {
297             helpManager.enterTopic(argv[i]);
298         }
299     }
300     catch (const InvalidInputError &ex)
301     {
302         fprintf(stderr, "%s\n", ex.what());
303         return 2;
304     }
305     helpManager.writeCurrentTopic();
306     return 0;
307 }
308
309 void CommandLineHelpModule::writeHelp(const HelpWriterContext &context) const
310 {
311     context.writeTextBlock(
312             "Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
313     // TODO: More information.
314 }
315
316 namespace
317 {
318
319 /********************************************************************
320  * CMainCommandLineModule
321  */
322
323 /*! \internal \brief
324  * Implements a CommandLineModuleInterface, given a function with C/C++ main()
325  * signature.
326  *
327  * \ingroup module_commandline
328  */
329 class CMainCommandLineModule : public CommandLineModuleInterface
330 {
331     public:
332         //! \copydoc gmx::CommandLineModuleManager::CMainFunction
333         typedef CommandLineModuleManager::CMainFunction CMainFunction;
334
335         /*! \brief
336          * Creates a wrapper module for the given main function.
337          *
338          * \param[in] name             Name for the module.
339          * \param[in] shortDescription One-line description for the module.
340          * \param[in] mainFunction     Main function to wrap.
341          *
342          * Does not throw.  This is essential for correct implementation of
343          * CommandLineModuleManager::runAsMainCMain().
344          */
345         CMainCommandLineModule(const char *name, const char *shortDescription,
346                                CMainFunction mainFunction)
347             : name_(name), shortDescription_(shortDescription),
348               mainFunction_(mainFunction)
349         {
350         }
351
352         virtual const char *name() const
353         {
354             return name_;
355         }
356         virtual const char *shortDescription() const
357         {
358             return shortDescription_;
359         }
360
361         virtual int run(int argc, char *argv[])
362         {
363             return mainFunction_(argc, argv);
364         }
365         virtual void writeHelp(const HelpWriterContext &context) const
366         {
367             if (context.outputFormat() != eHelpOutputFormat_Console)
368             {
369                 GMX_THROW(NotImplementedError(
370                                   "Command-line help is not implemented for this output format"));
371             }
372             char *argv[2];
373             // TODO: The constness should not be cast away.
374             argv[0] = const_cast<char *>(name_);
375             argv[1] = const_cast<char *>("-h");
376             mainFunction_(2, argv);
377         }
378
379     private:
380         const char             *name_;
381         const char             *shortDescription_;
382         CMainFunction           mainFunction_;
383
384 };
385
386 } // namespace
387
388 /********************************************************************
389  * CommandLineModuleManager::Impl
390  */
391
392 /*! \internal \brief
393  * Private implementation class for CommandLineModuleManager.
394  *
395  * \ingroup module_commandline
396  */
397 class CommandLineModuleManager::Impl
398 {
399     public:
400         /*! \brief
401          * Initializes the implementation class.
402          *
403          * \param     programInfo  Program information for the running binary.
404          */
405         explicit Impl(ProgramInfo *programInfo);
406
407         /*! \brief
408          * Finds a module that matches a name.
409          *
410          * \param[in] name  Module name to find.
411          * \returns   Iterator to the found module, or
412          *      \c modules_.end() if not found.
413          *
414          * Does not throw.
415          */
416         CommandLineModuleMap::const_iterator
417         findModuleByName(const std::string &name) const;
418         /*! \brief
419          * Finds a module that the name of the binary.
420          *
421          * \param[in] programInfo  Program information object to use.
422          * \throws    std::bad_alloc if out of memory.
423          * \returns   Iterator to the found module, or
424          *      \c modules_.end() if not found.
425          *
426          * Checks whether the program is invoked through a symlink whose name
427          * is different from ProgramInfo::realBinaryName(), and if so, checks
428          * if a module name matches the name of the symlink.
429          *
430          * Note that the \p programInfo parameter is currently not necessary
431          * (as the program info object is also contained as a member), but it
432          * clarifies the control flow.
433          */
434         CommandLineModuleMap::const_iterator
435         findModuleFromBinaryName(const ProgramInfo &programInfo) const;
436
437         /*! \brief
438          * Processes command-line options for the wrapper binary.
439          *
440          * \param[in,out] argc On input, argc passed to run().
441          *     On output, argc to be passed to the module.
442          * \param[in,out] argv On input, argv passed to run().
443          *     On output, argv to be passed to the module.
444          * \throws    InvalidInputError if there are invalid options.
445          * \returns   The module that should be run.
446          *
447          * Handles command-line options that affect the wrapper binary
448          * (potentially changing the members of \c this in response to the
449          * options).  Also finds the module that should be run and the
450          * arguments that should be passed to it.
451          */
452         CommandLineModuleInterface *
453         processCommonOptions(int *argc, char ***argv);
454
455         /*! \brief
456          * Maps module names to module objects.
457          *
458          * Owns the contained modules.
459          */
460         CommandLineModuleMap         modules_;
461         //! Information about the currently running program.
462         ProgramInfo                 &programInfo_;
463         /*! \brief
464          * Module that implements help for the binary.
465          *
466          * The pointed module is owned by the \a modules_ container.
467          */
468         CommandLineHelpModule       *helpModule_;
469         //! Settings for what to write in the startup header.
470         BinaryInformationSettings    binaryInfoSettings_;
471         //! If non-NULL, run this module in single-module mode.
472         CommandLineModuleInterface  *singleModule_;
473         //! Whether all stderr output should be suppressed.
474         bool                         bQuiet_;
475         //! Whether to write the startup information to stdout iso stderr.
476         bool                         bStdOutInfo_;
477
478     private:
479         GMX_DISALLOW_COPY_AND_ASSIGN(Impl);
480 };
481
482 CommandLineModuleManager::Impl::Impl(ProgramInfo *programInfo)
483     : programInfo_(*programInfo), helpModule_(NULL), singleModule_(NULL),
484       bQuiet_(false), bStdOutInfo_(false)
485 {
486 }
487
488 CommandLineModuleMap::const_iterator
489 CommandLineModuleManager::Impl::findModuleByName(const std::string &name) const
490 {
491     // TODO: Accept unambiguous prefixes?
492     return modules_.find(name);
493 }
494
495 CommandLineModuleMap::const_iterator
496 CommandLineModuleManager::Impl::findModuleFromBinaryName(
497         const ProgramInfo &programInfo) const
498 {
499     std::string binaryName = programInfo.invariantProgramName();
500     if (binaryName == programInfo.realBinaryName())
501     {
502         return modules_.end();
503     }
504     if (binaryName.compare(0, 2, "g_") == 0)
505     {
506         binaryName.erase(0, 2);
507     }
508     return findModuleByName(binaryName);
509 }
510
511 CommandLineModuleInterface *
512 CommandLineModuleManager::Impl::processCommonOptions(int *argc, char ***argv)
513 {
514     // Check if we are directly invoking a certain module.
515     CommandLineModuleInterface *module = singleModule_;
516     if (module == NULL)
517     {
518         // Also check for invokation through named symlinks.
519         CommandLineModuleMap::const_iterator moduleIter
520             = findModuleFromBinaryName(programInfo_);
521         if (moduleIter != modules_.end())
522         {
523             module = moduleIter->second.get();
524         }
525     }
526
527     bool bHelp      = false;
528     bool bVersion   = false;
529     bool bCopyright = false;
530     // TODO: Print the common options into the help.
531     // TODO: It would be nice to propagate at least the -quiet option to
532     // the modules so that they can also be quiet in response to this.
533     // TODO: Consider handling -h and related options here instead of in the
534     // modules (also -hidden needs to be transfered here to make that work).
535     // That would mean that with -h, all module-specific options would get
536     // ignored.  This means that the help output would not depend on the
537     // command line, but would always show the default values (making it
538     // possible to simplify it further), but also that mdrun -h could not be
539     // used for option validation in g_tune_pme.
540     Options options(NULL, NULL);
541     if (module == NULL)
542     {
543         options.addOption(BooleanOption("h").store(&bHelp));
544     }
545     options.addOption(BooleanOption("quiet").store(&bQuiet_));
546     options.addOption(BooleanOption("version").store(&bVersion));
547     options.addOption(BooleanOption("copyright").store(&bCopyright));
548
549     if (module == NULL)
550     {
551         // If not in single-module mode, process options to the wrapper binary.
552         // TODO: Ideally, this could be done by CommandLineParser.
553         int argcForWrapper = 1;
554         while (argcForWrapper < *argc && (*argv)[argcForWrapper][0] == '-')
555         {
556             ++argcForWrapper;
557         }
558         if (argcForWrapper > 1)
559         {
560             CommandLineParser(&options).parse(&argcForWrapper, *argv);
561         }
562         // If no action requested and there is a module specified, process it.
563         if (argcForWrapper < *argc && !bHelp && !bVersion && !bCopyright)
564         {
565             const char *moduleName = (*argv)[argcForWrapper];
566             CommandLineModuleMap::const_iterator moduleIter
567                 = findModuleByName(moduleName);
568             if (moduleIter == modules_.end())
569             {
570                 std::string message =
571                     formatString("'%s' is not a GROMACS command.", moduleName);
572                 GMX_THROW(InvalidInputError(message));
573             }
574             module = moduleIter->second.get();
575             programInfo_.setDisplayName(
576                     programInfo_.realBinaryName() + "-" + moduleIter->first);
577             *argc -= argcForWrapper;
578             *argv += argcForWrapper;
579             // After this point, argc and argv are the same independent of
580             // which path is taken: (*argv)[0] is the module name.
581         }
582     }
583     else
584     {
585         // In single-module mode, recognize the common options also after the
586         // module name.
587         CommandLineParser(&options).skipUnknown(true).parse(argc, *argv);
588     }
589     options.finish();
590     binaryInfoSettings_.extendedInfo(bVersion);
591     binaryInfoSettings_.copyright(bCopyright);
592     if (bVersion || bCopyright)
593     {
594         bQuiet_      = false;
595         bStdOutInfo_ = true;
596         return NULL;
597     }
598     // If no module specified and no other action, show the help.
599     // Also explicitly specifying -h for the wrapper binary goes here.
600     if (module == NULL)
601     {
602         *argc = 1;
603         return helpModule_;
604     }
605     return module;
606 }
607
608 /********************************************************************
609  * CommandLineModuleManager
610  */
611
612 CommandLineModuleManager::CommandLineModuleManager(ProgramInfo *programInfo)
613     : impl_(new Impl(programInfo))
614 {
615 }
616
617 CommandLineModuleManager::~CommandLineModuleManager()
618 {
619 }
620
621 void CommandLineModuleManager::setQuiet(bool bQuiet)
622 {
623     impl_->bQuiet_ = bQuiet;
624 }
625
626 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
627 {
628     GMX_ASSERT(impl_->modules_.find(module->name()) == impl_->modules_.end(),
629                "Attempted to register a duplicate module name");
630     HelpTopicPointer helpTopic(new ModuleHelpTopic(*module));
631     impl_->modules_.insert(std::make_pair(std::string(module->name()),
632                                           move(module)));
633     addHelpTopic(move(helpTopic));
634 }
635
636 void CommandLineModuleManager::addModuleCMain(
637         const char *name, const char *shortDescription,
638         CMainFunction mainFunction)
639 {
640     CommandLineModulePointer module(
641             new CMainCommandLineModule(name, shortDescription, mainFunction));
642     addModule(move(module));
643 }
644
645 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
646 {
647     if (impl_->helpModule_ == NULL)
648     {
649         impl_->helpModule_ = new CommandLineHelpModule(impl_->modules_);
650         addModule(CommandLineModulePointer(impl_->helpModule_));
651     }
652     impl_->helpModule_->addTopic(move(topic));
653 }
654
655 int CommandLineModuleManager::run(int argc, char *argv[])
656 {
657     CommandLineModuleInterface *module;
658     const bool bMaster = (!gmx_mpi_initialized() || gmx_node_rank() == 0);
659     try
660     {
661         module = impl_->processCommonOptions(&argc, &argv);
662     }
663     catch (const std::exception &)
664     {
665         if (bMaster && !impl_->bQuiet_)
666         {
667             printBinaryInformation(stderr, impl_->programInfo_);
668         }
669         throw;
670     }
671     if (!bMaster)
672     {
673         impl_->bQuiet_ = true;
674     }
675     if (!impl_->bQuiet_)
676     {
677         FILE *out = (impl_->bStdOutInfo_ ? stdout : stderr);
678         printBinaryInformation(out, impl_->programInfo_,
679                                impl_->binaryInfoSettings_);
680         fprintf(out, "\n");
681     }
682     if (module == NULL)
683     {
684         return 0;
685     }
686     int rc = module->run(argc, argv);
687     if (!impl_->bQuiet_)
688     {
689         gmx_thanx(stderr);
690     }
691     return rc;
692 }
693
694 // static
695 int CommandLineModuleManager::runAsMainSingleModule(
696         int argc, char *argv[], CommandLineModuleInterface *module)
697 {
698     ProgramInfo &programInfo = gmx::init(&argc, &argv);
699     try
700     {
701         CommandLineModuleManager manager(&programInfo);
702         manager.impl_->singleModule_ = module;
703         int rc = manager.run(argc, argv);
704         gmx::finalize();
705         return rc;
706     }
707     catch (const std::exception &ex)
708     {
709         printFatalErrorMessage(stderr, ex);
710         return processExceptionAtExit(ex);
711     }
712 }
713
714 // static
715 int CommandLineModuleManager::runAsMainCMain(
716         int argc, char *argv[], CMainFunction mainFunction)
717 {
718     CMainCommandLineModule module(argv[0], NULL, mainFunction);
719     return runAsMainSingleModule(argc, argv, &module);
720 }
721
722 } // namespace gmx