Generic handling of online help topics.
authorTeemu Murtola <teemu.murtola@gmail.com>
Fri, 11 May 2012 18:08:47 +0000 (21:08 +0300)
committerTeemu Murtola <teemu.murtola@gmail.com>
Tue, 29 May 2012 13:56:53 +0000 (16:56 +0300)
- Added a few simple classes for handling a tree of help topics
  (src/gromacs/onlinehelp/).
- Rewrote the selection help to use the new classes. Required changing
  the help parser slightly to pass all requested help topics in a single
  call to _gmx_sel_handle_help_cmd(). Although this changes the usage of
  the help slightly, did not yet update the help texts.
- Added a way to specify the bison and flex binaries to use for
  regenerate_parser.sh (useful, e.g., if multiple versions are
  installed).

Related to #666.

Change-Id: I318fc0fc42e4af39734a5aa65a503582302ecdd8

22 files changed:
src/gromacs/onlinehelp/CMakeLists.txt
src/gromacs/onlinehelp/helpformat.cpp
src/gromacs/onlinehelp/helpformat.h
src/gromacs/onlinehelp/helpmanager.cpp [new file with mode: 0644]
src/gromacs/onlinehelp/helpmanager.h [new file with mode: 0644]
src/gromacs/onlinehelp/helptopic.cpp [new file with mode: 0644]
src/gromacs/onlinehelp/helptopic.h [new file with mode: 0644]
src/gromacs/onlinehelp/helptopicinterface.h [new file with mode: 0644]
src/gromacs/onlinehelp/tests/CMakeLists.txt
src/gromacs/onlinehelp/tests/helpmanager.cpp [new file with mode: 0644]
src/gromacs/onlinehelp/tests/refdata/HelpTopicFormattingTest_FormatsCompositeTopicWithSubTopics.xml [new file with mode: 0644]
src/gromacs/onlinehelp/tests/refdata/HelpTopicFormattingTest_FormatsSimpleTopic.xml [new file with mode: 0644]
src/gromacs/selection/parser.cpp
src/gromacs/selection/parser.y
src/gromacs/selection/parsetree.cpp
src/gromacs/selection/parsetree.h
src/gromacs/selection/regenerate_parser.sh
src/gromacs/selection/selectioncollection-impl.h
src/gromacs/selection/selhelp.cpp
src/gromacs/selection/selhelp.h
src/testutils/mock_helptopic.cpp [new file with mode: 0644]
src/testutils/mock_helptopic.h [new file with mode: 0644]

index 739d457a66e4ed300cb22bf4ad16b220a7ae950e..54dec846b44af8ccd7c84101472a764958340242 100644 (file)
@@ -1,6 +1,12 @@
 file(GLOB ONLINEHELP_SOURCES *.cpp)
 set(LIBGROMACS_SOURCES ${LIBGROMACS_SOURCES} ${ONLINEHELP_SOURCES} PARENT_SCOPE)
 
+set(ONLINEHELP_PUBLIC_HEADERS
+    helptopicinterface.h)
+install(FILES ${ONLINEHELP_PUBLIC_HEADERS}
+        DESTINATION ${INCL_INSTALL_DIR}/gromacs/onlinehelp
+        COMPONENT development)
+
 if (BUILD_TESTING)
     add_subdirectory(tests)
 endif (BUILD_TESTING)
index 0d269143b0d0bddb79d1b579318adc31f34a282c..af027e68ca4fa0737208777bee3310fc4001a3f6 100644 (file)
@@ -37,6 +37,8 @@
  */
 #include "helpformat.h"
 
+#include <cctype>
+
 #include <algorithm>
 #include <string>
 #include <vector>
