Add back mechanism for cross-tool hyperlinks.
authorTeemu Murtola <teemu.murtola@gmail.com>
Sun, 22 Sep 2013 18:11:56 +0000 (21:11 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Tue, 19 Nov 2013 04:49:27 +0000 (05:49 +0100)
The exported HTML pages again can contain hyperlinks to other tools.
Instead of replicating the old functionality, made the links of the form
[gmx-distance] instead of recognizing plain text words.  This requires
all the output formats to be aware of them (not a big deal in the
current code layout), and makes it possible to define the appearance of
the links centrally instead of relying on all links being wrapped in,
e.g., [TT]...[tt].  The new link syntax is similar to Markdown implicit
links (at least Doxygen accepts them without the second []).

Also added support for [THISMODULE] tag in the help text, which expands
to the name of the current module, again allowing the appearance to be
defined centrally.

Only changed the help text for gmx-mindist to take advantage of the new
mechanism as a proof-of-concept.  Bulk work for other tools is better
done as a separate commit once the general approach is accepted.

Part of #685 and #1242.

Change-Id: Ibd263cc2c131dc18d2a5d9046fecc6b1c08734f9

src/gromacs/commandline/cmdlinehelpcontext.cpp
src/gromacs/commandline/cmdlinehelpcontext.h
src/gromacs/commandline/cmdlinemodulemanager.cpp
src/gromacs/commandline/cmdlinemodulemanager.h
src/gromacs/commandline/tests/cmdlinemodulemanager.cpp
src/gromacs/commandline/wman.cpp
src/gromacs/gmxana/gmx_mindist.c
src/gromacs/onlinehelp/helpwritercontext.cpp
src/gromacs/onlinehelp/helpwritercontext.h

index 025a55a2fb7e4a9743114ca277e52ea7893085e5..eeb1ee44c117c95fcfc530f56c90340cf4b7171c 100644 (file)
@@ -90,12 +90,19 @@ CommandLineHelpContext::CommandLineHelpContext(
 {
 }
 
+CommandLineHelpContext::CommandLineHelpContext(
+        const CommandLineHelpContext &other)
+    : impl_(new Impl(*other.impl_))
+{
+}
+
 CommandLineHelpContext::~CommandLineHelpContext()
 {
 }
 
 void CommandLineHelpContext::setModuleDisplayName(const std::string &name)
 {
+    impl_->writerContext_.setReplacement("[THISMODULE]", "[TT]" + name + "[tt]");
     impl_->moduleDisplayName_ = name;
 }
 
index 72f41b692c18702e8184e4bf69193c5cc6cc6c82..e153c06ae1e307c0ece51319e8b487d084f88a57 100644 (file)
@@ -57,6 +57,11 @@ namespace gmx
  * onlinehelp module is not exposed to extra features of the command-line help
  * export.
  *
+ * Copying a context works like with HelpWriterContext: the output file and
+ * most state is shared.  However, setModuleDisplayName() and setShowHidden()
+ * can be set independently for the child context.  Defaults for these options
+ * are inherited from the parent.
+ *
  * \ingroup module_commandline
  */
 class CommandLineHelpContext
@@ -69,6 +74,8 @@ class CommandLineHelpContext
          */
         CommandLineHelpContext(File *file, HelpOutputFormat format,
                                const HelpLinks *links);
+        //! Creates a copy of the context.
+        explicit CommandLineHelpContext(const CommandLineHelpContext &other);
         ~CommandLineHelpContext();
 
         /*! \brief
@@ -95,6 +102,8 @@ class CommandLineHelpContext
         class Impl;
 
         PrivateImplPointer<Impl> impl_;
+
+        GMX_DISALLOW_ASSIGN(CommandLineHelpContext);
 };
 
 /*! \libinternal \brief
index daa4e706ecb59aeb8de31291efcf9cc50164a485..10708b6b284f72aff56b94dbc30c03f0ae1beb1c 100644 (file)
@@ -350,11 +350,14 @@ class HelpExportInterface
         /*! \brief
          * Called to export the help for each module.
          *
-         * \param[in] tag     Unique tag for the module (gmx-something).
-         * \param[in] module  Module for which the help should be exported.
+         * \param[in] module      Module for which the help should be exported.
+         * \param[in] tag         Unique tag for the module (gmx-something).
+         * \param[in] displayName Display name for the module (gmx something).
          */
-        virtual void exportModuleHelp(const std::string                &tag,
-                                      const CommandLineModuleInterface &module) = 0;
+        virtual void exportModuleHelp(
+            const CommandLineModuleInterface &module,
+            const std::string                &tag,
+            const std::string                &displayName) = 0;
         /*! \brief
          * Called after all modules have been exported.
          *
@@ -386,6 +389,38 @@ class HelpExportInterface
         virtual void finishModuleGroupExport() = 0;
 };
 
+/*! \internal \brief
+ * Adds hyperlinks to modules within this binary.
+ *
+ * \param[in,out] links   Links are added here.
+ * \param[in]     modules Modules in the current binary.
+ * \throws        std::bad_alloc if out of memory.
+ *
+ * Initializes a HelpLinks object with links to modules defined in \p modules.
+ *
+ * \ingroup module_commandline
+ */
+void initProgramLinks(HelpLinks *links, const CommandLineModuleMap &modules)
+{
+    // TODO: Use the local ProgramInfo reference from CommandLineModuleManager
+    // (to do this nicely requires reordering the code in the file).
+    const char *const                    program =
+        ProgramInfo::getInstance().realBinaryName().c_str();
+    CommandLineModuleMap::const_iterator module;
+    for (module = modules.begin(); module != modules.end(); ++module)
+    {
+        if (module->second->shortDescription() != NULL)
+        {
+            std::string linkName("[gmx-" + module->first + "]");
+            std::string targetName(
+                    formatString("%s-%s", program, module->first.c_str()));
+            std::string displayName(
+                    formatString("[TT]%s %s[tt]", program, module->first.c_str()));
+            links->addLink(linkName, targetName, displayName);
+        }
+    }
+}
+
 /********************************************************************
  * HelpExportMan
  */
@@ -398,9 +433,18 @@ class HelpExportInterface
 class HelpExportMan : public HelpExportInterface
 {
     public:
+        //! Initializes man page exporter.
+        explicit HelpExportMan(const CommandLineModuleMap &modules)
+            : links_(eHelpOutputFormat_Man)
+        {
+            initProgramLinks(&links_, modules);
+        }
+
         virtual void startModuleExport() {}
-        virtual void exportModuleHelp(const std::string                &tag,
-                                      const CommandLineModuleInterface &module);
+        virtual void exportModuleHelp(
+            const CommandLineModuleInterface &module,
+            const std::string                &tag,
+            const std::string                &displayName);
         virtual void finishModuleExport() {}
 
         virtual void startModuleGroupExport() {}
@@ -409,11 +453,13 @@ class HelpExportMan : public HelpExportInterface
         virtual void finishModuleGroupExport() {}
 
     private:
-        boost::scoped_ptr<File> indexFile_;
+        HelpLinks  links_;
 };
 
-void HelpExportMan::exportModuleHelp(const std::string                &tag,
-                                     const CommandLineModuleInterface &module)
+void HelpExportMan::exportModuleHelp(
+        const CommandLineModuleInterface &module,
+        const std::string                &tag,
+        const std::string                &displayName)
 {
     File file("man1/" + tag + ".1", "w");
 
@@ -427,9 +473,7 @@ void HelpExportMan::exportModuleHelp(const std::string                &tag,
                                 module.shortDescription()));
     file.writeLine();
 
-    CommandLineHelpContext context(&file, eHelpOutputFormat_Man, NULL);
-    std::string            displayName(tag);
-    std::replace(displayName.begin(), displayName.end(), '-', ' ');
+    CommandLineHelpContext context(&file, eHelpOutputFormat_Man, &links_);
     context.setModuleDisplayName(displayName);
     module.writeHelp(context);
 
@@ -451,11 +495,14 @@ void HelpExportMan::exportModuleHelp(const std::string                &tag,
 class HelpExportHtml : public HelpExportInterface
 {
     public:
-        HelpExportHtml();
+        //! Initializes HTML exporter.
+        explicit HelpExportHtml(const CommandLineModuleMap &modules);
 
         virtual void startModuleExport();
-        virtual void exportModuleHelp(const std::string                &tag,
-                                      const CommandLineModuleInterface &module);
+        virtual void exportModuleHelp(
+            const CommandLineModuleInterface &module,
+            const std::string                &tag,
+            const std::string                &displayName);
         virtual void finishModuleExport();
 
         virtual void startModuleGroupExport();
@@ -471,8 +518,10 @@ class HelpExportHtml : public HelpExportInterface
         HelpLinks                links_;
 };
 
-HelpExportHtml::HelpExportHtml()
+HelpExportHtml::HelpExportHtml(const CommandLineModuleMap &modules)
+    : links_(eHelpOutputFormat_Html)
 {
+    initProgramLinks(&links_, modules);
     char *linksFilename = low_gmxlibfn("links.dat", FALSE, FALSE);
     if (linksFilename != NULL)
     {
@@ -481,7 +530,7 @@ HelpExportHtml::HelpExportHtml()
         std::string      line;
         while (linksFile.readLine(&line))
         {
-            links_.addLink(line, "../online/" + line);
+            links_.addLink(line, "../online/" + line, line);
         }
     }
 }
@@ -493,15 +542,15 @@ void HelpExportHtml::startModuleExport()
     indexFile_->writeLine("<H3>GROMACS Programs Alphabetically</H3>");
 }
 
-void HelpExportHtml::exportModuleHelp(const std::string                &tag,
-                                      const CommandLineModuleInterface &module)
+void HelpExportHtml::exportModuleHelp(
+        const CommandLineModuleInterface &module,
+        const std::string                &tag,
+        const std::string                &displayName)
 {
     File file(tag + ".html", "w");
-    writeHtmlHeader(&file, tag);
+    writeHtmlHeader(&file, displayName);
 
     CommandLineHelpContext context(&file, eHelpOutputFormat_Html, &links_);
-    std::string            displayName(tag);
-    std::replace(displayName.begin(), displayName.end(), '-', ' ');
     context.setModuleDisplayName(displayName);
     module.writeHelp(context);
 
@@ -607,11 +656,13 @@ class CommandLineHelpModule : public CommandLineModuleInterface
         /*! \brief
          * Creates a command-line help module.
          *
+         * \param[in] programInfo Information about the running binary.
          * \param[in] modules  List of modules for to use for module listings.
          * \param[in] groups   List of module groups.
          * \throws    std::bad_alloc if out of memory.
          */
-        CommandLineHelpModule(const CommandLineModuleMap       &modules,
+        CommandLineHelpModule(const ProgramInfo                &programInfo,
+                              const CommandLineModuleMap       &modules,
                               const CommandLineModuleGroupList &groups);
 
         /*! \brief
@@ -639,6 +690,11 @@ class CommandLineHelpModule : public CommandLineModuleInterface
         {
             return *context_;
         }
+        //! Returns the program info object for the running binary.
+        const ProgramInfo &programInfo() const
+        {
+            return programInfo_;
+        }
 
         virtual const char *name() const { return "help"; }
         virtual const char *shortDescription() const
@@ -653,6 +709,7 @@ class CommandLineHelpModule : public CommandLineModuleInterface
         void exportHelp(HelpExportInterface *exporter) const;
 
         boost::scoped_ptr<RootHelpTopic>  rootTopic_;
+        const ProgramInfo                &programInfo_;
         const CommandLineModuleMap       &modules_;
         const CommandLineModuleGroupList &groups_;
 
@@ -664,9 +721,11 @@ class CommandLineHelpModule : public CommandLineModuleInterface
 };
 
 CommandLineHelpModule::CommandLineHelpModule(
+        const ProgramInfo                &programInfo,
         const CommandLineModuleMap       &modules,
         const CommandLineModuleGroupList &groups)
-    : rootTopic_(new RootHelpTopic(modules)), modules_(modules), groups_(groups),
+    : rootTopic_(new RootHelpTopic(modules)), programInfo_(programInfo),
+      modules_(modules), groups_(groups),
       context_(NULL), moduleOverride_(NULL), bHidden_(false)
 {
 }
@@ -689,11 +748,11 @@ int CommandLineHelpModule::run(int argc, char *argv[])
         boost::scoped_ptr<HelpExportInterface> exporter;
         if (exportFormat == "man")
         {
-            exporter.reset(new HelpExportMan);
+            exporter.reset(new HelpExportMan(modules_));
         }
         else if (exportFormat == "html")
         {
-            exporter.reset(new HelpExportHtml);
+            exporter.reset(new HelpExportHtml(modules_));
         }
         else
         {
@@ -703,16 +762,19 @@ int CommandLineHelpModule::run(int argc, char *argv[])
         return 0;
     }
 
+    HelpLinks                                 links(eHelpOutputFormat_Console);
+    initProgramLinks(&links, modules_);
     boost::scoped_ptr<CommandLineHelpContext> context(
             new CommandLineHelpContext(&File::standardOutput(),
-                                       eHelpOutputFormat_Console, NULL));
+                                       eHelpOutputFormat_Console, &links));
     context->setShowHidden(bHidden_);
-    context_ = context.get();
     if (moduleOverride_ != NULL)
     {
-        ModuleHelpTopic(*moduleOverride_, *this).writeHelp(context->writerContext());
+        context->setModuleDisplayName(programInfo_.displayName());
+        moduleOverride_->writeHelp(*context);
         return 0;
     }
+    context_ = context.get();
 
     HelpManager       helpManager(*rootTopic_, context->writerContext());
     try
@@ -748,8 +810,7 @@ void CommandLineHelpModule::exportHelp(HelpExportInterface *exporter) const
 {
     // TODO: Would be nicer to have the file names supplied by the build system
     // and/or export a list of files from here.
-    const char *const program =
-        ProgramInfo::getInstance().invariantProgramName().c_str();
+    const char *const program = programInfo_.realBinaryName().c_str();
 
     exporter->startModuleExport();
     CommandLineModuleMap::const_iterator module;
@@ -759,7 +820,9 @@ void CommandLineHelpModule::exportHelp(HelpExportInterface *exporter) const
         {
             const char *const moduleName = module->first.c_str();
             std::string       tag(formatString("%s-%s", program, moduleName));
-            exporter->exportModuleHelp(tag, *module->second);
+            std::string       displayName(tag);
+            std::replace(displayName.begin(), displayName.end(), '-', ' ');
+            exporter->exportModuleHelp(*module->second, tag, displayName);
         }
     }
     exporter->finishModuleExport();
@@ -782,7 +845,11 @@ namespace
 
 void ModuleHelpTopic::writeHelp(const HelpWriterContext & /*context*/) const
 {
-    module_.writeHelp(helpModule_.context());
+    CommandLineHelpContext context(helpModule_.context());
+    const char *const      program =
+        helpModule_.programInfo().realBinaryName().c_str();
+    context.setModuleDisplayName(formatString("%s %s", program, module_.name()));
+    module_.writeHelp(context);
 }
 
 /********************************************************************
@@ -1009,7 +1076,8 @@ void CommandLineModuleManager::Impl::ensureHelpModuleExists()
 {
     if (helpModule_ == NULL)
     {
-        helpModule_ = new CommandLineHelpModule(modules_, moduleGroups_);
+        helpModule_ = new CommandLineHelpModule(programInfo_, modules_,
+                                                moduleGroups_);
         addModule(CommandLineModulePointer(helpModule_));
     }
 }
@@ -1097,8 +1165,6 @@ CommandLineModuleManager::Impl::processCommonOptions(int *argc, char ***argv)
                 GMX_THROW(InvalidInputError(message));
             }
             module = moduleIter->second.get();
-            programInfo_.setDisplayName(
-                    programInfo_.realBinaryName() + "-" + moduleIter->first);
             *argc -= argcForWrapper;
             *argv += argcForWrapper;
             // After this point, argc and argv are the same independent of
@@ -1107,6 +1173,11 @@ CommandLineModuleManager::Impl::processCommonOptions(int *argc, char ***argv)
     }
     if (module != NULL)
     {
+        if (singleModule_ == NULL)
+        {
+            programInfo_.setDisplayName(
+                    programInfo_.realBinaryName() + " " + module->name());
+        }
         // Recognize the common options also after the module name.
         // TODO: It could be nicer to only recognize -h/-hidden if module is not
         // null.
@@ -1158,6 +1229,11 @@ void CommandLineModuleManager::setQuiet(bool bQuiet)
     impl_->bQuiet_ = bQuiet;
 }
 
+void CommandLineModuleManager::setSingleModule(CommandLineModuleInterface *module)
+{
+    impl_->singleModule_ = module;
+}
+
 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
 {
     impl_->addModule(move(module));
@@ -1235,7 +1311,7 @@ int CommandLineModuleManager::runAsMainSingleModule(
     try
     {
         CommandLineModuleManager manager(&programInfo);
-        manager.impl_->singleModule_ = module;
+        manager.setSingleModule(module);
         int rc = manager.run(argc, argv);
         gmx::finalize();
         return rc;
index 60838d2071c8e682254cd32e374308146ba226de..d51e5e35b7a106bfa2af66c1cbfd363df18b00cd 100644 (file)
@@ -187,6 +187,24 @@ class CommandLineModuleManager
          */
         void setQuiet(bool bQuiet);
 
+        /*! \brief
+         * Makes the manager always run a single module.
+         *
+         * \param     module  Module to run.
+         *
+         * This method disables all mechanisms for selecting a module, and
+         * directly passes all command-line arguments to \p module.
+         * Help arguments are an exception: these are still recognized by the
+         * manager and translated into a call to
+         * CommandLineModuleInterface::writeHelp().
+         *
+         * This is public mainly for unit testing purposes; for other code,
+         * runAsMainSingleModule() typically provides the desired
+         * functionality.
+         *
+         * Does not throw.
+         */
+        void setSingleModule(CommandLineModuleInterface *module);
         /*! \brief
          * Adds a given module to this manager.
          *
index 79998f652b149a45b1bf221c8132aa7f251518fc..2fdd7fa2adcd4ae38be3834a7df9ff7d235b760e 100644 (file)
@@ -84,14 +84,40 @@ class MockModule : public gmx::CommandLineModuleInterface
         MOCK_METHOD2(run, int(int argc, char *argv[]));
         MOCK_CONST_METHOD1(writeHelp, void(const gmx::CommandLineHelpContext &context));
 
+        //! Sets the expected display name for writeHelp() calls.
+        void setExpectedDisplayName(const char *expected)
+        {
+            expectedDisplayName_ = expected;
+        }
+
     private:
+        //! Checks the context passed to writeHelp().
+        void checkHelpContext(const gmx::CommandLineHelpContext &context) const;
+
         const char             *name_;
         const char             *descr_;
+        std::string             expectedDisplayName_;
 };
 
 MockModule::MockModule(const char *name, const char *description)
     : name_(name), descr_(description)
 {
+    using ::testing::_;
+    using ::testing::Invoke;
+    using ::testing::WithArg;
+    ON_CALL(*this, writeHelp(_))
+        .WillByDefault(WithArg<0>(Invoke(this, &MockModule::checkHelpContext)));
+}
+
+void MockModule::checkHelpContext(const gmx::CommandLineHelpContext &context) const
+{
+    EXPECT_EQ(expectedDisplayName_, context.moduleDisplayName());
+
+    gmx::TextLineWrapperSettings settings;
+    std::string                  moduleName =
+        context.writerContext().substituteMarkupAndWrapToString(
+                settings, "[THISMODULE]");
+    EXPECT_EQ(expectedDisplayName_, moduleName);
 }
 
 /********************************************************************
@@ -101,7 +127,7 @@ MockModule::MockModule(const char *name, const char *description)
 class CommandLineModuleManagerTest : public ::testing::Test
 {
     public:
-        void initManager(const CommandLine &args);
+        void initManager(const CommandLine &args, const char *realBinaryName);
         MockModule    &addModule(const char *name, const char *description);
         MockHelpTopic &addHelpTopic(const char *name, const char *title);
 
@@ -112,10 +138,11 @@ class CommandLineModuleManagerTest : public ::testing::Test
         boost::scoped_ptr<gmx::CommandLineModuleManager> manager_;
 };
 
-void CommandLineModuleManagerTest::initManager(const CommandLine &args)
+void CommandLineModuleManagerTest::initManager(
+        const CommandLine &args, const char *realBinaryName)
 {
     manager_.reset();
-    programInfo_.reset(new gmx::ProgramInfo("g_test", args.argc(), args.argv()));
+    programInfo_.reset(new gmx::ProgramInfo(realBinaryName, args.argc(), args.argv()));
     manager_.reset(new gmx::CommandLineModuleManager(programInfo_.get()));
     manager_->setQuiet(true);
 }
@@ -143,10 +170,10 @@ CommandLineModuleManagerTest::addHelpTopic(const char *name, const char *title)
 TEST_F(CommandLineModuleManagerTest, RunsModule)
 {
     const char *const cmdline[] = {
-        "g_test", "module", "-flag", "yes"
+        "test", "module", "-flag", "yes"
     };
     CommandLine       args(CommandLine::create(cmdline));
-    initManager(args);
+    initManager(args, "test");
     MockModule       &mod1 = addModule("module", "First module");
     addModule("other", "Second module");
     using ::testing::_;
@@ -162,14 +189,15 @@ TEST_F(CommandLineModuleManagerTest, RunsModule)
 TEST_F(CommandLineModuleManagerTest, RunsModuleHelp)
 {
     const char *const cmdline[] = {
-        "g_test", "help", "module"
+        "test", "help", "module"
     };
     CommandLine       args(CommandLine::create(cmdline));
-    initManager(args);
+    initManager(args, "test");
     MockModule       &mod1 = addModule("module", "First module");
     addModule("other", "Second module");
     using ::testing::_;
     EXPECT_CALL(mod1, writeHelp(_));
+    mod1.setExpectedDisplayName("test module");
     int rc = 0;
     ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv()));
     ASSERT_EQ(0, rc);
@@ -178,14 +206,49 @@ TEST_F(CommandLineModuleManagerTest, RunsModuleHelp)
 TEST_F(CommandLineModuleManagerTest, RunsModuleHelpWithDashH)
 {
     const char *const cmdline[] = {
-        "g_test", "module", "-h"
+        "test", "module", "-h"
+    };
+    CommandLine       args(CommandLine::create(cmdline));
+    initManager(args, "test");
+    MockModule       &mod1 = addModule("module", "First module");
+    addModule("other", "Second module");
+    using ::testing::_;
+    EXPECT_CALL(mod1, writeHelp(_));
+    mod1.setExpectedDisplayName("test module");
+    int rc = 0;
+    ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv()));
+    ASSERT_EQ(0, rc);
+}
+
+TEST_F(CommandLineModuleManagerTest, RunsModuleHelpWithDashHWithSymLink)
+{
+    const char *const cmdline[] = {
+        "g_module", "-h"
     };
     CommandLine       args(CommandLine::create(cmdline));
-    initManager(args);
+    initManager(args, "test");
     MockModule       &mod1 = addModule("module", "First module");
     addModule("other", "Second module");
     using ::testing::_;
     EXPECT_CALL(mod1, writeHelp(_));
+    mod1.setExpectedDisplayName("test module");
+    int rc = 0;
+    ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv()));
+    ASSERT_EQ(0, rc);
+}
+
+TEST_F(CommandLineModuleManagerTest, RunsModuleHelpWithDashHWithSingleModule)
+{
+    const char *const cmdline[] = {
+        "g_module", "-h"
+    };
+    CommandLine       args(CommandLine::create(cmdline));
+    initManager(args, "g_module");
+    MockModule        mod(NULL, NULL);
+    manager().setSingleModule(&mod);
+    using ::testing::_;
+    EXPECT_CALL(mod, writeHelp(_));
+    mod.setExpectedDisplayName("g_module");
     int rc = 0;
     ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv()));
     ASSERT_EQ(0, rc);
@@ -194,10 +257,10 @@ TEST_F(CommandLineModuleManagerTest, RunsModuleHelpWithDashH)
 TEST_F(CommandLineModuleManagerTest, PrintsHelpOnTopic)
 {
     const char *const cmdline[] = {
-        "g_test", "help", "topic"
+        "test", "help", "topic"
     };
     CommandLine       args(CommandLine::create(cmdline));
-    initManager(args);
+    initManager(args, "test");
     addModule("module", "First module");
     MockHelpTopic &topic = addHelpTopic("topic", "Test topic");
     using ::testing::_;
@@ -213,7 +276,7 @@ TEST_F(CommandLineModuleManagerTest, RunsModuleBasedOnBinaryName)
         "g_module", "-flag", "yes"
     };
     CommandLine       args(CommandLine::create(cmdline));
-    initManager(args);
+    initManager(args, "test");
     MockModule       &mod1 = addModule("module", "First module");
     addModule("other", "Second module");
     using ::testing::_;
@@ -232,7 +295,7 @@ TEST_F(CommandLineModuleManagerTest, RunsModuleBasedOnBinaryNameWithPathAndSuffi
         "/usr/local/gromacs/bin/g_module" GMX_BINARY_SUFFIX ".exe", "-flag", "yes"
     };
     CommandLine       args(CommandLine::create(cmdline));
-    initManager(args);
+    initManager(args, "test");
     MockModule       &mod1 = addModule("module", "First module");
     addModule("other", "Second module");
     using ::testing::_;
@@ -248,10 +311,10 @@ TEST_F(CommandLineModuleManagerTest, RunsModuleBasedOnBinaryNameWithPathAndSuffi
 TEST_F(CommandLineModuleManagerTest, HandlesConflictingBinaryAndModuleNames)
 {
     const char *const cmdline[] = {
-        "g_test", "test", "-flag", "yes"
+        "test", "test", "-flag", "yes"
     };
     CommandLine       args(CommandLine::create(cmdline));
-    initManager(args);
+    initManager(args, "test");
     MockModule       &mod1 = addModule("test", "Test module");
     addModule("other", "Second module");
     using ::testing::_;
index f7b763ddd70d40358731ea6ecb3254e4fb98762a..28fc1b58cd2c8d851e88d416124693297554ac1b 100644 (file)
@@ -68,6 +68,11 @@ static std::string check(const char *s, const gmx::HelpWriterContext &context)
     return context.substituteMarkupAndWrapToString(gmx::TextLineWrapperSettings(), s);
 }
 
+static std::string check(const char *s, const gmx::CommandLineHelpContext &context)
+{
+    return check(s, context.writerContext());
+}
+
 #define FLAG_SET(flag, mask) ((flag &mask) == mask)
 /* Return a string describing the file type in flag.
  * flag should the flag field of a filenm struct.
@@ -303,18 +308,17 @@ static void print_pargs(FILE *fp, int npargs, t_pargs pa[],
 }
 
 static void write_nroffman(FILE *out,
-                           const char *program,
                            int nldesc, const char **desc,
                            int nfile, t_filenm *fnm,
                            int npargs, t_pargs *pa,
                            int nbug, const char **bugs,
-                           const gmx::HelpWriterContext &context)
+                           const gmx::CommandLineHelpContext &context)
 {
     int  i;
     char tmp[256];
 
     fprintf(out, ".SH SYNOPSIS\n");
-    fprintf(out, "\\f3%s\\fP\n", program);
+    fprintf(out, "\\f3%s\\fP\n", context.moduleDisplayName());
 
     /* command line arguments */
     if (nfile > 0)
@@ -890,9 +894,8 @@ void write_man(const char *mantp,
     {
         GMX_RELEASE_ASSERT(context != NULL,
                            "Man page export only implemented with the new context");
-        write_nroffman(out, context->moduleDisplayName(), nldesc, desc,
-                       nfile, fnm, npar, par, nbug, bugs,
-                       context->writerContext());
+        write_nroffman(out, nldesc, desc, nfile, fnm, npar, par, nbug, bugs,
+                       *context);
     }
     if (strcmp(mantp, "help") == 0)
     {
index 1654f5d76b0afba77f5334494b718cb0852de6e6..2957a41f0c2eff8d165d2a62552c2b4144aad2b5 100644 (file)
@@ -617,7 +617,7 @@ void dump_res(FILE *out, int nres, atom_id *resindex, atom_id index[])
 int gmx_mindist(int argc, char *argv[])
 {
     const char     *desc[] = {
-        "[TT]g_mindist[tt] computes the distance between one group and a number of",
+        "[THISMODULE] computes the distance between one group and a number of",
         "other groups. Both the minimum distance",
         "(between any pair of atoms from the respective groups)",
         "and the number of contacts within a given",
@@ -633,8 +633,7 @@ int gmx_mindist(int argc, char *argv[])
         "each direction is considered, giving a total of 26 shifts.",
         "It also plots the maximum distance within the group and the lengths",
         "of the three box vectors.[PAR]",
-        "Other programs that calculate distances are [TT]g_dist[tt]",
-        "and [TT]g_bond[tt]."
+        "Also [gmx-distance] calculates distances."
     };
     const char     *bugs[] = {
         "The [TT]-pi[tt] option is very slow."
index e1e4256b87b7a3b094728b8e0a73fb6990e7b673..6f070ef4172af55b20bb56f4d714d2217a6f4c23 100644 (file)
@@ -74,7 +74,9 @@ struct t_sandr
 /* The order of these arrays is significant. Text search and replace
  * for each element occurs in order, so earlier changes can induce
  * subsequent changes even though the original text might not appear
- * to invoke the latter changes. */
+ * to invoke the latter changes.
+ * TODO: Get rid of this behavior. It makes it very difficult to manage
+ * replacements coming from multiple sources (e.g., hyperlinks).*/
 
 //! List of replacements for console output.
 const t_sandr sandrTty[] = {
@@ -337,7 +339,7 @@ class WrapperToVector : public WrapperInterface
 };
 
 /*! \brief
- * Make the string uppercase.
+ * Makes the string uppercase.
  *
  * \param[in] text  Input text.
  * \returns   \p text with all characters transformed to uppercase.
@@ -369,26 +371,33 @@ class HelpLinks::Impl
         struct LinkItem
         {
             LinkItem(const std::string &linkName,
-                     const std::string &targetName)
-                : linkName(linkName), targetName(targetName)
+                     const std::string &replacement)
+                : linkName(linkName), replacement(replacement)
             {
             }
             std::string         linkName;
-            std::string         targetName;
+            std::string         replacement;
         };
 
         //! Shorthand for a list of links.
         typedef std::vector<LinkItem> LinkList;
 
+        //! Initializes empty links with the given format.
+        explicit Impl(HelpOutputFormat format) : format_(format)
+        {
+        }
+
         //! List of links.
-        LinkList        links_;
+        LinkList          links_;
+        //! Output format for which the links are formatted.
+        HelpOutputFormat  format_;
 };
 
 /********************************************************************
  * HelpLinks
  */
 
-HelpLinks::HelpLinks() : impl_(new Impl)
+HelpLinks::HelpLinks(HelpOutputFormat format) : impl_(new Impl(format))
 {
 }
 
@@ -397,9 +406,27 @@ HelpLinks::~HelpLinks()
 }
 
 void HelpLinks::addLink(const std::string &linkName,
-                        const std::string &targetName)
+                        const std::string &targetName,
+                        const std::string &displayName)
 {
-    impl_->links_.push_back(Impl::LinkItem(linkName, targetName));
+    std::string replacement;
+    switch (impl_->format_)
+    {
+        case eHelpOutputFormat_Console:
+            replacement = repall(displayName, sandrTty);
+            break;
+        case eHelpOutputFormat_Man:
+            replacement = repall(displayName, sandrMan);
+            break;
+        case eHelpOutputFormat_Html:
+            replacement = formatString(
+                        "<a href=\"%s.html\">%s</a>", targetName.c_str(),
+                        repall(displayName, sandrHtml).c_str());
+            break;
+        default:
+            GMX_RELEASE_ASSERT(false, "Output format not implemented for links");
+    }
+    impl_->links_.push_back(Impl::LinkItem(linkName, replacement));
 }
 
 /********************************************************************
@@ -440,15 +467,41 @@ class HelpWriterContext::Impl
             const HelpLinks        *links_;
         };
 
+        struct ReplaceItem
+        {
+            ReplaceItem(const std::string &search,
+                        const std::string &replace)
+                : search(search), replace(replace)
+            {
+            }
+            std::string         search;
+            std::string         replace;
+        };
+
         //! Smart pointer type for managing the shared state.
         typedef boost::shared_ptr<const SharedState> StatePointer;
+        //! Shorthand for a list of markup/other replacements.
+        typedef std::vector<ReplaceItem> ReplaceList;
 
         //! Initializes the context with the given state.
         explicit Impl(const StatePointer &state)
             : state_(state)
         {
+            initDefaultReplacements();
+        }
+
+        //! Initializes default replacements for the chosen output format.
+        void initDefaultReplacements();
+        //! Adds a new replacement.
+        void addReplacement(const std::string &search,
+                            const std::string &replace)
+        {
+            replacements_.push_back(ReplaceItem(search, replace));
         }
 
+        //! Replaces links in a given string.
+        std::string replaceLinks(const std::string &input) const;
+
         /*! \brief
          * Process markup and wrap lines within a block of text.
          *
@@ -463,22 +516,49 @@ class HelpWriterContext::Impl
 
         //! Constant state shared by all child context objects.
         StatePointer            state_;
+        //! List of markup/other replacements.
+        ReplaceList             replacements_;
 
     private:
         GMX_DISALLOW_ASSIGN(Impl);
 };
 
+void HelpWriterContext::Impl::initDefaultReplacements()
+{
+    const char *program = ProgramInfo::getInstance().programName().c_str();
+    addReplacement("[PROGRAM]", program);
+}
+
+std::string HelpWriterContext::Impl::replaceLinks(const std::string &input) const
+{
+    std::string result(input);
+    if (state_->links_ != NULL)
+    {
+        HelpLinks::Impl::LinkList::const_iterator link;
+        for (link  = state_->links_->impl_->links_.begin();
+             link != state_->links_->impl_->links_.end(); ++link)
+        {
+            result = replaceAllWords(result, link->linkName, link->replacement);
+        }
+    }
+    return result;
+}
+
 void HelpWriterContext::Impl::processMarkup(const std::string &text,
                                             WrapperInterface  *wrapper) const
 {
-    const char *program = ProgramInfo::getInstance().programName().c_str();
     std::string result(text);
-    result = replaceAll(result, "[PROGRAM]", program);
+    for (ReplaceList::const_iterator i = replacements_.begin();
+         i != replacements_.end(); ++i)
+    {
+        result = replaceAll(result, i->search, i->replace);
+    }
     switch (state_->format_)
     {
         case eHelpOutputFormat_Console:
         {
             result = repall(result, sandrTty);
+            result = replaceLinks(result);
             if (wrapper->settings().lineLength() == 0)
             {
                 wrapper->settings().setLineLength(78);
@@ -487,25 +567,15 @@ void HelpWriterContext::Impl::processMarkup(const std::string &text,
         }
         case eHelpOutputFormat_Man:
         {
+            // Needs to be done first to avoid '-' -> '\-' messing up the links.
+            result = replaceLinks(result);
             result = repall(result, sandrMan);
             return wrapper->wrap(result);
         }
         case eHelpOutputFormat_Html:
         {
             result = repall(result, sandrHtml);
-            if (state_->links_ != NULL)
-            {
-                HelpLinks::Impl::LinkList::const_iterator link;
-                for (link  = state_->links_->impl_->links_.begin();
-                     link != state_->links_->impl_->links_.end(); ++link)
-                {
-                    std::string replacement
-                        = formatString("<a href=\"%s.html\">%s</a>",
-                                       link->targetName.c_str(),
-                                       link->linkName.c_str());
-                    result = replaceAllWords(result, link->linkName, replacement);
-                }
-            }
+            result = replaceLinks(result);
             return wrapper->wrap(result);
         }
         default:
@@ -526,6 +596,11 @@ HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format,
                                      const HelpLinks *links)
     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, links))))
 {
+    if (links != NULL)
+    {
+        GMX_RELEASE_ASSERT(links->impl_->format_ == format,
+                           "Links must have the same output format as the context");
+    }
 }
 
 HelpWriterContext::HelpWriterContext(Impl *impl)
@@ -542,6 +617,12 @@ HelpWriterContext::~HelpWriterContext()
 {
 }
 
+void HelpWriterContext::setReplacement(const std::string &search,
+                                       const std::string &replace)
+{
+    impl_->addReplacement(search, replace);
+}
+
 HelpOutputFormat HelpWriterContext::outputFormat() const
 {
     return impl_->state_->format_;
index 392316aa71f40cffa0a2844238ad940812046f2b..fc1d5f54b989b2465769e594484695302550a758 100644 (file)
@@ -73,27 +73,37 @@ enum HelpOutputFormat
  * This is used when exporting all the help from the wrapper binary to avoid
  * repeatedly constructing the same data structure for each help item.
  *
+ * While the links are in principle independent of the output format, the
+ * constructor takes the output format to be able to preformat the links,
+ * avoiding repeated processing during markup substitution.  Could be hidden
+ * behind the scenes in HelpWriterContext, but that would complicate the
+ * implementation.
+ *
  * \ingroup module_onlinehelp
  */
 class HelpLinks
 {
     public:
-        //! Initializes an empty links collection.
-        HelpLinks();
+        /*! \brief
+         * Initializes an empty links collection for the given output format.
+         */
+        explicit HelpLinks(HelpOutputFormat format);
         ~HelpLinks();
 
         /*! \brief
          * Adds a link.
          *
-         * \param[in] linkName   Name of the link in input text.
-         * \param[in] targetName Hyperlink target.
+         * \param[in] linkName     Name of the link in input text.
+         * \param[in] targetName   Hyperlink target.
+         * \param[in] displayName  Text to show as the link.
          *
          * Any occurrence of \p linkName in the text passed to markup
          * substitution methods in HelpWriterContext is made into a hyperlink
          * to \p targetName if the markup format supports that.
          */
         void addLink(const std::string &linkName,
-                     const std::string &targetName);
+                     const std::string &targetName,
+                     const std::string &displayName);
 
     private:
         class Impl;
@@ -144,6 +154,21 @@ class HelpWriterContext
         HelpWriterContext(const HelpWriterContext &other);
         ~HelpWriterContext();
 
+        /*! \brief
+         * Adds a string replacement for markup subsitution.
+         *
+         * \param[in] search   Text to replace in input.
+         * \param[in] replace  Text that each occurrence of \p search is
+         *     replaced with.
+         * \throws std::bad_alloc if out of memory.
+         *
+         * \todo
+         * Improve semantics if the same \p search item is set multiple
+         * times.
+         */
+        void setReplacement(const std::string &search,
+                            const std::string &replace);
+
         /*! \brief
          * Returns the active output format.
          *