Convenience methods for implementing main().
authorTeemu Murtola <teemu.murtola@gmail.com>
Mon, 15 Jul 2013 04:23:06 +0000 (07:23 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Mon, 22 Jul 2013 09:56:09 +0000 (11:56 +0200)
Added a method to TrajectoryAnalysisCommandLineRunner that allows a
one-line main() method in user tools, removing some boilerplate code
that could break with Gromacs changes.  Forward the actual
implementation to CommandLineModuleManager, allowing it to do the same
processing as it does for the wrapper binary/symlinks to it.  This
allows for some simplification of TrajectoryAnalysisCommandLineRunner.

An extra method that runs any
  int (*method)(int argc, char *argv[])
method that calls parse_common_args() as main() through
CommandLineModuleManager could also be added, but left out of this
change for now.

Change-Id: I0b0d626fda50abe04c16e90a1c876262dcff81a4

share/template/template.cpp
share/template/template_doc.cpp
src/gromacs/commandline/cmdlinemodulemanager.cpp
src/gromacs/commandline/cmdlinemodulemanager.h
src/gromacs/trajectoryanalysis/cmdlinerunner.cpp
src/gromacs/trajectoryanalysis/cmdlinerunner.h
src/gromacs/trajectoryanalysis/modules.cpp
src/gromacs/trajectoryanalysis/tests/moduletest.cpp
src/gromacs/utility/programinfo.cpp
src/gromacs/utility/programinfo.h

index 2fee9cff1edcdc2bdffd67e943430065463b5de8..7d0a833ab1f81b80fb8045a8fca8d0c703a388b4 100644 (file)
@@ -195,16 +195,5 @@ AnalysisTemplate::writeOutput()
 int
 main(int argc, char *argv[])
 {
-    ProgramInfo::init(argc, argv);
-    try
-    {
-        AnalysisTemplate                    module;
-        TrajectoryAnalysisCommandLineRunner runner(&module);
-        return runner.run(argc, argv);
-    }
-    catch (const std::exception &ex)
-    {
-        gmx::printFatalErrorMessage(stderr, ex);
-        return 1;
-    }
+    return gmx::TrajectoryAnalysisCommandLineRunner::runAsMain<AnalysisTemplate>(argc, argv);
 }
index 51ab79c0aa7bca6a8385d471ef142be60b6627d2..68116ee32f6d38b198893bfa58d8fdcd46b292fe 100644 (file)
  * To implement a command-line tool, it should create a module and run it using
  * gmx::TrajectoryAnalysisCommandLineRunner using the boilerplate code below:
  * \skip  int
- * \until return 1;
- * \until }
  * \until }
  *
  *
index 2f5969e312150f981866f877c40a0839ddfb56c8..be6dcc239845d5dd16a187252a941b8b89c44cd8 100644 (file)
@@ -383,29 +383,31 @@ class CommandLineModuleManager::Impl
          *
          * Owns the contained modules.
          */
-        CommandLineModuleMap    modules_;
+        CommandLineModuleMap         modules_;
         //! Information about the currently running program.
-        ProgramInfo            &programInfo_;
+        ProgramInfo                 &programInfo_;
         /*! \brief
          * Module that implements help for the binary.
          *
          * The pointed module is owned by the \a modules_ container.
          */
-        CommandLineHelpModule  *helpModule_;
+        CommandLineHelpModule       *helpModule_;
         //! Settings for what to write in the startup header.
-        BinaryInformationSettings binaryInfoSettings_;
+        BinaryInformationSettings    binaryInfoSettings_;
+        //! If non-NULL, run this module in single-module mode.
+        CommandLineModuleInterface  *singleModule_;
         //! Whether all stderr output should be suppressed.
-        bool                    bQuiet_;
+        bool                         bQuiet_;
         //! Whether to write the startup information to stdout iso stderr.
-        bool                    bStdOutInfo_;
+        bool                         bStdOutInfo_;
 
     private:
         GMX_DISALLOW_COPY_AND_ASSIGN(Impl);
 };
 
 CommandLineModuleManager::Impl::Impl(ProgramInfo *programInfo)
