2 * This file is part of the GROMACS molecular simulation package.
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.
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.
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.
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.
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.
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.
37 * Implements gmx::CommandLineModuleManager.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_commandline
42 #include "cmdlinemodulemanager.h"
50 #include <boost/scoped_ptr.hpp>
52 #include "gromacs/legacyheaders/copyrite.h"
53 #include "gromacs/legacyheaders/network.h"
55 #include "gromacs/commandline/cmdlinehelpcontext.h"
56 #include "gromacs/commandline/cmdlinemodule.h"
57 #include "gromacs/commandline/cmdlineparser.h"
58 #include "gromacs/onlinehelp/helpformat.h"
59 #include "gromacs/onlinehelp/helpmanager.h"
60 #include "gromacs/onlinehelp/helptopic.h"
61 #include "gromacs/onlinehelp/helpwritercontext.h"
62 #include "gromacs/options/basicoptions.h"
63 #include "gromacs/options/options.h"
64 #include "gromacs/utility/exceptions.h"
65 #include "gromacs/utility/file.h"
66 #include "gromacs/utility/gmxassert.h"
67 #include "gromacs/utility/init.h"
68 #include "gromacs/utility/programinfo.h"
69 #include "gromacs/utility/stringutil.h"
74 //! Container type for mapping module names to module objects.
75 typedef std::map<std::string, CommandLineModulePointer> CommandLineModuleMap;
80 /********************************************************************
86 static const char name[];
87 static const char title[];
88 static const char *const text[];
91 // The first two are not used.
92 const char RootHelpText::name[] = "";
93 const char RootHelpText::title[] = "";
94 const char *const RootHelpText::text[] = {
95 "Usage: [PROGRAM] <command> [<args>]",
99 * Help topic that forms the root of the help tree for the help subcommand.
101 * \ingroup module_commandline
103 class RootHelpTopic : public CompositeHelpTopic<RootHelpText>
107 * Creates a root help topic.
109 * \param[in] modules List of modules for to use for module listings.
113 explicit RootHelpTopic(const CommandLineModuleMap &modules)
118 virtual void writeHelp(const HelpWriterContext &context) const;
121 void printModuleList(const HelpWriterContext &context) const;
123 const CommandLineModuleMap &modules_;
125 GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
128 void RootHelpTopic::writeHelp(const HelpWriterContext &context) const
130 if (context.outputFormat() != eHelpOutputFormat_Console)
132 // TODO: Implement once the situation with Redmine issue #969 is more
134 GMX_THROW(NotImplementedError(
135 "Root help is not implemented for this output format"));
137 writeBasicHelpTopic(context, *this, helpText());
138 // TODO: If/when this list becomes long, it may be better to only print
139 // "common" commands here, and have a separate topic (e.g.,
140 // "help commands") that prints the full list.
141 printModuleList(context);
142 context.writeTextBlock(
143 "For additional help on a command, use '[PROGRAM] help <command>'");
144 writeSubTopicList(context,
145 "\nAdditional help is available on the following topics:");
146 context.writeTextBlock(
147 "To access the help, use '[PROGRAM] help <topic>'.");
150 void RootHelpTopic::printModuleList(const HelpWriterContext &context) const
152 if (context.outputFormat() != eHelpOutputFormat_Console)
154 // TODO: Implement once the situation with Redmine issue #969 is more
156 GMX_THROW(NotImplementedError(
157 "Module list is not implemented for this output format"));
159 int maxNameLength = 0;
160 CommandLineModuleMap::const_iterator module;
161 for (module = modules_.begin(); module != modules_.end(); ++module)
163 int nameLength = static_cast<int>(module->first.length());
164 if (module->second->shortDescription() != NULL
165 && nameLength > maxNameLength)
167 maxNameLength = nameLength;
170 File &file = context.outputFile();
171 TextTableFormatter formatter;
172 formatter.addColumn(NULL, maxNameLength + 1, false);
173 formatter.addColumn(NULL, 72 - maxNameLength, true);
174 formatter.setFirstColumnIndent(4);
176 file.writeLine("Available commands:");
177 for (module = modules_.begin(); module != modules_.end(); ++module)
179 const char *name = module->first.c_str();
180 const char *description = module->second->shortDescription();
181 if (description != NULL)
184 formatter.addColumnLine(0, name);
185 formatter.addColumnLine(1, description);
186 file.writeString(formatter.formatRow());
191 /********************************************************************
196 * Help topic wrapper for a command-line module.
198 * This class implements HelpTopicInterface such that it wraps a
199 * CommandLineModuleInterface, allowing subcommand "help <command>"
200 * to produce the help for "<command>".
202 * \ingroup module_commandline
204 class ModuleHelpTopic : public HelpTopicInterface
207 //! Constructs a help topic for a specific module.
208 explicit ModuleHelpTopic(const CommandLineModuleInterface &module)
213 virtual const char *name() const { return module_.name(); }
214 virtual const char *title() const { return NULL; }
215 virtual bool hasSubTopics() const { return false; }
216 virtual const HelpTopicInterface *findSubTopic(const char * /*name*/) const
220 virtual void writeHelp(const HelpWriterContext &context) const;
223 const CommandLineModuleInterface &module_;
225 GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
228 void ModuleHelpTopic::writeHelp(const HelpWriterContext &context) const
230 CommandLineHelpContext newContext(&context.outputFile(),
231 context.outputFormat());
232 module_.writeHelp(newContext);
235 /********************************************************************
236 * HelpExportInterface
240 * Callbacks for exporting help information for command-line modules.
242 * \ingroup module_commandline
244 class HelpExportInterface
247 virtual ~HelpExportInterface() {};
250 * Called to export the help for each module.
252 * \param[in] tag Unique tag for the module (gmx-something).
253 * \param[in] module Module for which the help should be exported.
255 virtual void exportModuleHelp(const std::string &tag,
256 const CommandLineModuleInterface &module) = 0;
261 /********************************************************************
262 * CommandLineHelpModule
266 * Command-line module for producing help.
268 * This module implements the 'help' subcommand that is automatically added by
269 * CommandLineModuleManager.
271 * \ingroup module_commandline
273 class CommandLineHelpModule : public CommandLineModuleInterface
277 * Creates a command-line help module.
279 * \param[in] modules List of modules for to use for module listings.
280 * \throws std::bad_alloc if out of memory.
282 explicit CommandLineHelpModule(const CommandLineModuleMap &modules);
285 * Adds a top-level help topic.
287 * \param[in] topic Help topic to add.
288 * \throws std::bad_alloc if out of memory.
290 void addTopic(HelpTopicPointer topic);
292 virtual const char *name() const { return "help"; }
293 virtual const char *shortDescription() const
295 return "Print help information";
298 virtual int run(int argc, char *argv[]);
299 virtual void writeHelp(const CommandLineHelpContext &context) const;
302 void exportHelp(HelpExportInterface *exporter) const;
304 boost::scoped_ptr<RootHelpTopic> rootTopic_;
305 const CommandLineModuleMap &modules_;
307 GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModule);
310 CommandLineHelpModule::CommandLineHelpModule(const CommandLineModuleMap &modules)
311 : rootTopic_(new RootHelpTopic(modules)), modules_(modules)
315 void CommandLineHelpModule::addTopic(HelpTopicPointer topic)
317 rootTopic_->addSubTopic(move(topic));
320 int CommandLineHelpModule::run(int argc, char *argv[])
322 const char *const exportFormats[] = { "man", "html", "completion" };
323 std::string exportFormat;
324 Options options(NULL, NULL);
325 options.addOption(StringOption("export").store(&exportFormat)
326 .enumValue(exportFormats));
327 CommandLineParser(&options).parse(&argc, argv);
328 if (!exportFormat.empty())
330 boost::scoped_ptr<HelpExportInterface> exporter;
332 GMX_THROW(NotImplementedError("This help format is not implemented"));
334 exportHelp(exporter.get());
338 HelpWriterContext context(&File::standardOutput(),
339 eHelpOutputFormat_Console);
340 HelpManager helpManager(*rootTopic_, context);
343 for (int i = 1; i < argc; ++i)
345 helpManager.enterTopic(argv[i]);
348 catch (const InvalidInputError &ex)
350 fprintf(stderr, "%s\n", ex.what());
353 helpManager.writeCurrentTopic();
357 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext &context) const
359 const HelpWriterContext &writerContext = context.writerContext();
361 if (writerContext.outputFormat() != eHelpOutputFormat_Console)
365 writerContext.writeTextBlock(
366 "Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
367 // TODO: More information.
370 void CommandLineHelpModule::exportHelp(HelpExportInterface *exporter) const
372 // TODO: Would be nicer to have the file names supplied by the build system
373 // and/or export a list of files from here.
374 const char *const program =
375 ProgramInfo::getInstance().invariantProgramName().c_str();
377 CommandLineModuleMap::const_iterator module;
378 for (module = modules_.begin(); module != modules_.end(); ++module)
380 if (module->second->shortDescription() != NULL)
382 const char *const moduleName = module->first.c_str();
383 std::string tag(formatString("%s-%s", program, moduleName));
384 exporter->exportModuleHelp(tag, *module->second);
392 /********************************************************************
393 * CMainCommandLineModule
397 * Implements a CommandLineModuleInterface, given a function with C/C++ main()
400 * \ingroup module_commandline
402 class CMainCommandLineModule : public CommandLineModuleInterface
405 //! \copydoc gmx::CommandLineModuleManager::CMainFunction
406 typedef CommandLineModuleManager::CMainFunction CMainFunction;
409 * Creates a wrapper module for the given main function.
411 * \param[in] name Name for the module.
412 * \param[in] shortDescription One-line description for the module.
413 * \param[in] mainFunction Main function to wrap.
415 * Does not throw. This is essential for correct implementation of
416 * CommandLineModuleManager::runAsMainCMain().
418 CMainCommandLineModule(const char *name, const char *shortDescription,
419 CMainFunction mainFunction)
420 : name_(name), shortDescription_(shortDescription),
421 mainFunction_(mainFunction)
425 virtual const char *name() const
429 virtual const char *shortDescription() const
431 return shortDescription_;
434 virtual int run(int argc, char *argv[])
436 return mainFunction_(argc, argv);
438 virtual void writeHelp(const CommandLineHelpContext &context) const
440 const HelpOutputFormat format = context.writerContext().outputFormat();
443 // TODO: The constness should not be cast away.
444 argv[0] = const_cast<char *>(name_);
447 case eHelpOutputFormat_Console:
448 argv[1] = const_cast<char *>("-h");
452 GMX_THROW(NotImplementedError(
453 "Command-line help is not implemented for this output format"));
455 GlobalCommandLineHelpContext global(context);
456 mainFunction_(argc, argv);
461 const char *shortDescription_;
462 CMainFunction mainFunction_;
468 /********************************************************************
469 * CommandLineModuleManager::Impl
473 * Private implementation class for CommandLineModuleManager.
475 * \ingroup module_commandline
477 class CommandLineModuleManager::Impl
481 * Initializes the implementation class.
483 * \param programInfo Program information for the running binary.
485 explicit Impl(ProgramInfo *programInfo);
488 * Finds a module that matches a name.
490 * \param[in] name Module name to find.
491 * \returns Iterator to the found module, or
492 * \c modules_.end() if not found.
496 CommandLineModuleMap::const_iterator
497 findModuleByName(const std::string &name) const;
499 * Finds a module that the name of the binary.
501 * \param[in] programInfo Program information object to use.
502 * \throws std::bad_alloc if out of memory.
503 * \returns Iterator to the found module, or
504 * \c modules_.end() if not found.
506 * Checks whether the program is invoked through a symlink whose name
507 * is different from ProgramInfo::realBinaryName(), and if so, checks
508 * if a module name matches the name of the symlink.
510 * Note that the \p programInfo parameter is currently not necessary
511 * (as the program info object is also contained as a member), but it
512 * clarifies the control flow.
514 CommandLineModuleMap::const_iterator
515 findModuleFromBinaryName(const ProgramInfo &programInfo) const;
518 * Processes command-line options for the wrapper binary.
520 * \param[in,out] argc On input, argc passed to run().
521 * On output, argc to be passed to the module.
522 * \param[in,out] argv On input, argv passed to run().
523 * On output, argv to be passed to the module.
524 * \throws InvalidInputError if there are invalid options.
525 * \returns The module that should be run.
527 * Handles command-line options that affect the wrapper binary
528 * (potentially changing the members of \c this in response to the
529 * options). Also finds the module that should be run and the
530 * arguments that should be passed to it.
532 CommandLineModuleInterface *
533 processCommonOptions(int *argc, char ***argv);
536 * Maps module names to module objects.
538 * Owns the contained modules.
540 CommandLineModuleMap modules_;
541 //! Information about the currently running program.
542 ProgramInfo &programInfo_;
544 * Module that implements help for the binary.
546 * The pointed module is owned by the \a modules_ container.
548 CommandLineHelpModule *helpModule_;
549 //! Settings for what to write in the startup header.
550 BinaryInformationSettings binaryInfoSettings_;
551 //! If non-NULL, run this module in single-module mode.
552 CommandLineModuleInterface *singleModule_;
553 //! Whether all stderr output should be suppressed.
555 //! Whether to write the startup information to stdout iso stderr.
559 GMX_DISALLOW_COPY_AND_ASSIGN(Impl);
562 CommandLineModuleManager::Impl::Impl(ProgramInfo *programInfo)
563 : programInfo_(*programInfo), helpModule_(NULL), singleModule_(NULL),
564 bQuiet_(false), bStdOutInfo_(false)
566 binaryInfoSettings_.copyright(true);
569 CommandLineModuleMap::const_iterator
570 CommandLineModuleManager::Impl::findModuleByName(const std::string &name) const
572 // TODO: Accept unambiguous prefixes?
573 return modules_.find(name);
576 CommandLineModuleMap::const_iterator
577 CommandLineModuleManager::Impl::findModuleFromBinaryName(
578 const ProgramInfo &programInfo) const
580 std::string binaryName = programInfo.invariantProgramName();
581 if (binaryName == programInfo.realBinaryName())
583 return modules_.end();
585 if (binaryName.compare(0, 2, "g_") == 0)
587 binaryName.erase(0, 2);
589 return findModuleByName(binaryName);
592 CommandLineModuleInterface *
593 CommandLineModuleManager::Impl::processCommonOptions(int *argc, char ***argv)
595 // Check if we are directly invoking a certain module.
596 CommandLineModuleInterface *module = singleModule_;
599 // Also check for invokation through named symlinks.
600 CommandLineModuleMap::const_iterator moduleIter
601 = findModuleFromBinaryName(programInfo_);
602 if (moduleIter != modules_.end())
604 module = moduleIter->second.get();
609 bool bVersion = false;
610 bool bCopyright = true;
611 // TODO: Print the common options into the help.
612 // TODO: It would be nice to propagate at least the -quiet option to
613 // the modules so that they can also be quiet in response to this.
614 // TODO: Consider handling -h and related options here instead of in the
615 // modules (also -hidden needs to be transfered here to make that work).
616 // That would mean that with -h, all module-specific options would get
617 // ignored. This means that the help output would not depend on the
618 // command line, but would always show the default values (making it
619 // possible to simplify it further), but also that mdrun -h could not be
620 // used for option validation in g_tune_pme.
621 Options options(NULL, NULL);
624 options.addOption(BooleanOption("h").store(&bHelp));
626 options.addOption(BooleanOption("quiet").store(&bQuiet_));
627 options.addOption(BooleanOption("version").store(&bVersion));
628 options.addOption(BooleanOption("copyright").store(&bCopyright));
632 // If not in single-module mode, process options to the wrapper binary.
633 // TODO: Ideally, this could be done by CommandLineParser.
634 int argcForWrapper = 1;
635 while (argcForWrapper < *argc && (*argv)[argcForWrapper][0] == '-')
639 if (argcForWrapper > 1)
641 CommandLineParser(&options).parse(&argcForWrapper, *argv);
643 // If no action requested and there is a module specified, process it.
644 if (argcForWrapper < *argc && !bHelp && !bVersion)
646 const char *moduleName = (*argv)[argcForWrapper];
647 CommandLineModuleMap::const_iterator moduleIter
648 = findModuleByName(moduleName);
649 if (moduleIter == modules_.end())
651 std::string message =
652 formatString("'%s' is not a GROMACS command.", moduleName);
653 GMX_THROW(InvalidInputError(message));
655 module = moduleIter->second.get();
656 programInfo_.setDisplayName(
657 programInfo_.realBinaryName() + "-" + moduleIter->first);
658 *argc -= argcForWrapper;
659 *argv += argcForWrapper;
660 // After this point, argc and argv are the same independent of
661 // which path is taken: (*argv)[0] is the module name.
666 // In single-module mode, recognize the common options also after the
668 CommandLineParser(&options).skipUnknown(true).parse(argc, *argv);
671 binaryInfoSettings_.extendedInfo(bVersion);
672 binaryInfoSettings_.copyright(bCopyright);
679 // If no module specified and no other action, show the help.
680 // Also explicitly specifying -h for the wrapper binary goes here.
689 /********************************************************************
690 * CommandLineModuleManager
693 CommandLineModuleManager::CommandLineModuleManager(ProgramInfo *programInfo)
694 : impl_(new Impl(programInfo))
698 CommandLineModuleManager::~CommandLineModuleManager()
702 void CommandLineModuleManager::setQuiet(bool bQuiet)
704 impl_->bQuiet_ = bQuiet;
707 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
709 GMX_ASSERT(impl_->modules_.find(module->name()) == impl_->modules_.end(),
710 "Attempted to register a duplicate module name");
711 HelpTopicPointer helpTopic(new ModuleHelpTopic(*module));
712 impl_->modules_.insert(std::make_pair(std::string(module->name()),
714 addHelpTopic(move(helpTopic));
717 void CommandLineModuleManager::addModuleCMain(
718 const char *name, const char *shortDescription,
719 CMainFunction mainFunction)
721 CommandLineModulePointer module(
722 new CMainCommandLineModule(name, shortDescription, mainFunction));
723 addModule(move(module));
726 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
728 if (impl_->helpModule_ == NULL)
730 impl_->helpModule_ = new CommandLineHelpModule(impl_->modules_);
731 addModule(CommandLineModulePointer(impl_->helpModule_));
733 impl_->helpModule_->addTopic(move(topic));
736 int CommandLineModuleManager::run(int argc, char *argv[])
738 CommandLineModuleInterface *module;
739 const bool bMaster = (!gmx_mpi_initialized() || gmx_node_rank() == 0);
742 module = impl_->processCommonOptions(&argc, &argv);
744 catch (const std::exception &)
746 if (bMaster && !impl_->bQuiet_)
748 printBinaryInformation(stderr, impl_->programInfo_,
749 impl_->binaryInfoSettings_);
755 impl_->bQuiet_ = true;
759 FILE *out = (impl_->bStdOutInfo_ ? stdout : stderr);
760 printBinaryInformation(out, impl_->programInfo_,
761 impl_->binaryInfoSettings_);
768 int rc = module->run(argc, argv);
777 int CommandLineModuleManager::runAsMainSingleModule(
778 int argc, char *argv[], CommandLineModuleInterface *module)
780 ProgramInfo &programInfo = gmx::init(&argc, &argv);
783 CommandLineModuleManager manager(&programInfo);
784 manager.impl_->singleModule_ = module;
785 int rc = manager.run(argc, argv);
789 catch (const std::exception &ex)
791 printFatalErrorMessage(stderr, ex);
792 return processExceptionAtExit(ex);
797 int CommandLineModuleManager::runAsMainCMain(
798 int argc, char *argv[], CMainFunction mainFunction)
800 CMainCommandLineModule module(argv[0], NULL, mainFunction);
801 return runAsMainSingleModule(argc, argv, &module);