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
{
}
+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;
}
* 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
*/
CommandLineHelpContext(File *file, HelpOutputFormat format,
const HelpLinks *links);
+ //! Creates a copy of the context.
+ explicit CommandLineHelpContext(const CommandLineHelpContext &other);
~CommandLineHelpContext();
/*! \brief
class Impl;
PrivateImplPointer<Impl> impl_;
+
+ GMX_DISALLOW_ASSIGN(CommandLineHelpContext);
};
/*! \libinternal \brief
/*! \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.
*
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
*/
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() {}
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");
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);
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();
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)
{
std::string line;
while (linksFile.readLine(&line))
{
- links_.addLink(line, "../online/" + line);
+ links_.addLink(line, "../online/" + line, line);
}
}
}
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);
/*! \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
{
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
void exportHelp(HelpExportInterface *exporter) const;
boost::scoped_ptr<RootHelpTopic> rootTopic_;
+ const ProgramInfo &programInfo_;
const CommandLineModuleMap &modules_;
const CommandLineModuleGroupList &groups_;
};
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)
{
}
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
{
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
{
// 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;
{
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();
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);
}
/********************************************************************
{
if (helpModule_ == NULL)
{
- helpModule_ = new CommandLineHelpModule(modules_, moduleGroups_);
+ helpModule_ = new CommandLineHelpModule(programInfo_, modules_,
+ moduleGroups_);
addModule(CommandLineModulePointer(helpModule_));
}
}
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
}
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.
impl_->bQuiet_ = bQuiet;
}
+void CommandLineModuleManager::setSingleModule(CommandLineModuleInterface *module)
+{
+ impl_->singleModule_ = module;
+}
+
void CommandLineModuleManager::addModule(CommandLineModulePointer module)
{
impl_->addModule(move(module));
try
{
CommandLineModuleManager manager(&programInfo);
- manager.impl_->singleModule_ = module;
+ manager.setSingleModule(module);
int rc = manager.run(argc, argv);
gmx::finalize();
return rc;
*/
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.
*
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);
}
/********************************************************************
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);
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);
}
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::_;
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);
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);
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::_;
"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::_;
"/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::_;
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::_;
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.
}
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)
{
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)
{
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",
"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."
/* 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[] = {
};
/*! \brief
- * Make the string uppercase.
+ * Makes the string uppercase.
*
* \param[in] text Input text.
* \returns \p text with all characters transformed to uppercase.
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))
{
}
}
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));
}
/********************************************************************
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.
*
//! 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);
}
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:
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)
{
}
+void HelpWriterContext::setReplacement(const std::string &search,
+ const std::string &replace)
+{
+ impl_->addReplacement(search, replace);
+}
+
HelpOutputFormat HelpWriterContext::outputFormat() const
{
return impl_->state_->format_;
* 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;
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.
*