Merge release-4-6 into master
[alexxy/gromacs.git] / src / gromacs / commandline / cmdlinemodulemanager.cpp
1 /*
2  *
3  *                This source code is part of
4  *
5  *                 G   R   O   M   A   C   S
6  *
7  *          GROningen MAchine for Chemical Simulations
8  *
9  * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
10  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
11  * Copyright (c) 2001-2009, The GROMACS development team,
12  * check out http://www.gromacs.org for more information.
13
14  * This program is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU General Public License
16  * as published by the Free Software Foundation; either version 2
17  * of the License, or (at your option) any later version.
18  *
19  * If you want to redistribute modifications, please consider that
20  * scientific software is very special. Version control is crucial -
21  * bugs must be traceable. We will be happy to consider code for
22  * inclusion in the official distribution, but derived work must not
23  * be called official GROMACS. Details are found in the README & COPYING
24  * files - if they are missing, get the official version at www.gromacs.org.
25  *
26  * To help us fund GROMACS development, we humbly ask that you cite
27  * the papers on the package - you can find them in the top README file.
28  *
29  * For more info, check our website at http://www.gromacs.org
30  */
31 /*! \internal \file
32  * \brief
33  * Implements gmx::CommandLineModuleManager.
34  *
35  * \author Teemu Murtola <teemu.murtola@cbr.su.se>
36  * \ingroup module_commandline
37  */
38 #include "cmdlinemodulemanager.h"
39
40 #include <cstdio>
41
42 #include <map>
43 #include <string>
44 #include <utility>
45
46 #include "gromacs/commandline/cmdlinemodule.h"
47 #include "gromacs/onlinehelp/helpformat.h"
48 #include "gromacs/onlinehelp/helpmanager.h"
49 #include "gromacs/onlinehelp/helptopic.h"
50 #include "gromacs/onlinehelp/helpwritercontext.h"
51 #include "gromacs/utility/file.h"
52 #include "gromacs/utility/exceptions.h"
53 #include "gromacs/utility/gmxassert.h"
54 #include "gromacs/utility/programinfo.h"
55 #include "gromacs/utility/stringutil.h"
56
57 namespace gmx
58 {
59
60 //! Container type for mapping module names to module objects.
61 typedef std::map<std::string, CommandLineModulePointer> CommandLineModuleMap;
62
63 namespace
64 {
65
66 /********************************************************************
67  * RootHelpTopic
68  */
69
70 struct RootHelpText
71 {
72     static const char        name[];
73     static const char        title[];
74     static const char *const text[];
75 };
76
77 // The first two are not used.
78 const char        RootHelpText::name[]  = "";
79 const char        RootHelpText::title[] = "";
80 const char *const RootHelpText::text[]  = {
81     "Usage: [PROGRAM] <command> [<args>]",
82 };
83
84 /*! \internal \brief
85  * Help topic that forms the root of the help tree for the help subcommand.
86  *
87  * \ingroup module_commandline
88  */
89 class RootHelpTopic : public CompositeHelpTopic<RootHelpText>
90 {
91     public:
92         /*! \brief
93          * Creates a root help topic.
94          *
95          * \param[in] modules  List of modules for to use for module listings.
96          *
97          * Does not throw.
98          */
99         explicit RootHelpTopic(const CommandLineModuleMap &modules)
100             : modules_(modules)
101         {
102         }
103
104         virtual void writeHelp(const HelpWriterContext &context) const;
105
106     private:
107         void printModuleList(const HelpWriterContext &context) const;
108
109         const CommandLineModuleMap &modules_;
110
111         GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
112 };
113
114 void RootHelpTopic::writeHelp(const HelpWriterContext &context) const
115 {
116     if (context.outputFormat() != eHelpOutputFormat_Console)
117     {
118         // TODO: Implement once the situation with Redmine issue #969 is more
119         // clear.
120         GMX_THROW(NotImplementedError(
121                           "Root help is not implemented for this output format"));
122     }
123     writeBasicHelpTopic(context, *this, helpText());
124     // TODO: If/when this list becomes long, it may be better to only print
125     // "common" commands here, and have a separate topic (e.g.,
126     // "help commands") that prints the full list.
127     printModuleList(context);
128     context.writeTextBlock(
129             "For additional help on a command, use '[PROGRAM] help <command>'");
130     writeSubTopicList(context,
131                       "\nAdditional help is available on the following topics:");
132     context.writeTextBlock(
133             "To access the help, use '[PROGRAM] help <topic>'.");
134 }
135
136 void RootHelpTopic::printModuleList(const HelpWriterContext &context) const
137 {
138     if (context.outputFormat() != eHelpOutputFormat_Console)
139     {
140         // TODO: Implement once the situation with Redmine issue #969 is more
141         // clear.
142         GMX_THROW(NotImplementedError(
143                           "Module list is not implemented for this output format"));
144     }
145     int maxNameLength = 0;
146     CommandLineModuleMap::const_iterator module;
147     for (module = modules_.begin(); module != modules_.end(); ++module)
148     {
149         int nameLength = static_cast<int>(module->first.length());
150         if (module->second->shortDescription() != NULL
151             && nameLength > maxNameLength)
152         {
153             maxNameLength = nameLength;
154         }
155     }
156     File              &file = context.outputFile();
157     TextTableFormatter formatter;
158     formatter.addColumn(NULL, maxNameLength + 1, false);
159     formatter.addColumn(NULL, 72 - maxNameLength, true);
160     formatter.setFirstColumnIndent(4);
161     file.writeLine();
162     file.writeLine("Available commands:");
163     for (module = modules_.begin(); module != modules_.end(); ++module)
164     {
165         const char *name        = module->first.c_str();
166         const char *description = module->second->shortDescription();
167         if (description != NULL)
168         {
169             formatter.clear();
170             formatter.addColumnLine(0, name);
171             formatter.addColumnLine(1, description);
172             file.writeString(formatter.formatRow());
173         }
174     }
175 }
176
177 /********************************************************************
178  * ModuleHelpTopic
179  */
180
181 /*! \internal \brief
182  * Help topic wrapper for a command-line module.
183  *
184  * This class implements HelpTopicInterface such that it wraps a
185  * CommandLineModuleInterface, allowing subcommand "help <command>"
186  * to produce the help for "<command>".
187  *
188  * \ingroup module_commandline
189  */
190 class ModuleHelpTopic : public HelpTopicInterface
191 {
192     public:
193         //! Constructs a help topic for a specific module.
194         explicit ModuleHelpTopic(const CommandLineModuleInterface &module)
195             : module_(module)
196         {
197         }
198
199         virtual const char *name() const { return module_.name(); }
200         virtual const char *title() const { return NULL; }
201         virtual bool hasSubTopics() const { return false; }
202         virtual const HelpTopicInterface *findSubTopic(const char * /*name*/) const
203         {
204             return NULL;
205         }
206         virtual void writeHelp(const HelpWriterContext &context) const;
207
208     private:
209         const CommandLineModuleInterface &module_;
210
211         GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
212 };
213
214 void ModuleHelpTopic::writeHelp(const HelpWriterContext &context) const
215 {
216     module_.writeHelp(context);
217 }
218
219 }   // namespace
220
221 /********************************************************************
222  * CommandLineHelpModule
223  */
224
225 /*! \internal \brief
226  * Command-line module for producing help.
227  *
228  * This module implements the 'help' subcommand that is automatically added by
229  * CommandLineModuleManager.
230  *
231  * \ingroup module_commandline
232  */
233 class CommandLineHelpModule : public CommandLineModuleInterface
234 {
235     public:
236         /*! \brief
237          * Creates a command-line help module.
238          *
239          * \param[in] modules  List of modules for to use for module listings.
240          * \throws    std::bad_alloc if out of memory.
241          */
242         explicit CommandLineHelpModule(const CommandLineModuleMap &modules);
243
244         /*! \brief
245          * Adds a top-level help topic.
246          *
247          * \param[in] topic  Help topic to add.
248          * \throws    std::bad_alloc if out of memory.
249          */
250         void addTopic(HelpTopicPointer topic);
251
252         virtual const char *name() const { return "help"; }
253         virtual const char *shortDescription() const
254         {
255             return "Print help information";
256         }
257
258         virtual int run(int argc, char *argv[]);
259         virtual void writeHelp(const HelpWriterContext &context) const;
260
261         //! Prints usage message to stderr.
262         void printUsage() const;
263
264     private:
265         CompositeHelpTopicPointer   rootTopic_;
266
267         GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModule);
268 };
269
270 CommandLineHelpModule::CommandLineHelpModule(const CommandLineModuleMap &modules)
271     : rootTopic_(new RootHelpTopic(modules))
272 {
273 }
274
275 void CommandLineHelpModule::addTopic(HelpTopicPointer topic)
276 {
277     rootTopic_->addSubTopic(move(topic));
278 }
279
280 int CommandLineHelpModule::run(int argc, char *argv[])
281 {
282     HelpWriterContext context(&File::standardOutput(),
283                               eHelpOutputFormat_Console);
284     HelpManager       helpManager(*rootTopic_, context);
285     try
286     {
287         for (int i = 1; i < argc; ++i)
288         {
289             helpManager.enterTopic(argv[i]);
290         }
291     }
292     catch (const InvalidInputError &ex)
293     {
294         fprintf(stderr, "%s\n", ex.what());
295         return 2;
296     }
297     helpManager.writeCurrentTopic();
298     fprintf(stderr, "\n");
299     return 0;
300 }
301
302 void CommandLineHelpModule::writeHelp(const HelpWriterContext &context) const
303 {
304     context.writeTextBlock(
305             "Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
306     // TODO: More information.
307 }
308
309 void CommandLineHelpModule::printUsage() const
310 {
311     HelpWriterContext context(&File::standardError(),
312                               eHelpOutputFormat_Console);
313     rootTopic_->writeHelp(context);
314 }
315
316 /********************************************************************
317  * CommandLineModuleManager::Impl
318  */
319
320 /*! \internal \brief
321  * Private implementation class for CommandLineModuleManager.
322  *
323  * \ingroup module_commandline
324  */
325 class CommandLineModuleManager::Impl
326 {
327     public:
328
329         /*! \brief
330          * Initializes the implementation class.
331          *
332          * \param[in] programInfo  Program information for the running binary.
333          */
334         explicit Impl(const ProgramInfo &programInfo);
335
336         /*! \brief
337          * Finds a module that matches a name.
338          *
339          * \param[in] name  Module name to find.
340          * \returns   Iterator to the found module, or
341          *      \c modules_.end() if not found.
342          *
343          * Does not throw.
344          */
345         CommandLineModuleMap::const_iterator
346         findModuleByName(const std::string &name) const;
347         /*! \brief
348          * Finds a module that the name of the binary.
349          *
350          * \param[in] programInfo  Program information object to use.
351          * \throws    std::bad_alloc if out of memory.
352          * \returns   Iterator to the found module, or
353          *      \c modules_.end() if not found.
354          *
355          * Checks whether the program is invoked through a symlink whose name
356          * is different from ProgramInfo::realBinaryName(), and if so, checks
357          * if a module name matches the name of the symlink.
358          *
359          * Note that the \p programInfo parameter is currently not necessary
360          * (as the program info object is also contained as a member), but it
361          * clarifies the control flow.
362          */
363         CommandLineModuleMap::const_iterator
364         findModuleFromBinaryName(const ProgramInfo &programInfo) const;
365
366         /*! \brief
367          * Maps module names to module objects.
368          *
369          * Owns the contained modules.
370          */
371         CommandLineModuleMap    modules_;
372         //! Information about the currently running program.
373         const ProgramInfo      &programInfo_;
374         /*! \brief
375          * Module that implements help for the binary.
376          *
377          * The pointed module is owned by the \a modules_ container.
378          */
379         CommandLineHelpModule  *helpModule_;
380 };
381
382 CommandLineModuleManager::Impl::Impl(const ProgramInfo &programInfo)
383     : programInfo_(programInfo)
384 {
385 }
386
387 CommandLineModuleMap::const_iterator
388 CommandLineModuleManager::Impl::findModuleByName(const std::string &name) const
389 {
390     // TODO: Accept unambiguous prefixes?
391     return modules_.find(name);
392 }
393
394 CommandLineModuleMap::const_iterator
395 CommandLineModuleManager::Impl::findModuleFromBinaryName(
396         const ProgramInfo &programInfo) const
397 {
398     std::string binaryName = programInfo.invariantProgramName();
399     if (binaryName == programInfo.realBinaryName())
400     {
401         return modules_.end();
402     }
403     if (binaryName.compare(0, 2, "g_") == 0)
404     {
405         binaryName.erase(0, 2);
406     }
407     return findModuleByName(binaryName);
408 }
409
410 /********************************************************************
411  * CommandLineModuleManager
412  */
413
414 CommandLineModuleManager::CommandLineModuleManager(const ProgramInfo &programInfo)
415     : impl_(new Impl(programInfo))
416 {
417     impl_->helpModule_ = new CommandLineHelpModule(impl_->modules_);
418     addModule(CommandLineModulePointer(impl_->helpModule_));
419 }
420
421 CommandLineModuleManager::~CommandLineModuleManager()
422 {
423 }
424
425 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
426 {
427     GMX_ASSERT(impl_->modules_.find(module->name()) == impl_->modules_.end(),
428                "Attempted to register a duplicate module name");
429     HelpTopicPointer helpTopic(new ModuleHelpTopic(*module));
430     impl_->modules_.insert(std::make_pair(std::string(module->name()),
431                                           move(module)));
432     addHelpTopic(move(helpTopic));
433 }
434
435 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
436 {
437     impl_->helpModule_->addTopic(move(topic));
438 }
439
440 int CommandLineModuleManager::run(int argc, char *argv[])
441 {
442     int argOffset = 0;
443     CommandLineModuleMap::const_iterator module
444         = impl_->findModuleFromBinaryName(impl_->programInfo_);
445     if (module == impl_->modules_.end())
446     {
447         if (argc < 2)
448         {
449             impl_->helpModule_->printUsage();
450             return 2;
451         }
452         module    = impl_->findModuleByName(argv[1]);
453         argOffset = 1;
454     }
455     if (module == impl_->modules_.end())
456     {
457         fprintf(stderr, "Unknown command: '%s'\n\n", argv[1]);
458         impl_->helpModule_->printUsage();
459         return 2;
460     }
461     return module->second->run(argc - argOffset, argv + argOffset);
462 }
463
464 } // namespace gmx