@@ -52,6 +54,13 @@ namespace gmx
 {
 
 /*! \cond libapi */
+std::string toUpperCase(const std::string &text)
+{
+    std::string result(text);
+    transform(result.begin(), result.end(), result.begin(), toupper);
+    return result;
+}
+
 std::string substituteMarkupForConsole(const std::string &text)
 {
     char *resultStr = check_tty(text.c_str());
index bddfa59c4f93050cccd713de378c93aa455f907f..f91021335c97457ecdc6d1e6654e565e5a667dad 100644 (file)
@@ -49,6 +49,17 @@ namespace gmx
 class File;
 
 /*! \cond libapi */
+/*! \libinternal \brief
+ * Make the string uppercase.
+ *
+ * \param[in] text  Input text.
+ * \returns   \p text with all characters transformed to uppercase.
+ * \throws    std::bad_alloc if out of memory.
+ *
+ * \inlibraryapi
+ */
+std::string toUpperCase(const std::string &text);
+
 /*! \libinternal \brief
  * Substitute markup used in help text for console output.
  *
diff --git a/src/gromacs/onlinehelp/helpmanager.cpp b/src/gromacs/onlinehelp/helpmanager.cpp
new file mode 100644 (file)
index 0000000..48918c5
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ *
+ *                This source code is part of
+ *
+ *                 G   R   O   M   A   C   S
+ *
+ *          GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \internal \file
+ * \brief
+ * Implements gmx::HelpManager.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \ingroup module_onlinehelp
+ */
+#include "helpmanager.h"
+
+#include <cstdio>
+
+#include <string>
+#include <vector>
+
+#include "gromacs/onlinehelp/helptopicinterface.h"
+#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/format.h"
+
+namespace gmx
+{
+
+/********************************************************************
+ * HelpManager::Impl
+ */
+
+/*! \internal \brief
+ * Private implementation class for HelpManager.
+ *
+ * \ingroup module_onlinehelp
+ */
+class HelpManager::Impl
+{
+    public:
+        //! Container type for keeping the stack of active topics.
+        typedef std::vector<const HelpTopicInterface *> TopicStack;
+
+        //! Whether the active topic is the root topic.
+        bool isAtRootTopic() const { return topicStack_.size() == 1; }
+        //! Returns the active topic.
+        const HelpTopicInterface &currentTopic() const
+        {
+            return *topicStack_.back();
+        }
+        //! Formats the active topic as a string, including its parent topics.
+        std::string currentTopicAsString() const;
+
+        /*! \brief
+         * Stack of active topics.
+         *
+         * The first item is always the root topic, and each item is a subtopic
+         * of the preceding item.  The last item is the currently active topic.
+         */
+        TopicStack              topicStack_;
+};
+
+std::string HelpManager::Impl::currentTopicAsString() const
+{
+    std::string result;
+    TopicStack::const_iterator topic;
+    for (topic = topicStack_.begin() + 1; topic != topicStack_.end(); ++topic)
+    {
+        if (!result.empty())
+        {
+            result.append(" ");
+        }
+        result.append((*topic)->name());
+    }
+    return result;
+}
+
+/********************************************************************
+ * HelpManager
+ */
+
+HelpManager::HelpManager(const HelpTopicInterface &rootTopic)
+    : impl_(new Impl)
+{
+    impl_->topicStack_.push_back(&rootTopic);
+}
+
+HelpManager::~HelpManager()
+{
+}
+
+void HelpManager::enterTopic(const char *name)
+{
+    const HelpTopicInterface &topic = impl_->currentTopic();
+    if (!topic.hasSubTopics())
+    {
+        GMX_THROW(InvalidInputError(
+                    formatString("Help topic '%s' has no subtopics",
+                                 impl_->currentTopicAsString().c_str())));
+    }
+    const HelpTopicInterface *newTopic = topic.findSubTopic(name);
+    if (newTopic == NULL)
+    {
+        if (impl_->isAtRootTopic())
+        {
+            GMX_THROW(InvalidInputError(
+                        formatString("No help available for '%s'", name)));
+        }
+        else
+        {
+            GMX_THROW(InvalidInputError(
+                        formatString("Help topic '%s' has no subtopic '%s'",
+                                     impl_->currentTopicAsString().c_str(), name)));
+        }
+    }
+    impl_->topicStack_.push_back(newTopic);
+}
+
+void HelpManager::writeCurrentTopic(File *file) const
+{
+    const HelpTopicInterface &topic = impl_->currentTopic();
+    topic.writeHelp(file);
+}
+
+} // namespace gmx
diff --git a/src/gromacs/onlinehelp/helpmanager.h b/src/gromacs/onlinehelp/helpmanager.h
new file mode 100644 (file)
index 0000000..1b6bcab
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ *
+ *                This source code is part of
+ *
+ *                 G   R   O   M   A   C   S
+ *
+ *          GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \libinternal \file
+ * \brief
+ * Declares gmx::HelpManager.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \inlibraryapi
+ * \ingroup module_onlinehelp
+ */
+#ifndef GMX_ONLINEHELP_HELPMANAGER_H
+#define GMX_ONLINEHELP_HELPMANAGER_H
+
+#include "../utility/common.h"
+
+namespace gmx
+{
+
+class File;
+class HelpTopicInterface;
+
+/*! \libinternal \brief
+ * Helper for providing interactive online help.
+ *
+ * \inlibraryapi
+ * \ingroup module_onlinehelp
+ */
+class HelpManager
+{
+    public:
+        /*! \brief
+         * Creates a manager that uses a given root topic.
+         *
+         * \param[in] rootTopic  Help topic that can be accessed through this
+         *      manager.
+         * \throws    std::bad_alloc if out of memory.
+         *
+         * The provided topic must remain valid for the lifetime of this
+         * manager object.
+         */
+        explicit HelpManager(const HelpTopicInterface &rootTopic);
+        ~HelpManager();
+
+        /*! \brief
+         * Enters a subtopic with the given name under the active topic.
+         *
+         * \param[in] name  Subtopic name to enter.
+         * \throws    std::bad_allod if out of memory.
+         * \throws    InvalidInputError if topic with \p name is not found.
+         */
+        void enterTopic(const char *name);
+
+        /*! \brief
+         * Writes out the help for the currently active topic.
+         *
+         * \param   file  File to write the help text to.
+         * \throws  std::bad_alloc if out of memory.
+         * \throws  FileIOError on any I/O error.
+         */
+        void writeCurrentTopic(File *file) const;
+
+    private:
+        class Impl;
+
+        PrivateImplPointer<Impl> impl_;
+};
+
+} // namespace gmx
+
+#endif
diff --git a/src/gromacs/onlinehelp/helptopic.cpp b/src/gromacs/onlinehelp/helptopic.cpp
new file mode 100644 (file)
index 0000000..05e6700
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ *
+ *                This source code is part of
+ *
+ *                 G   R   O   M   A   C   S
+ *
+ *          GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \internal \file
+ * \brief
+ * Implements classes and functions from helptopic.h.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \ingroup module_onlinehelp
+ */
+#include "helptopic.h"
+
+#include <map>
+#include <utility>
+
+#include "gromacs/onlinehelp/helpformat.h"
+#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/file.h"
+#include "gromacs/utility/format.h"
+#include "gromacs/utility/gmxassert.h"
+
+namespace gmx
+{
+
+/*! \cond libapi */
+void writeBasicHelpTopic(File *file, const HelpTopicInterface &topic,
+                         const std::string &text)
+{
+    const char *title = topic.title();
+    if (title != NULL && title[0] != '\0')
+    {
+        file->writeLine(toUpperCase(title));
+        file->writeLine();
+    }
+    writeHelpTextForConsole(file, text);
+}
+//! \endcond
+
+/********************************************************************
+ * AbstractSimpleHelpTopic
+ */
+
+bool AbstractSimpleHelpTopic::hasSubTopics() const
+{
+    return false;
+}
+
+const HelpTopicInterface *
+AbstractSimpleHelpTopic::findSubTopic(const char *name) const
+{
+    return NULL;
+}
+
+void AbstractSimpleHelpTopic::writeHelp(File *file) const
+{
+    writeBasicHelpTopic(file, *this, helpText());
+}
+
+/********************************************************************
+ * AbstractCompositeHelpTopic::Impl
+ */
+
+/*! \internal \brief
+ * Private implementation class for AbstractCompositeHelpTopic.
+ *
+ * \ingroup module_onlinehelp
+ */
+class AbstractCompositeHelpTopic::Impl
+{
+    public:
+        //! Container for mapping subtopic names to help topic objects.
+        typedef std::map<std::string, HelpTopicPointer> SubTopicMap;
+
+        /*! \brief
+         * Maps subtopic names to help topic objects.
+         *
+         * Owns the contained subtopics.
+         */
+        SubTopicMap             subtopics_;
+};
+
+/********************************************************************
+ * AbstractCompositeHelpTopic
+ */
+
+AbstractCompositeHelpTopic::AbstractCompositeHelpTopic()
+    : impl_(new Impl)
+{
+}
+
+AbstractCompositeHelpTopic::~AbstractCompositeHelpTopic()
+{
+}
+
+bool AbstractCompositeHelpTopic::hasSubTopics() const
+{
+    return !impl_->subtopics_.empty();
+}
+
+const HelpTopicInterface *
+AbstractCompositeHelpTopic::findSubTopic(const char *name) const
+{
+    Impl::SubTopicMap::const_iterator topic = impl_->subtopics_.find(name);
+    if (topic == impl_->subtopics_.end())
+    {
+        return NULL;
+    }
+    return topic->second.get();
+}
+
+void AbstractCompositeHelpTopic::writeHelp(File *file) const
+{
+    writeBasicHelpTopic(file, *this, helpText());
+    writeSubTopicList(file, "\nAvailable subtopics:");
+}
+
+bool
+AbstractCompositeHelpTopic::writeSubTopicList(File *file,
+                                              const std::string &title) const
+{
+    int maxNameLength = 0;
+    Impl::SubTopicMap::const_iterator topic;
+    for (topic = impl_->subtopics_.begin(); topic != impl_->subtopics_.end(); ++topic)
+    {
+        const char *title = topic->second->title();
+        if (title == NULL || title[0] == '\0')
+        {
+            continue;
+        }
+        int nameLength = static_cast<int>(topic->first.length());
+        if (nameLength > maxNameLength)
+        {
+            maxNameLength = nameLength;
+        }
+    }
+    if (maxNameLength == 0)
+    {
+        return false;
+    }
+    TextTableFormatter formatter;
+    formatter.addColumn(NULL, maxNameLength + 1, false);
+    formatter.addColumn(NULL, 72 - maxNameLength, true);
+    formatter.setFirstColumnIndent(4);
+    file->writeLine(title);
+    for (topic = impl_->subtopics_.begin(); topic != impl_->subtopics_.end(); ++topic)
+    {
+        const char *name = topic->first.c_str();
+        const char *title = topic->second->title();
+        if (title != NULL && title[0] != '\0')
+        {
+            formatter.clear();
+            formatter.addColumnLine(0, name);
+            formatter.addColumnLine(1, title);
+            file->writeString(formatter.formatRow());
+        }
+    }
+    return true;
+}
+
+void AbstractCompositeHelpTopic::addSubTopic(HelpTopicPointer topic)
+{
+    GMX_ASSERT(impl_->subtopics_.find(topic->name()) == impl_->subtopics_.end(),
+               "Attempted to register a duplicate help topic name");
+    impl_->subtopics_.insert(std::make_pair(std::string(topic->name()),
+                                            move(topic)));
+}
+
+} // namespace gmx
diff --git a/src/gromacs/onlinehelp/helptopic.h b/src/gromacs/onlinehelp/helptopic.h
new file mode 100644 (file)
index 0000000..8815595
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+ *
+ *                This source code is part of
+ *
+ *                 G   R   O   M   A   C   S
+ *
+ *          GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \libinternal \file
+ * \brief
+ * Declares helper classes and functions for implementing
+ * gmx::HelpTopicInterface.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \inlibraryapi
+ * \ingroup module_onlinehelp
+ */
+#ifndef GMX_ONLINEHELP_HELPTOPIC_H
+#define GMX_ONLINEHELP_HELPTOPIC_H
+
+#include "../utility/format.h"
+#include "../utility/uniqueptr.h"
+
+#include "helptopicinterface.h"
+
+namespace gmx
+{
+
+/*! \cond libapi */
+/*! \libinternal \brief
+ * Helper for writing simple help text.
+ *
+ * \param     file   File to write the help to.
+ * \param[in] topic  Topic to write the help for (used for title).
+ * \param[in] text   Text to write for the topic.
+ *
+ * Formats basic help by writing a title (obtained from \p topic), followed by
+ * \p text with markup substituted and lines properly wrapped.
+ *
+ * \inlibraryapi
+ */
+void writeBasicHelpTopic(File *file, const HelpTopicInterface &topic,
+                         const std::string &text);
+//! \endcond
+
+/*! \libinternal \brief
+ * Abstract base class for help topics that have simple text and no subtopics.
+ *
+ * This class implements subtopic-related methods from HelpTopicInterface such
+ * that there are no subtopics.  writeHelp() is also implemented such that it
+ * uses writeBasicHelpTopic() to write out the text returned by a new virtual
+ * method helpText().
+ *
+ * \see SimpleHelpTopic
+ *
+ * \inlibraryapi
+ * \ingroup module_onlinehelp
+ */
+class AbstractSimpleHelpTopic : public HelpTopicInterface
+{
+    public:
+        virtual const char *name() const = 0;
+        virtual const char *title() const = 0;
+
+        virtual bool hasSubTopics() const;
+        virtual const HelpTopicInterface *findSubTopic(const char *name) const;
+
+        virtual void writeHelp(File *file) const;
+
+    protected:
+        /*! \brief
+         * Returns the help text for this topic.
+         *
+         * writeHelp() calls this method to obtain the actual text to format
+         * for the topic.  Markup substitution etc. is done automatically by
+         * writeHelp().
+         */
+        virtual std::string helpText() const = 0;
+};
+
+/*! \libinternal \brief
+ * Abstract base class for help topics that have simple text and subtopics.
+ *
+ * This class implements an internal container for subtopics and provides
+ * public methods for adding subtopics (as HelpTopicInterface objects).
+ * Subtopic-related methods from HelpTopicInterface are implemented to access
+ * the internal container.  writeHelp() is also implemented such that it
+ * uses writeBasicHelpTopic() to write out the text returned by a new virtual
+ * method helpText(), and a list of subtopics is written after the actual text.
+ *
+ * \see CompositeHelpTopic
+ *
+ * \inlibraryapi
+ * \ingroup module_onlinehelp
+ */
+class AbstractCompositeHelpTopic : public HelpTopicInterface
+{
+    public:
+        AbstractCompositeHelpTopic();
+        virtual ~AbstractCompositeHelpTopic();
+
+        virtual const char *name() const = 0;
+        virtual const char *title() const = 0;
+
+        virtual bool hasSubTopics() const;
+        virtual const HelpTopicInterface *findSubTopic(const char *name) const;
+
+        virtual void writeHelp(File *file) const;
+
+        /*! \brief
+         * Adds a given topic as a subtopic of this topic.
+         *
+         * \param   topic  Topis to add.
+         * \throws  std::bad_alloc if out of memory.
+         *
+         * This topic takes ownership of the object.
+         *
+         * \see registerSubTopic()
+         */
+        void addSubTopic(HelpTopicPointer topic);
+        /*! \brief
+         * Registers a subtopic of a certain type to this topic.
+         *
+         * \tparam  Topic  Type of topic to register.
+         * \throws  std::bad_alloc if out of memory.
+         *
+         * \p Topic must be default-constructible and implement
+         * HelpTopicInterface.
+         *
+         * This method is provided as a convenient alternative to addSubTopic()
+         * for cases where each topic is implemented by a different type
+         * (which is a common case outside unit tests).
+         */
+        template <class Topic>
+        void registerSubTopic()
+        {
+            addSubTopic(HelpTopicPointer(new Topic));
+        }
+
+    protected:
+        //! \copydoc AbstractSimpleHelpTopic::helpText()
+        virtual std::string helpText() const = 0;
+
+        /*! \brief
+         * Writes the list of subtopics.
+         *
+         * \param     file   File to write the list to.
+         * \param[in] title  Title for the written list.
+         * \returns   true if anything was printed.
+         *
+         * Subtopics with empty titles are skipped from the list.
+         * If there would be no subtopics in the list, \p title is not printed
+         * either.
+         *
+         * This method is provided for cases where helpText() does not provide
+         * the needed flexibility and the derived class needs to override
+         * writeHelp().  This method can then be called to print the same
+         * subtopic list that is printed by the default writeHelp()
+         * implementation.
+         */
+        bool writeSubTopicList(File *file, const std::string &title) const;
+
+    private:
+        class Impl;
+
+        PrivateImplPointer<Impl> impl_;
+};
+
+/*! \cond libapi */
+/*! \libinternal \brief
+ * Smart pointer type to manage a AbstractCompositeHelpTopic object.
+ *
+ * \inlibraryapi
+ */
+typedef gmx_unique_ptr<AbstractCompositeHelpTopic>::type
+        CompositeHelpTopicPointer;
+//! \endcond
+
+/*! \libinternal \brief
+ * Template for simple implementation of AbstractSimpleHelpTopic.
+ *
+ * \tparam HelpText Struct that defines the data for the topic.
+ *
+ * \p HelpText should have public static members \c "const char name[]",
+ * \c "const char title[]" and \c "const char *const text[]".
+ *
+ * Typical use:
+ * \code
+struct ExampleHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
+
+const char ExampleHelpText::name[] = "example";
+const char ExampleHelpText::title[] =
+    "Example title";
+const char *const ExampleHelpText::text[] = {
+    "Text for the topic.",
+    "More text for the topic."
+};
+
+typedef SimpleHelpTopic<ExampleHelpText> ExampleHelpTopic;
+ * \endcode
+ *
+ * \inlibraryapi
+ * \ingroup module_onlinehelp
+ */
+template <class HelpText>
+class SimpleHelpTopic : public AbstractSimpleHelpTopic
+{
+    public:
+        virtual const char *name() const
+        {
+            return HelpText::name;
+        }
+        virtual const char *title() const
+        {
+            return HelpText::title;
+        }
+
+    protected:
+        virtual std::string helpText() const
+        {
+            return concatenateStrings(HelpText::text);
+        }
+};
+
+/*! \libinternal \brief
+ * Template for simple implementation of AbstractCompositeHelpTopic.
+ *
+ * \tparam HelpText Struct that defines the data for the topic.
+ *
+ * Used similarly to SimpleHelpTopic.
+ * \p HelpText should satisfy the same criteria as for SimpleHelpTopic.
+ *
+ * \see SimpleHelpTopic
+ *
+ * \inlibraryapi
+ * \ingroup module_onlinehelp
+ */
+template <class HelpText>
+class CompositeHelpTopic : public AbstractCompositeHelpTopic
+{
+    public:
+        virtual const char *name() const
+        {
+            return HelpText::name;
+        }
+        virtual const char *title() const
+        {
+            return HelpText::title;
+        }
+
+    protected:
+        virtual std::string helpText() const
+        {
+            return concatenateStrings(HelpText::text);
+        }
+};
+
+} // namespace gmx
+
+#endif
diff --git a/src/gromacs/onlinehelp/helptopicinterface.h b/src/gromacs/onlinehelp/helptopicinterface.h
new file mode 100644 (file)
index 0000000..c1e71e6
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ *
+ *                This source code is part of
+ *
+ *                 G   R   O   M   A   C   S
+ *
+ *          GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \file
+ * \brief
+ * Declares gmx::HelpTopicInterface.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \inpublicapi
+ * \ingroup module_onlinehelp
+ */
+#ifndef GMX_ONLINEHELP_HELPTOPICINTERFACE_H
+#define GMX_ONLINEHELP_HELPTOPICINTERFACE_H
+
+#include "../utility/uniqueptr.h"
+
+namespace gmx
+{
+
+class File;
+
+/*! \brief
+ * Provides a single online help topic.
+ *
+ * \if libapi
+ * Implementations of these methods should not throw, except that writeHelp()
+ * is allowed to throw on out-of-memory or I/O errors since those it cannot
+ * avoid.
+ *
+ * Header helptopic.h contains classes that implement this interface and make
+ * it simple to write concrete help topic classes.
+ * \endif
+ *
+ * This class is in a public header, and exposed through HelpTopicPointer, but
+ * it is not intended to be used outside the library.  To access a help topic
+ * with public API methods, use HelpManager.
+ *
+ * \inlibraryapi
+ * \ingroup module_onlinehelp
+ */
+class HelpTopicInterface
+{
+    public:
+        virtual ~HelpTopicInterface() {}
+
+        /*! \brief
+         * Returns the name of the topic.
+         *
+         * This should be a single lowercase word, used to identify the topic.
+         * It is not used for the root of the help topic tree.
+         */
+        virtual const char *name() const = 0;
+        /*! \brief
+         * Returns a title for the topic.
+         *
+         * May return NULL, in which case the topic is omitted from normal
+         * subtopic lists and no title is printed by the methods provided in
+         * helptopic.h.
+         */
+        virtual const char *title() const = 0;
+
+        //! Returns whether the topic has any subtopics.
+        virtual bool hasSubTopics() const = 0;
+        /*! \brief
+         * Finds a subtopic by name.
+         *
+         * \param[in] name  Name of subtopic to find.
+         * \returns   Pointer to the found subtopic, or NULL if matching topic
+         *      is not found.
+         */
+        virtual const HelpTopicInterface *findSubTopic(const char *name) const = 0;
+
+        /*! \brief
+         * Prints the help text for this topic.
+         *
+         * \param   file  File to write the help text to.
+         * \throws  std::bad_alloc if out of memory.
+         * \throws  FileIOError on any I/O error.
+         */
+        virtual void writeHelp(File *file) const = 0;
+};
+
+//! Smart pointer type to manage a HelpTopicInterface object.
+typedef gmx_unique_ptr<HelpTopicInterface>::type HelpTopicPointer;
+
+} // namespace gmx
+
+#endif
index 5a8cfd94bb95eba3e0fd874d069a9eba7dc989d4..e778e6159d0dfeecbdeb7d3385a965f5ada856f2 100644 (file)
@@ -1,2 +1,3 @@
 gmx_add_unit_test(OnlineHelpUnitTests onlinehelp-test
-                  helpformat.cpp)
+                  helpformat.cpp
+                  helpmanager.cpp)
diff --git a/src/gromacs/onlinehelp/tests/helpmanager.cpp b/src/gromacs/onlinehelp/tests/helpmanager.cpp
new file mode 100644 (file)
index 0000000..7127966
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ *
+ *                This source code is part of
+ *
+ *                 G   R   O   M   A   C   S
+ *
+ *          GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \internal \file
+ * \brief
+ * Tests for help topic management and help topic formatting.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \ingroup module_onlinehelp
+ */
+#include "gromacs/onlinehelp/helpmanager.h"
+
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "gromacs/onlinehelp/helptopic.h"
+#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/file.h"
+
+#include "testutils/datapath.h"
+#include "testutils/mock_helptopic.h"
+#include "testutils/stringtest.h"
+
+namespace
+{
+
+using gmx::test::MockHelpTopic;
+
+class HelpTestBase : public gmx::test::StringTestBase
+{
+    public:
+        HelpTestBase();
+
+        gmx::test::TestTemporaryFileManager tempFiles_;
+        MockHelpTopic           rootTopic_;
+        gmx::HelpManager        manager_;
+};
+
+HelpTestBase::HelpTestBase()
+    : rootTopic_("", NULL, "Root topic text"),
+      manager_(rootTopic_)
+{
+}
+
+/********************************************************************
+ * Tests for HelpManager
+ */
+
+class HelpManagerTest : public HelpTestBase
+{
+    public:
+        HelpManagerTest();
+
+        gmx::File               helpFile_;
+};
+
+HelpManagerTest::HelpManagerTest()
+    : helpFile_(tempFiles_.getTemporaryFilePath("helptext.txt"), "w")
+{
+}
+
+TEST_F(HelpManagerTest, HandlesRootTopic)
+{
+    EXPECT_CALL(rootTopic_, writeHelp(&helpFile_));
+    manager_.writeCurrentTopic(&helpFile_);
+}
+
+TEST_F(HelpManagerTest, HandlesSubTopics)
+{
+    MockHelpTopic &first =
+        rootTopic_.addSubTopic("first", "First topic", "First topic text");
+    MockHelpTopic &firstSub =
+        first.addSubTopic("firstsub", "First subtopic", "First subtopic text");
+    rootTopic_.addSubTopic("second", "Second topic", "Second topic text");
+
+    EXPECT_CALL(firstSub, writeHelp(&helpFile_));
+    ASSERT_NO_THROW(manager_.enterTopic("first"));
+    ASSERT_NO_THROW(manager_.enterTopic("firstsub"));
+    manager_.writeCurrentTopic(&helpFile_);
+}
+
+TEST_F(HelpManagerTest, HandlesInvalidTopics)
+{
+    MockHelpTopic &first =
+        rootTopic_.addSubTopic("first", "First topic", "First topic text");
+    first.addSubTopic("firstsub", "First subtopic", "First subtopic text");
+    rootTopic_.addSubTopic("second", "Second topic", "Second topic text");
+
+    ASSERT_THROW(manager_.enterTopic("unknown"), gmx::InvalidInputError);
+    ASSERT_NO_THROW(manager_.enterTopic("first"));
+    ASSERT_THROW(manager_.enterTopic("unknown"), gmx::InvalidInputError);
+    ASSERT_THROW(manager_.enterTopic("second"), gmx::InvalidInputError);
+    ASSERT_NO_THROW(manager_.enterTopic("firstsub"));
+}
+
+/********************************************************************
+ * Tests for help topic formatting
+ */
+
+struct TestHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
+
+const char TestHelpText::name[] = "testtopic";
+const char TestHelpText::title[] = "Topic title";
+const char *const TestHelpText::text[] = {
+    "Test topic text.[PAR]",
+    "Another paragraph of text."
+};
+
+class HelpTopicFormattingTest : public HelpTestBase
+{
+    public:
+        void checkHelpFormatting();
+};
+
+void HelpTopicFormattingTest::checkHelpFormatting()
+{
+    std::string filename = tempFiles_.getTemporaryFilePath("helptext.txt");
+    ASSERT_NO_THROW(manager_.enterTopic("testtopic"));
+    gmx::File file(filename, "w");
+    ASSERT_NO_THROW(manager_.writeCurrentTopic(&file));
+    file.close();
+
+    checkFileContents(filename, "HelpText");
+}
+
+TEST_F(HelpTopicFormattingTest, FormatsSimpleTopic)
+{
+    rootTopic_.addSubTopic(gmx::HelpTopicPointer(
+                new gmx::SimpleHelpTopic<TestHelpText>));
+    checkHelpFormatting();
+}
+
+TEST_F(HelpTopicFormattingTest, FormatsCompositeTopicWithSubTopics)
+{
+    gmx::CompositeHelpTopicPointer topic(new gmx::CompositeHelpTopic<TestHelpText>);
+    MockHelpTopic::addSubTopic(topic.get(), "subtopic", "First subtopic", "Text");
+    MockHelpTopic::addSubTopic(topic.get(), "other", "Second subtopic", "Text");
+    rootTopic_.addSubTopic(move(topic));
+    checkHelpFormatting();
+}
+
+} // namespace
diff --git a/src/gromacs/onlinehelp/tests/refdata/HelpTopicFormattingTest_FormatsCompositeTopicWithSubTopics.xml b/src/gromacs/onlinehelp/tests/refdata/HelpTopicFormattingTest_FormatsCompositeTopicWithSubTopics.xml
new file mode 100644 (file)
index 0000000..19de097
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <String Name="HelpText"><![CDATA[
+TOPIC TITLE
+
+Test topic text.
+
+Another paragraph of text.
+
+Available subtopics:
+    other     Second subtopic
+    subtopic  First subtopic
+]]></String>
+</ReferenceData>
diff --git a/src/gromacs/onlinehelp/tests/refdata/HelpTopicFormattingTest_FormatsSimpleTopic.xml b/src/gromacs/onlinehelp/tests/refdata/HelpTopicFormattingTest_FormatsSimpleTopic.xml
new file mode 100644 (file)
index 0000000..8ba2bf5
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <String Name="HelpText"><![CDATA[
+TOPIC TITLE
+
+Test topic text.
+
+Another paragraph of text.
+]]></String>
+</ReferenceData>
index 60798afa4ffb4c5ad5cbd4a16d1f953f5b576576..0385ca1a1b4f5433fd670ea87d368989fed52b57 100644 (file)
@@ -452,9 +452,9 @@ union yyalloc
 /* YYNNTS -- Number of nonterminals.  */
 #define YYNNTS  26
 /* YYNRULES -- Number of rules.  */
-#define YYNRULES  91
+#define YYNRULES  90
 /* YYNRULES -- Number of states.  */
-#define YYNSTATES  150
+#define YYNSTATES  149
 
 /* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX.  */
 #define YYUNDEFTOK  2
@@ -501,18 +501,18 @@ static const yytype_uint8 yytranslate[] =
 #if YYDEBUG
 /* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in
    YYRHS.  */
-static const yytype_uint16 yyprhs[] =
+static const yytype_uint8 yyprhs[] =
 {
        0,     0,     3,     4,     7,    10,    13,    14,    16,    18,
-      20,    22,    25,    29,    33,    37,    39,    41,    44,    47,
-      49,    51,    55,    59,    61,    64,    66,    69,    71,    73,
-      75,    77,    80,    84,    88,    92,    96,    99,   102,   104,
-     106,   109,   113,   117,   121,   123,   125,   128,   132,   136,
-     140,   144,   148,   151,   155,   159,   161,   164,   172,   176,
-     179,   183,   185,   187,   189,   191,   194,   195,   198,   201,
-     202,   204,   208,   210,   213,   217,   219,   223,   225,   228,
-     232,   234,   236,   238,   240,   242,   244,   246,   248,   250,
-     254,   258
+      20,    22,    25,    29,    33,    37,    40,    41,    44,    46,
+      48,    52,    56,    58,    61,    63,    66,    68,    70,    72,
+      74,    77,    81,    85,    89,    93,    96,    99,   101,   103,
+     106,   110,   114,   118,   120,   122,   125,   129,   133,   137,
+     141,   145,   148,   152,   156,   158,   161,   169,   173,   176,
+     180,   182,   184,   186,   188,   191,   192,   195,   198,   199,
+     201,   205,   207,   210,   214,   216,   220,   222,   225,   229,
+     231,   233,   235,   237,   239,   241,   243,   245,   247,   251,
+     255
 };
 
 /* YYRHS -- A `-1'-separated list of the rules' RHS.  */
@@ -521,45 +521,44 @@ static const yytype_int8 yyrhs[] =
       50,     0,    -1,    -1,    50,    51,    -1,    52,    10,    -1,
        1,    10,    -1,    -1,    53,    -1,     6,    -1,    59,    -1,
       55,    -1,    59,    55,    -1,     9,    41,    60,    -1,     9,
-      41,    62,    -1,     9,    41,    64,    -1,     4,    -1,    54,
-      -1,     4,     5,    -1,    54,     5,    -1,    64,    -1,    60,
-      -1,    42,    55,    43,    -1,    55,    23,    65,    -1,     6,
-      -1,    35,     6,    -1,     7,    -1,    35,     7,    -1,    56,
-      -1,    57,    -1,     8,    -1,     9,    -1,    33,    60,    -1,
-      60,    32,    60,    -1,    60,    31,    60,    -1,    42,    60,
-      43,    -1,    62,    28,    62,    -1,    11,    59,    -1,    11,
-       6,    -1,    24,    -1,    18,    -1,    61,    19,    -1,    61,
-      17,    70,    -1,    61,    16,    70,    -1,    61,    21,    65,
-      -1,     6,    -1,     7,    -1,    61,    16,    -1,    61,    20,
-      65,    -1,    62,    34,    62,    -1,    62,    35,    62,    -1,
-      62,    36,    62,    -1,    62,    37,    62,    -1,    35,    62,
-      -1,    62,    39,    62,    -1,    42,    62,    43,    -1,    59,
-      -1,    61,    17,    -1,    44,    58,    45,    58,    45,    58,
-      46,    -1,    42,    64,    43,    -1,    22,    65,    -1,    18,
-      27,    60,    -1,    14,    -1,    13,    -1,    15,    -1,    66,
-      -1,    66,    26,    -1,    -1,    66,    67,    -1,    25,    68,
-      -1,    -1,    69,    -1,    47,    69,    48,    -1,    72,    -1,
-      69,    72,    -1,    69,    45,    72,    -1,    71,    -1,    47,
-      71,    48,    -1,    73,    -1,    71,    73,    -1,    71,    45,
-      73,    -1,    60,    -1,    64,    -1,    62,    -1,    63,    -1,
-      74,    -1,    56,    -1,    57,    -1,    59,    -1,    74,    -1,
-      56,    12,    56,    -1,    56,    12,    57,    -1,    57,    12,
-      58,    -1
+      41,    62,    -1,     9,    41,    64,    -1,     4,    54,    -1,
+      -1,    54,     5,    -1,    64,    -1,    60,    -1,    42,    55,
+      43,    -1,    55,    23,    65,    -1,     6,    -1,    35,     6,
+      -1,     7,    -1,    35,     7,    -1,    56,    -1,    57,    -1,
+       8,    -1,     9,    -1,    33,    60,    -1,    60,    32,    60,
+      -1,    60,    31,    60,    -1,    42,    60,    43,    -1,    62,
+      28,    62,    -1,    11,    59,    -1,    11,     6,    -1,    24,
+      -1,    18,    -1,    61,    19,    -1,    61,    17,    70,    -1,
+      61,    16,    70,    -1,    61,    21,    65,    -1,     6,    -1,
+       7,    -1,    61,    16,    -1,    61,    20,    65,    -1,    62,
+      34,    62,    -1,    62,    35,    62,    -1,    62,    36,    62,
+      -1,    62,    37,    62,    -1,    35,    62,    -1,    62,    39,
+      62,    -1,    42,    62,    43,    -1,    59,    -1,    61,    17,
+      -1,    44,    58,    45,    58,    45,    58,    46,    -1,    42,
+      64,    43,    -1,    22,    65,    -1,    18,    27,    60,    -1,
+      14,    -1,    13,    -1,    15,    -1,    66,    -1,    66,    26,
+      -1,    -1,    66,    67,    -1,    25,    68,    -1,    -1,    69,
+      -1,    47,    69,    48,    -1,    72,    -1,    69,    72,    -1,
+      69,    45,    72,    -1,    71,    -1,    47,    71,    48,    -1,
+      73,    -1,    71,    73,    -1,    71,    45,    73,    -1,    60,
+      -1,    64,    -1,    62,    -1,    63,    -1,    74,    -1,    56,
+      -1,    57,    -1,    59,    -1,    74,    -1,    56,    12,    56,
+      -1,    56,    12,    57,    -1,    57,    12,    58,    -1
 };
 
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
 static const yytype_uint16 yyrline[] =
 {
-       0,   192,   192,   193,   202,   203,   223,   227,   228,   237,
-     247,   249,   251,   253,   255,   261,   262,   265,   266,   270,
-     271,   276,   277,   289,   290,   294,   295,   298,   299,   302,
-     303,   311,   317,   323,   335,   339,   347,   353,   361,   362,
-     366,   371,   376,   384,   396,   403,   413,   418,   426,   428,
-     430,   432,   434,   436,   438,   445,   452,   464,   469,   473,
-     481,   492,   496,   500,   509,   511,   516,   517,   522,   529,
-     530,   531,   535,   536,   538,   543,   544,   548,   549,   551,
-     555,   557,   559,   561,   563,   567,   572,   577,   582,   586,
-     591,   596
+       0,   194,   194,   195,   204,   205,   225,   229,   230,   239,
+     249,   251,   253,   255,   257,   263,   267,   268,   276,   277,
+     282,   283,   295,   296,   300,   301,   304,   305,   308,   309,
+     317,   323,   329,   341,   345,   353,   359,   367,   368,   372,
+     377,   382,   390,   402,   409,   419,   424,   432,   434,   436,
+     438,   440,   442,   444,   451,   458,   470,   475,   479,   487,
+     498,   502,   506,   515,   517,   522,   523,   528,   535,   536,
+     537,   541,   542,   544,   549,   550,   554,   555,   557,   561,
+     563,   565,   567,   569,   573,   578,   583,   588,   592,   597,
+     602
 };
 #endif
 
