Apply re-formatting to C++ in src/ tree.
[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,2017,2019,2020, 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/filetypes.h"
51 #include "gromacs/options/basicoptions.h"
52 #include "gromacs/options/filenameoption.h"
53 #include "gromacs/options/ioptionscontainer.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[] = { ".gz", ".Z" };
68
69 /********************************************************************
70  * Helper functions
71  */
72
73 /*! \brief
74  * Adds an extension to \p prefix if it results in an existing file.
75  *
76  * Tries to add each extension for this file type to \p prefix and
77  * checks whether this results in an existing file.
78  * The first match is returned.
79  * Returns an empty string if no existing file is found.
80  */
81 std::string findExistingExtension(const std::string&          prefix,
82                                   const FileNameOptionInfo&   option,
83                                   const IFileInputRedirector* redirector)
84 {
85     ArrayRef<const int>                 types = option.fileTypes();
86     ArrayRef<const 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 (redirector->fileExists(testFilename, File::throwOnError))
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() : redirector_(&defaultFileInputRedirector()), bInputCheckingDisabled_(false) {}
113
114     //! Redirector for file existence checks.
115     const IFileInputRedirector* redirector_;
116     //! Global default file name, if set.
117     std::string defaultFileName_;
118     //! Whether input option processing has been disabled.
119     bool bInputCheckingDisabled_;
120 };
121
122 /********************************************************************
123  * FileNameOptionManager
124  */
125
126 FileNameOptionManager::FileNameOptionManager() : impl_(new Impl()) {}
127
128 FileNameOptionManager::~FileNameOptionManager() {}
129
130 void FileNameOptionManager::setInputRedirector(const IFileInputRedirector* redirector)
131 {
132     impl_->redirector_ = redirector;
133 }
134
135 void FileNameOptionManager::disableInputOptionChecking(bool bDisable)
136 {
137     impl_->bInputCheckingDisabled_ = bDisable;
138 }
139
140 void FileNameOptionManager::addDefaultFileNameOption(IOptionsContainer* options, const char* name)
141 {
142     options->addOption(
143             StringOption(name).store(&impl_->defaultFileName_).description("Set the default filename for all file options"));
144 }
145
146 std::string FileNameOptionManager::completeFileName(const std::string& value, const FileNameOptionInfo& option)
147 {
148     const bool bAllowMissing = option.allowMissing();
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 && !bAllowMissing && !Directory::exists(value))
156         {
157             std::string message =
158                     formatString("Directory '%s' does not exist or is not accessible.", value.c_str());
159             // TODO: Get actual errno value from the attempt to open the file
160             // to provide better feedback to the user.
161             GMX_THROW(InvalidInputError(message));
162         }
163         return value;
164     }
165     const int fileType = fn2ftp(value.c_str());
166     if (bInput && !impl_->bInputCheckingDisabled_)
167     {
168         if (fileType == efNR && impl_->redirector_->fileExists(value, File::throwOnError))
169         {
170             ArrayRef<const char* const> compressedExtensions(c_compressedExtensions);
171             ArrayRef<const char* const>::const_iterator ext;
172             for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext)
173             {
174                 if (endsWith(value, *ext))
175                 {
176                     std::string newValue = value.substr(0, value.length() - std::strlen(*ext));
177                     if (option.isValidType(fn2ftp(newValue.c_str())))
178                     {
179                         return newValue;
180                     }
181                     else
182                     {
183                         return std::string();
184                     }
185                 }
186             }
187             // VMD plugins may be able to read the file.
188             if (option.isInputFile() && option.isTrajectoryOption())
189             {
190                 return value;
191             }
192         }
193         else if (fileType == efNR)
194         {
195             const std::string processedValue = findExistingExtension(value, option, impl_->redirector_);
196             if (!processedValue.empty())
197             {
198                 return processedValue;
199             }
200             if (bAllowMissing)
201             { // NOLINT bugprone-branch-clone
202                 return value + option.defaultExtension();
203             }
204             else if (option.isLibraryFile())
205             {
206                 // TODO: Treat also library files here and remove the NOLINT.
207                 return value + option.defaultExtension();
208             }
209             else
210             {
211                 std::string message = formatString(
212                         "File '%s' does not exist or is not accessible.\n"
213                         "The following extensions were tried to complete the file name:\n  %s",
214                         value.c_str(),
215                         joinStrings(option.extensions(), ", ").c_str());
216                 GMX_THROW(InvalidInputError(message));
217             }
218         }
219         else if (option.isValidType(fileType))
220         {
221             if (option.isLibraryFile())
222             {
223                 // TODO: Treat also library files.
224             }
225             else if (!bAllowMissing)
226             {
227                 if (!impl_->redirector_->fileExists(value, File::throwOnNotFound))
228                 {
229                     return std::string();
230                 }
231             }
232             return value;
233         }
234     }
235     else // Not an input file
236     {
237         if (fileType == efNR)
238         {
239             return value + option.defaultExtension();
240         }
241         else if (option.isValidType(fileType))
242         {
243             return value;
244         }
245     }
246     return std::string();
247 }
248
249 std::string FileNameOptionManager::completeDefaultFileName(const std::string&        prefix,
250                                                            const FileNameOptionInfo& option)
251 {
252     if (option.isDirectoryOption())
253     {
254         return std::string();
255     }
256     const bool        bInput     = option.isInputFile() || option.isInputOutputFile();
257     const std::string realPrefix = !impl_->defaultFileName_.empty() ? impl_->defaultFileName_ : prefix;
258     if (bInput && !impl_->bInputCheckingDisabled_)
259     {
260         const std::string completedName = findExistingExtension(realPrefix, option, impl_->redirector_);
261         if (!completedName.empty())
262         {
263             return completedName;
264         }
265         if (option.allowMissing())
266         { // NOLINT bugprone-branch-clone
267             return realPrefix + option.defaultExtension();
268         }
269         else if (option.isLibraryFile()) // NOLINT bugprone-branch-clone
270         {
271             // TODO: Treat also library files here and remove the NOLINT
272             return realPrefix + option.defaultExtension();
273         }
274         else if (option.isSet())
275         {
276             std::string message = formatString(
277                     "No file name was provided, and the default file "
278                     "'%s' does not exist or is not accessible.\n"
279                     "The following extensions were tried to complete the file name:\n  %s",
280                     prefix.c_str(),
281                     joinStrings(option.extensions(), ", ").c_str());
282             GMX_THROW(InvalidInputError(message));
283         }
284         else if (option.isRequired())
285         {
286             std::string message = formatString(
287                     "Required option was not provided, and the default file "
288                     "'%s' does not exist or is not accessible.\n"
289                     "The following extensions were tried to complete the file name:\n  %s",
290                     prefix.c_str(),
291                     joinStrings(option.extensions(), ", ").c_str());
292             GMX_THROW(InvalidInputError(message));
293         }
294         // We get here with the legacy optional behavior.
295     }
296     return realPrefix + option.defaultExtension();
297 }
298
299 } // namespace gmx