Merge branch release-5-0
[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/file.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 {
85     ConstArrayRef<int>                 types = option.fileTypes();
86     ConstArrayRef<int>::const_iterator i;
87     for (i = types.begin(); i != types.end(); ++i)
88     {
89         std::string testFilename(prefix + ftp2ext_with_dot(*i));
90         if (File::exists(testFilename))
91         {
92             return testFilename;
93         }
94     }
95     return std::string();
96 }
97
98 }   // namespace
99
100 /********************************************************************
101  * FileNameOptionManager::Impl
102  */
103
104 /*! \internal \brief
105  * Private implemention class for FileNameOptionManager.
106  *
107  * \ingroup module_options
108  */
109 class FileNameOptionManager::Impl
110 {
111     public:
112         Impl() : bInputCheckingDisabled_(false) {}
113
114         //! Global default file name, if set.
115         std::string     defaultFileName_;
116         //! Whether input option processing has been disabled.
117         bool            bInputCheckingDisabled_;
118 };
119
120 /********************************************************************
121  * FileNameOptionManager
122  */
123
124 FileNameOptionManager::FileNameOptionManager()
125     : impl_(new Impl())
126 {
127 }
128
129 FileNameOptionManager::~FileNameOptionManager()
130 {
131 }
132
133 void FileNameOptionManager::disableInputOptionChecking(bool bDisable)
134 {
135     impl_->bInputCheckingDisabled_ = bDisable;
136 }
137
138 void FileNameOptionManager::addDefaultFileNameOption(
139         Options *options, const char *name)
140 {
141     options->addOption(
142             StringOption(name).store(&impl_->defaultFileName_)
143                 .description("Set the default filename for all file options"));
144 }
145
146 std::string FileNameOptionManager::completeFileName(
147         const std::string &value, const FileNameOptionInfo &option)
148 {
149     const bool bAllowMissing = option.allowMissing();
150     const bool bInput
151         = option.isInputFile() || option.isInputOutputFile();
152     // Currently, directory options are simple, and don't need any
153     // special processing.
154     // TODO: Consider splitting them into a separate DirectoryOption.
155     if (option.isDirectoryOption())
156     {
157         if (!impl_->bInputCheckingDisabled_ && bInput && !bAllowMissing
158             && !Directory::exists(value))
159         {
160             std::string message
161                 = formatString("Directory '%s' does not exist or is not accessible.",
162                                value.c_str());
163             // TODO: Get actual errno value from the attempt to open the file
164             // to provide better feedback to the user.
165             GMX_THROW(InvalidInputError(message));
166         }
167         return value;
168     }
169     const int fileType = fn2ftp(value.c_str());
170     if (bInput && !impl_->bInputCheckingDisabled_)
171     {
172         if (fileType == efNR && File::exists(value))
173         {
174             ConstArrayRef<const char *>                 compressedExtensions(c_compressedExtensions);
175             ConstArrayRef<const char *>::const_iterator ext;
176             for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext)
177             {
178                 if (endsWith(value, *ext))
179                 {
180                     std::string newValue = value.substr(0, value.length() - std::strlen(*ext));
181                     if (option.isValidType(fn2ftp(newValue.c_str())))
182                     {
183                         return newValue;
184                     }
185                     else
186                     {
187                         return std::string();
188                     }
189                 }
190             }
191             // VMD plugins may be able to read the file.
192             if (option.isInputFile() && option.isTrajectoryOption())
193             {
194                 return value;
195             }
196         }
197         else if (fileType == efNR)
198         {
199             std::string processedValue = findExistingExtension(value, option);
200             if (!processedValue.empty())
201             {
202                 return processedValue;
203             }
204             if (bAllowMissing)
205             {
206                 return value + option.defaultExtension();
207             }
208             else if (option.isLibraryFile())
209             {
210                 // TODO: Treat also library files here.
211                 return value + option.defaultExtension();
212             }
213             else
214             {
215                 std::string message
216                     = formatString("File '%s' does not exist or is not accessible.\n"
217                                    "The following extensions were tried to complete the file name:\n  %s",
218                                    value.c_str(), joinStrings(option.extensions(), ", ").c_str());
219                 GMX_THROW(InvalidInputError(message));
220             }
221         }
222         else if (option.isValidType(fileType))
223         {
224             if (option.isLibraryFile())
225             {
226                 // TODO: Treat also library files.
227             }
228             else if (!bAllowMissing && !File::exists(value))
229             {
230                 std::string message
231                     = formatString("File '%s' does not exist or is not accessible.",
232                                    value.c_str());
233                 // TODO: Get actual errno value from the attempt to open the file
234                 // to provide better feedback to the user.
235                 GMX_THROW(InvalidInputError(message));
236             }
237             return value;
238         }
239     }
240     else // Not an input file
241     {
242         if (fileType == efNR)
243         {
244             return value + option.defaultExtension();
245         }
246         else if (option.isValidType(fileType))
247         {
248             return value;
249         }
250     }
251     return std::string();
252 }
253
254 std::string FileNameOptionManager::completeDefaultFileName(
255         const std::string &prefix, const FileNameOptionInfo &option)
256 {
257     if (option.isDirectoryOption() || impl_->bInputCheckingDisabled_)
258     {
259         return std::string();
260     }
261     const bool        bInput = option.isInputFile() || option.isInputOutputFile();
262     const std::string realPrefix
263         = !impl_->defaultFileName_.empty() ? impl_->defaultFileName_ : prefix;
264     const bool        bAllowMissing = option.allowMissing();
265     if (bInput)
266     {
267         std::string completedName = findExistingExtension(realPrefix, option);
268         if (!completedName.empty())
269         {
270             return completedName;
271         }
272         if (bAllowMissing)
273         {
274             return realPrefix + option.defaultExtension();
275         }
276         else if (option.isLibraryFile())
277         {
278             // TODO: Treat also library files here.
279             return realPrefix + option.defaultExtension();
280         }
281         else if (option.isSet())
282         {
283             std::string message
284                 = formatString("No file name was provided, and the default file "
285                                "'%s' does not exist or is not accessible.\n"
286                                "The following extensions were tried to complete the file name:\n  %s",
287                                prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
288             GMX_THROW(InvalidInputError(message));
289         }
290         else if (option.isRequired())
291         {
292             std::string message
293                 = formatString("Required option was not provided, and the default file "
294                                "'%s' does not exist or is not accessible.\n"
295                                "The following extensions were tried to complete the file name:\n  %s",
296                                prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
297             GMX_THROW(InvalidInputError(message));
298         }
299         // We get here with the legacy optional behavior.
300     }
301     return realPrefix + option.defaultExtension();
302 }
303
304 } // namespace gmx