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 "gromacs/legacyheaders/copyrite.h"
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"
69 //! Container type for mapping module names to module objects.
70 typedef std::map<std::string, CommandLineModulePointer> CommandLineModuleMap;
75 /********************************************************************
81 static const char name[];
82 static const char title[];
83 static const char *const text[];
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>]",
94 * Help topic that forms the root of the help tree for the help subcommand.
96 * \ingroup module_commandline
98 class RootHelpTopic : public CompositeHelpTopic<RootHelpText>
102 * Creates a root help topic.
104 * \param[in] modules List of modules for to use for module listings.
108 explicit RootHelpTopic(const CommandLineModuleMap &modules)
113 virtual void writeHelp(const HelpWriterContext &context) const;
116 void printModuleList(const HelpWriterContext &context) const;
118 const CommandLineModuleMap &modules_;
120 GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
123 void RootHelpTopic::writeHelp(const HelpWriterContext &context) const
125 if (context.outputFormat() != eHelpOutputFormat_Console)
127 // TODO: Implement once the situation with Redmine issue #969 is more
129 GMX_THROW(NotImplementedError(
130 "Root help is not implemented for this output format"));
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>'.");
145 void RootHelpTopic::printModuleList(const HelpWriterContext &context) const
147 if (context.outputFormat() != eHelpOutputFormat_Console)
149 // TODO: Implement once the situation with Redmine issue #969 is more
151 GMX_THROW(NotImplementedError(
152 "Module list is not implemented for this output format"));
154 int maxNameLength = 0;
155 CommandLineModuleMap::const_iterator module;
156 for (module = modules_.begin(); module != modules_.end(); ++module)
158 int nameLength = static_cast<int>(module->first.length());
159 if (module->second->shortDescription() != NULL
160 && nameLength > maxNameLength)
162 maxNameLength = nameLength;
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);
171 file.writeLine("Available commands:");
172 for (module = modules_.begin(); module != modules_.end(); ++module)
174 const char *name = module->first.c_str();
175 const char *description = module->second->shortDescription();
176 if (description != NULL)
179 formatter.addColumnLine(0, name);
180 formatter.addColumnLine(1, description);
181 file.writeString(formatter.formatRow());
186 /********************************************************************
191 * Help topic wrapper for a command-line module.
193 * This class implements HelpTopicInterface such that it wraps a
194 * CommandLineModuleInterface, allowing subcommand "help <command>"
195 * to produce the help for "<command>".
197 * \ingroup module_commandline
199 class ModuleHelpTopic : public HelpTopicInterface
202 //! Constructs a help topic for a specific module.
203 explicit ModuleHelpTopic(const CommandLineModuleInterface &module)
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
215 virtual void writeHelp(const HelpWriterContext &context) const;
218 const CommandLineModuleInterface &module_;
220 GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
223 void ModuleHelpTopic::writeHelp(const HelpWriterContext &context) const
225 module_.writeHelp(context);
230 /********************************************************************
231 * CommandLineHelpModule
235 * Command-line module for producing help.
237 * This module implements the 'help' subcommand that is automatically added by
238 * CommandLineModuleManager.
240 * \ingroup module_commandline
242 class CommandLineHelpModule : public CommandLineModuleInterface
246 * Creates a command-line help module.
248 * \param[in] modules List of modules for to use for module listings.
249 * \throws std::bad_alloc if out of memory.
251 explicit CommandLineHelpModule(const CommandLineModuleMap &modules);
254 * Adds a top-level help topic.
256 * \param[in] topic Help topic to add.
257 * \throws std::bad_alloc if out of memory.
259 void addTopic(HelpTopicPointer topic);
261 virtual const char *name() const { return "help"; }
262 virtual const char *shortDescription() const
264 return "Print help information";
267 virtual int run(int argc, char *argv[]);
268 virtual void writeHelp(const HelpWriterContext &context) const;
271 CompositeHelpTopicPointer rootTopic_;
273 GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModule);
276 CommandLineHelpModule::CommandLineHelpModule(const CommandLineModuleMap &modules)
277 : rootTopic_(new RootHelpTopic(modules))
281 void CommandLineHelpModule::addTopic(HelpTopicPointer topic)
283 rootTopic_->addSubTopic(move(topic));
286 int CommandLineHelpModule::run(int argc, char *argv[])
288 HelpWriterContext context(&File::standardOutput(),
289 eHelpOutputFormat_Console);
290 HelpManager helpManager(*rootTopic_, context);
293 for (int i = 1; i < argc; ++i)
295 helpManager.enterTopic(argv[i]);
298 catch (const InvalidInputError &ex)
300 fprintf(stderr, "%s\n", ex.what());
303 helpManager.writeCurrentTopic();
307 void CommandLineHelpModule::writeHelp(const HelpWriterContext &context) const
309 context.writeTextBlock(
310 "Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
311 // TODO: More information.
314 /********************************************************************
315 * CommandLineModuleManager::Impl
319 * Private implementation class for CommandLineModuleManager.
321 * \ingroup module_commandline
323 class CommandLineModuleManager::Impl
327 * Initializes the implementation class.
329 * \param programInfo Program information for the running binary.
331 explicit Impl(ProgramInfo *programInfo);
334 * Finds a module that matches a name.
336 * \param[in] name Module name to find.
337 * \returns Iterator to the found module, or
338 * \c modules_.end() if not found.
342 CommandLineModuleMap::const_iterator
343 findModuleByName(const std::string &name) const;
345 * Finds a module that the name of the binary.
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.
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.
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.
360 CommandLineModuleMap::const_iterator
361 findModuleFromBinaryName(const ProgramInfo &programInfo) const;
364 * Processes command-line options for the wrapper binary.
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.
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.
378 CommandLineModuleInterface *
379 processCommonOptions(int *argc, char ***argv);
382 * Maps module names to module objects.
384 * Owns the contained modules.
386 CommandLineModuleMap modules_;
387 //! Information about the currently running program.
388 ProgramInfo &programInfo_;
390 * Module that implements help for the binary.
392 * The pointed module is owned by the \a modules_ container.
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.
401 //! Whether to write the startup information to stdout iso stderr.
405 GMX_DISALLOW_COPY_AND_ASSIGN(Impl);
408 CommandLineModuleManager::Impl::Impl(ProgramInfo *programInfo)
409 : programInfo_(*programInfo), helpModule_(NULL), singleModule_(NULL),
410 bQuiet_(false), bStdOutInfo_(false)
414 CommandLineModuleMap::const_iterator
415 CommandLineModuleManager::Impl::findModuleByName(const std::string &name) const
417 // TODO: Accept unambiguous prefixes?
418 return modules_.find(name);
421 CommandLineModuleMap::const_iterator
422 CommandLineModuleManager::Impl::findModuleFromBinaryName(
423 const ProgramInfo &programInfo) const
425 std::string binaryName = programInfo.invariantProgramName();
426 if (binaryName == programInfo.realBinaryName())
428 return modules_.end();
430 if (binaryName.compare(0, 2, "g_") == 0)
432 binaryName.erase(0, 2);
434 return findModuleByName(binaryName);
437 CommandLineModuleInterface *
438 CommandLineModuleManager::Impl::processCommonOptions(int *argc, char ***argv)
440 if (singleModule_ != NULL)
442 // TODO: Process common options also in this case.
443 return singleModule_;
445 // Check if the module is called through a symlink.
446 CommandLineModuleMap::const_iterator module
447 = findModuleFromBinaryName(programInfo_);
448 if (module != modules_.end())
450 // TODO: Process common options also in this case.
451 return module->second.get();
453 // If not, process options to the wrapper binary.
454 // TODO: This should be done by CommandLineParser
455 // (together with the above TODO).
456 int moduleArgOffset = 1;
457 while (moduleArgOffset < *argc && (*argv)[moduleArgOffset][0] == '-')
462 bool bVersion = false;
463 bool bCopyright = false;
464 if (moduleArgOffset > 1)
466 // TODO: Print these options into the help.
467 // TODO: It would be nice to propagate at least the -quiet option to
468 // the modules so that they can also be quiet in response to this.
469 Options options(NULL, NULL);
470 options.addOption(BooleanOption("h").store(&bHelp));
471 options.addOption(BooleanOption("quiet").store(&bQuiet_));
472 options.addOption(BooleanOption("version").store(&bVersion));
473 options.addOption(BooleanOption("copyright").store(&bCopyright));
474 CommandLineParser(&options).parse(&moduleArgOffset, *argv);
476 binaryInfoSettings_.extendedInfo(bVersion);
477 binaryInfoSettings_.copyright(bCopyright);
479 if (bVersion || bCopyright)
485 // If no module or help requested, show the help.
486 if (moduleArgOffset == *argc || bHelp)
491 // Find the module to run and arguments to it.
492 const char *moduleName = (*argv)[moduleArgOffset];
493 module = findModuleByName(moduleName);
494 if (module == modules_.end())
496 std::string message = formatString("'%s' is not a GROMACS command.", moduleName);
497 GMX_THROW(InvalidInputError(message));
499 programInfo_.setDisplayName(
500 programInfo_.realBinaryName() + "-" + module->first);
501 *argc -= moduleArgOffset;
502 *argv += moduleArgOffset;
503 return module->second.get();
506 /********************************************************************
507 * CommandLineModuleManager
510 CommandLineModuleManager::CommandLineModuleManager(ProgramInfo *programInfo)
511 : impl_(new Impl(programInfo))
515 CommandLineModuleManager::~CommandLineModuleManager()
519 void CommandLineModuleManager::setQuiet(bool bQuiet)
521 impl_->bQuiet_ = bQuiet;
524 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
526 GMX_ASSERT(impl_->modules_.find(module->name()) == impl_->modules_.end(),
527 "Attempted to register a duplicate module name");
528 HelpTopicPointer helpTopic(new ModuleHelpTopic(*module));
529 impl_->modules_.insert(std::make_pair(std::string(module->name()),
531 addHelpTopic(move(helpTopic));
534 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
536 if (impl_->helpModule_ == NULL)
538 impl_->helpModule_ = new CommandLineHelpModule(impl_->modules_);
539 addModule(CommandLineModulePointer(impl_->helpModule_));
541 impl_->helpModule_->addTopic(move(topic));
544 int CommandLineModuleManager::run(int argc, char *argv[])
546 CommandLineModuleInterface *module;
549 module = impl_->processCommonOptions(&argc, &argv);
551 catch (const std::exception &)
555 printBinaryInformation(stderr, impl_->programInfo_);
561 FILE *out = (impl_->bStdOutInfo_ ? stdout : stderr);
562 printBinaryInformation(out, impl_->programInfo_,
563 impl_->binaryInfoSettings_);
570 int rc = module->run(argc, argv);
579 int CommandLineModuleManager::runAsMainSingleModule(
580 int argc, char *argv[], CommandLineModuleInterface *module)
582 ProgramInfo &programInfo = ProgramInfo::init(argc, argv);
585 CommandLineModuleManager manager(&programInfo);
586 manager.impl_->singleModule_ = module;
587 return manager.run(argc, argv);
589 catch (const std::exception &ex)
591 printFatalErrorMessage(stderr, ex);