Use local ProgramInfo in CommandLineModuleManager
[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,2014, 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::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 <string>
47 #include <utility>
48
49 #include "gromacs/legacyheaders/copyrite.h"
50 #include "gromacs/legacyheaders/network.h"
51
52 #include "gromacs/commandline/cmdlinehelpcontext.h"
53 #include "gromacs/commandline/cmdlinehelpmodule.h"
54 #include "gromacs/commandline/cmdlinemodule.h"
55 #include "gromacs/commandline/cmdlinemodulemanager-impl.h"
56 #include "gromacs/commandline/cmdlineparser.h"
57 #include "gromacs/options/basicoptions.h"
58 #include "gromacs/options/options.h"
59 #include "gromacs/utility/exceptions.h"
60 #include "gromacs/utility/gmxassert.h"
61 #include "gromacs/utility/init.h"
62 #include "gromacs/utility/programinfo.h"
63 #include "gromacs/utility/stringutil.h"
64
65 namespace gmx
66 {
67
68 namespace
69 {
70
71 /********************************************************************
72  * CMainCommandLineModule
73  */
74
75 /*! \internal \brief
76  * Implements a CommandLineModuleInterface, given a function with C/C++ main()
77  * signature.
78  *
79  * \ingroup module_commandline
80  */
81 class CMainCommandLineModule : public CommandLineModuleInterface
82 {
83     public:
84         //! \copydoc gmx::CommandLineModuleManager::CMainFunction
85         typedef CommandLineModuleManager::CMainFunction CMainFunction;
86
87         /*! \brief
88          * Creates a wrapper module for the given main function.
89          *
90          * \param[in] name             Name for the module.
91          * \param[in] shortDescription One-line description for the module.
92          * \param[in] mainFunction     Main function to wrap.
93          *
94          * Does not throw.  This is essential for correct implementation of
95          * CommandLineModuleManager::runAsMainCMain().
96          */
97         CMainCommandLineModule(const char *name, const char *shortDescription,
98                                CMainFunction mainFunction)
99             : name_(name), shortDescription_(shortDescription),
100               mainFunction_(mainFunction)
101         {
102         }
103
104         virtual const char *name() const
105         {
106             return name_;
107         }
108         virtual const char *shortDescription() const
109         {
110             return shortDescription_;
111         }
112
113         virtual int run(int argc, char *argv[])
114         {
115             return mainFunction_(argc, argv);
116         }
117         virtual void writeHelp(const CommandLineHelpContext &context) const
118         {
119             char *argv[2];
120             int   argc = 1;
121             // TODO: The constness should not be cast away.
122             argv[0] = const_cast<char *>(name_);
123             argv[1] = NULL;
124             GlobalCommandLineHelpContext global(context);
125             mainFunction_(argc, argv);
126         }
127
128     private:
129         const char             *name_;
130         const char             *shortDescription_;
131         CMainFunction           mainFunction_;
132
133 };
134
135 }   // namespace
136
137 /********************************************************************
138  * CommandLineModuleManager::Impl
139  */
140
141 /*! \internal \brief
142  * Private implementation class for CommandLineModuleManager.
143  *
144  * \ingroup module_commandline
145  */
146 class CommandLineModuleManager::Impl
147 {
148     public:
149         /*! \brief
150          * Initializes the implementation class.
151          *
152          * \param     programInfo  Program information for the running binary.
153          */
154         explicit Impl(ProgramInfo *programInfo);
155
156         /*! \brief
157          * Helper method that adds a given module to the module manager.
158          *
159          * \throws    std::bad_alloc if out of memory.
160          */
161         void addModule(CommandLineModulePointer module);
162         /*! \brief
163          * Creates the help module if it does not yet exist.
164          *
165          * \throws    std::bad_alloc if out of memory.
166          *
167          * This method should be called before accessing \a helpModule_.
168          */
169         void ensureHelpModuleExists();
170
171         /*! \brief
172          * Finds a module that matches a name.
173          *
174          * \param[in] name  Module name to find.
175          * \returns   Iterator to the found module, or
176          *      \c modules_.end() if not found.
177          *
178          * Does not throw.
179          */
180         CommandLineModuleMap::const_iterator
181         findModuleByName(const std::string &name) const;
182         /*! \brief
183          * Finds a module that the name of the binary.
184          *
185          * \param[in] programInfo  Program information object to use.
186          * \throws    std::bad_alloc if out of memory.
187          * \returns   Iterator to the found module, or
188          *      \c modules_.end() if not found.
189          *
190          * Checks whether the program is invoked through a symlink whose name
191          * is different from ProgramInfo::realBinaryName(), and if so, checks
192          * if a module name matches the name of the symlink.
193          *
194          * Note that the \p programInfo parameter is currently not necessary
195          * (as the program info object is also contained as a member), but it
196          * clarifies the control flow.
197          */
198         CommandLineModuleMap::const_iterator
199         findModuleFromBinaryName(const ProgramInfo &programInfo) const;
200
201         /*! \brief
202          * Processes command-line options for the wrapper binary.
203          *
204          * \param[in,out] argc On input, argc passed to run().
205          *     On output, argc to be passed to the module.
206          * \param[in,out] argv On input, argv passed to run().
207          *     On output, argv to be passed to the module.
208          * \throws    InvalidInputError if there are invalid options.
209          * \returns   The module that should be run.
210          *
211          * Handles command-line options that affect the wrapper binary
212          * (potentially changing the members of \c this in response to the
213          * options).  Also finds the module that should be run and the
214          * arguments that should be passed to it.
215          */
216         CommandLineModuleInterface *
217         processCommonOptions(int *argc, char ***argv);
218
219         /*! \brief
220          * Maps module names to module objects.
221          *
222          * Owns the contained modules.
223          */
224         CommandLineModuleMap         modules_;
225         /*! \brief
226          * List of groupings for modules for help output.
227          *
228          * Owns the contained module group data objects.
229          * CommandLineModuleGroup objects point to the data objects contained
230          * here.
231          */
232         CommandLineModuleGroupList   moduleGroups_;
233         //! Information about the currently running program.
234         ProgramInfo                 &programInfo_;
235         /*! \brief
236          * Module that implements help for the binary.
237          *
238          * The pointed module is owned by the \a modules_ container.
239          */
240         CommandLineHelpModule       *helpModule_;
241         //! Settings for what to write in the startup header.
242         BinaryInformationSettings    binaryInfoSettings_;
243         //! If non-NULL, run this module in single-module mode.
244         CommandLineModuleInterface  *singleModule_;
245         //! Whether all stderr output should be suppressed.
246         bool                         bQuiet_;
247         //! Whether to write the startup information to stdout iso stderr.
248         bool                         bStdOutInfo_;
249
250     private:
251         GMX_DISALLOW_COPY_AND_ASSIGN(Impl);
252 };
253
254 CommandLineModuleManager::Impl::Impl(ProgramInfo *programInfo)
255     : programInfo_(*programInfo), helpModule_(NULL), singleModule_(NULL),
256       bQuiet_(false), bStdOutInfo_(false)
257 {
258     binaryInfoSettings_.copyright(true);
259 }
260
261 void CommandLineModuleManager::Impl::addModule(CommandLineModulePointer module)
262 {
263     GMX_ASSERT(modules_.find(module->name()) == modules_.end(),
264                "Attempted to register a duplicate module name");
265     ensureHelpModuleExists();
266     HelpTopicPointer helpTopic(helpModule_->createModuleHelpTopic(*module));
267     modules_.insert(std::make_pair(std::string(module->name()),
268                                    move(module)));
269     helpModule_->addTopic(move(helpTopic));
270 }
271
272 void CommandLineModuleManager::Impl::ensureHelpModuleExists()
273 {
274     if (helpModule_ == NULL)
275     {
276         helpModule_ = new CommandLineHelpModule(programInfo_, modules_,
277                                                 moduleGroups_);
278         addModule(CommandLineModulePointer(helpModule_));
279     }
280 }
281
282 CommandLineModuleMap::const_iterator
283 CommandLineModuleManager::Impl::findModuleByName(const std::string &name) const
284 {
285     // TODO: Accept unambiguous prefixes?
286     return modules_.find(name);
287 }
288
289 CommandLineModuleMap::const_iterator
290 CommandLineModuleManager::Impl::findModuleFromBinaryName(
291         const ProgramInfo &programInfo) const
292 {
293     std::string binaryName = programInfo.invariantProgramName();
294     if (binaryName == programInfo.realBinaryName())
295     {
296         return modules_.end();
297     }
298     if (binaryName.compare(0, 2, "g_") == 0)
299     {
300         binaryName.erase(0, 2);
301     }
302     if (binaryName.compare(0, 3, "gmx") == 0)
303     {
304         binaryName.erase(0, 3);
305     }
306     return findModuleByName(binaryName);
307 }
308
309 CommandLineModuleInterface *
310 CommandLineModuleManager::Impl::processCommonOptions(int *argc, char ***argv)
311 {
312     // Check if we are directly invoking a certain module.
313     CommandLineModuleInterface *module = singleModule_;
314     if (module == NULL)
315     {
316         // Also check for invokation through named symlinks.
317         CommandLineModuleMap::const_iterator moduleIter
318             = findModuleFromBinaryName(programInfo_);
319         if (moduleIter != modules_.end())
320         {
321             module = moduleIter->second.get();
322         }
323     }
324
325     bool bHelp      = false;
326     bool bHidden    = false;
327     bool bVersion   = false;
328     bool bCopyright = true;
329     // TODO: Print the common options into the help.
330     // TODO: It would be nice to propagate at least the -quiet option to
331     // the modules so that they can also be quiet in response to this.
332     Options options(NULL, NULL);
333     options.addOption(BooleanOption("h").store(&bHelp));
334     options.addOption(BooleanOption("hidden").store(&bHidden));
335     options.addOption(BooleanOption("quiet").store(&bQuiet_));
336     options.addOption(BooleanOption("version").store(&bVersion));
337     options.addOption(BooleanOption("copyright").store(&bCopyright));
338
339     if (module == NULL)
340     {
341         // If not in single-module mode, process options to the wrapper binary.
342         // TODO: Ideally, this could be done by CommandLineParser.
343         int argcForWrapper = 1;
344         while (argcForWrapper < *argc && (*argv)[argcForWrapper][0] == '-')
345         {
346             ++argcForWrapper;
347         }
348         if (argcForWrapper > 1)
349         {
350             CommandLineParser(&options).parse(&argcForWrapper, *argv);
351         }
352         // If no action requested and there is a module specified, process it.
353         if (argcForWrapper < *argc && !bHelp && !bVersion)
354         {
355             const char *moduleName = (*argv)[argcForWrapper];
356             CommandLineModuleMap::const_iterator moduleIter
357                 = findModuleByName(moduleName);
358             if (moduleIter == modules_.end())
359             {
360                 std::string message =
361                     formatString("'%s' is not a GROMACS command.", moduleName);
362                 GMX_THROW(InvalidInputError(message));
363             }
364             module = moduleIter->second.get();
365             *argc -= argcForWrapper;
366             *argv += argcForWrapper;
367             // After this point, argc and argv are the same independent of
368             // which path is taken: (*argv)[0] is the module name.
369         }
370     }
371     if (module != NULL)
372     {
373         if (singleModule_ == NULL)
374         {
375             programInfo_.setDisplayName(
376                     programInfo_.realBinaryName() + " " + module->name());
377         }
378         // Recognize the common options also after the module name.
379         // TODO: It could be nicer to only recognize -h/-hidden if module is not
380         // null.
381         CommandLineParser(&options).skipUnknown(true).parse(argc, *argv);
382     }
383     options.finish();
384     binaryInfoSettings_.extendedInfo(bVersion);
385     binaryInfoSettings_.copyright(bCopyright);
386     if (bVersion)
387     {
388         bQuiet_      = false;
389         bStdOutInfo_ = true;
390         return NULL;
391     }
392     // If no module specified and no other action, show the help.
393     // Also explicitly specifying -h for the wrapper binary goes here.
394     if (module == NULL || bHelp)
395     {
396         ensureHelpModuleExists();
397         if (module != NULL)
398         {
399             helpModule_->setModuleOverride(*module);
400         }
401         *argc  = 1;
402         module = helpModule_;
403     }
404     if (module == helpModule_)
405     {
406         helpModule_->setShowHidden(bHidden);
407     }
408     return module;
409 }
410
411 /********************************************************************
412  * CommandLineModuleManager
413  */
414
415 CommandLineModuleManager::CommandLineModuleManager(ProgramInfo *programInfo)
416     : impl_(new Impl(programInfo))
417 {
418 }
419
420 CommandLineModuleManager::~CommandLineModuleManager()
421 {
422 }
423
424 void CommandLineModuleManager::setQuiet(bool bQuiet)
425 {
426     impl_->bQuiet_ = bQuiet;
427 }
428
429 void CommandLineModuleManager::setSingleModule(CommandLineModuleInterface *module)
430 {
431     impl_->singleModule_ = module;
432 }
433
434 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
435 {
436     impl_->addModule(move(module));
437 }
438
439 void CommandLineModuleManager::addModuleCMain(
440         const char *name, const char *shortDescription,
441         CMainFunction mainFunction)
442 {
443     CommandLineModulePointer module(
444             new CMainCommandLineModule(name, shortDescription, mainFunction));
445     addModule(move(module));
446 }
447
448 CommandLineModuleGroup CommandLineModuleManager::addModuleGroup(
449         const char *title)
450 {
451     const char *const                 binaryName =
452         impl_->programInfo_.invariantProgramName().c_str();
453     CommandLineModuleGroupDataPointer group(
454             new CommandLineModuleGroupData(impl_->modules_, binaryName, title));
455     impl_->moduleGroups_.push_back(move(group));
456     return CommandLineModuleGroup(impl_->moduleGroups_.back().get());
457 }
458
459 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
460 {
461     impl_->ensureHelpModuleExists();
462     impl_->helpModule_->addTopic(move(topic));
463 }
464
465 int CommandLineModuleManager::run(int argc, char *argv[])
466 {
467     CommandLineModuleInterface *module;
468     const bool                  bMaster = (!gmx_mpi_initialized() || gmx_node_rank() == 0);
469     try
470     {
471         module = impl_->processCommonOptions(&argc, &argv);
472     }
473     catch (const std::exception &)
474     {
475         if (bMaster && !impl_->bQuiet_)
476         {
477             printBinaryInformation(stderr, impl_->programInfo_,
478                                    impl_->binaryInfoSettings_);
479         }
480         throw;
481     }
482     if (!bMaster)
483     {
484         impl_->bQuiet_ = true;
485     }
486     if (!impl_->bQuiet_)
487     {
488         FILE *out = (impl_->bStdOutInfo_ ? stdout : stderr);
489         printBinaryInformation(out, impl_->programInfo_,
490                                impl_->binaryInfoSettings_);
491         fprintf(out, "\n");
492     }
493     if (module == NULL)
494     {
495         return 0;
496     }
497     int rc = module->run(argc, argv);
498     if (!impl_->bQuiet_)
499     {
500         gmx_thanx(stderr);
501     }
502     return rc;
503 }
504
505 // static
506 int CommandLineModuleManager::runAsMainSingleModule(
507         int argc, char *argv[], CommandLineModuleInterface *module)
508 {
509     ProgramInfo &programInfo = gmx::init(&argc, &argv);
510     try
511     {
512         CommandLineModuleManager manager(&programInfo);
513         manager.setSingleModule(module);
514         int rc = manager.run(argc, argv);
515         gmx::finalize();
516         return rc;
517     }
518     catch (const std::exception &ex)
519     {
520         printFatalErrorMessage(stderr, ex);
521         return processExceptionAtExit(ex);
522     }
523 }
524
525 // static
526 int CommandLineModuleManager::runAsMainCMain(
527         int argc, char *argv[], CMainFunction mainFunction)
528 {
529     CMainCommandLineModule module(argv[0], NULL, mainFunction);
530     return runAsMainSingleModule(argc, argv, &module);
531 }
532
533 /********************************************************************
534  * CommandLineModuleGroupData
535  */
536
537 void CommandLineModuleGroupData::addModule(const char *name,
538                                            const char *description)
539 {
540     CommandLineModuleMap::const_iterator moduleIter = allModules_.find(name);
541     GMX_RELEASE_ASSERT(moduleIter != allModules_.end(),
542                        "Non-existent module added to a group");
543     if (description == NULL)
544     {
545         description = moduleIter->second->shortDescription();
546         GMX_RELEASE_ASSERT(description != NULL,
547                            "Module without a description added to a group");
548     }
549     std::string       tag(formatString("%s-%s", binaryName_, name));
550     modules_.push_back(std::make_pair(tag, description));
551 }
552
553 /********************************************************************
554  * CommandLineModuleGroup
555  */
556
557 void CommandLineModuleGroup::addModule(const char *name)
558 {
559     impl_->addModule(name, NULL);
560 }
561
562 void CommandLineModuleGroup::addModuleWithDescription(const char *name,
563                                                       const char *description)
564 {
565     impl_->addModule(name, description);
566 }
567
568 } // namespace gmx