Merge branch release-5-1
[alexxy/gromacs.git] / src / gromacs / options / filenameoptionmanager.cpp
index db7b2fde10e24ee25ebf66d929735df64426e965..1a627cd85c6d7f550f915cd71458928357855854 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2014, by the GROMACS development team, led by
+ * Copyright (c) 2014,2015, 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.
  * \author Teemu Murtola <teemu.murtola@gmail.com>
  * \ingroup module_options
  */
+#include "gmxpre.h"
+
 #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/options/optionsvisitor.h"
+#include "gromacs/utility/arrayref.h"
+#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/fileredirector.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,
+                                  const IFileInputRedirector         *redirector)
+{
+    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 (redirector->fileExists(testFilename))
+        {
+            return testFilename;
+        }
+    }
+    return std::string();
+}
+
+}   // namespace
+
 /********************************************************************
  * FileNameOptionManager::Impl
  */
@@ -63,8 +110,18 @@ namespace gmx
 class FileNameOptionManager::Impl
 {
     public:
+        Impl()
+            : redirector_(&defaultFileInputRedirector()),
+              bInputCheckingDisabled_(false)
+        {
+        }
+
+        //! Redirector for file existence checks.
+        const IFileInputRedirector         *redirector_;
         //! Global default file name, if set.
-        std::string     defaultFileName_;
+        std::string                         defaultFileName_;
+        //! Whether input option processing has been disabled.
+        bool                                bInputCheckingDisabled_;
 };
 
 /********************************************************************
@@ -80,67 +137,182 @@ FileNameOptionManager::~FileNameOptionManager()
 {
 }
 
-void FileNameOptionManager::addDefaultFileNameOption(
-        Options *options, const char *name)
+void FileNameOptionManager::setInputRedirector(
+        const IFileInputRedirector *redirector)
 {
-    options->addOption(
-            StringOption(name).store(&impl_->defaultFileName_)
-                .description("Set the default filename for all file options"));
+    impl_->redirector_ = redirector;
 }
 
-const std::string &FileNameOptionManager::defaultFileName() const
+void FileNameOptionManager::disableInputOptionChecking(bool bDisable)
 {
-    return impl_->defaultFileName_;
+    impl_->bInputCheckingDisabled_ = bDisable;
 }
 
-/********************************************************************
- * Global functions
- */
-
-namespace
+void FileNameOptionManager::addDefaultFileNameOption(
+        Options *options, const char *name)
 {
+    options->addOption(
+            StringOption(name).store(&impl_->defaultFileName_)
+                .description("Set the default filename for all file options"));
+}
 
-/*! \internal \brief
- * Visitor that sets the manager for each file name option.
- *
- * \ingroup module_options
- */
-class FileNameOptionManagerSetter : public OptionsModifyingVisitor
+std::string FileNameOptionManager::completeFileName(
+        const std::string &value, const FileNameOptionInfo &option)
 {
-    public:
-        //! Construct a visitor that sets given manager.
-        explicit FileNameOptionManagerSetter(FileNameOptionManager *manager)
-            : manager_(manager)
+    const bool bAllowMissing = option.allowMissing();
+    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 && !bAllowMissing
+            && !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));
         }
-
-        void visitSubSection(Options *section)
+        return value;
+    }
+    const int fileType = fn2ftp(value.c_str());
+    if (bInput && !impl_->bInputCheckingDisabled_)
+    {
+        if (fileType == efNR && impl_->redirector_->fileExists(value))
         {
-            OptionsModifyingIterator iterator(section);
-            iterator.acceptSubSections(this);
-            iterator.acceptOptions(this);
+            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;
+            }
         }
-
-        void visitOption(OptionInfo *option)
+        else if (fileType == efNR)
         {
-            FileNameOptionInfo *fileOption
-                = option->toType<FileNameOptionInfo>();
-            if (fileOption != NULL)
+            const std::string processedValue
+                = findExistingExtension(value, option, impl_->redirector_);
+            if (!processedValue.empty())
+            {
+                return processedValue;
+            }
+            if (bAllowMissing)
             {
-                fileOption->setManager(manager_);
+                return value + option.defaultExtension();
+            }
+            else 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 (!bAllowMissing && !impl_->redirector_->fileExists(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();
+}
 
-    private:
-        FileNameOptionManager *manager_;
-};
-
-}   // namespace
-
-void setManagerForFileNameOptions(Options               *options,
-                                  FileNameOptionManager *manager)
+std::string FileNameOptionManager::completeDefaultFileName(
+        const std::string &prefix, const FileNameOptionInfo &option)
 {
-    FileNameOptionManagerSetter(manager).visitSubSection(options);
+    if (option.isDirectoryOption())
+    {
+        return std::string();
+    }
+    const bool        bInput = option.isInputFile() || option.isInputOutputFile();
+    const std::string realPrefix
+        = !impl_->defaultFileName_.empty() ? impl_->defaultFileName_ : prefix;
+    if (bInput && !impl_->bInputCheckingDisabled_)
+    {
+        const std::string completedName
+            = findExistingExtension(realPrefix, option, impl_->redirector_);
+        if (!completedName.empty())
+        {
+            return completedName;
+        }
+        if (option.allowMissing())
+        {
+            return realPrefix + option.defaultExtension();
+        }
+        else 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