-    : programInfo_(*programInfo), helpModule_(NULL), bQuiet_(false),
-      bStdOutInfo_(false)
+    : programInfo_(*programInfo), helpModule_(NULL), singleModule_(NULL),
+      bQuiet_(false), bStdOutInfo_(false)
 {
 }
 
@@ -435,6 +437,11 @@ CommandLineModuleManager::Impl::findModuleFromBinaryName(
 CommandLineModuleInterface *
 CommandLineModuleManager::Impl::processCommonOptions(int *argc, char ***argv)
 {
+    if (singleModule_ != NULL)
+    {
+        // TODO: Process common options also in this case.
+        return singleModule_;
+    }
     // Check if the module is called through a symlink.
     CommandLineModuleMap::const_iterator module
         = findModuleFromBinaryName(programInfo_);
@@ -503,8 +510,6 @@ CommandLineModuleManager::Impl::processCommonOptions(int *argc, char ***argv)
 CommandLineModuleManager::CommandLineModuleManager(ProgramInfo *programInfo)
     : impl_(new Impl(programInfo))
 {
-    impl_->helpModule_ = new CommandLineHelpModule(impl_->modules_);
-    addModule(CommandLineModulePointer(impl_->helpModule_));
 }
 
 CommandLineModuleManager::~CommandLineModuleManager()
@@ -528,6 +533,11 @@ void CommandLineModuleManager::addModule(CommandLineModulePointer module)
 
 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
 {
+    if (impl_->helpModule_ == NULL)
+    {
+        impl_->helpModule_ = new CommandLineHelpModule(impl_->modules_);
+        addModule(CommandLineModulePointer(impl_->helpModule_));
+    }
     impl_->helpModule_->addTopic(move(topic));
 }
 
@@ -565,4 +575,22 @@ int CommandLineModuleManager::run(int argc, char *argv[])
     return rc;
 }
 
+// static
+int CommandLineModuleManager::runAsMainSingleModule(
+        int argc, char *argv[], CommandLineModuleInterface *module)
+{
+    ProgramInfo &programInfo = ProgramInfo::init(argc, argv);
+    try
+    {
+       CommandLineModuleManager manager(&programInfo);
+       manager.impl_->singleModule_ = module;
+       return manager.run(argc, argv);
+    }
+    catch (const std::exception &ex)
+    {
+       printFatalErrorMessage(stderr, ex);
+       return 1;
+    }
+}
+
 } // namespace gmx
index 7230d878eb5c609346af5b46c608803e165df87a..70a35418b4abd090565e1e73ef6ad53bd9b66e71 100644 (file)
@@ -62,8 +62,7 @@ typedef gmx_unique_ptr<CommandLineModuleInterface>::type
  *
  * Typical usage:
  * \code
