Initial implementation of selection file input.
authorTeemu Murtola <teemu.murtola@gmail.com>
Mon, 30 Apr 2012 05:58:12 +0000 (08:58 +0300)
committerTeemu Murtola <teemu.murtola@gmail.com>
Thu, 3 May 2012 04:32:50 +0000 (07:32 +0300)
- Add SelectionFileOption and related classes to implement an option
  that can be used to provide selections from a file.
- Add SelectionCollection::parseRequestedFromFile() to do the actual
  work.
- Add special handling for the option in command-line help to print it
  together with other selection options.
- Add tests.
- Removed support for specifying a selection option multiple times on
  the command line, since it is not clear how it should work together
  with the new option.
- Temporary exception safety fix for selection file input.
- Updated valgrind suppression rules for MacOS for some
  exception-handling stuff.

Initial implementation for IssueID #656.

Change-Id: Id4ddf545ec13986fa57dac42a8b1dc4075a42840

19 files changed:
cmake/legacy_and_external.supp
src/gromacs/commandline/cmdlinehelpwriter.cpp
src/gromacs/commandline/tests/cmdlinehelpwriter.cpp
src/gromacs/commandline/tests/refdata/CommandLineHelpWriterTest_HandlesOptionTypes.xml
src/gromacs/commandline/tests/refdata/CommandLineHelpWriterTest_HandlesSelectionOptions.xml
src/gromacs/selection/selectioncollection-impl.h
src/gromacs/selection/selectioncollection.cpp
src/gromacs/selection/selectioncollection.h
src/gromacs/selection/selectionfileoption.h [new file with mode: 0644]
src/gromacs/selection/selectionfileoptioninfo.h [new file with mode: 0644]
src/gromacs/selection/selectionfileoptionstorage.h [new file with mode: 0644]
src/gromacs/selection/selectionoption.cpp
src/gromacs/selection/selectionoption.h
src/gromacs/selection/selectionoptioninfo.h
src/gromacs/selection/tests/selectioncollection.cpp
src/gromacs/selection/tests/selectionoption.cpp
src/gromacs/selection/tests/selfile.dat [new file with mode: 0644]
src/gromacs/trajectoryanalysis/runnercommon.cpp
src/gromacs/trajectoryanalysis/tests/test_selection.cpp

index 0916964ea00d5201d13d7576366535ba1292f36e..6788ba24b8a7e2cf5150d3e65729ef021be99b76 100644 (file)
    fun:__cxa_throw
 }
 
