Improve FileNameOption error handling
authorTeemu Murtola <teemu.murtola@gmail.com>
Sun, 11 May 2014 18:28:45 +0000 (21:28 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Tue, 8 Jul 2014 15:05:10 +0000 (17:05 +0200)
- Make FileNameOption check the presence of an input file for required
  input options, and give an informative error message if that is not
  the case.  This hopefully reduces confusion about the
  "File I/O error: topol.tpr" messages that are reported.
- Make FileNameOption check if the file name provided by the user for an
  input file exists, and if it does, only check that it is valid for
  fn2ftp() instead of overriding the user.  If the file name is not
  valid for the option, an informative error is given.
- Add a special mechanism for -multi and -multidir to avoid the check
  above, as well as all other file name completion (that was done
  earlier, but didn't really work as expected).  The same mechanism
  could also replace PCA_NOT_READ_NODE, if someone wants to make that
  work again.
- Move all logic that changes user input to something else or checks
  file system contents to FileNameOptionManager to make that more easily
  customizable.  FileNameOption without a manager now only checks that
  the file name has a recognized and valid type for the option.
- Expose the necessary functionality through FileNameOptionInfo to do
  the above.
- Split tests according to the above division of responsibilities, and
  add tests for most of the error paths.

There is very little code that remains the same in this change, so the
rewrite and move are combined.

The first bullet causes some changes in behavior if programs have
declared file name options with ffREAD, but don't actually use the input
always, but those are easy to fix when found.  This required changing
one existing test for pargs.cpp.

Change-Id: Iea2c60e5de35160cf1faf599afde99b9858e98c7

13 files changed:
src/gromacs/commandline/pargs.cpp
src/gromacs/commandline/pargs.h
src/gromacs/commandline/tests/pargs.cpp
src/gromacs/options/filenameoption.cpp
src/gromacs/options/filenameoption.h
src/gromacs/options/filenameoptionmanager.cpp
src/gromacs/options/filenameoptionmanager.h
src/gromacs/options/filenameoptionstorage.h
src/gromacs/options/tests/CMakeLists.txt
src/gromacs/options/tests/filenameoption.cpp
src/gromacs/options/tests/filenameoptionmanager.cpp [new file with mode: 0644]
src/gromacs/trajectoryanalysis/cmdlinerunner.cpp
src/programs/mdrun/mdrun.cpp

index 505c5802c18649cbe222dc1fafc54f8c190a8a56..98377ca4b47a4faf4ff3dd89489841050e345db7 100644 (file)
@@ -420,10 +420,6 @@ void OptionsAdapter::copyValues(bool bReadNode)
     std::list<FileNameData>::const_iterator file;
     for (file = fileNameOptions_.begin(); file != fileNameOptions_.end(); ++file)
     {
-        // FIXME: FF_NOT_READ_NODE should also skip all fexist() calls in
-        // FileNameOption.  However, it is not currently used, and other
-        // commented out code for using it is also outdated, so left this for
-        // later.
         if (!bReadNode && (file->fnm->flag & ffREAD))
         {
             continue;
@@ -499,6 +495,8 @@ gmx_bool parse_common_args(int *argc, char *argv[], unsigned long Flags,
         gmx::Options               options(NULL, NULL);
         gmx::FileNameOptionManager fileOptManager;
 
+        fileOptManager.disableInputOptionChecking(
+                FF(PCA_NOT_READ_NODE) || FF(PCA_DISABLE_INPUT_FILE_CHECKING));
         options.addManager(&fileOptManager);
         options.setDescription(gmx::ConstArrayRef<const char *>(desc, ndesc));
 
index a5c500cbaa550e4d75bd1a854d62ffc38a3ff872..ac8fcf7e03df8de584c61140f71723b2ccd4a4e6 100644 (file)
@@ -236,6 +236,8 @@ gmx_bool opt2parg_bSet(const char *option, int nparg, t_pargs pa[]);
 #define PCA_BE_NICE        (1<<13)
 /** Is this node not reading: for parallel all nodes but the master */
 #define PCA_NOT_READ_NODE  (1<<16)
+/** Don't do any special processing for ffREAD files */
+#define PCA_DISABLE_INPUT_FILE_CHECKING (1<<17)
 
 /*! \brief
  * Parse command-line arguments.
index a27f3b98f4e90a107356e23778416b63895e1df6..79e203658a38b6b420d4ca5b855a5ab2cd5d5d0b 100644 (file)
@@ -394,7 +394,7 @@ TEST_F(ParseCommonArgsTest, HandlesNonExistentInputFiles)
     const char *const cmdline[] = {
         "test", "-f2", "-f3", "other", "-f4", "trj.gro", "-g2", "foo"
     };
-    parseFromArray(cmdline, 0, fnm, gmx::EmptyArrayRef());
+    parseFromArray(cmdline, PCA_DISABLE_INPUT_FILE_CHECKING, fnm, gmx::EmptyArrayRef());
     EXPECT_STREQ("topol.tpr", ftp2fn(efTPS, nfile(), fnm));
     EXPECT_STREQ("trj.xtc", opt2fn("-f", nfile(), fnm));
     EXPECT_STREQ("trj2.xtc", opt2fn("-f2", nfile(), fnm));
@@ -405,6 +405,21 @@ TEST_F(ParseCommonArgsTest, HandlesNonExistentInputFiles)
     done_filenms(nfile(), fnm);
 }
 
+TEST_F(ParseCommonArgsTest, HandlesNonExistentOptionalInputFiles)
+{
+    t_filenm          fnm[] = {
+        { efTPS, "-s",  NULL,   ffOPTRD },
+        { efTRX, "-f",  "trj",  ffOPTRD }
+    };
+    const char *const cmdline[] = {
+        "test"
+    };
+    parseFromArray(cmdline, 0, fnm, gmx::EmptyArrayRef());
+    EXPECT_STREQ("topol.tpr", ftp2fn(efTPS, nfile(), fnm));
+    EXPECT_STREQ("trj.xtc", opt2fn("-f", nfile(), fnm));
+    done_filenms(nfile(), fnm);
+}
+
 TEST_F(ParseCommonArgsTest, HandlesCompressedFiles)
 {
     t_filenm          fnm[] = {
index 5bc9da6cc6a5beb034b79f0bea4ab38ff22a55ee..a0a4a4023ba0651d14eae89940e5cdcf6eb60450 100644 (file)
@@ -51,7 +51,7 @@
 #include "gromacs/options/filenameoptionmanager.h"
 #include "gromacs/options/optionmanagercontainer.h"
 #include "gromacs/utility/arrayref.h"
-#include "gromacs/utility/file.h"
+#include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/gmxassert.h"
 #include "gromacs/utility/stringutil.h"
 
@@ -86,10 +86,6 @@ const FileTypeMapping c_fileTypeMapping[] =
     { eftGenericData, efDAT }
 };
 
-//! Extensions that are recognized as compressed files.
-const char *const c_compressedExtensions[] =
-{ ".gz", ".Z" };
-
 /********************************************************************
  * FileTypeHandler
  */
@@ -116,19 +112,8 @@ class FileTypeHandler
         //! Returns the extension with the given index.
         const char *extension(int i) const;
 
-        //! Returns whether \p filename has a valid extension for this type.
-        bool hasKnownExtension(const std::string &filename) const;
-        //! Adds a default extension for this type to \p filename.
-        std::string addExtension(const std::string &filename) const;
-        /*! \brief
-         * Adds an extension to \p filename if it results in an existing file.
-         *
-         * Tries to add each extension for this file type to \p filename and
-         * checks whether this results in an existing file.
-         * The first match is returned.
-         * Returns an empty string if no existing file is found.
-         */
-        std::string findFileWithExtension(const std::string &filename) const;
+        //! Returns whether \p fileType (from filenm.h) is accepted for this type.
+        bool isValidType(int fileType) const;
 
     private:
         /*! \brief
@@ -185,84 +170,23 @@ const char *FileTypeHandler::extension(int i) const
 }
 
 bool
-FileTypeHandler::hasKnownExtension(const std::string &filename) const
-{
-    for (int i = 0; i < extensionCount(); ++i)
-    {
-        if (endsWith(filename, extension(i)))
-        {
-            return true;
-        }
-    }
-    return false;
-}
-
-std::string
-FileTypeHandler::addExtension(const std::string &filename) const
-{
-    if (extensionCount() == 0)
-    {
-        return filename;
-    }
-    return filename + extension(0);
-}
-
-std::string
-FileTypeHandler::findFileWithExtension(const std::string &filename) const
-{
-    for (int i = 0; i < extensionCount(); ++i)
-    {
-        std::string testFilename(filename + extension(i));
-        if (File::exists(testFilename))
-        {
-            return testFilename;
-        }
-    }
-    return std::string();
-}
-
-/*! \brief
- * Helper method to complete a file name provided to a file name option.
- *
- * \param[in] value       Value provided to the file name option.
- * \param[in] typeHandler Handler for the file type.
- * \param[in] bCompleteToExisting
- *     Whether to check existing files when completing the extension.
- * \returns   \p value with possible extension added.
- */
-std::string completeFileName(const std::string     &value,
-                             const FileTypeHandler &typeHandler,
-                             bool                   bCompleteToExisting)
+FileTypeHandler::isValidType(int fileType) const
 {
-    if (bCompleteToExisting && File::exists(value))
+    if (genericTypes_ != NULL)
     {
-        // TODO: This may not work as expected if the value is passed to a
-        // function that uses fn2ftp() to determine the file type and the input
-        // file has an unrecognized extension.
-        ConstArrayRef<const char *>                 compressedExtensions(c_compressedExtensions);
-        ConstArrayRef<const char *>::const_iterator ext;
-        for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext)
+        for (int i = 0; i < extensionCount(); ++i)
         {
-            if (endsWith(value, *ext))
+            if (fileType == genericTypes_[i])
             {
-                return value.substr(0, value.length() - std::strlen(*ext));
+                return true;
             }
         }
-        return value;
+        return false;
     }
-    if (typeHandler.hasKnownExtension(value))
-    {
-        return value;
-    }
-    if (bCompleteToExisting)
+    else
     {
-        std::string newValue = typeHandler.findFileWithExtension(value);
-        if (!newValue.empty())
-        {
-            return newValue;
-        }
+        return fileType == fileType_;
     }
-    return typeHandler.addExtension(value);
 }
 
 //! \}
@@ -365,35 +289,84 @@ std::string FileNameOptionStorage::formatSingleValue(const std::string &value) c
 
 void FileNameOptionStorage::convertValue(const std::string &value)
 {
-    const bool      bInput = isInputFile() || isInputOutputFile();
-    FileTypeHandler typeHandler(fileType_);
-    addValue(completeFileName(value, typeHandler, bInput));
+    if (manager_ != NULL)
+    {
+        std::string processedValue = manager_->completeFileName(value, info_);
+        if (!processedValue.empty())
+        {
+            // If the manager returns a value, use it without further checks,
+            // except for sanity checking.
+            if (!isDirectoryOption())
+            {
+                const int fileType = fn2ftp(processedValue.c_str());
+                if (fileType == efNR)
+                {
+                    // If the manager returned an invalid file name, assume
+                    // that it knows what it is doing.  But assert that it
+                    // only does that for the only case that it is currently
+                    // required for: VMD plugins.
+                    GMX_ASSERT(isInputFile() && isTrajectoryOption(),
+                               "Manager returned an invalid file name");
+                }
+                else
+                {
+                    GMX_ASSERT(isValidType(fileType),
+                               "Manager returned an invalid file name");
+                }
+            }
+            addValue(processedValue);
+            return;
+        }
+    }
+    // Currently, directory options are simple, and don't need any
+    // special processing.
+    // TODO: Consider splitting them into a separate DirectoryOption.
+    if (isDirectoryOption())
+    {
+        addValue(value);
+        return;
+    }
+    const int fileType = fn2ftp(value.c_str());
+    if (fileType == efNR)
+    {
+        std::string message
+            = formatString("File '%s' cannot be used by GROMACS because it "
+                           "does not have a recognizable extension.\n"
+                           "The following extensions are possible for this option:\n  %s",
+                           value.c_str(), joinStrings(extensions(), ", ").c_str());
+        GMX_THROW(InvalidInputError(message));
+    }
+    else if (!isValidType(fileType))
+    {
+        std::string message
+            = formatString("File name '%s' cannot be used for this option.\n"
+                           "Only the following extensions are possible:\n  %s",
+                           value.c_str(), joinStrings(extensions(), ", ").c_str());
+        GMX_THROW(InvalidInputError(message));
+    }
+    addValue(value);
 }
 
 void FileNameOptionStorage::processAll()
 {
-    FileTypeHandler typeHandler(fileType_);
-    if (hasFlag(efOption_HasDefaultValue) && typeHandler.extensionCount() > 0)
+    if (manager_ != NULL && hasFlag(efOption_HasDefaultValue))
     {
-        const bool  bInput      = isInputFile() || isInputOutputFile();
-        ValueList  &valueList   = values();
+        ValueList &valueList = values();
         GMX_RELEASE_ASSERT(valueList.size() == 1,
                            "There should be only one default value");
-        const bool  bGlobalDefault =
-            (manager_ != NULL && !manager_->defaultFileName().empty());
-        if (!valueList[0].empty() && (typeHandler.extensionCount() > 1 || bGlobalDefault))
+        if (!valueList[0].empty())
         {
             const std::string &oldValue = valueList[0];
             GMX_ASSERT(endsWith(oldValue, defaultExtension()),
                        "Default value does not have the expected extension");
-            std::string prefix = stripSuffixIfPresent(oldValue, defaultExtension());
-            if (bGlobalDefault)
-            {
-                prefix = manager_->defaultFileName();
-            }
-            std::string newValue = completeFileName(prefix, typeHandler, bInput);
-            if (newValue != oldValue)
+            const std::string  prefix
+                = stripSuffixIfPresent(oldValue, defaultExtension());
+            const std::string  newValue
+                = manager_->completeDefaultFileName(prefix, info_);
+            if (!newValue.empty() && newValue != oldValue)
             {
+                GMX_ASSERT(isValidType(fn2ftp(newValue.c_str())),
+                           "Manager returned an invalid default value");
                 valueList[0] = newValue;
                 refreshValues();
             }
@@ -406,6 +379,11 @@ bool FileNameOptionStorage::isDirectoryOption() const
     return fileType_ == efRND;
 }
 
+bool FileNameOptionStorage::isTrajectoryOption() const
+{
+    return fileType_ == efTRX;
+}
+
 const char *FileNameOptionStorage::defaultExtension() const
 {
     FileTypeHandler typeHandler(fileType_);
@@ -428,6 +406,26 @@ std::vector<const char *> FileNameOptionStorage::extensions() const
     return result;
 }
 
+bool FileNameOptionStorage::isValidType(int fileType) const
+{
+    FileTypeHandler typeHandler(fileType_);
+    return typeHandler.isValidType(fileType);
+}
+
+ConstArrayRef<int> FileNameOptionStorage::fileTypes() const
+{
+    if (fileType_ < 0)
+    {
+        return ConstArrayRef<int>();
+    }
+    const int genericTypeCount = ftp2generic_count(fileType_);
+    if (genericTypeCount > 0)
+    {
+        return ConstArrayRef<int>(ftp2generic_list(fileType_), genericTypeCount);
+    }
+    return ConstArrayRef<int>(&fileType_, 1);
+}
+
 /********************************************************************
  * FileNameOptionInfo
  */
@@ -467,6 +465,11 @@ bool FileNameOptionInfo::isDirectoryOption() const
     return option().isDirectoryOption();
 }
 
+bool FileNameOptionInfo::isTrajectoryOption() const
+{
+    return option().isTrajectoryOption();
+}
+
 const char *FileNameOptionInfo::defaultExtension() const
 {
     return option().defaultExtension();
@@ -477,6 +480,16 @@ FileNameOptionInfo::ExtensionList FileNameOptionInfo::extensions() const
     return option().extensions();
 }
 
+bool FileNameOptionInfo::isValidType(int fileType) const
+{
+    return option().isValidType(fileType);
+}
+
+ConstArrayRef<int> FileNameOptionInfo::fileTypes() const
+{
+    return option().fileTypes();
+}
+
 /********************************************************************
  * FileNameOption
  */
index e3ab252f7a615465cd97bbf6fdf37a3ffaa87e4a..453f4cd5f1dafcb7b190d6206bd13cb96fd27aea 100644 (file)
@@ -214,10 +214,16 @@ class FileNameOptionInfo : public OptionInfo
 
         //! Whether the option specifies directories.
         bool isDirectoryOption() const;
+        //! Whether the option specifies a generic trajectory file.
+        bool isTrajectoryOption() const;
         //! Returns the default extension for this option.
         const char *defaultExtension() const;
         //! Returns the list of extensions this option accepts.
         ExtensionList extensions() const;
+        //! Returns whether \p fileType (from filenm.h) is accepted for this option.
+        bool isValidType(int fileType) const;
+        //! Returns the list of file types this option accepts.
+        ConstArrayRef<int> fileTypes() const;
 
     private:
         const FileNameOptionStorage &option() const;
index 3ba0aeac410719eb5bbb21c17828c22d72608d35..390f3998183722981855da0aeeee15a6ae213066 100644 (file)
  */
 #include "filenameoptionmanager.h"
 
+#include <cstring>
+
 #include <string>
 
+#include "gromacs/fileio/filenm.h"
 #include "gromacs/options/basicoptions.h"
 #include "gromacs/options/filenameoption.h"
 #include "gromacs/options/options.h"
+#include "gromacs/utility/arrayref.h"
+#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/file.h"
+#include "gromacs/utility/path.h"
+#include "gromacs/utility/stringutil.h"
 
 namespace gmx
 {
 
+namespace
+{
+
+//! Extensions that are recognized as compressed files.
+const char *const c_compressedExtensions[] =
+{ ".gz", ".Z" };
+
+/********************************************************************
+ * Helper functions
+ */
+
+/*! \brief
+ * Adds an extension to \p prefix if it results in an existing file.
+ *
+ * Tries to add each extension for this file type to \p prefix and
+ * checks whether this results in an existing file.
+ * The first match is returned.
+ * Returns an empty string if no existing file is found.
+ */
+std::string findExistingExtension(const std::string        &prefix,
+                                  const FileNameOptionInfo &option)
+{
+    ConstArrayRef<int>                 types = option.fileTypes();
+    ConstArrayRef<int>::const_iterator i;
+    for (i = types.begin(); i != types.end(); ++i)
+    {
+        std::string testFilename(prefix + ftp2ext_with_dot(*i));
+        if (File::exists(testFilename))
+        {
+            return testFilename;
+        }
+    }
+    return std::string();
+}
+
+}   // namespace
+
 /********************************************************************
  * FileNameOptionManager::Impl
  */
@@ -62,8 +107,12 @@ namespace gmx
 class FileNameOptionManager::Impl
 {
     public:
+        Impl() : bInputCheckingDisabled_(false) {}
+
         //! Global default file name, if set.
         std::string     defaultFileName_;
+        //! Whether input option processing has been disabled.
+        bool            bInputCheckingDisabled_;
 };
 
 /********************************************************************
@@ -79,6 +128,11 @@ FileNameOptionManager::~FileNameOptionManager()
 {
 }
 
+void FileNameOptionManager::disableInputOptionChecking(bool bDisable)
+{
+    impl_->bInputCheckingDisabled_ = bDisable;
+}
+
 void FileNameOptionManager::addDefaultFileNameOption(
         Options *options, const char *name)
 {
@@ -87,9 +141,150 @@ void FileNameOptionManager::addDefaultFileNameOption(
                 .description("Set the default filename for all file options"));
 }
 
-const std::string &FileNameOptionManager::defaultFileName() const
+std::string FileNameOptionManager::completeFileName(
+        const std::string &value, const FileNameOptionInfo &option)
+{
+    const bool bInput = option.isInputFile() || option.isInputOutputFile();
+    // Currently, directory options are simple, and don't need any
+    // special processing.
+    // TODO: Consider splitting them into a separate DirectoryOption.
+    if (option.isDirectoryOption())
+    {
+        if (!impl_->bInputCheckingDisabled_ && bInput && !Directory::exists(value))
+        {
+            std::string message
+                = formatString("Directory '%s' does not exist or is not accessible.",
+                               value.c_str());
+            // TODO: Get actual errno value from the attempt to open the file
+            // to provide better feedback to the user.
+            GMX_THROW(InvalidInputError(message));
+        }
+        return value;
+    }
+    const int fileType = fn2ftp(value.c_str());
+    if (bInput && !impl_->bInputCheckingDisabled_)
+    {
+        if (fileType == efNR && File::exists(value))
+        {
+            ConstArrayRef<const char *>                 compressedExtensions(c_compressedExtensions);
+            ConstArrayRef<const char *>::const_iterator ext;
+            for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext)
+            {
+                if (endsWith(value, *ext))
+                {
+                    std::string newValue = value.substr(0, value.length() - std::strlen(*ext));
+                    if (option.isValidType(fn2ftp(newValue.c_str())))
+                    {
+                        return newValue;
+                    }
+                    else
+                    {
+                        return std::string();
+                    }
+                }
+            }
+            // VMD plugins may be able to read the file.
+            if (option.isInputFile() && option.isTrajectoryOption())
+            {
+                return value;
+            }
+        }
+        else if (fileType == efNR)
+        {
+            std::string processedValue = findExistingExtension(value, option);
+            if (!processedValue.empty())
+            {
+                return processedValue;
+            }
+            if (option.isLibraryFile())
+            {
+                // TODO: Treat also library files here.
+                return value + option.defaultExtension();
+            }
+            else
+            {
+                std::string message
+                    = formatString("File '%s' does not exist or is not accessible.\n"
+                                   "The following extensions were tried to complete the file name:\n  %s",
+                                   value.c_str(), joinStrings(option.extensions(), ", ").c_str());
+                GMX_THROW(InvalidInputError(message));
+            }
+        }
+        else if (option.isValidType(fileType))
+        {
+            if (option.isLibraryFile())
+            {
+                // TODO: Treat also library files.
+            }
+            else if (!File::exists(value))
+            {
+                std::string message
+                    = formatString("File '%s' does not exist or is not accessible.",
+                                   value.c_str());
+                // TODO: Get actual errno value from the attempt to open the file
+                // to provide better feedback to the user.
+                GMX_THROW(InvalidInputError(message));
+            }
+            return value;
+        }
+    }
+    else // Not an input file
+    {
+        if (fileType == efNR)
+        {
+            return value + option.defaultExtension();
+        }
+        else if (option.isValidType(fileType))
+        {
+            return value;
+        }
+    }
+    return std::string();
+}
+
+std::string FileNameOptionManager::completeDefaultFileName(
+        const std::string &prefix, const FileNameOptionInfo &option)
 {
-    return impl_->defaultFileName_;
+    if (option.isDirectoryOption() || impl_->bInputCheckingDisabled_)
+    {
+        return std::string();
+    }
+    const bool        bInput = option.isInputFile() || option.isInputOutputFile();
+    const std::string realPrefix
+        = !impl_->defaultFileName_.empty() ? impl_->defaultFileName_ : prefix;
+    if (bInput)
+    {
+        std::string completedName = findExistingExtension(realPrefix, option);
+        if (!completedName.empty())
+        {
+            return completedName;
+        }
+        if (option.isLibraryFile())
+        {
+            // TODO: Treat also library files here.
+            return realPrefix + option.defaultExtension();
+        }
+        else if (option.isSet())
+        {
+            std::string message
+                = formatString("No file name was provided, and the default file "
+                               "'%s' does not exist or is not accessible.\n"
+                               "The following extensions were tried to complete the file name:\n  %s",
+                               prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
+            GMX_THROW(InvalidInputError(message));
+        }
+        else if (option.isRequired())
+        {
+            std::string message
+                = formatString("Required option was not provided, and the default file "
+                               "'%s' does not exist or is not accessible.\n"
+                               "The following extensions were tried to complete the file name:\n  %s",
+                               prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
+            GMX_THROW(InvalidInputError(message));
+        }
+        // We get here with the legacy optional behavior.
+    }
+    return realPrefix + option.defaultExtension();
 }
 
 } // namespace gmx
index caa96443639bc98acbf1409d98491a05ebe0722c..1c492e19709a391e4e680e852c948b158bef1870 100644 (file)
 namespace gmx
 {
 
+class FileNameOptionInfo;
 class Options;
 
 /*! \brief
  * Handles interaction of file name options with global options.
  *
- * Currently, this class implements support for a global default file name
- * that overrides any option-specific default.
+ * This class contains all logic that completes file names based on user input
+ * and file system contents.  Additionally, this class implements support for a
+ * global default file name that overrides any option-specific default, as well
+ * as additional control over how the completion is done.
  *
  * \todo
- * Currently, this class has very little logic, and just provides the global
- * values to FileNameOptionStorage implementation.  A cleaner design would have
- * most of the non-trivial file name completion logic in this class, so that
- * the customizations would be centralized here.
+ * Most of the functionality in this class is specific to command line parsing,
+ * so it would be cleaner to replace this with an interface, and have the
+ * actual code in the `commandline` module.
  *
  * Adding a FileNameOptionManager for an Options object is optional, even if
  * the Options contains FileNameOption options.  Features from the manager are
- * not available if the manager is not created, but otherwise the options work.
+ * not available if the manager is not created, but otherwise the options work:
+ * the values provided to FileNameOption are used as they are, and exceptions
+ * are thrown if they are no valid instead of attempting to complete them.
  *
  * \see Options::addManager()
  *
@@ -81,6 +85,23 @@ class FileNameOptionManager : public OptionManagerInterface
         FileNameOptionManager();
         virtual ~FileNameOptionManager();
 
+        /*! \brief
+         * Disables special input file option handling.
+         *
+         * If disabled, this removes all file system calls from the file
+         * name option parsing.
+         * The values returned by FileNameOption for input and input/output
+         * files are handled with the same simple rule as for output files:
+         * the default extension is added if the file does not end in a
+         * recognized extension, and no other checking is done.
+         *
+         * This changes the following behavior:
+         *  - Providing non-existent files does not trigger errors.
+         *  - Extensions for input files are not completed to an existing file.
+         *  - Compressed input files do not work.
+         */
+        void disableInputOptionChecking(bool bDisable);
+
         /*! \brief
          * Adds an option for setting the default global file name.
          *
@@ -95,8 +116,41 @@ class FileNameOptionManager : public OptionManagerInterface
          */
         void addDefaultFileNameOption(Options *options, const char *name);
 
-        //! Returns the currently set default file name.
-        const std::string &defaultFileName() const;
+        /*! \brief
+         * Completes file name option values.
+         *
+         * \param[in] value  Value provided by the user.
+         * \param[in] option Option for which the value should be completed.
+         * \returns   Value for the file name option.
+         * \throws    std::bad_alloc if out of memory.
+         * \throws    InvalidInputError if the value is not valid for this
+         *     option.
+         *
+         * This method is called for each value that the user provides to
+         * a FileNameOption.  The return value (if non-empty) is used as the
+         * value of the option instead of the user-provided one.
+         */
+        std::string completeFileName(const std::string        &value,
+                                     const FileNameOptionInfo &option);
+        /*! \brief
+         * Completes default values for file name options.
+         *
+         * \param[in] prefix Default prefix for the file name.
+         * \param[in] option Option for which the value should be completed.
+         * \returns   Value for the file name option.
+         * \throws    std::bad_alloc if out of memory.
+         * \throws    InvalidInputError if the value is not valid for this
+         *     option.
+         *
+         * This method is called for each FileNameOption that has a default
+         * value (either a standard default value, or if the user provided the
+         * option without an explicit value).  \p prefix is the default value
+         * without the default extension for the option.
+         * If the return value is non-empty, it is used as the default value
+         * for the option instead of \p prefix + default extension.
+         */
+        std::string completeDefaultFileName(const std::string        &prefix,
+                                            const FileNameOptionInfo &option);
 
     private:
         class Impl;
index c7c1bf171f085f3234e10aa933bb7f216e5b0c93..7d22180edba2e80fcbd91f2fa3289f87c1da9432 100644 (file)
@@ -86,10 +86,16 @@ class FileNameOptionStorage : public OptionStorageTemplate<std::string>
 
         //! \copydoc FileNameOptionInfo::isDirectoryOption()
         bool isDirectoryOption() const;
+        //! \copydoc FileNameOptionInfo::isTrajectoryOption()
+        bool isTrajectoryOption() const;
         //! \copydoc FileNameOptionInfo::defaultExtension()
         const char *defaultExtension() const;
         //! \copydoc FileNameOptionInfo::extensions()
         std::vector<const char *> extensions() const;
+        //! \copydoc FileNameOptionInfo::isValidType()
+        bool isValidType(int fileType) const;
+        //! \copydoc FileNameOptionInfo::fileTypes()
+        ConstArrayRef<int> fileTypes() const;
 
     private:
         virtual void convertValue(const std::string &value);
index 0991c1fb4d2bc7ea88cd7c8e5574e57aa72e9a11..21806d0aed9d027c61fbd37d476f7336763d8f89 100644 (file)
@@ -1,7 +1,7 @@
 #
 # This file is part of the GROMACS molecular simulation package.
 #
-# Copyright (c) 2010,2011,2012, by the GROMACS development team, led by
+# Copyright (c) 2010,2011,2012,2014, by the GROMACS development team, led by
 # Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
 # and including many others, as listed in the AUTHORS file in the
 # top-level source directory and at http://www.gromacs.org.
@@ -35,6 +35,7 @@
 gmx_add_unit_test(OptionsUnitTests options-test
                   abstractoptionstorage.cpp
                   filenameoption.cpp
+                  filenameoptionmanager.cpp
                   option.cpp
                   optionsassigner.cpp
                   timeunitmanager.cpp)
index be0acd969b8fa9084c440b52be7e5f2a59f24cf4..75eb2be5f85f1da729fee36801029d9ef592f28f 100644 (file)
  */
 /*! \internal \file
  * \brief
- * Tests file name option implementation.
+ * Tests basic file name option implementation.
  *
  * \author Teemu Murtola <teemu.murtola@gmail.com>
  * \ingroup module_options
  */
-#include <vector>
+#include "gromacs/options/filenameoption.h"
 
 #include <gtest/gtest.h>
 
-#include "gromacs/options/filenameoption.h"
-#include "gromacs/options/filenameoptionmanager.h"
 #include "gromacs/options/options.h"
 #include "gromacs/options/optionsassigner.h"
 #include "gromacs/utility/exceptions.h"
-#include "gromacs/utility/file.h"
-#include "gromacs/utility/path.h"
 
 #include "testutils/testasserts.h"
-#include "testutils/testfilemanager.h"
 
 namespace
 {
 
 using gmx::FileNameOption;
-using gmx::test::TestFileManager;
-
-TEST(FileNameOptionTest, AddsMissingExtension)
-{
-    gmx::Options           options(NULL, NULL);
-    std::string            value;
-    ASSERT_NO_THROW_GMX(options.addOption(
-                                FileNameOption("f").store(&value)
-                                    .filetype(gmx::eftTrajectory).outputFile()));
-
-    gmx::OptionsAssigner assigner(&options);
-    EXPECT_NO_THROW_GMX(assigner.start());
-    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
-    EXPECT_NO_THROW_GMX(assigner.appendValue("testfile"));
-    EXPECT_NO_THROW_GMX(assigner.finishOption());
-    EXPECT_NO_THROW_GMX(assigner.finish());
-    EXPECT_NO_THROW_GMX(options.finish());
-
-    EXPECT_EQ("testfile.xtc", value);
-}
 
 TEST(FileNameOptionTest, HandlesRequiredDefaultValueWithoutExtension)
 {
@@ -105,6 +80,7 @@ TEST(FileNameOptionTest, HandlesRequiredOptionWithoutValue)
                                 FileNameOption("f").store(&value).required()
                                     .filetype(gmx::eftGenericData).outputFile()
                                     .defaultBasename("testfile")));
+    EXPECT_EQ("testfile.dat", value);
 
     gmx::OptionsAssigner assigner(&options);
     EXPECT_NO_THROW_GMX(assigner.start());
@@ -154,136 +130,44 @@ TEST(FileNameOptionTest, HandlesOptionalDefaultValueWithoutExtension)
     EXPECT_EQ("testfile.ndx", value);
 }
 
-TEST(FileNameOptionTest, AddsMissingExtensionBasedOnExistingFile)
-{
-    TestFileManager      tempFiles;
-    std::string          filename(tempFiles.getTemporaryFilePath(".trr"));
-    gmx::File::writeFileFromString(filename, "Dummy trajectory file");
-    std::string          inputValue(gmx::Path::stripExtension(filename));
-
-    gmx::Options         options(NULL, NULL);
-    std::string          value;
-    ASSERT_NO_THROW_GMX(options.addOption(
-                                FileNameOption("f").store(&value)
-                                    .filetype(gmx::eftTrajectory).inputFile()));
-
-    gmx::OptionsAssigner assigner(&options);
-    EXPECT_NO_THROW_GMX(assigner.start());
-    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
-    EXPECT_NO_THROW_GMX(assigner.appendValue(inputValue));
-    EXPECT_NO_THROW_GMX(assigner.finishOption());
-    EXPECT_NO_THROW_GMX(assigner.finish());
-    EXPECT_NO_THROW_GMX(options.finish());
-
-    EXPECT_EQ(filename, value);
-}
-
-TEST(FileNameOptionTest, AddsMissingExtensionForRequiredDefaultNameBasedOnExistingFile)
-{
-    TestFileManager      tempFiles;
-    std::string          filename(tempFiles.getTemporaryFilePath(".trr"));
-    gmx::File::writeFileFromString(filename, "Dummy trajectory file");
-    std::string          inputValue(gmx::Path::stripExtension(filename));
-
-    gmx::Options         options(NULL, NULL);
-    std::string          value;
-    ASSERT_NO_THROW_GMX(options.addOption(
-                                FileNameOption("f").store(&value).required()
-                                    .filetype(gmx::eftTrajectory).inputFile()
-                                    .defaultBasename(inputValue.c_str())));
-    EXPECT_EQ(inputValue + ".xtc", value);
-
-    gmx::OptionsAssigner assigner(&options);
-    EXPECT_NO_THROW_GMX(assigner.start());
-    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
-    EXPECT_NO_THROW_GMX(assigner.finishOption());
-    EXPECT_NO_THROW_GMX(assigner.finish());
-    EXPECT_NO_THROW_GMX(options.finish());
-
-    EXPECT_EQ(filename, value);
-}
-
-TEST(FileNameOptionTest, AddsMissingExtensionForOptionalDefaultNameBasedOnExistingFile)
+TEST(FileNameOptionTest, GivesErrorOnUnknownFileSuffix)
 {
-    TestFileManager      tempFiles;
-    std::string          filename(tempFiles.getTemporaryFilePath(".trr"));
-    gmx::File::writeFileFromString(filename, "Dummy trajectory file");
-    std::string          inputValue(gmx::Path::stripExtension(filename));
-
-    gmx::Options         options(NULL, NULL);
-    std::string          value;
+    gmx::Options           options(NULL, NULL);
+    std::string            value;
     ASSERT_NO_THROW_GMX(options.addOption(
                                 FileNameOption("f").store(&value)
-                                    .filetype(gmx::eftTrajectory).inputFile()
-                                    .defaultBasename(inputValue.c_str())));
+                                    .filetype(gmx::eftIndex).outputFile()));
+    EXPECT_TRUE(value.empty());
 
     gmx::OptionsAssigner assigner(&options);
     EXPECT_NO_THROW_GMX(assigner.start());
     EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_THROW_GMX(assigner.appendValue("testfile.foo"), gmx::InvalidInputError);
     EXPECT_NO_THROW_GMX(assigner.finishOption());
     EXPECT_NO_THROW_GMX(assigner.finish());
     EXPECT_NO_THROW_GMX(options.finish());
 
-    EXPECT_EQ(filename, value);
-}
-
-TEST(FileNameOptionTest, AddsMissingExtensionForRequiredFromDefaultNameOptionBasedOnExistingFile)
-{
-    TestFileManager            tempFiles;
-    std::string                filename(tempFiles.getTemporaryFilePath(".trr"));
-    gmx::File::writeFileFromString(filename, "Dummy trajectory file");
-    std::string                inputValue(gmx::Path::stripExtension(filename));
-
-    gmx::FileNameOptionManager manager;
-    gmx::Options               options(NULL, NULL);
-    std::string                value;
-    ASSERT_NO_THROW_GMX(options.addManager(&manager));
-    ASSERT_NO_THROW_GMX(options.addOption(
-                                FileNameOption("f").store(&value).required()
-                                    .filetype(gmx::eftTrajectory).inputFile()
-                                    .defaultBasename("foo")));
-    ASSERT_NO_THROW_GMX(manager.addDefaultFileNameOption(&options, "deffnm"));
-    EXPECT_EQ("foo.xtc", value);
-
-    gmx::OptionsAssigner assigner(&options);
-    EXPECT_NO_THROW_GMX(assigner.start());
-    EXPECT_NO_THROW_GMX(assigner.startOption("deffnm"));
-    EXPECT_NO_THROW_GMX(assigner.appendValue(inputValue));
-    EXPECT_NO_THROW_GMX(assigner.finishOption());
-    EXPECT_NO_THROW_GMX(assigner.finish());
-    EXPECT_NO_THROW_GMX(options.finish());
-
-    EXPECT_EQ(filename, value);
+    EXPECT_TRUE(value.empty());
 }
 
-TEST(FileNameOptionTest, AddsMissingExtensionForOptionalFromDefaultNameOptionBasedOnExistingFile)
+TEST(FileNameOptionTest, GivesErrorOnInvalidFileSuffix)
 {
-    TestFileManager            tempFiles;
-    std::string                filename(tempFiles.getTemporaryFilePath(".trr"));
-    gmx::File::writeFileFromString(filename, "Dummy trajectory file");
-    std::string                inputValue(gmx::Path::stripExtension(filename));
-
-    gmx::FileNameOptionManager manager;
-    gmx::Options               options(NULL, NULL);
-    std::string                value;
-    ASSERT_NO_THROW_GMX(options.addManager(&manager));
+    gmx::Options           options(NULL, NULL);
+    std::string            value;
     ASSERT_NO_THROW_GMX(options.addOption(
                                 FileNameOption("f").store(&value)
-                                    .filetype(gmx::eftTrajectory).inputFile()
-                                    .defaultBasename("foo")));
-    ASSERT_NO_THROW_GMX(manager.addDefaultFileNameOption(&options, "deffnm"));
+                                    .filetype(gmx::eftTrajectory).outputFile()));
+    EXPECT_TRUE(value.empty());
 
     gmx::OptionsAssigner assigner(&options);
     EXPECT_NO_THROW_GMX(assigner.start());
-    EXPECT_NO_THROW_GMX(assigner.startOption("deffnm"));
-    EXPECT_NO_THROW_GMX(assigner.appendValue(inputValue));
-    EXPECT_NO_THROW_GMX(assigner.finishOption());
     EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_THROW_GMX(assigner.appendValue("testfile.dat"), gmx::InvalidInputError);
     EXPECT_NO_THROW_GMX(assigner.finishOption());
     EXPECT_NO_THROW_GMX(assigner.finish());
     EXPECT_NO_THROW_GMX(options.finish());
 
-    EXPECT_EQ(filename, value);
+    EXPECT_TRUE(value.empty());
 }
 
 } // namespace
