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