@@ -602,30 +601,30 @@ static const yytype_uint16 yytoknum[] =
 static const yytype_uint8 yyr1[] =
 {
        0,    49,    50,    50,    51,    51,    52,    52,    52,    52,
-      52,    52,    52,    52,    52,    53,    53,    54,    54,    55,
-      55,    55,    55,    56,    56,    57,    57,    58,    58,    59,
-      59,    60,    60,    60,    60,    60,    60,    60,    61,    61,
-      60,    60,    60,    60,    62,    62,    62,    62,    62,    62,
-      62,    62,    62,    62,    62,    63,    63,    64,    64,    64,
-      64,    60,    62,    64,    65,    65,    66,    66,    67,    68,
-      68,    68,    69,    69,    69,    70,    70,    71,    71,    71,
-      72,    72,    72,    72,    72,    73,    73,    73,    73,    74,
-      74,    74
+      52,    52,    52,    52,    52,    53,    54,    54,    55,    55,
+      55,    55,    56,    56,    57,    57,    58,    58,    59,    59,
+      60,    60,    60,    60,    60,    60,    60,    61,    61,    60,
+      60,    60,    60,    62,    62,    62,    62,    62,    62,    62,
+      62,    62,    62,    62,    63,    63,    64,    64,    64,    64,
+      60,    62,    64,    65,    65,    66,    66,    67,    68,    68,
+      68,    69,    69,    69,    70,    70,    71,    71,    71,    72,
+      72,    72,    72,    72,    73,    73,    73,    73,    74,    74,
+      74
 };
 
 /* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN.  */
 static const yytype_uint8 yyr2[] =
 {
        0,     2,     0,     2,     2,     2,     0,     1,     1,     1,
-       1,     2,     3,     3,     3,     1,     1,     2,     2,     1,
-       1,     3,     3,     1,     2,     1,     2,     1,     1,     1,
-       1,     2,     3,     3,     3,     3,     2,     2,     1,     1,
-       2,     3,     3,     3,     1,     1,     2,     3,     3,     3,
-       3,     3,     2,     3,     3,     1,     2,     7,     3,     2,
-       3,     1,     1,     1,     1,     2,     0,     2,     2,     0,
-       1,     3,     1,     2,     3,     1,     3,     1,     2,     3,
-       1,     1,     1,     1,     1,     1,     1,     1,     1,     3,
-       3,     3
+       1,     2,     3,     3,     3,     2,     0,     2,     1,     1,
+       3,     3,     1,     2,     1,     2,     1,     1,     1,     1,
+       2,     3,     3,     3,     3,     2,     2,     1,     1,     2,
+       3,     3,     3,     1,     1,     2,     3,     3,     3,     3,
+       3,     2,     3,     3,     1,     2,     7,     3,     2,     3,
+       1,     1,     1,     1,     2,     0,     2,     2,     0,     1,
+       3,     1,     2,     3,     1,     3,     1,     2,     3,     1,
+       1,     1,     1,     1,     1,     1,     1,     1,     3,     3,
+       3
 };
 
 /* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state
@@ -633,156 +632,156 @@ static const yytype_uint8 yyr2[] =
    means the default is an error.  */
 static const yytype_uint8 yydefact[] =
 {
-       2,     0,     1,     0,    15,    44,    45,    29,    30,     0,
-      62,    61,    63,    39,    66,    38,     0,     0,     0,     0,
-       3,     0,     7,    16,    10,     9,    20,     0,     0,    19,
-       5,    17,     0,    37,    30,    36,     0,    59,    64,    44,
-      39,     0,    31,     0,     0,    52,     0,    20,     0,    19,
-      23,    25,     0,    27,    28,     0,     4,    18,    66,    11,
-       0,     0,    46,     0,    40,    66,    66,     0,     0,     0,
-       0,     0,     0,     0,    12,    13,    14,    60,    69,    65,
-      67,     0,     0,    46,    21,    34,    54,    58,    24,    26,
-       0,    22,    33,    32,     0,    85,    86,    87,    42,    75,
-      77,    88,    41,    47,    43,    35,    48,    49,    50,    51,
-      53,     0,    44,    45,     0,     0,     0,     0,    55,    80,
-       0,    82,    83,    81,    68,    70,    72,    84,     0,     0,
-       0,     0,     0,    78,    44,    45,     0,    56,     0,    73,
-       0,    76,    89,    90,    91,    79,    71,    74,     0,    57
+       2,     0,     1,     0,    16,    43,    44,    28,    29,     0,
+      61,    60,    62,    38,    65,    37,     0,     0,     0,     0,
+       3,     0,     7,    10,     9,    19,     0,     0,    18,     5,
+      15,     0,    36,    29,    35,     0,    58,    63,    43,    38,
+       0,    30,     0,     0,    51,     0,    19,     0,    18,    22,
+      24,     0,    26,    27,     0,     4,    65,    11,     0,     0,
+      45,     0,    39,    65,    65,     0,     0,     0,     0,     0,
+       0,    17,     0,    12,    13,    14,    59,    68,    64,    66,
+       0,     0,    45,    20,    33,    53,    57,    23,    25,     0,
+      21,    32,    31,     0,    84,    85,    86,    41,    74,    76,
+      87,    40,    46,    42,    34,    47,    48,    49,    50,    52,
+       0,    43,    44,     0,     0,     0,     0,    54,    79,     0,
+      81,    82,    80,    67,    69,    71,    83,     0,     0,     0,
+       0,     0,    77,    43,    44,     0,    55,     0,    72,     0,
+      75,    88,    89,    90,    78,    70,    73,     0,    56
 };
 
 /* YYDEFGOTO[NTERM-NUM].  */
 static const yytype_int8 yydefgoto[] =
 {
-      -1,     1,    20,    21,    22,    23,    24,    95,    96,    55,
-      97,   119,    27,    28,   122,   123,    37,    38,    80,   124,
-     125,   102,    99,   126,   100,   101
+      -1,     1,    20,    21,    22,    30,    23,    94,    95,    54,
+      96,   118,    26,    27,   121,   122,    36,    37,    79,   123,
+     124,   101,    98,   125,    99,   100
 };
 
 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
    STATE-NUM.  */