diff --git a/src/gromacs/options/tests/filenameoptionmanager.cpp b/src/gromacs/options/tests/filenameoptionmanager.cpp
new file mode 100644 (file)
index 0000000..ab75cd5
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2014, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, 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 http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Tests file name option implementation dependent on gmx::FileNameOptionManager.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \ingroup module_options
+ */
+#include "gromacs/options/filenameoptionmanager.h"
+
+#include <gtest/gtest.h>
+
+#include "gromacs/options/filenameoption.h"
+#include "gromacs/options/options.h"
+#include "gromacs/options/optionsassigner.h"
+#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/file.h"
+#include "gromacs/utility/path.h"
+
+#include "testutils/testasserts.h"
+#include "testutils/testfilemanager.h"
+
+namespace
+{
+
+using gmx::FileNameOption;
+
+class FileNameOptionManagerTest : public ::testing::Test
+{
+    public:
+        FileNameOptionManagerTest()
+            : options_(NULL, NULL)
+        {
+            options_.addManager(&manager_);
+        }
+
+        std::string createDummyFile(const char *suffix)
+        {
+            std::string filename(tempFiles_.getTemporaryFilePath(suffix));
+            gmx::File::writeFileFromString(filename, "Dummy file");
+            return filename;
+        }
+
+        gmx::FileNameOptionManager manager_;
+        gmx::Options               options_;
+        gmx::test::TestFileManager tempFiles_;
+};
+
+/********************************************************************
+ * Actual tests
+ */
+
+TEST_F(FileNameOptionManagerTest, AddsMissingExtension)
+{
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value)
+                                    .filetype(gmx::eftTrajectory).outputFile()));
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_NO_THROW_GMX(assigner.appendValue("testfile"));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_EQ("testfile.xtc", value);
+}
+
+TEST_F(FileNameOptionManagerTest, GivesErrorOnMissingInputFile)
+{
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value)
+                                    .filetype(gmx::eftIndex).inputFile()));
+    EXPECT_TRUE(value.empty());
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_THROW_GMX(assigner.appendValue("missing.ndx"), gmx::InvalidInputError);
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_TRUE(value.empty());
+}
+
+TEST_F(FileNameOptionManagerTest, GivesErrorOnMissingGenericInputFile)
+{
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value)
+                                    .filetype(gmx::eftTrajectory).inputFile()));
+    EXPECT_TRUE(value.empty());
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_THROW_GMX(assigner.appendValue("missing.trr"), gmx::InvalidInputError);
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_TRUE(value.empty());
+}
+
+TEST_F(FileNameOptionManagerTest, GivesErrorOnMissingDefaultInputFile)
+{
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value)
+                                    .filetype(gmx::eftIndex).inputFile()
+                                    .defaultBasename("missing")));
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_THROW_GMX(options_.finish(), gmx::InvalidInputError);
+}
+
+TEST_F(FileNameOptionManagerTest, GivesErrorOnMissingRequiredInputFile)
+{
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value).required()
+                                    .filetype(gmx::eftIndex).inputFile()
+                                    .defaultBasename("missing")));
+    EXPECT_EQ("missing.ndx", value);
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_THROW_GMX(options_.finish(), gmx::InvalidInputError);
+}
+
+TEST_F(FileNameOptionManagerTest, AddsMissingExtensionBasedOnExistingFile)
+{
+    std::string filename(createDummyFile(".trr"));
+    std::string inputValue(gmx::Path::stripExtension(filename));
+
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value)
+                                    .filetype(gmx::eftTrajectory).inputFile()));
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_NO_THROW_GMX(assigner.appendValue(inputValue));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_EQ(filename, value);
+}
+
+TEST_F(FileNameOptionManagerTest,
+       AddsMissingExtensionForRequiredDefaultNameBasedOnExistingFile)
+{
+    std::string filename(createDummyFile(".trr"));
+    std::string inputValue(gmx::Path::stripExtension(filename));
+
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value).required()
+                                    .filetype(gmx::eftTrajectory).inputFile()
+                                    .defaultBasename(inputValue.c_str())));
+    EXPECT_EQ(inputValue + ".xtc", value);
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_EQ(filename, value);
+}
+
+TEST_F(FileNameOptionManagerTest,
+       AddsMissingExtensionForOptionalDefaultNameBasedOnExistingFile)
+{
+    std::string filename(createDummyFile(".trr"));
+    std::string inputValue(gmx::Path::stripExtension(filename));
+
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value)
+                                    .filetype(gmx::eftTrajectory).inputFile()
+                                    .defaultBasename(inputValue.c_str())));
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_EQ(filename, value);
+}
+
+TEST_F(FileNameOptionManagerTest,
+       AddsMissingExtensionForRequiredFromDefaultNameOptionBasedOnExistingFile)
+{
+    std::string filename(createDummyFile(".trr"));
+    std::string inputValue(gmx::Path::stripExtension(filename));
+
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value).required()
+                                    .filetype(gmx::eftTrajectory).inputFile()
+                                    .defaultBasename("foo")));
+    ASSERT_NO_THROW_GMX(manager_.addDefaultFileNameOption(&options_, "deffnm"));
+    EXPECT_EQ("foo.xtc", value);
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("deffnm"));
+    EXPECT_NO_THROW_GMX(assigner.appendValue(inputValue));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_EQ(filename, value);
+}
+
+TEST_F(FileNameOptionManagerTest,
+       AddsMissingExtensionForOptionalFromDefaultNameOptionBasedOnExistingFile)
+{
+    std::string filename(createDummyFile(".trr"));
+    std::string inputValue(gmx::Path::stripExtension(filename));
+
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value)
+                                    .filetype(gmx::eftTrajectory).inputFile()
+                                    .defaultBasename("foo")));
+    ASSERT_NO_THROW_GMX(manager_.addDefaultFileNameOption(&options_, "deffnm"));
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("deffnm"));
+    EXPECT_NO_THROW_GMX(assigner.appendValue(inputValue));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_EQ(filename, value);
+}
+
+} // namespace
index d80bc883aee8221a2d4c0bc9d4d3ea966b95d737..d907cd8e8883f1622fc42935ff21da43d9bf98f8 100644 (file)
@@ -52,6 +52,7 @@
 #include "gromacs/commandline/cmdlinemodulemanager.h"
 #include "gromacs/commandline/cmdlineparser.h"
 #include "gromacs/fileio/trx.h"
