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