-#define YYPACT_NINF -93
+#define YYPACT_NINF -72
 static const yytype_int16 yypact[] =
 {
-     -93,   155,   -93,    10,    19,    26,   -93,   -93,    -3,    73,
-     -93,   -93,   -93,    22,   -93,   -93,   356,   372,   317,    11,
-     -93,    79,   -93,    86,    70,   317,    29,   384,   180,   -93,
-     -93,   -93,   342,   -93,   -93,   -93,   356,   -93,     6,   -93,
-     -93,   356,   -93,   372,   -10,    57,   -20,   -17,   256,    54,
-     -93,   -93,    88,   -93,   -93,    55,   -93,   -93,   -93,    70,
-     356,   356,   197,   174,   -93,   -93,   -93,   372,   372,   372,
-     372,   372,   372,   342,    29,   180,   -93,    29,   221,   -93,
-     -93,   -17,   223,   -93,   -93,   -93,   -93,   -93,   -93,   -93,
-      11,   -93,    69,   -93,    78,    90,    94,   -93,   -93,   244,
-     -93,   -93,   -93,   -93,   -93,   267,    36,    36,    57,    57,
-      57,    54,    95,    96,   375,   303,    90,    94,   -93,    29,
-     392,   267,   -93,   -93,   -93,   263,   -93,   -93,    71,    35,
-      11,    11,    78,   -93,   105,   106,   178,   174,   303,   -93,
-      11,   -93,   -93,   -93,   -93,   -93,   -93,   -93,    80,   -93
+     -72,   157,   -72,     4,   -72,     8,   -72,   -72,   -21,   116,
+     -72,   -72,   -72,    20,   -72,   -72,   359,   243,   320,     6,
+     -72,    25,   -72,    14,   320,    31,   286,   258,   -72,   -72,
+      76,   345,   -72,   -72,   -72,   359,   -72,    52,   -72,   -72,
+     359,   -72,   243,    18,    46,   -20,   -22,    75,    56,   -72,
+     -72,    84,   -72,   -72,    50,   -72,   -72,    14,   359,   359,
+      -2,   176,   -72,   -72,   -72,   243,   243,   243,   243,   243,
+     243,   -72,   345,    31,   258,   -72,    31,   224,   -72,   -72,
+     -22,   361,   -72,   -72,   -72,   -72,   -72,   -72,   -72,     6,
+     -72,    69,   -72,    23,   108,   117,   -72,   -72,   210,   -72,
+     -72,   -72,   -72,   -72,   114,    68,    68,    46,    46,    46,
+      56,   118,   123,   375,   306,   108,   117,   -72,    31,   386,
+     114,   -72,   -72,   -72,   266,   -72,   -72,    83,   199,     6,
+       6,    23,   -72,   131,   132,   180,   176,   306,   -72,     6,
+     -72,   -72,   -72,   -72,   -72,   -72,   -72,    99,   -72
 };
 
 /* YYPGOTO[NTERM-NUM].  */
 static const yytype_int8 yypgoto[] =
 {
-     -93,   -93,   -93,   -93,   -93,   -93,   -13,     0,    14,   -81,
-      -1,    87,    -4,   -16,   -93,     3,   -36,   -93,   -93,   -93,
-      12,    81,    39,   -91,   -92,   -67
+     -72,   -72,   -72,   -72,   -72,   -72,    -7,     3,    17,   -46,
+      -1,    24,     2,   -16,   -72,    15,    10,   -72,   -72,   -72,
+      41,   100,    66,   -35,   -71,   -49
 };
 
 /* YYTABLE[YYPACT[STATE-NUM]].  What to do in state STATE-NUM.  If
    positive, shift that token.  If negative, reduce the rule which
    number is the opposite.  If zero, do what YYDEFACT says.
    If YYTABLE_NINF, syntax error.  */
-#define YYTABLE_NINF -27
+#define YYTABLE_NINF -26
 static const yytype_int16 yytable[] =
 {
-      25,    45,    48,    58,    29,    46,    83,   133,    35,   128,
-      65,   127,    59,    44,    60,    61,    75,    50,    51,    53,
-      30,    49,    91,    84,    31,    48,    85,    82,    29,   103,
-     104,    78,    79,    54,   139,    76,    -8,   133,    32,    44,
-     145,    50,    51,     7,    34,   139,    52,   147,   127,    36,
-     144,   105,   106,   107,   108,   109,   110,    48,   127,   148,
-      60,    61,   121,    44,    44,    44,    44,    44,    44,   127,
-      52,   127,    70,    71,   120,    72,   111,   118,   116,    33,
-     132,     7,    34,   141,    50,    51,     7,    34,    26,    56,
-      53,    57,   117,    58,    88,    89,    72,    87,    45,   121,
-      90,    61,   130,    42,    54,    47,   131,   -23,   -25,   121,
-      44,   120,    26,    52,   118,   116,   140,   -24,   -26,    74,
-     121,   120,   121,    77,   118,   116,   149,   136,    81,   117,
-     142,    53,   120,   129,   120,   118,   116,   118,   116,   117,
-      53,     0,     0,    98,   143,    54,     0,    92,    93,     0,
-     117,     0,   117,     0,    54,     2,     3,     0,     0,     4,
-      81,     5,     6,     7,     8,    -6,     9,     0,    10,    11,
-      12,     0,     0,    13,     0,     0,     0,    14,     0,    15,
-      50,    51,     7,    34,   112,   113,     7,    34,    16,     9,
-      17,    10,    11,    12,     0,     0,    13,    18,     0,    19,
-      14,     0,    15,    50,    51,     7,    34,     0,    67,    52,
-       0,    16,     0,   114,    68,    69,    70,    71,     0,    72,
-      73,    94,    19,   138,     0,     0,   146,   112,   113,     7,
-      34,     0,     9,     0,    10,    11,    12,     0,     0,    13,
-       0,     0,     0,    14,    94,    15,     0,     0,     0,     0,
-      50,    51,     7,    34,    16,     0,   114,    68,    69,    70,
-      71,     0,    72,    73,     0,    19,    86,     0,   115,   112,
-     113,     7,    34,     0,     9,     0,    10,    11,    12,    52,
-       0,    13,     0,     0,    67,    14,     0,    15,     0,   132,
-      68,    69,    70,    71,     0,    72,    16,     0,   114,    86,
-       0,    68,    69,    70,    71,    73,    72,    19,   138,   112,
-     113,     7,    34,     0,     9,     0,    10,    11,    12,     0,
-       0,    13,     0,    39,     6,    14,     0,    15,     9,     0,
-      10,    11,    12,     0,     0,    13,    16,     0,   114,    14,
-       0,    15,     0,     0,     0,    73,     0,    19,    39,     6,
-      16,     0,    17,     9,     0,    10,    11,    12,     0,    18,
-      13,    19,    39,     6,    14,     0,    15,     9,     0,    10,
-      11,     0,     0,     0,    40,    16,     0,    17,    39,     6,
-      15,   134,   135,     0,    73,    10,    19,     0,    10,    16,
-      40,    17,     0,    40,     0,     0,    15,     0,    41,    15,
-      62,    63,     0,    64,    65,    66,     0,    17,    62,   137,
-      17,    64,    65,    66,    43,     0,     0,    43
+      24,    44,    47,    56,    49,    50,     7,    33,    34,    58,
+      59,    45,    49,    50,    29,    74,    28,    57,    -8,    43,
+      31,    84,    52,    83,    47,    25,    81,   132,   126,    49,
+      50,     7,    33,    48,    82,    55,    53,    56,    63,    28,
+      41,    51,    46,   127,    43,    93,    75,    35,    25,   104,
+     105,   106,   107,   108,   109,    73,    47,   132,    51,    76,
+     144,   120,    58,    59,    80,   126,    90,    43,    43,    43,
+      43,    43,    43,   102,   103,   126,   117,    77,    78,   119,
+     115,    71,    91,    92,   143,    70,   126,   110,   126,   138,
+      87,    88,    52,   147,   116,    89,    80,    44,   120,    86,
+     138,    59,   146,    65,    68,    69,    53,    70,   120,    66,
+      67,    68,    69,   117,    70,    43,   119,   115,    85,   120,
+     129,   120,    32,   117,     7,    33,   119,   115,   139,   130,
+     -22,   116,   141,    52,   117,   -24,   117,   119,   115,   119,
+     115,   116,    52,   -23,   -25,   148,   142,    53,    66,    67,
+      68,    69,   116,    70,   116,   135,    53,     2,     3,   128,
+      97,     4,     0,     5,     6,     7,     8,    -6,     9,     0,
+      10,    11,    12,     0,     0,    13,     0,     0,     0,    14,
+       0,    15,    49,    50,     7,    33,   111,   112,     7,    33,
+      16,     9,    17,    10,    11,    12,     0,     0,    13,    18,
+       0,    19,    14,     0,    15,    49,    50,     7,    33,     0,
+       0,    51,     0,    16,     0,   113,    49,    50,     7,    33,
+       0,     0,    72,    93,    19,   137,     0,     0,   145,     0,
+     111,   112,     7,    33,    51,     9,     0,    10,    11,    12,
+       0,     0,    13,     0,   131,    51,    14,   140,    15,    38,
+       6,     0,     0,     0,     0,   131,    10,    16,     0,   113,
+       0,    39,     0,     0,     0,     0,    72,    15,    19,     0,
+       0,   114,   111,   112,     7,    33,     0,     9,    17,    10,
+      11,    12,     0,     0,    13,    42,    65,     0,    14,     0,
+      15,     0,    66,    67,    68,    69,     0,    70,     0,    16,
+       0,   113,    60,    61,     0,    62,    63,    64,    72,     0,
+      19,   137,   111,   112,     7,    33,     0,     9,     0,    10,
+      11,    12,     0,     0,    13,     0,    38,     6,    14,     0,
+      15,     9,     0,    10,    11,    12,     0,     0,    13,    16,
+       0,   113,    14,     0,    15,     0,     0,     0,    72,     0,
+      19,    38,     6,    16,     0,    17,     9,     0,    10,    11,
+      12,     0,    18,    13,    19,    38,     6,    14,     0,    15,
+       9,     0,    10,    11,     0,     0,     0,    39,    16,     0,
+      17,   133,   134,    15,     0,     0,     0,    72,    10,    19,
+       0,     0,    16,    39,    17,    66,    67,    68,    69,    15,
+      70,    40,    60,   136,    85,    62,    63,    64,     0,     0,
+      17,     0,     0,     0,     0,     0,     0,    42
 };
 
 static const yytype_int16 yycheck[] =
 {
-       1,    17,    18,    23,     1,    18,    16,    99,     9,    90,
-      20,    78,    25,    17,    31,    32,    32,     6,     7,    19,
-      10,    18,    58,    43,     5,    41,    43,    43,    25,    65,
-      66,    25,    26,    19,   125,    32,    10,   129,    41,    43,
-     132,     6,     7,     8,     9,   136,    35,   138,   115,    27,
-     131,    67,    68,    69,    70,    71,    72,    73,   125,   140,
-      31,    32,    78,    67,    68,    69,    70,    71,    72,   136,
-      35,   138,    36,    37,    78,    39,    73,    78,    78,     6,
-      45,     8,     9,    48,     6,     7,     8,     9,     1,    10,
-      90,     5,    78,    23,     6,     7,    39,    43,   114,   115,
-      45,    32,    12,    16,    90,    18,    12,    12,    12,   125,
-     114,   115,    25,    35,   115,   115,    45,    12,    12,    32,
-     136,   125,   138,    36,   125,   125,    46,   115,    41,   115,
-     130,   131,   136,    94,   138,   136,   136,   138,   138,   125,
-     140,    -1,    -1,    62,   130,   131,    -1,    60,    61,    -1,
-     136,    -1,   138,    -1,   140,     0,     1,    -1,    -1,     4,
-      73,     6,     7,     8,     9,    10,    11,    -1,    13,    14,
-      15,    -1,    -1,    18,    -1,    -1,    -1,    22,    -1,    24,
-       6,     7,     8,     9,     6,     7,     8,     9,    33,    11,
-      35,    13,    14,    15,    -1,    -1,    18,    42,    -1,    44,
-      22,    -1,    24,     6,     7,     8,     9,    -1,    28,    35,
-      -1,    33,    -1,    35,    34,    35,    36,    37,    -1,    39,
-      42,    47,    44,    45,    -1,    -1,    48,     6,     7,     8,
-       9,    -1,    11,    -1,    13,    14,    15,    -1,    -1,    18,
-      -1,    -1,    -1,    22,    47,    24,    -1,    -1,    -1,    -1,
-       6,     7,     8,     9,    33,    -1,    35,    34,    35,    36,
-      37,    -1,    39,    42,    -1,    44,    43,    -1,    47,     6,
-       7,     8,     9,    -1,    11,    -1,    13,    14,    15,    35,
-      -1,    18,    -1,    -1,    28,    22,    -1,    24,    -1,    45,
-      34,    35,    36,    37,    -1,    39,    33,    -1,    35,    43,
-      -1,    34,    35,    36,    37,    42,    39,    44,    45,     6,
-       7,     8,     9,    -1,    11,    -1,    13,    14,    15,    -1,
-      -1,    18,    -1,     6,     7,    22,    -1,    24,    11,    -1,
-      13,    14,    15,    -1,    -1,    18,    33,    -1,    35,    22,
-      -1,    24,    -1,    -1,    -1,    42,    -1,    44,     6,     7,
-      33,    -1,    35,    11,    -1,    13,    14,    15,    -1,    42,
-      18,    44,     6,     7,    22,    -1,    24,    11,    -1,    13,
-      14,    -1,    -1,    -1,    18,    33,    -1,    35,     6,     7,
-      24,     6,     7,    -1,    42,    13,    44,    -1,    13,    33,
-      18,    35,    -1,    18,    -1,    -1,    24,    -1,    42,    24,
-      16,    17,    -1,    19,    20,    21,    -1,    35,    16,    17,
-      35,    19,    20,    21,    42,    -1,    -1,    42
+       1,    17,    18,    23,     6,     7,     8,     9,     9,    31,
+      32,    18,     6,     7,    10,    31,     1,    24,    10,    17,
+      41,    43,    19,    43,    40,     1,    42,    98,    77,     6,
+       7,     8,     9,    18,    16,    10,    19,    23,    20,    24,
+      16,    35,    18,    89,    42,    47,    31,    27,    24,    65,
+      66,    67,    68,    69,    70,    31,    72,   128,    35,    35,
+     131,    77,    31,    32,    40,   114,    56,    65,    66,    67,
+      68,    69,    70,    63,    64,   124,    77,    25,    26,    77,
+      77,     5,    58,    59,   130,    39,   135,    72,   137,   124,
+       6,     7,    89,   139,    77,    45,    72,   113,   114,    43,
+     135,    32,   137,    28,    36,    37,    89,    39,   124,    34,
+      35,    36,    37,   114,    39,   113,   114,   114,    43,   135,
+      12,   137,     6,   124,     8,     9,   124,   124,    45,    12,
+      12,   114,   129,   130,   135,    12,   137,   135,   135,   137,
+     137,   124,   139,    12,    12,    46,   129,   130,    34,    35,
+      36,    37,   135,    39,   137,   114,   139,     0,     1,    93,
+      60,     4,    -1,     6,     7,     8,     9,    10,    11,    -1,
+      13,    14,    15,    -1,    -1,    18,    -1,    -1,    -1,    22,
+      -1,    24,     6,     7,     8,     9,     6,     7,     8,     9,
+      33,    11,    35,    13,    14,    15,    -1,    -1,    18,    42,
+      -1,    44,    22,    -1,    24,     6,     7,     8,     9,    -1,
+      -1,    35,    -1,    33,    -1,    35,     6,     7,     8,     9,
+      -1,    -1,    42,    47,    44,    45,    -1,    -1,    48,    -1,
+       6,     7,     8,     9,    35,    11,    -1,    13,    14,    15,
+      -1,    -1,    18,    -1,    45,    35,    22,    48,    24,     6,
+       7,    -1,    -1,    -1,    -1,    45,    13,    33,    -1,    35,
+      -1,    18,    -1,    -1,    -1,    -1,    42,    24,    44,    -1,
+      -1,    47,     6,     7,     8,     9,    -1,    11,    35,    13,
+      14,    15,    -1,    -1,    18,    42,    28,    -1,    22,    -1,
+      24,    -1,    34,    35,    36,    37,    -1,    39,    -1,    33,
+      -1,    35,    16,    17,    -1,    19,    20,    21,    42,    -1,
+      44,    45,     6,     7,     8,     9,    -1,    11,    -1,    13,
+      14,    15,    -1,    -1,    18,    -1,     6,     7,    22,    -1,
+      24,    11,    -1,    13,    14,    15,    -1,    -1,    18,    33,
+      -1,    35,    22,    -1,    24,    -1,    -1,    -1,    42,    -1,
+      44,     6,     7,    33,    -1,    35,    11,    -1,    13,    14,
+      15,    -1,    42,    18,    44,     6,     7,    22,    -1,    24,
+      11,    -1,    13,    14,    -1,    -1,    -1,    18,    33,    -1,
+      35,     6,     7,    24,    -1,    -1,    -1,    42,    13,    44,
+      -1,    -1,    33,    18,    35,    34,    35,    36,    37,    24,
+      39,    42,    16,    17,    43,    19,    20,    21,    -1,    -1,
+      35,    -1,    -1,    -1,    -1,    -1,    -1,    42
 };
 
 /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
