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