-   int
-   main(int argc, char *argv[])
+   int main(int argc, char *argv[])
    {
        const gmx::ProgramInfo &programInfo =
            gmx::ProgramInfo::init("gmx", argc, argv);
@@ -87,6 +86,39 @@ typedef gmx_unique_ptr<CommandLineModuleInterface>::type
 class CommandLineModuleManager
 {
     public:
+        /*! \brief
+         * Implements a main() method that runs a single module.
+         *
+         * \param argc   \c argc passed to main().
+         * \param argv   \c argv passed to main().
+         * \param module Module to run.
+         *
+         * This method allows for uniform behavior for binaries that only
+         * contain a single module without duplicating any of the
+         * implementation from CommandLineModuleManager (startup headers,
+         * common options etc.).
+         *
+         * The signature assumes that \p module construction does not throw
+         * (because otherwise the caller would need to duplicate all the
+         * exception handling code).  It is possible to move the construction
+         * inside the try/catch in this method using an indirection similar to
+         * TrajectoryAnalysisCommandLineRunner::runAsMain(), but until that is
+         * necessary, the current approach leads to simpler code.
+         *
+         * Usage:
+         * \code
+           int main(int argc, char *argv[])
+           {
+               CustomCommandLineModule module;
+               return gmx::CommandLineModuleManager::runAsMainSingleModule(argc, argv, &module);
+           }
+         * \endcode
+         *
+         * Does not throw.  All exceptions are caught and handled internally.
+         */
+        static int runAsMainSingleModule(int argc, char *argv[],
+                                         CommandLineModuleInterface *module);
+
         /*! \brief
          * Initializes a command-line module manager.
          *
index 2e7c1f27b6b7c19595e44e6bfdafaecbca4cf8c6..8b5c60d703f496880b1e5c8f0dc3dde91490a8f3 100644 (file)
 #include "config.h"
 #endif
 
-#include "gromacs/legacyheaders/copyrite.h"
 #include "gromacs/legacyheaders/pbc.h"
 #include "gromacs/legacyheaders/rmpbc.h"
 #include "gromacs/legacyheaders/statutil.h"
 
 #include "gromacs/analysisdata/paralleloptions.h"
 #include "gromacs/commandline/cmdlinehelpwriter.h"
+#include "gromacs/commandline/cmdlinemodule.h"
+#include "gromacs/commandline/cmdlinemodulemanager.h"
 #include "gromacs/commandline/cmdlineparser.h"
 #include "gromacs/onlinehelp/helpwritercontext.h"
 #include "gromacs/options/options.h"
@@ -75,6 +76,8 @@ namespace gmx
 class TrajectoryAnalysisCommandLineRunner::Impl
 {
     public:
+        class RunnerCommandLineModule;
+
         Impl(TrajectoryAnalysisModule *module);
         ~Impl();
 
@@ -88,13 +91,12 @@ class TrajectoryAnalysisCommandLineRunner::Impl
 
         TrajectoryAnalysisModule *module_;
         int                       debugLevel_;
-        bool                      bStandalone_;
 };
 
 
 TrajectoryAnalysisCommandLineRunner::Impl::Impl(
         TrajectoryAnalysisModule *module)
-    : module_(module), debugLevel_(0), bStandalone_(true)
+    : module_(module), debugLevel_(0)
 {
 }
 
@@ -195,13 +197,6 @@ TrajectoryAnalysisCommandLineRunner::~TrajectoryAnalysisCommandLineRunner()
 }
 
 
-void
-TrajectoryAnalysisCommandLineRunner::setStandalone(bool bStandalone)
-{
-    impl_->bStandalone_ = bStandalone;
-}
-
-
 void
 TrajectoryAnalysisCommandLineRunner::setSelectionDebugLevel(int debuglevel)
 {
@@ -214,11 +209,6 @@ TrajectoryAnalysisCommandLineRunner::run(int argc, char *argv[])
 {
     TrajectoryAnalysisModule *module = impl_->module_;
 
-    if (impl_->bStandalone_)
-    {
-        printBinaryInformation(stderr, ProgramInfo::getInstance());
-    }
-
     SelectionCollection  selections;
     selections.setDebugLevel(impl_->debugLevel_);
 
@@ -285,11 +275,6 @@ TrajectoryAnalysisCommandLineRunner::run(int argc, char *argv[])
     module->finishAnalysis(nframes);
     module->writeOutput();
 
-    if (impl_->bStandalone_)
-    {
-        gmx_thanx(stderr);
-    }
-
     return 0;
 }
 
@@ -325,4 +310,80 @@ TrajectoryAnalysisCommandLineRunner::writeHelp(const HelpWriterContext &context)
         .writeHelp(context);
 }
 
+
+/*! \internal \brief
+ * Command line module for a trajectory analysis module.
+ *
+ * \ingroup module_trajectoryanalysis
+ */
+class TrajectoryAnalysisCommandLineRunner::Impl::RunnerCommandLineModule
+    : public CommandLineModuleInterface
+{
+    public:
+        /*! \brief
+         * Constructs a module.
+         *
+         * \param[in] name         Name for the module.
+         * \param[in] description  One-line description for the module.
+         * \param[in] factory      Factory method to create the analysis module.
+         *
+         * Does not throw.  This is important for correct implementation of
+         * runAsMain().
+         */
+        RunnerCommandLineModule(const char *name, const char *description,
+                                ModuleFactoryMethod factory)
+            : name_(name), description_(description), factory_(factory)
+        {
+        }
+
+        virtual const char *name() const { return name_; }
+        virtual const char *shortDescription() const { return description_; };
+
+        virtual int run(int argc, char *argv[]);
+        virtual void writeHelp(const HelpWriterContext &context) const;
+
+    private:
+        const char             *name_;
+        const char             *description_;
+        ModuleFactoryMethod     factory_;
+
+        GMX_DISALLOW_COPY_AND_ASSIGN(RunnerCommandLineModule);
+};
+
+int TrajectoryAnalysisCommandLineRunner::Impl::RunnerCommandLineModule::run(
+        int argc, char *argv[])
+{
+    TrajectoryAnalysisModulePointer     module(factory_());
+    TrajectoryAnalysisCommandLineRunner runner(module.get());
+    return runner.run(argc, argv);
+}
+
+void TrajectoryAnalysisCommandLineRunner::Impl::RunnerCommandLineModule::writeHelp(
+        const HelpWriterContext &context) const
+{
+    TrajectoryAnalysisModulePointer     module(factory_());
+    TrajectoryAnalysisCommandLineRunner runner(module.get());
+    runner.writeHelp(context);
+}
+
+// static
+int
+TrajectoryAnalysisCommandLineRunner::runAsMain(
+        int argc, char *argv[], ModuleFactoryMethod factory)
+{
+    Impl::RunnerCommandLineModule module(NULL, NULL, factory);
+    return CommandLineModuleManager::runAsMainSingleModule(argc, argv, &module);
+}
+
+// static
+void
+TrajectoryAnalysisCommandLineRunner::registerModule(
+        CommandLineModuleManager *manager, const char *name,
+        const char *description, ModuleFactoryMethod factory)
+{
+    CommandLineModulePointer module(
+            new Impl::RunnerCommandLineModule(name, description, factory));
+    manager->addModule(move(module));
+}
+
 } // namespace gmx
index 035b8ce4b17614c73735b5f856315b568257706e..540e3bc9328de5132437689c85a535f00d8337f4 100644 (file)
 #define GMX_TRAJECTORYANALYSIS_CMDLINERUNNER_H
 
 #include "../utility/common.h"
+#include "../utility/uniqueptr.h"
 
 namespace gmx
 {
 
+class CommandLineModuleManager;
 class HelpWriterContext;
 class TrajectoryAnalysisModule;
 
@@ -66,6 +68,48 @@ class TrajectoryAnalysisModule;
 class TrajectoryAnalysisCommandLineRunner
 {
     public:
+        /*! \brief
+         * Implements a main() method that runs a given module.
+         *
+         * \tparam ModuleType  Trajectory analysis module.
+         * \param  argc        \c argc passed to main().
+         * \param  argv        \c argv passed to main().
+         *
+         * This method abstracts away all the logic required to implement a
+         * main() method in user tools, allowing that to be changed without
+         * requiring changes to the tools themselves.
+         *
+         * \p ModuleType should be default-constructible and derive from
+         * TrajectoryAnalysisModule.
+         *
+         * Does not throw.  All exceptions are caught and handled internally.
+         */
+        template <class ModuleType>
+        static int runAsMain(int argc, char *argv[])
+        {
+            return runAsMain(argc, argv, &createModule<ModuleType>);
+        }
+        /*! \brief
+         * Registers a command-line module that runs a given module.
+         *
+         * \tparam ModuleType  Trajectory analysis module.
+         * \param  manager     Manager to register the module to.
+         * \param  name        Name of the module to register.
+         * \param  description One-line description for the module to register.
+         *
+         * \p ModuleType should be default-constructible and derive from
+         * TrajectoryAnalysisModule.
+         *
+         * \p name and \p descriptions must be string constants or otherwise
+         * stay valid for the duration of the program execution.
+         */
+        template <class ModuleType>
+        static void registerModule(CommandLineModuleManager *manager,
+                                   const char *name, const char *description)
+        {
+            registerModule(manager, name, description, &createModule<ModuleType>);
+        }
+
         /*! \brief
          * Create a new runner with the provided module.
          *
@@ -78,19 +122,6 @@ class TrajectoryAnalysisCommandLineRunner
         TrajectoryAnalysisCommandLineRunner(TrajectoryAnalysisModule *module);
         ~TrajectoryAnalysisCommandLineRunner();
 
-        /*! \brief
-         * Sets whether the runner will print standalone messages.
-         *
-         * \param[in] bStandalone  Whether the runner is run directly from main().
-         *
-         * By default, the runner takes responsibility of printing some
-         * standard Gromacs messages at startup and at the end.
-         * This is used internally when executing the runner in a context where
-         * these messages are handled on a higher level.
-         *
-         * Does not throw.
-         */
-        void setStandalone(bool bStandalone);
         /*! \brief
          * Sets the default debugging level for selections.
          *
@@ -118,6 +149,38 @@ class TrajectoryAnalysisCommandLineRunner
         void writeHelp(const HelpWriterContext &context);
 
     private:
+        //! Smart pointer type for managing a trajectory analysis module.
+        typedef gmx_unique_ptr<TrajectoryAnalysisModule>::type
+            TrajectoryAnalysisModulePointer;
+
+        /*! \brief
+         * Factory method type for creating a trajectory analysis module.
+         *
+         * This method allows the module creation to be postponed to be inside
+         * the try/catch block in runAsMain()/registerModule() implementation
+         * methods and still keep the implementation out of the header, making
+         * the ABI more stable.
+         */
+        typedef TrajectoryAnalysisModulePointer (*ModuleFactoryMethod)();
+        /*! \brief
+         * Creates a trajectory analysis module of a given type.
+         *
+         * \tparam ModuleType  Module to create.
+         */
+        template <class ModuleType>
+        static TrajectoryAnalysisModulePointer createModule()
+        {
+            return TrajectoryAnalysisModulePointer(new ModuleType());
+        }
+
+        //! Implements the template runAsMain() method.
+        static int runAsMain(int argc, char *argv[],
+                             ModuleFactoryMethod factory);
+        //! Implements the template registerModule() method.
+        static void registerModule(CommandLineModuleManager *manager,
+                                   const char *name, const char *description,
+                                   ModuleFactoryMethod factory);
+
         class Impl;
 
         PrivateImplPointer<Impl> impl_;
index 902094f955b78571e12c6437553aef83da1a9c0a..1d7c6ef4a5cf1f3df7e0be9f5c9a45ffd8aabff3 100644 (file)
@@ -41,8 +41,6 @@
  */
 #include "gromacs/trajectoryanalysis/modules.h"
 
-#include "gromacs/commandline/cmdlinemodule.h"
-#include "gromacs/commandline/cmdlinemodulemanager.h"
 #include "gromacs/trajectoryanalysis/cmdlinerunner.h"
 
 #include "modules/angle.h"
@@ -56,81 +54,34 @@ namespace gmx
 namespace
 {
 
-/*! \internal \brief
- * Base class for trajectory analysis module command-line wrappers.
+/*! \brief
+ * Convenience method for registering a command-line module for trajectory
+ * analysis.
  *
- * This class exists to avoid multiple identical copies of the \p run() method
- * to be generated for the TrajAnalysisCmdLineWrapper template classes.
+ * \tparam ModuleType  Trajectory analysis module to wrap.
  *
- * \ingroup module_trajectoryanalysis
- */
-class AbstractTrajAnalysisCmdLineWrapper : public CommandLineModuleInterface
-{
-    public:
-        virtual const char *name() const             = 0;
-        virtual const char *shortDescription() const = 0;
-
-        virtual int run(int argc, char *argv[]);
-        virtual void writeHelp(const HelpWriterContext &context) const;
-
-    protected:
-        //! Creates the analysis module for this wrapper.
-        virtual TrajectoryAnalysisModulePointer createModule() const = 0;
-};
-
-int AbstractTrajAnalysisCmdLineWrapper::run(int argc, char *argv[])
-{
-    TrajectoryAnalysisModulePointer     module(createModule());
-    TrajectoryAnalysisCommandLineRunner runner(module.get());
-    runner.setStandalone(false);
-    return runner.run(argc, argv);
-}
-
-void AbstractTrajAnalysisCmdLineWrapper::writeHelp(const HelpWriterContext &context) const
-{
-    TrajectoryAnalysisModulePointer     module(createModule());
-    TrajectoryAnalysisCommandLineRunner runner(module.get());
-    runner.writeHelp(context);
-}
-
-/*! \internal \brief
- * Template for command-line wrapper of a trajectory analysis module.
- *
- * \tparam Module  Trajectory analysis module to wrap.
- *
- * \p Module should be default-constructible, derive from
- * TrajectoryAnalysisModule, and have a static public members
+ * \p ModuleType should be default-constructible, derive from
+ * TrajectoryAnalysisModule, and have static public members
  * \c "const char name[]" and \c "const char shortDescription[]".
  *
  * \ingroup module_trajectoryanalysis
  */
-template <class Module>
-class TrajAnalysisCmdLineWrapper : public AbstractTrajAnalysisCmdLineWrapper
+template <class ModuleType>
+void registerModule(CommandLineModuleManager *manager)
 {
-    public:
-        virtual const char *name() const
-        {
-            return Module::name;
-        }
-        virtual const char *shortDescription() const
-        {
-            return Module::shortDescription;
-        }
-        virtual TrajectoryAnalysisModulePointer createModule() const
-        {
-            return TrajectoryAnalysisModulePointer(new Module);
-        }
-};
+    TrajectoryAnalysisCommandLineRunner::registerModule<ModuleType>(
+            manager, ModuleType::name, ModuleType::shortDescription);
+}
 
 }   // namespace
 
 void registerTrajectoryAnalysisModules(CommandLineModuleManager *manager)
 {
     using namespace gmx::analysismodules;
-    manager->registerModule<TrajAnalysisCmdLineWrapper<Angle> >();
-    manager->registerModule<TrajAnalysisCmdLineWrapper<Distance> >();
-    manager->registerModule<TrajAnalysisCmdLineWrapper<FreeVolume> >();
-    manager->registerModule<TrajAnalysisCmdLineWrapper<Select> >();
+    registerModule<Angle>(manager);
+    registerModule<Distance>(manager);
+    registerModule<FreeVolume>(manager);
+    registerModule<Select>(manager);
 }
 
 } // namespace gmx
index a86ed8c55f7e1fa3dfe542da1b2a75a252e4f590..de3dd547e92c346049d3aad992d1314580864c12 100644 (file)
@@ -214,7 +214,6 @@ AbstractTrajectoryAnalysisModuleTestFixture::runTest(const CommandLine &args)
     }
 
     TrajectoryAnalysisCommandLineRunner runner(&module);
-    runner.setStandalone(false);
     int rc = 0;
     EXPECT_NO_THROW_GMX(rc = runner.run(impl_->cmdline_.argc(), impl_->cmdline_.argv()));
     EXPECT_EQ(0, rc);
index afa5089e0becf05480601739a84cde0a26931bd7..927ce87a3e293bb3b5bdec4e012fae83b3ab20c9 100644 (file)
@@ -165,7 +165,7 @@ const ProgramInfo &ProgramInfo::getInstance()
 }
 
 // static
-const ProgramInfo &ProgramInfo::init(int argc, const char *const argv[])
+ProgramInfo &ProgramInfo::init(int argc, const char *const argv[])
 {
     return init(NULL, argc, argv);
 }
index 101cd2c26cf8747a59b45684878f28afd886e9f7..66899579d1fe635bdc3cd20c5f249b231b9963bf 100644 (file)
@@ -96,7 +96,7 @@ class ProgramInfo
          *
          * Does not throw. Terminates the program on out-of-memory error.
          */
-        static const ProgramInfo &init(int argc, const char *const argv[]);
+        static ProgramInfo &init(int argc, const char *const argv[]);
         /*! \brief
          * Initializes global program information with explicit binary name.
          *