@@ -791,19 +790,19 @@ static const yytype_uint8 yystos[] =
 {
        0,    50,     0,     1,     4,     6,     7,     8,     9,    11,
       13,    14,    15,    18,    22,    24,    33,    35,    42,    44,
-      51,    52,    53,    54,    55,    59,    60,    61,    62,    64,
-      10,     5,    41,     6,     9,    59,    27,    65,    66,     6,
-      18,    42,    60,    42,    61,    62,    55,    60,    62,    64,
-       6,     7,    35,    56,    57,    58,    10,     5,    23,    55,
-      31,    32,    16,    17,    19,    20,    21,    28,    34,    35,
-      36,    37,    39,    42,    60,    62,    64,    60,    25,    26,
-      67,    60,    62,    16,    43,    43,    43,    43,     6,     7,
-      45,    65,    60,    60,    47,    56,    57,    59,    70,    71,
-      73,    74,    70,    65,    65,    62,    62,    62,    62,    62,
-      62,    64,     6,     7,    35,    47,    56,    57,    59,    60,
-      61,    62,    63,    64,    68,    69,    72,    74,    58,    71,
-      12,    12,    45,    73,     6,     7,    69,    17,    45,    72,
-      45,    48,    56,    57,    58,    73,    48,    72,    58,    46
+      51,    52,    53,    55,    59,    60,    61,    62,    64,    10,
+      54,    41,     6,     9,    59,    27,    65,    66,     6,    18,
+      42,    60,    42,    61,    62,    55,    60,    62,    64,     6,
+       7,    35,    56,    57,    58,    10,    23,    55,    31,    32,
+      16,    17,    19,    20,    21,    28,    34,    35,    36,    37,
+      39,     5,    42,    60,    62,    64,    60,    25,    26,    67,
+      60,    62,    16,    43,    43,    43,    43,     6,     7,    45,
+      65,    60,    60,    47,    56,    57,    59,    70,    71,    73,
+      74,    70,    65,    65,    62,    62,    62,    62,    62,    62,
+      64,     6,     7,    35,    47,    56,    57,    59,    60,    61,
+      62,    63,    64,    68,    69,    72,    74,    58,    71,    12,
+      12,    45,    73,     6,     7,    69,    17,    45,    72,    45,
+      48,    56,    57,    58,    73,    48,    72,    58,    46
 };
 
 #define yyerrok                (yyerrstatus = 0)
