Merge branch release-5-1
[alexxy/gromacs.git] / src / gromacs / options / filenameoptionmanager.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2014,2015, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::FileNameOptionManager.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_options
41  */
42 #include "gmxpre.h"
43
44 #include "filenameoptionmanager.h"
45
46 #include <cstring>
47
48 #include <string>
49
50 #include "gromacs/fileio/filenm.h"
51 #include "gromacs/options/basicoptions.h"
52 #include "gromacs/options/filenameoption.h"
53 #include "gromacs/options/options.h"
54 #include "gromacs/utility/arrayref.h"
55 #include "gromacs/utility/exceptions.h"
56 #include "gromacs/utility/fileredirector.h"
57 #include "gromacs/utility/path.h"
58 #include "gromacs/utility/stringutil.h"
59
60 namespace gmx
61 {
62
63 namespace
64 {
65
66 //! Extensions that are recognized as compressed files.
67 const char *const c_compressedExtensions[] =
68 { ".gz", ".Z" };
69
70 /********************************************************************
71  * Helper functions
72  */
73
74 /*! \brief
75  * Adds an extension to \p prefix if it results in an existing file.
76  *
77  * Tries to add each extension for this file type to \p prefix and
78  * checks whether this results in an existing file.
79  * The first match is returned.
80  * Returns an empty string if no existing file is found.
81  */
82 std::string findExistingExtension(const std::string                  &prefix,
83                                   const FileNameOptionInfo           &option,
84                                   const IFileInputRedirector         *redirector)
85 {
86     ConstArrayRef<int>                 types = option.fileTypes();
87     ConstArrayRef<int>::const_iterator i;
88     for (i = types.begin(); i != types.end(); ++i)
89     {
90         std::string testFilename(prefix + ftp2ext_with_dot(*i));
91         if (redirector->fileExists(testFilename))
92         {
93             return testFilename;
94         }
95     }
96     return std::string();
97 }
98
99 }   // namespace
100
101 /********************************************************************
102  * FileNameOptionManager::Impl
103  */
104
105 /*! \internal \brief
106  * Private implemention class for FileNameOptionManager.
107  *
108  * \ingroup module_options
109  */
110 class FileNameOptionManager::Impl
111 {
112     public:
113         Impl()
114             : redirector_(&defaultFileInputRedirector()),
115               bInputCheckingDisabled_(false)
116         {
117         }
118
119         //! Redirector for file existence checks.
120         const IFileInputRedirector         *redirector_;
121         //! Global default file name, if set.
122         std::string                         defaultFileName_;
123         //! Whether input option processing has been disabled.
124         bool                                bInputCheckingDisabled_;
125 };
126
127 /********************************************************************
128  * FileNameOptionManager
129  */
130
131 FileNameOptionManager::FileNameOptionManager()
132     : impl_(new Impl())
133 {
134 }
135
136 FileNameOptionManager::~FileNameOptionManager()
137 {
138 }
139
140 void FileNameOptionManager::setInputRedirector(
141         const IFileInputRedirector *redirector)
142 {
143     impl_->redirector_ = redirector;
144 }
145
146 void FileNameOptionManager::disableInputOptionChecking(bool bDisable)
147 {
148     impl_->bInputCheckingDisabled_ = bDisable;
149 }
150
151 void FileNameOptionManager::addDefaultFileNameOption(
152         Options *options, const char *name)
153 {
154     options->addOption(
155             StringOption(name).store(&impl_->defaultFileName_)
156                 .description("Set the default filename for all file options"));
157 }
158
159 std::string FileNameOptionManager::completeFileName(
160         const std::string &value, const FileNameOptionInfo &option)
161 {
162     const bool bAllowMissing = option.allowMissing();
163     const bool bInput
164         = option.isInputFile() || option.isInputOutputFile();
165     // Currently, directory options are simple, and don't need any
166     // special processing.
167     // TODO: Consider splitting them into a separate DirectoryOption.
168     if (option.isDirectoryOption())
169     {
170         if (!impl_->bInputCheckingDisabled_ && bInput && !bAllowMissing
171             && !Directory::exists(value))
172         {
173             std::string message
174                 = formatString("Directory '%s' does not exist or is not accessible.",
175                                value.c_str());
176             // TODO: Get actual errno value from the attempt to open the file
177             // to provide better feedback to the user.
178             GMX_THROW(InvalidInputError(message));
179         }
180         return value;
181     }
182     const int fileType = fn2ftp(value.c_str());
183     if (bInput && !impl_->bInputCheckingDisabled_)
184     {
185         if (fileType == efNR && impl_->redirector_->fileExists(value))
186         {
187             ConstArrayRef<const char *>                 compressedExtensions(c_compressedExtensions);
188             ConstArrayRef<const char *>::const_iterator ext;
189             for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext)
190             {
191                 if (endsWith(value, *ext))
192                 {
193                     std::string newValue = value.substr(0, value.length() - std::strlen(*ext));
194                     if (option.isValidType(fn2ftp(newValue.c_str())))
195                     {
196                         return newValue;
197                     }
198                     else
199                     {
200                         return std::string();
201                     }
202                 }
203             }
204             // VMD plugins may be able to read the file.
205             if (option.isInputFile() && option.isTrajectoryOption())
206             {
207                 return value;
208             }
209         }
210         else if (fileType == efNR)
211         {
212             const std::string processedValue
213                 = findExistingExtension(value, option, impl_->redirector_);
214             if (!processedValue.empty())
215             {
216                 return processedValue;
217             }
218             if (bAllowMissing)
219             {
220                 return value + option.defaultExtension();
221             }
222             else if (option.isLibraryFile())
223             {
224                 // TODO: Treat also library files here.
225                 return value + option.defaultExtension();
226             }
227             else
228             {
229                 std::string message
230                     = formatString("File '%s' does not exist or is not accessible.\n"
231                                    "The following extensions were tried to complete the file name:\n  %s",
232                                    value.c_str(), joinStrings(option.extensions(), ", ").c_str());
233                 GMX_THROW(InvalidInputError(message));
234             }
235         }
236         else if (option.isValidType(fileType))
237         {
238             if (option.isLibraryFile())
239             {
240                 // TODO: Treat also library files.
241             }
242             else if (!bAllowMissing && !impl_->redirector_->fileExists(value))
243             {
244                 std::string message
245                     = formatString("File '%s' does not exist or is not accessible.",
246                                    value.c_str());
247                 // TODO: Get actual errno value from the attempt to open the file
248                 // to provide better feedback to the user.
249                 GMX_THROW(InvalidInputError(message));
250             }
251             return value;
252         }
253     }
254     else // Not an input file
255     {
256         if (fileType == efNR)
257         {
258             return value + option.defaultExtension();
259         }
260         else if (option.isValidType(fileType))
261         {
262             return value;
263         }
264     }
265     return std::string();
266 }
267
268 std::string FileNameOptionManager::completeDefaultFileName(
269         const std::string &prefix, const FileNameOptionInfo &option)
270 {
271     if (option.isDirectoryOption())
272     {
273         return std::string();
274     }
275     const bool        bInput = option.isInputFile() || option.isInputOutputFile();
276     const std::string realPrefix
277         = !impl_->defaultFileName_.empty() ? impl_->defaultFileName_ : prefix;
278     if (bInput && !impl_->bInputCheckingDisabled_)
279     {
280         const std::string completedName
281             = findExistingExtension(realPrefix, option, impl_->redirector_);
282         if (!completedName.empty())
283         {
284             return completedName;
285         }
286         if (option.allowMissing())
287         {
288             return realPrefix + option.defaultExtension();
289         }
290         else if (option.isLibraryFile())
291         {
292             // TODO: Treat also library files here.
293             return realPrefix + option.defaultExtension();
294         }
295         else if (option.isSet())
296         {
297             std::string message
298                 = formatString("No file name was provided, and the default file "
299                                "'%s' does not exist or is not accessible.\n"
300                                "The following extensions were tried to complete the file name:\n  %s",
301                                prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
302             GMX_THROW(InvalidInputError(message));
303         }
304         else if (option.isRequired())
305         {
306             std::string message
307                 = formatString("Required option was not provided, and the default file "
308                                "'%s' does not exist or is not accessible.\n"
309                                "The following extensions were tried to complete the file name:\n  %s",
310                                prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
311             GMX_THROW(InvalidInputError(message));
312         }
313         // We get here with the legacy optional behavior.
314     }
315     return realPrefix + option.defaultExtension();
316 }
317
318 } // namespace gmx