Merge release-5-0 into master
[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, 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 bInput = option.isInputFile() || option.isInputOutputFile();
150     // Currently, directory options are simple, and don't need any
151     // special processing.
152     // TODO: Consider splitting them into a separate DirectoryOption.
153     if (option.isDirectoryOption())
154     {
155         if (!impl_->bInputCheckingDisabled_ && bInput && !Directory::exists(value))
156         {
157             std::string message
158                 = formatString("Directory '%s' does not exist or is not accessible.",
159                                value.c_str());
160             // TODO: Get actual errno value from the attempt to open the file
161             // to provide better feedback to the user.
162             GMX_THROW(InvalidInputError(message));
163         }
164         return value;
165     }
166     const int fileType = fn2ftp(value.c_str());
167     if (bInput && !impl_->bInputCheckingDisabled_)
168     {
169         if (fileType == efNR && File::exists(value))
170         {
171             ConstArrayRef<const char *>                 compressedExtensions(c_compressedExtensions);
172             ConstArrayRef<const char *>::const_iterator ext;
173             for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext)
174             {
175                 if (endsWith(value, *ext))
176                 {
177                     std::string newValue = value.substr(0, value.length() - std::strlen(*ext));
178                     if (option.isValidType(fn2ftp(newValue.c_str())))
179                     {
180                         return newValue;
181                     }
182                     else
183                     {
184                         return std::string();
185                     }
186                 }
187             }
188             // VMD plugins may be able to read the file.
189             if (option.isInputFile() && option.isTrajectoryOption())
190             {
191                 return value;
192             }
193         }
194         else if (fileType == efNR)
195         {
196             std::string processedValue = findExistingExtension(value, option);
197             if (!processedValue.empty())
198             {
199                 return processedValue;
200             }
201             if (option.isLibraryFile())
202             {
203                 // TODO: Treat also library files here.
204                 return value + option.defaultExtension();
205             }
206             else
207             {
208                 std::string message
209                     = formatString("File '%s' does not exist or is not accessible.\n"
210                                    "The following extensions were tried to complete the file name:\n  %s",
211                                    value.c_str(), joinStrings(option.extensions(), ", ").c_str());
212                 GMX_THROW(InvalidInputError(message));
213             }
214         }
215         else if (option.isValidType(fileType))
216         {
217             if (option.isLibraryFile())
218             {
219                 // TODO: Treat also library files.
220             }
221             else if (!File::exists(value))
222             {
223                 std::string message
224                     = formatString("File '%s' does not exist or is not accessible.",
225                                    value.c_str());
226                 // TODO: Get actual errno value from the attempt to open the file
227                 // to provide better feedback to the user.
228                 GMX_THROW(InvalidInputError(message));
229             }
230             return value;
231         }
232     }
233     else // Not an input file
234     {
235         if (fileType == efNR)
236         {
237             return value + option.defaultExtension();
238         }
239         else if (option.isValidType(fileType))
240         {
241             return value;
242         }
243     }
244     return std::string();
245 }
246
247 std::string FileNameOptionManager::completeDefaultFileName(
248         const std::string &prefix, const FileNameOptionInfo &option)
249 {
250     if (option.isDirectoryOption() || impl_->bInputCheckingDisabled_)
251     {
252         return std::string();
253     }
254     const bool        bInput = option.isInputFile() || option.isInputOutputFile();
255     const std::string realPrefix
256         = !impl_->defaultFileName_.empty() ? impl_->defaultFileName_ : prefix;
257     if (bInput)
258     {
259         std::string completedName = findExistingExtension(realPrefix, option);
260         if (!completedName.empty())
261         {
262             return completedName;
263         }
264         if (option.isLibraryFile())
265         {
266             // TODO: Treat also library files here.
267             return realPrefix + option.defaultExtension();
268         }
269         else if (option.isSet())
270         {
271             std::string message
272                 = formatString("No file name was provided, and the default file "
273                                "'%s' does not exist or is not accessible.\n"
274                                "The following extensions were tried to complete the file name:\n  %s",
275                                prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
276             GMX_THROW(InvalidInputError(message));
277         }
278         else if (option.isRequired())
279         {
280             std::string message
281                 = formatString("Required option was not provided, and the default file "
282                                "'%s' does not exist or is not accessible.\n"
283                                "The following extensions were tried to complete the file name:\n  %s",
284                                prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
285             GMX_THROW(InvalidInputError(message));
286         }
287         // We get here with the legacy optional behavior.
288     }
289     return realPrefix + option.defaultExtension();
290 }
291
292 } // namespace gmx