+#include "gromacs/options/filenameoptionmanager.h"
 #include "gromacs/options/options.h"
 #include "gromacs/pbcutil/pbc.h"
 #include "gromacs/selection/selectioncollection.h"
@@ -108,12 +109,14 @@ TrajectoryAnalysisCommandLineRunner::Impl::parseOptions(
         SelectionCollection *selections,
         int *argc, char *argv[])
 {
+    FileNameOptionManager  fileoptManager;
     SelectionOptionManager seloptManager(selections);
     Options                options(NULL, NULL);
     Options                moduleOptions(module_->name(), module_->description());
     Options                commonOptions("common", "Common analysis control");
     Options                selectionOptions("selection", "Common selection control");
 
+    options.addManager(&fileoptManager);
     options.addManager(&seloptManager);
     options.addSubSection(&commonOptions);
     options.addSubSection(&selectionOptions);
index f6229f67b0130e4004e916d2c6303525724c674a..9b0219e702c7e65d5bc9fad2073ebd711466efef 100644 (file)
@@ -41,6 +41,7 @@
 #endif
 
 #include <stdio.h>
+#include <string.h>
 
 #include "gromacs/legacyheaders/checkpoint.h"
 #include "gromacs/legacyheaders/copyrite.h"
 #include "gromacs/fileio/filenm.h"
 #include "gromacs/utility/fatalerror.h"
 
+static bool is_multisim_option_set(int argc, const char *const argv[])
+{
+    for (int i = 0; i < argc; ++i)
+    {
+        if (strcmp(argv[i], "-multi") == 0 || strcmp(argv[i], "-multidir") == 0)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
 int gmx_mdrun(int argc, char *argv[])
 {
     const char   *desc[] = {
@@ -607,10 +620,17 @@ int gmx_mdrun(int argc, char *argv[])
     int             rc;
     char          **multidir = NULL;
 
-
     cr = init_commrec();
 
     PCA_Flags = (PCA_CAN_SET_DEFFNM | (MASTER(cr) ? 0 : PCA_QUIET));
+    // With -multi or -multidir, the file names are going to get processed
+    // further (or the working directory changed), so we can't check for their
+    // existence during parsing.  It isn't useful to do any completion based on
+    // file system contents, either.
+    if (is_multisim_option_set(argc, argv))
+    {
+        PCA_Flags |= PCA_DISABLE_INPUT_FILE_CHECKING;
+    }
 
     /* Comment this in to do fexist calls only on master
      * works not with rerun or tables at the moment