+{
+   _Unwind_RaiseException
+   Memcheck:Leak
+   fun:malloc
+   ...
+   fun:_Unwind_RaiseException
+   fun:__cxa_throw
+}
+
 # Intel compiler on MacOS
 {
    __cilkrts_os_mutex_create
index 131c5dd903e8d201be8478985247257016d9cd63..07458e9e5abe2ff190d8cb3b23f69f029b761ca2 100644 (file)
@@ -49,6 +49,7 @@
 #include "gromacs/options/filenameoptioninfo.h"
 #include "gromacs/options/options.h"
 #include "gromacs/options/optionsvisitor.h"
+#include "gromacs/selection/selectionfileoptioninfo.h"
 #include "gromacs/selection/selectionoptioninfo.h"
 #include "gromacs/utility/format.h"
 
@@ -287,6 +288,7 @@ void ParameterWriter::visitSubSection(const Options &section)
 void ParameterWriter::visitOption(const OptionInfo &option)
 {
     if (option.isType<FileNameOptionInfo>()
+        || option.isType<SelectionFileOptionInfo>()
         || option.isType<SelectionOptionInfo>()
         || (!bShowHidden_ && option.isHidden()))
     {
@@ -334,7 +336,7 @@ void ParameterWriter::visitOption(const OptionInfo &option)
  *
  * \ingroup module_commandline
  */
-class SelectionParameterWriter : public OptionsTypeVisitor<SelectionOptionInfo>
+class SelectionParameterWriter : public OptionsVisitor
 {
     public:
         //! Creates a helper object for writing selection parameters.
@@ -344,7 +346,7 @@ class SelectionParameterWriter : public OptionsTypeVisitor<SelectionOptionInfo>
         bool didOutput() const { return formatter_.didOutput(); }
 
         virtual void visitSubSection(const Options &section);
-        virtual void visitOptionType(const SelectionOptionInfo &option);
+        virtual void visitOption(const OptionInfo &option);
 
     private:
         FILE                   *fp_;
@@ -365,8 +367,14 @@ void SelectionParameterWriter::visitSubSection(const Options &section)
     iterator.acceptOptions(this);
 }
 
-void SelectionParameterWriter::visitOptionType(const SelectionOptionInfo &option)
+void SelectionParameterWriter::visitOption(const OptionInfo &option)
 {
+    if (!option.isType<SelectionFileOptionInfo>()
+        && !option.isType<SelectionOptionInfo>())
+    {
+        return;
+    }
+
     formatter_.clear();
     std::string name(formatString("-%s", option.name().c_str()));
     formatter_.addColumnLine(0, name);
index 7636827d06886a3f238ac424a5b0742f0bdfe723..f9317015e4958d032fb1a13844fd2e5333a5c20d 100644 (file)
@@ -50,6 +50,7 @@
 #include "gromacs/options/basicoptions.h"
 #include "gromacs/options/filenameoption.h"
 #include "gromacs/options/options.h"
+#include "gromacs/selection/selectionfileoption.h"
 #include "gromacs/selection/selectionoption.h"
 #include "gromacs/selection/selectionoptioninfo.h"
 #include "gromacs/selection/selectioncollection.h"
@@ -185,6 +186,7 @@ TEST_F(CommandLineHelpWriterTest, HandlesOptionTypes)
                         .description("Output file description")
                         .filetype(eftPlot).outputFile());
 
+    options.addOption(SelectionFileOption("sf"));
     options.addOption(SelectionOption("sel").description("Selection option"));
 
     CommandLineHelpWriter writer(options);
@@ -263,9 +265,11 @@ TEST_F(CommandLineHelpWriterTest, HandlesLongOptions)
  */
 TEST_F(CommandLineHelpWriterTest, HandlesSelectionOptions)
 {
+    using gmx::SelectionFileOption;
     using gmx::SelectionOption;
 
     gmx::Options options(NULL, NULL);
+    options.addOption(SelectionFileOption("sf"));
     options.addOption(SelectionOption("refsel").required()
                         .description("Reference selection option"));
     options.addOption(SelectionOption("sel").required().valueCount(2)
index 0a7f9a993be965b70821158d9030e610775279cf..5a5029c115f1e6f4da717e9b99f8ef7c5eb19821 100644 (file)
@@ -25,6 +25,7 @@ Option       Type   Value  Description
 
 Selection  Description
 -----------------------------------
+-sf        Provide selections from files
 -sel       Selection option
 
 ]]></String>
index 425b4b054da6fdbe379df157c5d554766d9aeb36..e15a38c20caf8ba35deab158b78386e7ab22329f 100644 (file)
@@ -4,6 +4,7 @@
   <String Name="HelpText"><![CDATA[
 Selection  Description
 -----------------------------------
+-sf        Provide selections from files
 -refsel    Reference selection option
     resname SOL
 -sel       Selection option
index 76207dd2ba8978002daacbdb053743bb41adbfda..fecbdbb5a6328d1be8dea742db0c47ab2d821819 100644 (file)
@@ -214,6 +214,22 @@ class SelectionCollection::Impl
         void requestSelections(const std::string &name,
                                const std::string &descr,
                                SelectionOptionStorage *storage);
+        /*! \brief
+         * Assign selections from a list to pending requests.
+         *
+         * \param[in] selections  List of selections to assign.
+         * \throws    std::bad_alloc if out of memory.
+         * \throws    InvalidInputError if the assignment cannot be done
+         *      (see parseRequestedFromFile() for documented conditions).
+         *
+         * Loops through \p selections and the pending requests lists in order,
+         * and for each requests, assigns the first yet unassigned selections
+         * from the list.
+         *
+         * Used to implement parseRequestedFromFile() and
+         * parseRequestedFromStdin().
+         */
+        void placeSelectionsInRequests(const SelectionList &selections);
         /*! \brief
          * Replace group references by group contents.
          *
index 2f5d77a734ac845aa06eebcc8705f0cb0caf93bb..ad80d3499b8510749fd37f35b56698ec7e063e34 100644 (file)
@@ -52,6 +52,7 @@
 #include "gromacs/selection/selectioncollection.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/file.h"
+#include "gromacs/utility/format.h"
 #include "gromacs/utility/gmxassert.h"
 #include "gromacs/utility/messagestringcollector.h"
 
@@ -178,6 +179,48 @@ void SelectionCollection::Impl::requestSelections(
 }
 
 
+void SelectionCollection::Impl::placeSelectionsInRequests(
+        const SelectionList &selections)
+{
+    RequestsClearer clearRequestsOnExit(&_requests);
+
+    SelectionList::const_iterator first = selections.begin();
+    SelectionList::const_iterator last = first;
+    RequestList::const_iterator i;
+    // TODO: Improve error messages.
+    for (i = _requests.begin(); i != _requests.end(); ++i)
+    {
+        const SelectionRequest &request = *i;
+        if (request.count() > 0)
+        {
+            if (selections.end() - first < request.count())
+            {
+                GMX_THROW(InvalidInputError("Too few selections provided"));
+            }
+            last = first + request.count();
+        }
+        else
+        {
+            if (i != _requests.end() - 1)
+            {
+                GMX_THROW(InvalidInputError(
+                            formatString("Request for selection '%s' must "
+                                         "not be followed by others",
+                                         request.name.c_str())));
+            }
+            last = selections.end();
+        }
+        SelectionList curr(first, last);
+        request.storage->addSelections(curr, true);
+        first = last;
+    }
+    if (last != selections.end())
+    {
+        GMX_THROW(InvalidInputError("Too many selections provided"));
+    }
+}
+
+
 void SelectionCollection::Impl::resolveExternalGroups(
         t_selelem *root, MessageStringCollector *errors)
 {
@@ -432,43 +475,20 @@ SelectionCollection::parseRequestedFromStdin(bool bInteractive)
 
 
 void
-SelectionCollection::parseRequestedFromString(const std::string &str)
+SelectionCollection::parseRequestedFromFile(const std::string &filename)
 {
-    Impl::RequestsClearer clearRequestsOnExit(&_impl->_requests);
+    SelectionList selections;
+    parseFromFile(filename, &selections);
+    _impl->placeSelectionsInRequests(selections);
+}
+
 
+void
+SelectionCollection::parseRequestedFromString(const std::string &str)
+{
     SelectionList selections;
     parseFromString(str, &selections);
-
-    SelectionList::const_iterator first = selections.begin();
-    SelectionList::const_iterator last = first;
-    Impl::RequestList::const_iterator i;
-    for (i = _impl->_requests.begin(); i != _impl->_requests.end(); ++i)
-    {
-        const Impl::SelectionRequest &request = *i;
-        if (request.count() > 0)
-        {
-            if (selections.end() - first < request.count())
-            {
-                GMX_THROW(InvalidInputError("Too few selections provided"));
-            }
-            last = first + request.count();
-        }
-        else
-        {
-            if (i != _impl->_requests.end() - 1)
-            {
-                GMX_THROW(APIError("Request for all selections not the last option"));
-            }
-            last = selections.end();
-        }
-        SelectionList curr(first, last);
-        request.storage->addSelections(curr, true);
-        first = last;
-    }
-    if (last != selections.end())
-    {
-        GMX_THROW(InvalidInputError("Too many selections provided"));
-    }
+    _impl->placeSelectionsInRequests(selections);
 }
 
 
@@ -493,10 +513,11 @@ SelectionCollection::parseFromFile(const std::string &filename,
 {
     yyscan_t scanner;
 
+    File file(filename, "r");
+    // TODO: Exception-safe way of using the lexer.
     _gmx_sel_init_lexer(&scanner, &_impl->_sc, false, -1,
                         _impl->_bExternalGroupsSet,
                         _impl->_grps);
-    File file(filename, "r");
     _gmx_sel_set_lex_input_file(scanner, file.handle());
     _impl->runParser(scanner, -1, output);
     file.close();
index 7a689388e4671cfe8043f2b2c708b0dfade91171..6e6ad0dbca8e580e8afe4b9f73be26521760b3e8 100644 (file)
@@ -247,28 +247,44 @@ class SelectionCollection
          */
         void parseRequestedFromStdin(bool bInteractive);
         /*! \brief
-         * Parses selection(s) from a string for options not yet provided.
+         * Parses selection(s) from a file for options not yet provided.
          *
-         * \param[in]  str     String to parse.
+         * \param[in]  filename Name of the file to parse selections from.
          * \throws     unspecified  Can throw any exception thrown by
-         *      parseFromString().
+         *      parseFromFile().
          * \throws     std::bad_alloc if out of memory.
          * \throws     InvalidInputError if
-         *      - the number of selections in \p str doesn't match the number
-         *        requested.
+         *      - the number of selections in \p filename doesn't match the
+         *        number requested.
          *      - any selection uses a feature that is not allowed for the
          *        corresponding option.
-         * \throws     APIError if there is a request for any number of
-         *      selections that is not the last (in which case it is not
-         *      possible to determine which selections belong to which
-         *      request).
+         *      - if there is a request for any number of selections that is
+         *        not the last (in which case it is not possible to determine
+         *        which selections belong to which request).
          *
          * This method behaves as parseRequestedFromStdin(), but reads the
-         * selections from a string instead of standard input.
-         * This method is mainly used for testing.
+         * selections from a file instead of standard input.
+         * This is used to implement SelectionFileOption.
          *
          * \see parseRequestedFromStdin()
          */
+        void parseRequestedFromFile(const std::string &filename);
+        /*! \brief
+         * Parses selection(s) from a string for options not yet provided.
+         *
+         * \param[in]  str     String to parse.
+         * \throws     unspecified  Can throw any exception thrown by
+         *      parseFromString().
+         * \throws     std::bad_alloc if out of memory.
+         * \throws     InvalidInputError in same conditions as
+         *      parseRequestedFromFile().
+         *
+         * This method behaves as parseRequestedFromFile(), but reads the
+         * selections from a string instead of a file.
+         * This method is mainly used for testing.
+         *
+         * \see parseRequestedFromFile()
+         */
         void parseRequestedFromString(const std::string &str);
         /*! \brief
          * Parses selection(s) from standard input.
diff --git a/src/gromacs/selection/selectionfileoption.h b/src/gromacs/selection/selectionfileoption.h
new file mode 100644 (file)
index 0000000..f158709
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ *
+ *                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::SelectionFileOption.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \inlibraryapi
+ * \ingroup module_selection
+ */
+#ifndef GMX_SELECTION_SELECTIONFILEOPTION_H
+#define GMX_SELECTION_SELECTIONFILEOPTION_H
+
+#include "../options/abstractoption.h"
+
+namespace gmx
+{
+
+/*! \libinternal \brief
+ * Specifies a special option that provides selections from a file.
+ *
+ * This option is used internally by the command-line framework to implement
+ * file input for selections.  The option takes a file name, and reads it in
+ * using SelectionCollection::parseRequestedFromFile().  This means that
+ * selections from the file are assigned to selection options that have been
+ * explicitly provided without values earlier on the command line.
+ *
+ * Public methods in this class do not throw.
+ *
+ * \inlibraryapi
+ * \ingroup module_selection
+ */
+class SelectionFileOption : public AbstractOption
+{
+    public:
+        //! Initializes an option with the given name.
+        explicit SelectionFileOption(const char *name);
+
+    private:
+        virtual AbstractOptionStoragePointer createStorage() const;
+};
+
+} // namespace gmx
+
+#endif
diff --git a/src/gromacs/selection/selectionfileoptioninfo.h b/src/gromacs/selection/selectionfileoptioninfo.h
new file mode 100644 (file)
index 0000000..bff3a5b
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ *
+ *                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::SelectionFileOptionInfo.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \inlibraryapi
+ * \ingroup module_selection
+ */
+#ifndef GMX_SELECTION_SELECTIONFILEOPTIONINFO_H
+#define GMX_SELECTION_SELECTIONFILEOPTIONINFO_H
+
+#include "../options/optioninfo.h"
+
+namespace gmx
+{
+
+class SelectionCollection;
+class SelectionFileOptionStorage;
+
+/*! \libinternal \brief
+ * Wrapper class for accessing and modifying selection file option information.
+ *
+ * \inlibraryapi
+ * \ingroup module_selection
+ */
+class SelectionFileOptionInfo : public OptionInfo
+{
+    public:
+        /*! \brief
+         * Creates option info object for given storage object.
+         *
+         * Does not throw.
+         */
+        explicit SelectionFileOptionInfo(SelectionFileOptionStorage *option);
+
+        /*! \brief
+         * Set selection collection into which this option adds selections.
+         *
+         * \param   selections  Selection collection to set.
+         *
+         * This must be called before values are added.
+         *
+         * Typically it is called through setSelectionCollectionForOptions(),
+         * which recursively sets the collection for all selection options in
+         * an Options object.
+         *
+         * Does not throw.
+         */
+        void setSelectionCollection(SelectionCollection *selections);
+
+    private:
+        SelectionFileOptionStorage &option();
+        const SelectionFileOptionStorage &option() const;
+};
+
+} // namespace gmx
+
+#endif
diff --git a/src/gromacs/selection/selectionfileoptionstorage.h b/src/gromacs/selection/selectionfileoptionstorage.h
new file mode 100644 (file)
index 0000000..de1555e
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ *
+ *                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
+ * Declares gmx::SelectionFileOptionStorage.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \ingroup module_selection
+ */
+#ifndef GMX_SELECTION_SELECTIONFILEOPTIONSTORAGE_H
+#define GMX_SELECTION_SELECTIONFILEOPTIONSTORAGE_H
+
+#include "../options/abstractoptionstorage.h"
+#include "selectionfileoptioninfo.h"
+
+namespace gmx
+{
+
+class SelectionCollection;
+class SelectionFileOption;
+
+/*! \internal \brief
+ * Implementation for a special option for reading selections from files.
+ *
+ * \ingroup module_selection
+ */
+class SelectionFileOptionStorage : public AbstractOptionStorage
+{
+    public:
+        /*! \brief
+         * Initializes the storage from option settings.
+         *
+         * \param[in] settings   Storage settings.
+         */
+        SelectionFileOptionStorage(const SelectionFileOption &settings);
+
+        virtual OptionInfo &optionInfo() { return info_; }
+        virtual const char *typeString() const { return "file"; }
+        virtual int valueCount() const { return 0; }
+        virtual std::string formatValue(int /*i*/) const { return ""; }
+
+        //! \copydoc SelectionFileOptionInfo::setSelectionCollection()
+        void setSelectionCollection(SelectionCollection *selections)
+        {
+            sc_ = selections;
+        }
+
+    private:
+        virtual void clearSet();
+        virtual void convertValue(const std::string &value);
+        virtual void processSet();
+        virtual void processAll() {}
+
+        SelectionFileOptionInfo info_;
+        SelectionCollection    *sc_;
+        bool                    bValueParsed_;
+};
+
+} // namespace gmx
+
+#endif
index 78d3794c997e821e11f34edd17bbdde356766b0d..0bf0eb4c452c96e3002aa28db4b3d31791292fc7 100644 (file)
 #include "gromacs/options/optionsvisitor.h"
 #include "gromacs/selection/selection.h"
 #include "gromacs/selection/selectioncollection.h"
+#include "gromacs/selection/selectionfileoption.h"
 #include "gromacs/selection/selectionoptioninfo.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/gmxassert.h"
 #include "gromacs/utility/messagestringcollector.h"
 
 #include "selectioncollection-impl.h"
+#include "selectionfileoptionstorage.h"
 #include "selectionoptionstorage.h"
 
 namespace gmx
@@ -63,6 +65,8 @@ SelectionOptionStorage::SelectionOptionStorage(const SelectionOption &settings)
     : MyBase(settings, OptionFlags() | efNoDefaultValue | efDontCheckMinimumCount),
       _info(this), _sc(NULL), _selectionFlags(settings._selectionFlags)
 {
+    GMX_RELEASE_ASSERT(!hasFlag(efMulti),
+                       "allowMultiple() is not supported for selection options");
     if (settings._infoPtr != NULL)
     {
         *settings._infoPtr = &_info;
@@ -111,14 +115,19 @@ void SelectionOptionStorage::convertValue(const std::string &value)
     GMX_RELEASE_ASSERT(_sc != NULL, "Selection collection is not set");
 
     SelectionList selections;
-    // TODO: Implement reading from a file.
     _sc->parseFromString(value, &selections);
     addSelections(selections, false);
 }
 
 void SelectionOptionStorage::processSetValues(ValueList *values)
 {
-    if (values->size() > 0 && values->size() < static_cast<size_t>(minValueCount()))
+    GMX_RELEASE_ASSERT(_sc != NULL, "Selection collection is not set");
+
+    if (values->size() == 0)
+    {
+        _sc->_impl->requestSelections(name(), description(), this);
+    }
+    else if (values->size() < static_cast<size_t>(minValueCount()))
     {
         GMX_THROW(InvalidInputError("Too few (valid) values provided"));
     }
@@ -131,7 +140,7 @@ void SelectionOptionStorage::processSetValues(ValueList *values)
 
 void SelectionOptionStorage::processAll()
 {
-    if ((isRequired() || isSet()) && valueCount() == 0)
+    if (isRequired() && !isSet())
     {
         GMX_RELEASE_ASSERT(_sc != NULL, "Selection collection is not set");
 
@@ -261,6 +270,84 @@ AbstractOptionStoragePointer SelectionOption::createStorage() const
 }
 
 
+/********************************************************************
+ * SelectionFileOptionStorage
+ */
+
+SelectionFileOptionStorage::SelectionFileOptionStorage(const SelectionFileOption &settings)
+    : AbstractOptionStorage(settings, OptionFlags() | efMulti | efDontCheckMinimumCount),
+      info_(this), sc_(NULL), bValueParsed_(false)
+{
+}
+
+void SelectionFileOptionStorage::clearSet()
+{
+    bValueParsed_ = false;
+}
+
+void SelectionFileOptionStorage::convertValue(const std::string &value)
+{
+    GMX_RELEASE_ASSERT(sc_ != NULL, "Selection collection is not set");
+
+    if (bValueParsed_)
+    {
+        GMX_THROW(InvalidInputError("More than one file name provided"));
+    }
+    bValueParsed_ = true;
+    // TODO: Should we throw an InvalidInputError if the file does not exist?
+    sc_->parseRequestedFromFile(value);
+}
+
+void SelectionFileOptionStorage::processSet()
+{
+    if (!bValueParsed_)
+    {
+        GMX_THROW(InvalidInputError("No file name provided"));
+    }
+}
+
+
+/********************************************************************
+ * SelectionFileOptionInfo
+ */
+
+SelectionFileOptionInfo::SelectionFileOptionInfo(SelectionFileOptionStorage *option)
+    : OptionInfo(option)
+{
+}
+
+SelectionFileOptionStorage &SelectionFileOptionInfo::option()
+{
+    return static_cast<SelectionFileOptionStorage &>(OptionInfo::option());
+}
+
+const SelectionFileOptionStorage &SelectionFileOptionInfo::option() const
+{
+    return static_cast<const SelectionFileOptionStorage &>(OptionInfo::option());
+}
+
+void SelectionFileOptionInfo::setSelectionCollection(SelectionCollection *selections)
+{
+    option().setSelectionCollection(selections);
+}
+
+
+/********************************************************************
+ * SelectionFileOption
+ */
+
+SelectionFileOption::SelectionFileOption(const char *name)
+    : AbstractOption(name)
+{
+    setDescription("Provide selections from files");
+}
+
+AbstractOptionStoragePointer SelectionFileOption::createStorage() const
+{
+    return AbstractOptionStoragePointer(new SelectionFileOptionStorage(*this));
+}
+
+
 /********************************************************************
  * Global functions
  */
@@ -273,7 +360,7 @@ namespace
  *
  * \ingroup module_selection
  */
-class SelectionCollectionSetter : public OptionsModifyingTypeVisitor<SelectionOptionInfo>
+class SelectionCollectionSetter : public OptionsModifyingVisitor
 {
     public:
         //! Construct a visitor that sets given selection collection.
@@ -289,9 +376,20 @@ class SelectionCollectionSetter : public OptionsModifyingTypeVisitor<SelectionOp
             iterator.acceptOptions(this);
         }
 
-        void visitOptionType(SelectionOptionInfo *option)
+        void visitOption(OptionInfo *option)
         {
-            option->setSelectionCollection(selections_);
+            SelectionOptionInfo *selOption
+                = option->toType<SelectionOptionInfo>();
+            if (selOption != NULL)
+            {
+                selOption->setSelectionCollection(selections_);
+            }
+            SelectionFileOptionInfo *selFileOption
+                = option->toType<SelectionFileOptionInfo>();
+            if (selFileOption != NULL)
+            {
+                selFileOption->setSelectionCollection(selections_);
+            }
         }
 
     private:
index 169eab1b040f4e3295c7f41da23226b1bf61bb4f..dd427dde9dcb52a187fd374c1b227f5c0ce95425 100644 (file)
@@ -119,6 +119,9 @@ class SelectionOption : public OptionTemplate<Selection, SelectionOption>
         { _infoPtr = infoPtr; return me(); }
 
     private:
+        // Disable possibility to allow multiple occurrences, since it isn't
+        // implemented.
+        using MyBase::allowMultiple;
         // Disable default value because it is impossible to provide a
         // Selection object.
         using MyBase::defaultValue;
index 9a56ea738d1b4d8c37fc0a088ebcda69fe2e8f46..9e28e0943df17e5598040db86ee33eb882bb0c50 100644 (file)
@@ -213,7 +213,6 @@ class SelectionOptionInfo : public OptionInfo
  */
 void setSelectionCollectionForOptions(Options *options,
                                       SelectionCollection *selections);
-//! \endcond
 
 } // namespace gmx
 
index 4a68c12b0b48c3842d51542b80f3c40b3d71bcee..da012471ec95aacd2dfa82e4cf8ddf9baf0fdb3d 100644 (file)
@@ -355,6 +355,16 @@ TEST_F(SelectionCollectionTest, HandlesNoSelections)
     EXPECT_NO_THROW(_sc.compile());
 }
 
+TEST_F(SelectionCollectionTest, ParsesSelectionsFromFile)
+{
+    ASSERT_NO_THROW(_sc.parseFromFile(gmx::test::getTestFilePath("selfile.dat"),
+                                      &_sel));
+    // These should match the contents of selfile.dat
+    ASSERT_EQ(2U, _sel.size());
+    EXPECT_STREQ("resname RA RB", _sel[0].selectionText());
+    EXPECT_STREQ("resname RB RC", _sel[1].selectionText());
+}
+
 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue)
 {
     EXPECT_THROW(_sc.parseFromString("mindist from atomnr 1 cutoff", &_sel),
index 7eba71cc527cfdc0396be859fddeaea3bf7c2823..4e3c2feae6859a9fda0aa0e42abfed205d6b4e64 100644 (file)
 #include "gromacs/selection/selectioncollection.h"
 #include "gromacs/selection/selectionoption.h"
 #include "gromacs/selection/selectionoptioninfo.h"
+#include "gromacs/selection/selectionfileoption.h"
 #include "gromacs/utility/exceptions.h"
 
+#include "testutils/datapath.h"
+
 namespace
 {
 
-class SelectionOptionTest : public ::testing::Test
+/********************************************************************
+ * Base fixture for tests in this file.
+ */
+
+class SelectionOptionTestBase : public ::testing::Test
 {
     public:
-        SelectionOptionTest();
+        SelectionOptionTestBase();
 
         void setCollection();
 
@@ -59,19 +66,25 @@ class SelectionOptionTest : public ::testing::Test
         gmx::Options             _options;
 };
 
-SelectionOptionTest::SelectionOptionTest()
+SelectionOptionTestBase::SelectionOptionTestBase()
     : _options(NULL, NULL)
 {
     _sc.setReferencePosType("atom");
     _sc.setOutputPosType("atom");
 }
 
-void SelectionOptionTest::setCollection()
+void SelectionOptionTestBase::setCollection()
 {
     setSelectionCollectionForOptions(&_options, &_sc);
 }
 
 
+/********************************************************************
+ * Tests for SelectionOption
+ */
+
+typedef SelectionOptionTestBase SelectionOptionTest;
+
 TEST_F(SelectionOptionTest, ParsesSimpleSelection)
 {
     gmx::Selection sel;
@@ -304,4 +317,177 @@ TEST_F(SelectionOptionTest, HandlesDelayedSelectionWithAdjuster)
     EXPECT_NO_THROW(_sc.parseRequestedFromString("resname RA RB; resname RB RC"));
 }
 
+
+/********************************************************************
+ * Tests for SelectionFileOption
+ */
+
+class SelectionFileOptionTest : public SelectionOptionTestBase
+{
+    public:
+        SelectionFileOptionTest();
+};
+
+SelectionFileOptionTest::SelectionFileOptionTest()
+{
+    _options.addOption(gmx::SelectionFileOption("sf"));
+}
+
+
+TEST_F(SelectionFileOptionTest, HandlesSingleSelectionOptionFromFile)
+{
+    gmx::SelectionList sel;
+    using gmx::SelectionOption;
+    ASSERT_NO_THROW(_options.addOption(
+                        SelectionOption("sel").storeVector(&sel).multiValue()));
+    setCollection();
+
+    gmx::OptionsAssigner assigner(&_options);
+    EXPECT_NO_THROW(assigner.start());
+    ASSERT_NO_THROW(assigner.startOption("sel"));
+    EXPECT_NO_THROW(assigner.finishOption());
+    ASSERT_NO_THROW(assigner.startOption("sf"));
+    EXPECT_NO_THROW(assigner.appendValue(gmx::test::getTestFilePath("selfile.dat")));
+    EXPECT_NO_THROW(assigner.finishOption());
+    EXPECT_NO_THROW(assigner.finish());
+    EXPECT_NO_THROW(_options.finish());
+
+    // These should match the contents of selfile.dat
+    ASSERT_EQ(2U, sel.size());
+    EXPECT_STREQ("resname RA RB", sel[0].selectionText());
+    EXPECT_STREQ("resname RB RC", sel[1].selectionText());
+}
+
+
+TEST_F(SelectionFileOptionTest, HandlesTwoSeparateSelectionOptions)
+{
+    gmx::SelectionList sel1;
+    gmx::SelectionList sel2;
+    using gmx::SelectionOption;
+    ASSERT_NO_THROW(_options.addOption(
+                        SelectionOption("sel1").storeVector(&sel1).multiValue()));
+    ASSERT_NO_THROW(_options.addOption(
+                        SelectionOption("sel2").storeVector(&sel2).multiValue()));
+    setCollection();
+
+    gmx::OptionsAssigner assigner(&_options);
+    std::string value(gmx::test::getTestFilePath("selfile.dat"));
+    EXPECT_NO_THROW(assigner.start());
+    ASSERT_NO_THROW(assigner.startOption("sel1"));
+    EXPECT_NO_THROW(assigner.finishOption());
+    ASSERT_NO_THROW(assigner.startOption("sf"));
+    EXPECT_NO_THROW(assigner.appendValue(value));
+    EXPECT_NO_THROW(assigner.finishOption());
+    ASSERT_NO_THROW(assigner.startOption("sel2"));
+    EXPECT_NO_THROW(assigner.finishOption());
+    ASSERT_NO_THROW(assigner.startOption("sf"));
+    EXPECT_NO_THROW(assigner.appendValue(value));
+    EXPECT_NO_THROW(assigner.finishOption());
+    EXPECT_NO_THROW(assigner.finish());
+    EXPECT_NO_THROW(_options.finish());
+
+    // These should match the contents of selfile.dat
+    ASSERT_EQ(2U, sel1.size());
+    EXPECT_STREQ("resname RA RB", sel1[0].selectionText());
+    EXPECT_STREQ("resname RB RC", sel1[1].selectionText());
+    ASSERT_EQ(2U, sel2.size());
+    EXPECT_STREQ("resname RA RB", sel2[0].selectionText());
+    EXPECT_STREQ("resname RB RC", sel2[1].selectionText());
+}
+
+
+TEST_F(SelectionFileOptionTest, HandlesTwoSelectionOptionsFromSingleFile)
+{
+    gmx::SelectionList sel1;
+    gmx::SelectionList sel2;
+    using gmx::SelectionOption;
+    ASSERT_NO_THROW(_options.addOption(
+                        SelectionOption("sel1").storeVector(&sel1)));
+    ASSERT_NO_THROW(_options.addOption(
+                        SelectionOption("sel2").storeVector(&sel2)));
+    setCollection();
+
+    gmx::OptionsAssigner assigner(&_options);
+    std::string value(gmx::test::getTestFilePath("selfile.dat"));
+    EXPECT_NO_THROW(assigner.start());
+    ASSERT_NO_THROW(assigner.startOption("sel1"));
+    EXPECT_NO_THROW(assigner.finishOption());
+    ASSERT_NO_THROW(assigner.startOption("sel2"));
+    EXPECT_NO_THROW(assigner.finishOption());
+    ASSERT_NO_THROW(assigner.startOption("sf"));
+    EXPECT_NO_THROW(assigner.appendValue(value));
+    EXPECT_NO_THROW(assigner.finishOption());
+    EXPECT_NO_THROW(assigner.finish());
+    EXPECT_NO_THROW(_options.finish());
+
+    // These should match the contents of selfile.dat
+    ASSERT_EQ(1U, sel1.size());
+    EXPECT_STREQ("resname RA RB", sel1[0].selectionText());
+    ASSERT_EQ(1U, sel2.size());
+    EXPECT_STREQ("resname RB RC", sel2[0].selectionText());
+}
+
+
+TEST_F(SelectionFileOptionTest, GivesErrorWithNoFile)
+{
+    gmx::SelectionList sel;
+    using gmx::SelectionOption;
+    ASSERT_NO_THROW(_options.addOption(
+                        SelectionOption("sel").storeVector(&sel).multiValue()));
+    setCollection();
+
+    gmx::OptionsAssigner assigner(&_options);
+    EXPECT_NO_THROW(assigner.start());
+    ASSERT_NO_THROW(assigner.startOption("sel"));
+    EXPECT_NO_THROW(assigner.finishOption());
+    ASSERT_NO_THROW(assigner.startOption("sf"));
+    EXPECT_THROW(assigner.finishOption(), gmx::InvalidInputError);
+    EXPECT_NO_THROW(assigner.finish());
+    EXPECT_NO_THROW(_options.finish());
+}
+
+
+TEST_F(SelectionFileOptionTest, GivesErrorWithNonExistentFile)
+{
+    gmx::SelectionList sel;
+    using gmx::SelectionOption;
+    ASSERT_NO_THROW(_options.addOption(
+                        SelectionOption("sel").storeVector(&sel).multiValue()));
+    setCollection();
+
+    gmx::OptionsAssigner assigner(&_options);
+    EXPECT_NO_THROW(assigner.start());
+    ASSERT_NO_THROW(assigner.startOption("sel"));
+    EXPECT_NO_THROW(assigner.finishOption());
+    ASSERT_NO_THROW(assigner.startOption("sf"));
+    // TODO: Should this be changed to an InvalidInputError?
+    EXPECT_THROW(assigner.appendValue("nonexistentfile"), gmx::FileIOError);
+    EXPECT_THROW(assigner.appendValue(gmx::test::getTestFilePath("selfile.dat")),
+                 gmx::InvalidInputError);
+    EXPECT_NO_THROW(assigner.finishOption());
+    EXPECT_NO_THROW(assigner.finish());
+    EXPECT_NO_THROW(_options.finish());
+}
+
+
+TEST_F(SelectionFileOptionTest, GivesErrorWithMultipleFiles)
+{
+    gmx::SelectionList sel;
+    using gmx::SelectionOption;
+    ASSERT_NO_THROW(_options.addOption(
+                        SelectionOption("sel").storeVector(&sel).multiValue()));
+    setCollection();
+
+    gmx::OptionsAssigner assigner(&_options);
+    EXPECT_NO_THROW(assigner.start());
+    ASSERT_NO_THROW(assigner.startOption("sel"));
+    EXPECT_NO_THROW(assigner.finishOption());
+    ASSERT_NO_THROW(assigner.startOption("sf"));
+    EXPECT_NO_THROW(assigner.appendValue(gmx::test::getTestFilePath("selfile.dat")));
+    EXPECT_THROW(assigner.appendValue("nonexistentfile"), gmx::InvalidInputError);
+    EXPECT_NO_THROW(assigner.finishOption());
+    EXPECT_NO_THROW(assigner.finish());
+    EXPECT_NO_THROW(_options.finish());
+}
+
 } // namespace
diff --git a/src/gromacs/selection/tests/selfile.dat b/src/gromacs/selection/tests/selfile.dat
new file mode 100644 (file)
index 0000000..f390fe4
--- /dev/null
@@ -0,0 +1,2 @@
+resname RA RB;
+resname RB RC;
index 7868dafb32d676927a24987a37767cd0b118311f..d91ae3f0398174d35001d0c537e96e207fbe2ee5 100644 (file)
@@ -53,6 +53,7 @@
 #include "gromacs/options/options.h"
 #include "gromacs/selection/indexutil.h"
 #include "gromacs/selection/selectioncollection.h"
+#include "gromacs/selection/selectionfileoption.h"
 #include "gromacs/trajectoryanalysis/analysissettings.h"
 #include "gromacs/trajectoryanalysis/runnercommon.h"
 #include "gromacs/utility/exceptions.h"
@@ -192,6 +193,7 @@ TrajectoryAnalysisRunnerCommon::initOptions()
                           .filetype(eftIndex).inputFile()
                           .store(&_impl->_ndxfile)
                           .description("Extra index groups"));
+    options.addOption(SelectionFileOption("sf"));
 
     // Add options for trajectory time control.
     options.addOption(DoubleOption("b").store(&_impl->_startTime).timeValue()
index 61ceca062f6d313ec04c6814ef53483ae4aea2f9..97e942ff487c3af00c5dec3da320a692d349d283 100644 (file)
@@ -105,7 +105,7 @@ SelectionTester::initOptions(TrajectoryAnalysisSettings * /*settings*/)
     _options.setDescription(concatenateStrings(desc));
 
     _options.addOption(SelectionOption("select").storeVector(&_selections)
-                           .required().multiValue().allowMultiple()
+                           .required().multiValue()
                            .description("Selections to test"));
     _options.addOption(IntegerOption("pmax").store(&_nmaxind)
                            .description("Maximum number of indices to print in lists (-1 = print all)"));