@@ -1318,119 +1317,124 @@ yydestruct (yymsg, yytype, yyvaluep, scanner)
   switch (yytype)
     {
       case 5: /* "HELP_TOPIC" */
-#line 171 "parser.y"
+#line 172 "parser.y"
        { free((yyvaluep->str));                     };
-#line 1324 "parser.cpp"
+#line 1323 "parser.cpp"
        break;
       case 8: /* "STR" */
-#line 171 "parser.y"
+#line 172 "parser.y"
        { free((yyvaluep->str));                     };
-#line 1329 "parser.cpp"
+#line 1328 "parser.cpp"
        break;
       case 9: /* "IDENTIFIER" */
-#line 171 "parser.y"
+#line 172 "parser.y"
        { free((yyvaluep->str));                     };
-#line 1334 "parser.cpp"
+#line 1333 "parser.cpp"
        break;
       case 25: /* "PARAM" */
-#line 172 "parser.y"
+#line 173 "parser.y"
        { if((yyvaluep->str)) free((yyvaluep->str));              };
-#line 1339 "parser.cpp"
+#line 1338 "parser.cpp"
        break;
       case 28: /* "CMP_OP" */
-#line 171 "parser.y"
+#line 172 "parser.y"
        { free((yyvaluep->str));                     };
-#line 1344 "parser.cpp"
+#line 1343 "parser.cpp"
        break;
       case 51: /* "command" */
-#line 173 "parser.y"
+#line 174 "parser.y"
        { if((yyvaluep->sel)) _gmx_selelem_free((yyvaluep->sel)); };
-#line 1349 "parser.cpp"
+#line 1348 "parser.cpp"
        break;
       case 52: /* "cmd_plain" */
-#line 173 "parser.y"
+#line 174 "parser.y"
        { if((yyvaluep->sel)) _gmx_selelem_free((yyvaluep->sel)); };
-#line 1354 "parser.cpp"
+#line 1353 "parser.cpp"
+       break;
+      case 54: /* "help_topic" */
+#line 180 "parser.y"
+       { _gmx_selexpr_free_values((yyvaluep->val)); };
+#line 1358 "parser.cpp"
        break;
       case 55: /* "selection" */
-#line 174 "parser.y"
+#line 175 "parser.y"
        { _gmx_selelem_free_chain((yyvaluep->sel));  };
-#line 1359 "parser.cpp"
+#line 1363 "parser.cpp"
        break;
       case 59: /* "string" */
-#line 171 "parser.y"
+#line 172 "parser.y"
        { free((yyvaluep->str));                     };
-#line 1364 "parser.cpp"
+#line 1368 "parser.cpp"
        break;
       case 60: /* "sel_expr" */
-#line 175 "parser.y"
+#line 176 "parser.y"
        { _gmx_selelem_free((yyvaluep->sel));        };
-#line 1369 "parser.cpp"
+#line 1373 "parser.cpp"
        break;
       case 62: /* "num_expr" */
-#line 175 "parser.y"
+#line 176 "parser.y"
        { _gmx_selelem_free((yyvaluep->sel));        };
-#line 1374 "parser.cpp"
+#line 1378 "parser.cpp"
        break;
       case 63: /* "str_expr" */
-#line 175 "parser.y"
+#line 176 "parser.y"
        { _gmx_selelem_free((yyvaluep->sel));        };
-#line 1379 "parser.cpp"
+#line 1383 "parser.cpp"
        break;
       case 64: /* "pos_expr" */
-#line 175 "parser.y"
+#line 176 "parser.y"
        { _gmx_selelem_free((yyvaluep->sel));        };
-#line 1384 "parser.cpp"
+#line 1388 "parser.cpp"
        break;
       case 65: /* "method_params" */
-#line 176 "parser.y"
+#line 177 "parser.y"
        { _gmx_selexpr_free_params((yyvaluep->param)); };
-#line 1389 "parser.cpp"
+#line 1393 "parser.cpp"
        break;
       case 66: /* "method_param_list" */
-#line 176 "parser.y"
+#line 177 "parser.y"
        { _gmx_selexpr_free_params((yyvaluep->param)); };
-#line 1394 "parser.cpp"
+#line 1398 "parser.cpp"
        break;
       case 67: /* "method_param" */
-#line 176 "parser.y"
+#line 177 "parser.y"
        { _gmx_selexpr_free_params((yyvaluep->param)); };
-#line 1399 "parser.cpp"
+#line 1403 "parser.cpp"
        break;
       case 68: /* "value_list" */
-#line 177 "parser.y"
+#line 178 "parser.y"
        { _gmx_selexpr_free_values((yyvaluep->val)); };
-#line 1404 "parser.cpp"
+#line 1408 "parser.cpp"
        break;
       case 69: /* "value_list_contents" */
-#line 177 "parser.y"
+#line 178 "parser.y"
        { _gmx_selexpr_free_values((yyvaluep->val)); };
-#line 1409 "parser.cpp"
+#line 1413 "parser.cpp"
        break;
       case 70: /* "basic_value_list" */
-#line 178 "parser.y"
+#line 179 "parser.y"
        { _gmx_selexpr_free_values((yyvaluep->val)); };
-#line 1414 "parser.cpp"
+#line 1418 "parser.cpp"
        break;
       case 71: /* "basic_value_list_contents" */
-#line 178 "parser.y"
+#line 179 "parser.y"
        { _gmx_selexpr_free_values((yyvaluep->val)); };
-#line 1419 "parser.cpp"
+#line 1423 "parser.cpp"
        break;
       case 72: /* "value_item" */
-#line 177 "parser.y"
+#line 178 "parser.y"
        { _gmx_selexpr_free_values((yyvaluep->val)); };
-#line 1424 "parser.cpp"
+#line 1428 "parser.cpp"
        break;
       case 73: /* "basic_value_item" */
-#line 178 "parser.y"
+#line 179 "parser.y"
        { _gmx_selexpr_free_values((yyvaluep->val)); };
-#line 1429 "parser.cpp"
+#line 1433 "parser.cpp"
        break;
       case 74: /* "value_item_range" */
-#line 177 "parser.y"
+#line 178 "parser.y"
        { _gmx_selexpr_free_values((yyvaluep->val)); };
-#line 1434 "parser.cpp"
+#line 1438 "parser.cpp"
        break;
 
       default:
@@ -1739,12 +1743,12 @@ yyreduce:
   switch (yyn)
     {
         case 2:
-#line 192 "parser.y"
+#line 194 "parser.y"
     { (yyval.sel) = NULL ;}
     break;
 
   case 3:
-#line 194 "parser.y"
+#line 196 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_append_selection((yyvsp[(2) - (2)].sel), (yyvsp[(1) - (2)].sel), scanner);
                  if (_gmx_sel_parser_should_finish(scanner))
@@ -1753,12 +1757,12 @@ yyreduce:
     break;
 
   case 4:
-#line 202 "parser.y"
+#line 204 "parser.y"
     { (yyval.sel) = (yyvsp[(1) - (2)].sel); ;}
     break;
 
   case 5:
-#line 204 "parser.y"
+#line 206 "parser.y"
     {
                  (yyval.sel) = NULL;
                  _gmx_selparser_error(scanner, "invalid selection '%s'",
@@ -1777,7 +1781,7 @@ yyreduce:
     break;
 
   case 6:
-#line 223 "parser.y"
+#line 225 "parser.y"
     {
                  (yyval.sel) = NULL;
                  _gmx_sel_handle_empty_cmd(scanner);
@@ -1785,12 +1789,12 @@ yyreduce:
     break;
 
   case 7:
-#line 227 "parser.y"
+#line 229 "parser.y"
     { (yyval.sel) = NULL; ;}
     break;
 
   case 8:
-#line 229 "parser.y"
+#line 231 "parser.y"
     {
                  t_selelem *s, *p;
                  s = _gmx_sel_init_group_by_id((yyvsp[(1) - (1)].i), scanner);
@@ -1802,7 +1806,7 @@ yyreduce:
     break;
 
   case 9:
-#line 238 "parser.y"
+#line 240 "parser.y"
     {
                  t_selelem *s, *p;
                  s = _gmx_sel_init_group_by_name((yyvsp[(1) - (1)].str), scanner);
@@ -1815,113 +1819,116 @@ yyreduce:
     break;
 
   case 10:
-#line 248 "parser.y"
+#line 250 "parser.y"
     { (yyval.sel) = _gmx_sel_init_selection(NULL, (yyvsp[(1) - (1)].sel), scanner); ;}
     break;
 
   case 11:
-#line 250 "parser.y"
+#line 252 "parser.y"
     { (yyval.sel) = _gmx_sel_init_selection((yyvsp[(1) - (2)].str), (yyvsp[(2) - (2)].sel), scanner);   ;}
     break;
 
   case 12:
-#line 252 "parser.y"
+#line 254 "parser.y"
     { (yyval.sel) = _gmx_sel_assign_variable((yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].sel), scanner);  ;}
     break;
 
   case 13:
-#line 254 "parser.y"
+#line 256 "parser.y"
     { (yyval.sel) = _gmx_sel_assign_variable((yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].sel), scanner);  ;}
     break;
 
   case 14:
-#line 256 "parser.y"
+#line 258 "parser.y"
     { (yyval.sel) = _gmx_sel_assign_variable((yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].sel), scanner);  ;}
     break;
 
   case 15:
-#line 261 "parser.y"
-    { _gmx_sel_handle_help_cmd(NULL, scanner); ;}
+#line 264 "parser.y"
+    { _gmx_sel_handle_help_cmd(process_value_list((yyvsp[(2) - (2)].val), NULL), scanner); ;}
     break;
 
-  case 17:
-#line 265 "parser.y"
-    { _gmx_sel_handle_help_cmd((yyvsp[(2) - (2)].str), scanner); ;}
+  case 16:
+#line 267 "parser.y"
+    { (yyval.val) = NULL; ;}
     break;
 
-  case 18:
-#line 266 "parser.y"
-    { _gmx_sel_handle_help_cmd((yyvsp[(2) - (2)].str), scanner); ;}
+  case 17:
+#line 269 "parser.y"
+    {
+                 (yyval.val) = _gmx_selexpr_create_value(STR_VALUE);
+                 (yyval.val)->u.s = (yyvsp[(2) - (2)].str); (yyval.val)->next = (yyvsp[(1) - (2)].val);
+             ;}
     break;
 
-  case 19:
-#line 270 "parser.y"
+  case 18:
+#line 276 "parser.y"
     { (yyval.sel) = (yyvsp[(1) - (1)].sel); ;}
     break;
 
-  case 20:
-#line 272 "parser.y"
+  case 19:
+#line 278 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_position((yyvsp[(1) - (1)].sel), NULL, scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 21:
-#line 276 "parser.y"
+  case 20:
+#line 282 "parser.y"
     { (yyval.sel) = (yyvsp[(2) - (3)].sel); ;}
     break;
 
-  case 22:
-#line 278 "parser.y"
+  case 21:
+#line 284 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_modifier((yyvsp[(2) - (3)].meth), (yyvsp[(3) - (3)].param), (yyvsp[(1) - (3)].sel), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 23:
-#line 289 "parser.y"
+  case 22:
+#line 295 "parser.y"
     { (yyval.i) = (yyvsp[(1) - (1)].i); ;}
     break;
 
-  case 24:
-#line 290 "parser.y"
+  case 23:
+#line 296 "parser.y"
     { (yyval.i) = -(yyvsp[(2) - (2)].i); ;}
     break;
 
-  case 25:
-#line 294 "parser.y"
+  case 24:
+#line 300 "parser.y"
     { (yyval.r) = (yyvsp[(1) - (1)].r); ;}
     break;
 
-  case 26:
-#line 295 "parser.y"
+  case 25:
+#line 301 "parser.y"
     { (yyval.r) = -(yyvsp[(2) - (2)].r); ;}
     break;
 
-  case 27:
-#line 298 "parser.y"
+  case 26:
+#line 304 "parser.y"
     { (yyval.r) = (yyvsp[(1) - (1)].i); ;}
     break;
 
-  case 28:
-#line 299 "parser.y"
+  case 27:
+#line 305 "parser.y"
     { (yyval.r) = (yyvsp[(1) - (1)].r); ;}
     break;
 
-  case 29:
-#line 302 "parser.y"
+  case 28:
+#line 308 "parser.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str); ;}
     break;
 
-  case 30:
-#line 303 "parser.y"
+  case 29:
+#line 309 "parser.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str); ;}
     break;
 
-  case 31:
-#line 312 "parser.y"
+  case 30:
+#line 318 "parser.y"
     {
                  (yyval.sel) = _gmx_selelem_create(SEL_BOOLEAN);
                  (yyval.sel)->u.boolt = BOOL_NOT;
@@ -1929,8 +1936,8 @@ yyreduce:
              ;}
     break;
 
-  case 32:
-#line 318 "parser.y"
+  case 31:
+#line 324 "parser.y"
     {
                  (yyval.sel) = _gmx_selelem_create(SEL_BOOLEAN);
                  (yyval.sel)->u.boolt = BOOL_AND;
@@ -1938,8 +1945,8 @@ yyreduce:
              ;}
     break;
 
-  case 33:
-#line 324 "parser.y"
+  case 32:
+#line 330 "parser.y"
     {
                  (yyval.sel) = _gmx_selelem_create(SEL_BOOLEAN);
                  (yyval.sel)->u.boolt = BOOL_OR;
@@ -1947,21 +1954,21 @@ yyreduce:
              ;}
     break;
 
-  case 34:
-#line 335 "parser.y"
+  case 33:
+#line 341 "parser.y"
     { (yyval.sel) = (yyvsp[(2) - (3)].sel); ;}
     break;
 
-  case 35:
-#line 340 "parser.y"
+  case 34:
+#line 346 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_comparison((yyvsp[(1) - (3)].sel), (yyvsp[(3) - (3)].sel), (yyvsp[(2) - (3)].str), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 36:
-#line 348 "parser.y"
+  case 35:
+#line 354 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_group_by_name((yyvsp[(2) - (2)].str), scanner);
                  free((yyvsp[(2) - (2)].str));
@@ -1969,58 +1976,58 @@ yyreduce:
              ;}
     break;
 
-  case 37:
-#line 354 "parser.y"
+  case 36:
+#line 360 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_group_by_id((yyvsp[(2) - (2)].i), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 38:
-#line 361 "parser.y"
+  case 37:
+#line 367 "parser.y"
     { (yyval.str) = NULL; ;}
     break;
 
-  case 39:
-#line 362 "parser.y"
+  case 38:
+#line 368 "parser.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str);   ;}
     break;
 
-  case 40:
-#line 367 "parser.y"
+  case 39:
+#line 373 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_keyword((yyvsp[(2) - (2)].meth), NULL, (yyvsp[(1) - (2)].str), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 41:
-#line 372 "parser.y"
+  case 40:
+#line 378 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_keyword((yyvsp[(2) - (3)].meth), process_value_list((yyvsp[(3) - (3)].val), NULL), (yyvsp[(1) - (3)].str), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 42:
-#line 377 "parser.y"
+  case 41:
+#line 383 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_keyword((yyvsp[(2) - (3)].meth), process_value_list((yyvsp[(3) - (3)].val), NULL), (yyvsp[(1) - (3)].str), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 43:
-#line 385 "parser.y"
+  case 42:
+#line 391 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_method((yyvsp[(2) - (3)].meth), (yyvsp[(3) - (3)].param), (yyvsp[(1) - (3)].str), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 44:
-#line 397 "parser.y"
+  case 43:
+#line 403 "parser.y"
     {
                  (yyval.sel) = _gmx_selelem_create(SEL_CONST);
                  _gmx_selelem_set_vtype((yyval.sel), INT_VALUE);
@@ -2029,8 +2036,8 @@ yyreduce:
              ;}
     break;
 
-  case 45:
-#line 404 "parser.y"
+  case 44:
+#line 410 "parser.y"
     {
                  (yyval.sel) = _gmx_selelem_create(SEL_CONST);
                  _gmx_selelem_set_vtype((yyval.sel), REAL_VALUE);
@@ -2039,59 +2046,59 @@ yyreduce:
              ;}
     break;
 
-  case 46:
-#line 414 "parser.y"
+  case 45:
+#line 420 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_keyword((yyvsp[(2) - (2)].meth), NULL, (yyvsp[(1) - (2)].str), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 47:
-#line 419 "parser.y"
+  case 46:
+#line 425 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_method((yyvsp[(2) - (3)].meth), (yyvsp[(3) - (3)].param), (yyvsp[(1) - (3)].str), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 48:
-#line 427 "parser.y"
+  case 47:
+#line 433 "parser.y"
     { (yyval.sel) = _gmx_sel_init_arithmetic((yyvsp[(1) - (3)].sel), (yyvsp[(3) - (3)].sel), '+', scanner); ;}
     break;
 
-  case 49:
-#line 429 "parser.y"
+  case 48:
+#line 435 "parser.y"
     { (yyval.sel) = _gmx_sel_init_arithmetic((yyvsp[(1) - (3)].sel), (yyvsp[(3) - (3)].sel), '-', scanner); ;}
     break;
 
-  case 50:
-#line 431 "parser.y"
+  case 49:
+#line 437 "parser.y"
     { (yyval.sel) = _gmx_sel_init_arithmetic((yyvsp[(1) - (3)].sel), (yyvsp[(3) - (3)].sel), '*', scanner); ;}
     break;
 
-  case 51:
-#line 433 "parser.y"
+  case 50:
+#line 439 "parser.y"
     { (yyval.sel) = _gmx_sel_init_arithmetic((yyvsp[(1) - (3)].sel), (yyvsp[(3) - (3)].sel), '/', scanner); ;}
     break;
 
-  case 52:
-#line 435 "parser.y"
+  case 51:
+#line 441 "parser.y"
     { (yyval.sel) = _gmx_sel_init_arithmetic((yyvsp[(2) - (2)].sel), NULL, '-', scanner); ;}
     break;
 
-  case 53:
-#line 437 "parser.y"
+  case 52:
+#line 443 "parser.y"
     { (yyval.sel) = _gmx_sel_init_arithmetic((yyvsp[(1) - (3)].sel), (yyvsp[(3) - (3)].sel), '^', scanner); ;}
     break;
 
-  case 54:
-#line 438 "parser.y"
+  case 53:
+#line 444 "parser.y"
     { (yyval.sel) = (yyvsp[(2) - (3)].sel); ;}
     break;
 
-  case 55:
-#line 446 "parser.y"
+  case 54:
+#line 452 "parser.y"
     {
                  (yyval.sel) = _gmx_selelem_create(SEL_CONST);
                  _gmx_selelem_set_vtype((yyval.sel), STR_VALUE);
@@ -2100,210 +2107,210 @@ yyreduce:
              ;}
     break;
 
-  case 56:
-#line 453 "parser.y"
+  case 55:
+#line 459 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_keyword((yyvsp[(2) - (2)].meth), NULL, (yyvsp[(1) - (2)].str), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 57:
-#line 465 "parser.y"
+  case 56:
+#line 471 "parser.y"
     { (yyval.sel) = _gmx_sel_init_const_position((yyvsp[(2) - (7)].r), (yyvsp[(4) - (7)].r), (yyvsp[(6) - (7)].r)); ;}
     break;
 
-  case 58:
-#line 469 "parser.y"
+  case 57:
+#line 475 "parser.y"
     { (yyval.sel) = (yyvsp[(2) - (3)].sel); ;}
     break;
 
-  case 59:
-#line 474 "parser.y"
+  case 58:
+#line 480 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_method((yyvsp[(1) - (2)].meth), (yyvsp[(2) - (2)].param), NULL, scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 60:
-#line 482 "parser.y"
+  case 59:
+#line 488 "parser.y"
     {
                  (yyval.sel) = _gmx_sel_init_position((yyvsp[(3) - (3)].sel), (yyvsp[(1) - (3)].str), scanner);
                  if ((yyval.sel) == NULL) YYERROR;
              ;}
     break;
 
-  case 61:
-#line 493 "parser.y"
+  case 60:
+#line 499 "parser.y"
     { (yyval.sel) = _gmx_sel_init_variable_ref((yyvsp[(1) - (1)].sel)); ;}
     break;
 
-  case 62:
-#line 497 "parser.y"
+  case 61:
+#line 503 "parser.y"
     { (yyval.sel) = _gmx_sel_init_variable_ref((yyvsp[(1) - (1)].sel)); ;}
     break;
 
-  case 63:
-#line 501 "parser.y"
+  case 62:
+#line 507 "parser.y"
     { (yyval.sel) = _gmx_sel_init_variable_ref((yyvsp[(1) - (1)].sel)); ;}
     break;
 
-  case 64:
-#line 510 "parser.y"
+  case 63:
+#line 516 "parser.y"
     { (yyval.param) = process_param_list((yyvsp[(1) - (1)].param)); ;}
     break;
 
-  case 65:
-#line 512 "parser.y"
+  case 64:
+#line 518 "parser.y"
     { (yyval.param) = process_param_list((yyvsp[(1) - (2)].param)); ;}
     break;
 
-  case 66:
-#line 516 "parser.y"
+  case 65:
+#line 522 "parser.y"
     { (yyval.param) = NULL;              ;}
     break;
 
-  case 67:
-#line 518 "parser.y"
+  case 66:
+#line 524 "parser.y"
     { (yyvsp[(2) - (2)].param)->next = (yyvsp[(1) - (2)].param); (yyval.param) = (yyvsp[(2) - (2)].param); ;}
     break;
 
-  case 68:
-#line 523 "parser.y"
+  case 67:
+#line 529 "parser.y"
     {
                  (yyval.param) = _gmx_selexpr_create_param((yyvsp[(1) - (2)].str));
                  (yyval.param)->value = process_value_list((yyvsp[(2) - (2)].val), &(yyval.param)->nval);
              ;}
     break;
 
-  case 69:
-#line 529 "parser.y"
+  case 68:
+#line 535 "parser.y"
     { (yyval.val) = NULL; ;}
     break;
 
-  case 70:
-#line 530 "parser.y"
+  case 69:
+#line 536 "parser.y"
     { (yyval.val) = (yyvsp[(1) - (1)].val);   ;}
     break;
 
-  case 71:
-#line 531 "parser.y"
+  case 70:
+#line 537 "parser.y"
     { (yyval.val) = (yyvsp[(2) - (3)].val);   ;}
     break;
 
-  case 72:
-#line 535 "parser.y"
+  case 71:
+#line 541 "parser.y"
     { (yyval.val) = (yyvsp[(1) - (1)].val); ;}
     break;
 
-  case 73:
-#line 537 "parser.y"
+  case 72:
+#line 543 "parser.y"
     { (yyvsp[(2) - (2)].val)->next = (yyvsp[(1) - (2)].val); (yyval.val) = (yyvsp[(2) - (2)].val); ;}
     break;
 
-  case 74:
-#line 539 "parser.y"
+  case 73:
+#line 545 "parser.y"
     { (yyvsp[(3) - (3)].val)->next = (yyvsp[(1) - (3)].val); (yyval.val) = (yyvsp[(3) - (3)].val); ;}
     break;
 
-  case 75:
-#line 543 "parser.y"
+  case 74:
+#line 549 "parser.y"
     { (yyval.val) = (yyvsp[(1) - (1)].val); ;}
     break;
 
-  case 76:
-#line 544 "parser.y"
+  case 75:
+#line 550 "parser.y"
     { (yyval.val) = (yyvsp[(2) - (3)].val); ;}
     break;
 
-  case 77:
-#line 548 "parser.y"
+  case 76:
+#line 554 "parser.y"
     { (yyval.val) = (yyvsp[(1) - (1)].val); ;}
     break;
 
-  case 78:
-#line 550 "parser.y"
+  case 77:
+#line 556 "parser.y"
     { (yyvsp[(2) - (2)].val)->next = (yyvsp[(1) - (2)].val); (yyval.val) = (yyvsp[(2) - (2)].val); ;}
     break;
 
-  case 79:
-#line 552 "parser.y"
+  case 78:
+#line 558 "parser.y"
     { (yyvsp[(3) - (3)].val)->next = (yyvsp[(1) - (3)].val); (yyval.val) = (yyvsp[(3) - (3)].val); ;}
     break;
 
+  case 79:
+#line 562 "parser.y"
+    { (yyval.val) = _gmx_selexpr_create_value_expr((yyvsp[(1) - (1)].sel)); ;}
+    break;
+
   case 80:
-#line 556 "parser.y"
+#line 564 "parser.y"
     { (yyval.val) = _gmx_selexpr_create_value_expr((yyvsp[(1) - (1)].sel)); ;}
     break;
 
   case 81:
-#line 558 "parser.y"
+#line 566 "parser.y"
     { (yyval.val) = _gmx_selexpr_create_value_expr((yyvsp[(1) - (1)].sel)); ;}
     break;
 
   case 82:
-#line 560 "parser.y"
+#line 568 "parser.y"
     { (yyval.val) = _gmx_selexpr_create_value_expr((yyvsp[(1) - (1)].sel)); ;}
     break;
 
   case 83:
-#line 562 "parser.y"
-    { (yyval.val) = _gmx_selexpr_create_value_expr((yyvsp[(1) - (1)].sel)); ;}
-    break;
-
-  case 84:
-#line 563 "parser.y"
+#line 569 "parser.y"
     { (yyval.val) = (yyvsp[(1) - (1)].val); ;}
     break;
 
-  case 85:
-#line 568 "parser.y"
+  case 84:
+#line 574 "parser.y"
     {
                  (yyval.val) = _gmx_selexpr_create_value(INT_VALUE);
                  (yyval.val)->u.i.i1 = (yyval.val)->u.i.i2 = (yyvsp[(1) - (1)].i);
              ;}
     break;
 
-  case 86:
-#line 573 "parser.y"
+  case 85:
+#line 579 "parser.y"
     {
                  (yyval.val) = _gmx_selexpr_create_value(REAL_VALUE);
                  (yyval.val)->u.r.r1 = (yyval.val)->u.r.r2 = (yyvsp[(1) - (1)].r);
              ;}
     break;
 
-  case 87:
-#line 578 "parser.y"
+  case 86:
+#line 584 "parser.y"
     {
                  (yyval.val) = _gmx_selexpr_create_value(STR_VALUE);
                  (yyval.val)->u.s = (yyvsp[(1) - (1)].str);
              ;}
     break;
 
-  case 88:
-#line 582 "parser.y"
+  case 87:
+#line 588 "parser.y"
     { (yyval.val) = (yyvsp[(1) - (1)].val); ;}
     break;
 
-  case 89:
-#line 587 "parser.y"
+  case 88:
+#line 593 "parser.y"
     {
                  (yyval.val) = _gmx_selexpr_create_value(INT_VALUE);
                  (yyval.val)->u.i.i1 = (yyvsp[(1) - (3)].i); (yyval.val)->u.i.i2 = (yyvsp[(3) - (3)].i);
              ;}
     break;
 
-  case 90:
-#line 592 "parser.y"
+  case 89:
+#line 598 "parser.y"
     {
                  (yyval.val) = _gmx_selexpr_create_value(REAL_VALUE);
                  (yyval.val)->u.r.r1 = (yyvsp[(1) - (3)].i); (yyval.val)->u.r.r2 = (yyvsp[(3) - (3)].r);
              ;}
     break;
 
-  case 91:
-#line 597 "parser.y"
+  case 90:
+#line 603 "parser.y"
     {
                  (yyval.val) = _gmx_selexpr_create_value(REAL_VALUE);
                  (yyval.val)->u.r.r1 = (yyvsp[(1) - (3)].r); (yyval.val)->u.r.r2 = (yyvsp[(3) - (3)].r);
@@ -2312,7 +2319,7 @@ yyreduce:
 
 
 /* Line 1267 of yacc.c.  */
-#line 2316 "parser.cpp"
+#line 2323 "parser.cpp"
       default: break;
     }
   YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
@@ -2526,7 +2533,7 @@ yyreturn:
 }
 
 
-#line 603 "parser.y"
+#line 609 "parser.y"
 
 
 static t_selexpr_value *
index 368804ab8e6658b051ba9ebc9ee16ff243f08886..cb7071b8133bbed8e63ee794bfc35b7f365fadda 100644 (file)
@@ -167,6 +167,7 @@ yyerror(yyscan_t, char const *s);
 %type <param> method_params method_param_list method_param
 %type <val>   value_list value_list_contents value_item value_item_range
 %type <val>   basic_value_list basic_value_list_contents basic_value_item
+%type <val>   help_topic
 
 %destructor { free($$);                     } HELP_TOPIC STR IDENTIFIER CMP_OP string
 %destructor { if($$) free($$);              } PARAM
@@ -176,6 +177,7 @@ yyerror(yyscan_t, char const *s);
 %destructor { _gmx_selexpr_free_params($$); } method_params method_param_list method_param
 %destructor { _gmx_selexpr_free_values($$); } value_list value_list_contents value_item value_item_range
 %destructor { _gmx_selexpr_free_values($$); } basic_value_list basic_value_list_contents basic_value_item
+%destructor { _gmx_selexpr_free_values($$); } help_topic
 
 %expect 50
 %debug
@@ -258,12 +260,16 @@ cmd_plain:   /* empty */
 
 /* Help requests */
 help_request:
-             HELP                   { _gmx_sel_handle_help_cmd(NULL, scanner); }
-           | help_topic
+             HELP help_topic
+             { _gmx_sel_handle_help_cmd(process_value_list($2, NULL), scanner); }
 ;
 
-help_topic:  HELP HELP_TOPIC        { _gmx_sel_handle_help_cmd($2, scanner); }
-           | help_topic HELP_TOPIC  { _gmx_sel_handle_help_cmd($2, scanner); }
+help_topic:  /* empty */            { $$ = NULL; }
+           | help_topic HELP_TOPIC
+             {
+                 $$ = _gmx_selexpr_create_value(STR_VALUE);
+                 $$->u.s = $2; $$->next = $1;
+             }
 ;
 
 /* Selection is made of an expression and zero or more modifiers */
index 910af34a9f20e5304f977df0b7259e104754acc7..0d78f397ef919b6f5142296ccbcf5899666c9813 100644 (file)
 #include <stdio.h>
 #include <stdarg.h>
 
+#include <boost/shared_ptr.hpp>
+
 #include "futil.h"
 #include "smalloc.h"
 #include "string2.h"
 
+#include "gromacs/onlinehelp/helpmanager.h"
 #include "gromacs/selection/poscalc.h"
 #include "gromacs/selection/selection.h"
 #include "gromacs/selection/selmethod.h"
 #include "gromacs/utility/errorcodes.h"
 #include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/file.h"
 #include "gromacs/utility/messagestringcollector.h"
 
 #include "keywords.h"
@@ -1353,13 +1357,30 @@ _gmx_sel_handle_empty_cmd(yyscan_t scanner)
  * \p topic is freed by this function.
  */
 void
-_gmx_sel_handle_help_cmd(char *topic, yyscan_t scanner)
+_gmx_sel_handle_help_cmd(t_selexpr_value *topic, yyscan_t scanner)
 {
+    boost::shared_ptr<t_selexpr_value> topicGuard(topic, &_gmx_selexpr_free_values);
+
     gmx_ana_selcollection_t *sc = _gmx_sel_lexer_selcollection(scanner);
 
-    _gmx_sel_print_help(stderr, sc->symtab, topic);
-    if (topic)
+    if (sc->rootHelp.get() == NULL)
     {
-        sfree(topic);
+        sc->rootHelp = gmx::createSelectionHelpTopic();
+    }
+    gmx::HelpManager manager(*sc->rootHelp);
+    try
+    {
+        t_selexpr_value *value = topic;
+        while (value != NULL)
+        {
+            manager.enterTopic(value->u.s);
+            value = value->next;
+        }
+    }
+    catch (const gmx::InvalidInputError &ex)
+    {
+        fprintf(stderr, "%s\n", ex.what());
+        return;
     }
+    manager.writeCurrentTopic(&gmx::File::standardError());
 }
index f018698cea133aa3cea9b0fd180bcb1c6bdd7b6e..402f467bb763a266f508d76f0513118bb69b33be 100644 (file)
@@ -198,7 +198,7 @@ void
 _gmx_sel_handle_empty_cmd(void *scanner);
 /** Process help commands. */
 void
-_gmx_sel_handle_help_cmd(char *topic, void *scanner);
+_gmx_sel_handle_help_cmd(t_selexpr_value *topic, void *scanner);
 
 /* In params.c */
 /** Initializes an array of parameters based on input from the selection parser. */
index e8af512d3714127e881a6691452e7a3386a995d2..3e2792199e4c4ac32de7d6b956a93842d2654a5a 100755 (executable)
@@ -1,8 +1,8 @@
 #!/bin/bash
 #
 # This script runs Bison and/or Flex to regenerate the files as follows:
-#   parser.y  -> parser.c, parser.h
-#   scanner.l -> scanner.c, scanner_flex.h
+#   parser.y  -> parser.cpp, parser.h
+#   scanner.l -> scanner.cpp, scanner_flex.h
 # The commands are run only if the generated files are older than the
 # Bison/Flex input files, or if a '-f' flag is provided.
 
@@ -11,6 +11,13 @@ if [ "x$1" == "x-f" ] ; then
     FORCE=1
 fi
 
+if [ "x$BISON" == "x" ] ; then
+    BISON=bison
+fi
+if [ "x$FLEX" == "x" ] ; then
+    FLEX=flex
+fi
+
 # For convenience, change to the directory where the files are located
 # if the script is run from the root of the source tree.
 dirname=src/gromacs/selection
@@ -18,5 +25,5 @@ if [[ -f $dirname/parser.y && -f $dirname/scanner.l ]] ; then
     cd $dirname
 fi
 
-[[ $FORCE || parser.y  -nt parser.cpp ]]  && bison -t -o parser.cpp --defines=parser.h parser.y
-[[ $FORCE || scanner.l -nt scanner.cpp ]] && flex -o scanner.cpp scanner.l
+[[ $FORCE || parser.y  -nt parser.cpp ]]  && $BISON -t -o parser.cpp --defines=parser.h parser.y
+[[ $FORCE || scanner.l -nt scanner.cpp ]] && $FLEX -o scanner.cpp scanner.l
index 5b36531ed2f1d0836b0312df2fa2c23f71eb10e8..e04ad2caa57a7b98e9fc4ba3d90cdceac281b65d 100644 (file)
@@ -46,6 +46,7 @@
 
 #include "../legacyheaders/typedefs.h"
 
+#include "../onlinehelp/helptopicinterface.h"
 #include "../options/options.h"
 #include "../utility/uniqueptr.h"
 #include "indexutil.h"
@@ -93,6 +94,8 @@ struct gmx_ana_selcollection_t
     struct gmx_sel_mempool_t      *mempool;
     /** Parser symbol table. */
     struct gmx_sel_symtab_t     *symtab;
+    //! Root of help topic tree (NULL is no help yet requested).
+    gmx::HelpTopicPointer          rootHelp;
 };
 
 namespace gmx
index c37a7a181818eefb5d7786340a3b7bda448725d5..9fe10e525996f9adf4e572e4a50a2ea3f689e93b 100644 (file)
  * \author Teemu Murtola <teemu.murtola@cbr.su.se>
  * \ingroup module_selection
  */
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
+#include <string>
+#include <vector>
+#include <utility>
 
-#include <macros.h>
-#include <string2.h>
-#include <wman.h>
+#include <boost/shared_ptr.hpp>
 
-#include "gromacs/selection/selmethod.h"
+#include "gromacs/onlinehelp/helptopic.h"
+#include "gromacs/utility/file.h"
+#include "gromacs/utility/format.h"
 
 #include "selhelp.h"
+#include "selmethod.h"
 #include "symrec.h"
 
-/*! \internal \brief
- * Describes a selection help section.
- */
-typedef struct {
-    //! Topic keyword that produces this help.
-    const char  *topic;
-    //! Number of items in the \a text array.
-    int          nl;
-    //! Help text as a list of strings that will be concatenated.
-    const char **text;
-} t_selection_help_item;
-
-static const char *help_common[] = {
-    "SELECTION HELP[PAR]",
+namespace
+{
+
+struct CommonHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
 
+const char CommonHelpText::name[] = "selections";
+const char CommonHelpText::title[] =
+    "Selection syntax and usage";
+const char *const CommonHelpText::text[] = {
     "This program supports selections in addition to traditional index files.",
     "Please read the subtopic pages (available through \"help topic\") for",
     "more information.",
@@ -74,18 +74,34 @@ static const char *help_common[] = {
     "\"help all\" prints the help for all subtopics.",
 };
 
-static const char *help_arithmetic[] = {
-    "ARITHMETIC EXPRESSIONS IN SELECTIONS[PAR]",
+struct ArithmeticHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
 
+const char ArithmeticHelpText::name[] = "arithmetic";
+const char ArithmeticHelpText::title[] =
+    "Arithmetic expressions in selections";
+const char *const ArithmeticHelpText::text[] = {
     "Basic arithmetic evaluation is supported for numeric expressions.",
     "Supported operations are addition, subtraction, negation, multiplication,",
     "division, and exponentiation (using ^).",
     "Result of a division by zero or other illegal operations is undefined.",
 };
 
-static const char *help_cmdline[] = {
-    "SELECTION COMMAND-LINE ARGUMENTS[PAR]",
+struct CmdLineHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
 
+const char CmdLineHelpText::name[] = "cmdline";
+const char CmdLineHelpText::title[] =
+    "Specifying selections from command line";
+const char *const CmdLineHelpText::text[] = {
     "There are two alternative command-line arguments for specifying",
     "selections:[BR]",
     "1. [TT]-select[tt] can be used to specify the complete selection as a",
@@ -115,9 +131,17 @@ static const char *help_cmdline[] = {
     "See \"help positions\" for more information on these options.",
 };
 
-static const char *help_eval[] = {
-    "SELECTION EVALUATION AND OPTIMIZATION[PAR]",
+struct EvaluationHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
 
+const char EvaluationHelpText::name[] = "evaluation";
+const char EvaluationHelpText::title[] =
+    "Selection evaluation and optimization";
+const char *const EvaluationHelpText::text[] = {
     "Boolean evaluation proceeds from left to right and is short-circuiting",
     "i.e., as soon as it is known whether an atom will be selected, the",
     "remaining expressions are not evaluated at all.",
@@ -146,9 +170,17 @@ static const char *help_eval[] = {
     "a major problem.",
 };
 
-static const char *help_examples[] = {
-    "SELECTION EXAMPLES[PAR]",
+struct ExamplesHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
 
+const char ExamplesHelpText::name[] = "examples";
+const char ExamplesHelpText::title[] =
+    "Selection examples";
+const char *const ExamplesHelpText::text[] = {
     "Below, examples of increasingly complex selections are given.[PAR]",
 
     "Selection of all water oxygens:[BR]",
@@ -180,17 +212,33 @@ static const char *help_examples[] = {
     "  name \"C[1-8]\" merge name \"C[2-9]\"",
 };
 
-static const char *help_keywords[] = {
-    "SELECTION KEYWORDS[PAR]",
+struct KeywordsHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
 
+const char KeywordsHelpText::name[] = "keywords";
+const char KeywordsHelpText::title[] =
+    "Selection keywords";
+const char *const KeywordsHelpText::text[] = {
     "The following selection keywords are currently available.",
     "For keywords marked with a star, additional help is available through",
     "\"help KEYWORD\", where KEYWORD is the name of the keyword.",
 };
 
-static const char *help_limits[] = {
-    "SELECTION LIMITATIONS[PAR]",
+struct LimitationsHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
 
+const char LimitationsHelpText::name[] = "limitations";
+const char LimitationsHelpText::title[] =
+    "Selection limitations";
+const char *const LimitationsHelpText::text[] = {
     "Some analysis programs may require a special structure for the input",
     "selections (e.g., [TT]g_angle[tt] requires the index group to be made",
     "of groups of three or four atoms).",
@@ -206,9 +254,17 @@ static const char *help_limits[] = {
     "instead.",
 };
 
-static const char *help_positions[] = {
-    "SPECIFYING POSITIONS[PAR]",
+struct PositionsHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
 
+const char PositionsHelpText::name[] = "positions";
+const char PositionsHelpText::title[] =
+    "Specifying positions in selections";
+const char *const PositionsHelpText::text[] = {
     "Possible ways of specifying positions in selections are:[PAR]",
 
     "1. A constant position can be defined as [TT][XX, YY, ZZ][tt], where",
@@ -251,9 +307,17 @@ static const char *help_positions[] = {
     "are either selected or not, based on the single distance calculated.",
 };
 
-static const char *help_syntax[] = {
-    "SELECTION SYNTAX[PAR]",
+struct SyntaxHelpText
+{
+    static const char name[];
+    static const char title[];
+    static const char *const text[];
+};
 
+const char SyntaxHelpText::name[] = "syntax";
+const char SyntaxHelpText::title[] =
+    "Selection syntax";
+const char *const SyntaxHelpText::text[] = {
     "A set of selections consists of one or more selections, separated by",
     "semicolons. Each selection defines a set of positions for the analysis.",
     "Each selection can also be preceded by a string that gives a name for",
@@ -312,178 +376,193 @@ static const char *help_syntax[] = {
     "in the beginning of a line.",
 };
 
-static const t_selection_help_item helpitems[] = {
-    {NULL,          asize(help_common),     help_common},
-    {"cmdline",     asize(help_cmdline),    help_cmdline},
-    {"syntax",      asize(help_syntax),     help_syntax},
-    {"positions",   asize(help_positions),  help_positions},
-    {"arithmetic",  asize(help_arithmetic), help_arithmetic},
-    {"keywords",    asize(help_keywords),   help_keywords},
-    {"evaluation",  asize(help_eval),       help_eval},
-    {"limitations", asize(help_limits),     help_limits},
-    {"examples",    asize(help_examples),   help_examples},
-};
+} // namespace
 
-/*! \brief
- * Prints a brief list of keywords (selection methods) available.
+namespace gmx
+{
+
+namespace
+{
+
+/*! \internal \brief
+ * Help topic implementation for an individual selection method.
  *
- * \param[in] fp    Where to write the list.
- * \param[in] symtab  Symbol table to use to find available keywords.
- * \param[in] type  Only methods that return this type are printed.
- * \param[in] bMod  If false, \ref SMETH_MODIFIER methods are excluded, otherwise
- *     only them are printed.
+ * \ingroup module_selection
  */
-static void
-print_keyword_list(FILE *fp, gmx_sel_symtab_t *symtab, e_selvalue_t type,
-                   bool bMod)
+class KeywordDetailsHelpTopic : public AbstractSimpleHelpTopic
 {
-    gmx_sel_symrec_t *symbol;
+    public:
+        //! Initialize help topic for the given selection method.
+        explicit KeywordDetailsHelpTopic(const gmx_ana_selmethod_t &method)
+            : method_(method)
+        {
+        }
 
-    symbol = _gmx_sel_first_symbol(symtab, SYMBOL_METHOD);
-    while (symbol)
-    {
-        gmx_ana_selmethod_t *method = _gmx_sel_sym_value_method(symbol);
-        bool                 bShow;
-        bShow = (method->type == type)
-            && ((bMod && (method->flags & SMETH_MODIFIER))
-                || (!bMod && !(method->flags & SMETH_MODIFIER)));
-        if (bShow)
+        virtual const char *name() const
         {
-            fprintf(fp, " %c ",
-                    (method->help.nlhelp > 0 && method->help.help) ? '*' : ' ');
-            if (method->help.syntax)
-            {
-                fprintf(fp, "%s\n", method->help.syntax);
-            }
-            else
-            {
-                const char *symname = _gmx_sel_sym_name(symbol);
+            return method_.name;
+        }
+        virtual const char *title() const
+        {
+            return NULL;
+        }
 
-                fprintf(fp, "%s", symname);
-                if (strcmp(symname, method->name) != 0)
-                {
-                    fprintf(fp, " (synonym for %s)", method->name);
-                }
-                fprintf(fp, "\n");
-            }
+    protected:
+        virtual std::string helpText() const
+        {
+            return concatenateStrings(method_.help.help, method_.help.nlhelp);
         }
-        symbol = _gmx_sel_next_symbol(symbol, SYMBOL_METHOD);
-    }
-}
 
-/*!
- * \param[in]  fp      Where to write the help.
- * \param[in]  symtab  Symbol table to use to find available keywords.
- * \param[in]  topic Topic to print help on, or NULL for general help.
+    private:
+        const gmx_ana_selmethod_t &method_;
+
+        GMX_DISALLOW_COPY_AND_ASSIGN(KeywordDetailsHelpTopic);
+};
+
+/*! \internal \brief
+ * Custom help topic for printing a list of selection keywords.
  *
- * \p symtab is used to get information on which keywords are available in the
- * present context.
+ * \ingroup module_selection
  */
-void
-_gmx_sel_print_help(FILE *fp, gmx_sel_symtab_t *symtab, const char *topic)
+class KeywordsHelpTopic : public CompositeHelpTopic<KeywordsHelpText>
 {
-    const t_selection_help_item *item = NULL;
-    size_t i;
+    public:
+        KeywordsHelpTopic();
+
+        virtual void writeHelp(File *file) const;
+
+    private:
+        /*! \brief
+         * Container for known selection methods.
+         *
+         * The first item in the pair is the name of the selection method, and
+         * the second points to the static data structure that describes the
+         * method.
+         * The name in the first item may differ from the name of the static
+         * data structure if an alias is defined for that method.
+         */
+        typedef std::vector<std::pair<std::string,
+                                      const gmx_ana_selmethod_t *> >
+                MethodList;
+
+        /*! \brief
+         * Prints a brief list of keywords (selection methods) available.
+         *
+         * \param[in] file  Where to write the list.
+         * \param[in] type  Only methods that return this type are printed.
+         * \param[in] bModifiers  If false, \ref SMETH_MODIFIER methods are
+         *      excluded, otherwise only them are printed.
+         */
+        void printKeywordList(File *file, e_selvalue_t type, bool bModifiers) const;
+
+        MethodList              methods_;
+};
 
-    /* Find the item for the topic */
-    if (!topic)
-    {
-        item = &helpitems[0];
-    }
-    else if (strcmp(topic, "all") == 0)
-    {
-        for (i = 0; i < asize(helpitems); ++i)
-        {
-            item = &helpitems[i];
-            _gmx_sel_print_help(fp, symtab, item->topic);
-            if (i != asize(helpitems) - 1)
-            {
-                fprintf(fp, "\n\n");
-            }
-        }
-        return;
-    }
-    else
+KeywordsHelpTopic::KeywordsHelpTopic()
+{
+    // TODO: This is not a very elegant way of getting the list of selection
+    // methods, but this needs to be rewritten in any case if/when #652 is
+    // implemented.
+    gmx_sel_symtab_t *symtab;
+    _gmx_sel_symtab_create(&symtab);
+    gmx_ana_selmethod_register_defaults(symtab);
+    boost::shared_ptr<gmx_sel_symtab_t> symtabGuard(symtab, &_gmx_sel_symtab_free);
+
+    gmx_sel_symrec_t *symbol = _gmx_sel_first_symbol(symtab, SYMBOL_METHOD);
+    while (symbol)
     {
-        for (i = 1; i < asize(helpitems); ++i)
+        const char *symname = _gmx_sel_sym_name(symbol);
+        const gmx_ana_selmethod_t *method = _gmx_sel_sym_value_method(symbol);
+        methods_.push_back(std::make_pair(std::string(symname), method));
+        if (method->help.nlhelp > 0 && method->help.help != NULL)
         {
-            if (strncmp(helpitems[i].topic, topic, strlen(topic)) == 0)
-            {
-                item = &helpitems[i];
-                break;
-            }
+            addSubTopic(HelpTopicPointer(new KeywordDetailsHelpTopic(*method)));
         }
+        symbol = _gmx_sel_next_symbol(symbol, SYMBOL_METHOD);
     }
-    /* If the topic is not found, check the available methods.
-     * If they don't provide any help either, tell the user and exit. */
-    if (!item)
-    {
-        gmx_sel_symrec_t *symbol;
+}
 
-        symbol = _gmx_sel_first_symbol(symtab, SYMBOL_METHOD);
-        while (symbol)
-        {
-            gmx_ana_selmethod_t *method = _gmx_sel_sym_value_method(symbol);
-            if (method->help.nlhelp > 0 && method->help.help
-                && strncmp(method->name, topic, strlen(topic)) == 0)
-            {
-                print_tty_formatted(fp, method->help.nlhelp,
-                        method->help.help, 0, NULL, NULL, false);
-                return;
-            }
-            symbol = _gmx_sel_next_symbol(symbol, SYMBOL_METHOD);
-        }
+void KeywordsHelpTopic::writeHelp(File *file) const
+{
+    writeBasicHelpTopic(file, *this, helpText());
+
+    /* Print the list of keywords */
+    file->writeLine();
+    file->writeLine("Keywords that select atoms by an integer property:");
+    file->writeLine("(use in expressions or like \"atomnr 1 to 5 7 9\")");
+    printKeywordList(file, INT_VALUE, false);
+
+    file->writeLine();
+    file->writeLine("Keywords that select atoms by a numeric property:");
+    file->writeLine("(use in expressions or like \"occupancy 0.5 to 1\")");
+    printKeywordList(file, REAL_VALUE, false);
+
+    file->writeLine();
+    file->writeLine("Keywords that select atoms by a string property:");
+    file->writeLine("(use like \"name PATTERN [PATTERN] ...\")");
+    printKeywordList(file, STR_VALUE, false);
+
+    file->writeLine();
+    file->writeLine("Additional keywords that directly select atoms:");
+    printKeywordList(file, GROUP_VALUE, false);
+
+    file->writeLine();
+    file->writeLine("Keywords that directly evaluate to positions:");
+    file->writeLine("(see also \"help positions\")");
+    printKeywordList(file, POS_VALUE, false);
+
+    file->writeLine();
+    file->writeLine("Additional keywords:");
+    printKeywordList(file, POS_VALUE, true);
+    printKeywordList(file, NO_VALUE, true);
+}
 
-        fprintf(fp, "No help available for '%s'.\n", topic);
-        return;
-    }
-    /* Print the help */
-    print_tty_formatted(fp, item->nl, item->text, 0, NULL, NULL, false);
-    /* Special handling of certain pages */
-    if (!topic)
+void KeywordsHelpTopic::printKeywordList(File *file, e_selvalue_t type,
+                                         bool bModifiers) const
+{
+    MethodList::const_iterator iter;
+    for (iter = methods_.begin(); iter != methods_.end(); ++iter)
     {
-        int len = 0;
-
-        /* Print the subtopics on the main page */
-        fprintf(fp, "\nAvailable subtopics:\n");
-        for (i = 1; i < asize(helpitems); ++i)
+        const gmx_ana_selmethod_t &method = *iter->second;
+        bool bIsModifier = (method.flags & SMETH_MODIFIER) != 0;
+        if (method.type == type && bModifiers == bIsModifier)
         {
-            int len1 = strlen(helpitems[i].topic) + 2;
-
-            len += len1;
-            if (len > 79)
+            bool bHasHelp = (method.help.nlhelp > 0 && method.help.help != NULL);
+            file->writeString(formatString(" %c ", bHasHelp ? '*' : ' '));
+            if (method.help.syntax != NULL)
+            {
+                file->writeLine(method.help.syntax);
+            }
+            else
             {
-                fprintf(fp, "\n");
-                len = len1;
+                const std::string &symname = iter->first;
+                file->writeString(symname);
+                if (symname != method.name)
+                {
+                    file->writeString(formatString(" (synonym for %s)", method.name));
+                }
+                file->writeLine();
             }
-            fprintf(fp, "  %s", helpitems[i].topic);
         }
-        fprintf(fp, "\n");
     }
-    else if (strcmp(item->topic, "keywords") == 0)
-    {
-        /* Print the list of keywords */
-        fprintf(fp, "\nKeywords that select atoms by an integer property:\n");
-        fprintf(fp, "(use in expressions or like \"atomnr 1 to 5 7 9\")\n");
-        print_keyword_list(fp, symtab, INT_VALUE, false);
-
-        fprintf(fp, "\nKeywords that select atoms by a numeric property:\n");
-        fprintf(fp, "(use in expressions or like \"occupancy 0.5 to 1\")\n");
-        print_keyword_list(fp, symtab, REAL_VALUE, false);
-
-        fprintf(fp, "\nKeywords that select atoms by a string property:\n");
-        fprintf(fp, "(use like \"name PATTERN [PATTERN] ...\")\n");
-        print_keyword_list(fp, symtab, STR_VALUE, false);
-
-        fprintf(fp, "\nAdditional keywords that directly select atoms:\n");
-        print_keyword_list(fp, symtab, GROUP_VALUE, false);
+}
 
-        fprintf(fp, "\nKeywords that directly evaluate to positions:\n");
-        fprintf(fp, "(see also \"help positions\")\n");
-        print_keyword_list(fp, symtab, POS_VALUE, false);
+} // namespace
 
-        fprintf(fp, "\nAdditional keywords:\n");
-        print_keyword_list(fp, symtab, POS_VALUE, true);
-        print_keyword_list(fp, symtab, NO_VALUE, true);
-    }
+/*! \cond internal */
+HelpTopicPointer createSelectionHelpTopic()
+{
+    CompositeHelpTopicPointer root(new CompositeHelpTopic<CommonHelpText>);
+    root->registerSubTopic<SimpleHelpTopic<ArithmeticHelpText> >();
+    root->registerSubTopic<SimpleHelpTopic<CmdLineHelpText> >();
+    root->registerSubTopic<SimpleHelpTopic<EvaluationHelpText> >();
+    root->registerSubTopic<SimpleHelpTopic<ExamplesHelpText> >();
+    root->registerSubTopic<KeywordsHelpTopic>();
+    root->registerSubTopic<SimpleHelpTopic<LimitationsHelpText> >();
+    root->registerSubTopic<SimpleHelpTopic<PositionsHelpText> >();
+    root->registerSubTopic<SimpleHelpTopic<SyntaxHelpText> >();
+    return move(root);
 }
+//! \cond
+
+} // namespace gmx
index a032c836ba3078841ea79aa0cd540a97021e9ed7..64c046ba5e00922a944690d2ed64308601d9614b 100644 (file)
  * For more info, check our website at http://www.gromacs.org
  */
 /*! \internal \file
- * \brief Functions for printing help for selections.
- *
- * This is an implementation header: there should be no need to use it outside
- * this directory.
+ * \brief
+ * Functions for initializing online help for selections.
  *
  * \author Teemu Murtola <teemu.murtola@cbr.su.se>
  * \ingroup module_selection
  */
-#ifndef GMX_SELECTION_HELP_H
-#define GMX_SELECTION_HELP_H
+#ifndef GMX_SELECTION_SELHELP_H
+#define GMX_SELECTION_SELHELP_H
+
+#include "../onlinehelp/helptopicinterface.h"
 
-#include <stdio.h>
+namespace gmx
+{
 
-struct gmx_sel_symtab_t;
+/*! \cond internal */
+/*! \internal \brief
+ * Creates a help tree for selections.
+ *
+ * \throws   std::bad_alloc if out of memory.
+ * \returns  Root topic of the created selection tree.
+ *
+ * \ingroup module_selection
+ */
+HelpTopicPointer createSelectionHelpTopic();
+//! \endcond
 
-/** Prints help for writing selections. */
-void
-_gmx_sel_print_help(FILE *fp, struct gmx_sel_symtab_t *symtab, const char *topic);
+} // namespace gmx
 
 #endif
diff --git a/src/testutils/mock_helptopic.cpp b/src/testutils/mock_helptopic.cpp
new file mode 100644 (file)
index 0000000..e1c20cd
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ *
+ *                This source code is part of
+ *
+ *                 G   R   O   M   A   C   S
+ *
+ *          GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \internal \file
+ * \brief
+ * Implements classes in mock_helptopic.h.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \ingroup module_testutils
+ */
+#include "mock_helptopic.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace gmx
+{
+namespace test
+{
+
+/********************************************************************
+ * MockHelpTopic
+ */
+
+// static
+MockHelpTopic &
+MockHelpTopic::addSubTopic(gmx::AbstractCompositeHelpTopic *parent,
+                           const char *name, const char *title,
+                           const char *text)
+{
+    MockHelpTopic *topic = new MockHelpTopic(name, title, text);
+    parent->addSubTopic(gmx::HelpTopicPointer(topic));
+    return *topic;
+}
+
+MockHelpTopic::MockHelpTopic(const char *name, const char *title, const char *text)
+    : name_(name), title_(title), text_(text)
+{
+}
+
+MockHelpTopic::~MockHelpTopic()
+{
+}
+
+const char *MockHelpTopic::name() const
+{
+    return name_;
+}
+
+const char *MockHelpTopic::title() const
+{
+    return title_;
+}
+
+std::string MockHelpTopic::helpText() const
+{
+    return text_;
+}
+
+MockHelpTopic &
+MockHelpTopic::addSubTopic(const char *name, const char *title,
+                           const char *text)
+{
+    return addSubTopic(this, name, title, text);
+}
+
+} // namespace test
+} // namespace gmx
diff --git a/src/testutils/mock_helptopic.h b/src/testutils/mock_helptopic.h
new file mode 100644 (file)
index 0000000..12ddf46
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ *
+ *                This source code is part of
+ *
+ *                 G   R   O   M   A   C   S
+ *
+ *          GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \libinternal \file
+ * \brief
+ * Declares mock implementation of gmx::HelpTopicInterface.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \inlibraryapi
+ * \ingroup module_testutils
+ */
+#ifndef GMX_TESTUTILS_MOCK_HELPTOPIC_H
+#define GMX_TESTUTILS_MOCK_HELPTOPIC_H
+
+#include <gmock/gmock.h>
+
+#include "gromacs/onlinehelp/helptopic.h"
+
+namespace gmx
+{
+namespace test
+{
+
+class MockHelpTopic : public AbstractCompositeHelpTopic
+{
+    public:
+        static MockHelpTopic &addSubTopic(
+                gmx::AbstractCompositeHelpTopic *parent,
+                const char *name, const char *title, const char *text);
+
+        MockHelpTopic(const char *name, const char *title, const char *text);
+        virtual ~MockHelpTopic();
+
+        virtual const char *name() const;
+        virtual const char *title() const;
+
+        MOCK_CONST_METHOD1(writeHelp, void(File *));
+
+        MockHelpTopic &addSubTopic(const char *name, const char *title,
+                                   const char *text);
+        using AbstractCompositeHelpTopic::addSubTopic;
+
+    private:
+        virtual std::string helpText() const;
+
+        const char             *name_;
+        const char             *title_;
+        const char             *text_;
+};
+
+} // namespace test
+} // namespace gmx
+
+#endif