390f3998183722981855da0aeeee15a6ae213066
[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 "filenameoptionmanager.h"
43
44 #include <cstring>
45
46 #include <string>
47
48 #include "gromacs/fileio/filenm.h"
49 #include "gromacs/options/basicoptions.h"
50 #include "gromacs/options/filenameoption.h"
51 #include "gromacs/options/options.h"
52 #include "gromacs/utility/arrayref.h"
53 #include "gromacs/utility/exceptions.h"
54 #include "gromacs/utility/file.h"
55 #include "gromacs/utility/path.h"
56 #include "gromacs/utility/stringutil.h"
57
58 namespace gmx
59 {
60
61 namespace
62 {
63
64 //! Extensions that are recognized as compressed files.
65 const char *const c_compressedExtensions[] =
66 { ".gz", ".Z" };
67
68 /********************************************************************
69  * Helper functions
70  */
71
72 /*! \brief
73  * Adds an extension to \p prefix if it results in an existing file.
74  *
75  * Tries to add each extension for this file type to \p prefix and
76  * checks whether this results in an existing file.
77  * The first match is returned.
78  * Returns an empty string if no existing file is found.
79  */
80 std::string findExistingExtension(const std::string        &prefix,
81                                   const FileNameOptionInfo &option)
82 {
83     ConstArrayRef<int>                 types = option.fileTypes();
84     ConstArrayRef<int>::const_iterator i;
85     for (i = types.begin(); i != types.end(); ++i)
86     {
87         std::string testFilename(prefix + ftp2ext_with_dot(*i));
88         if (File::exists(testFilename))
89         {
90             return testFilename;
91         }
92     }
93     return std::string();
94 }
95
96 }   // namespace
97
98 /********************************************************************
99  * FileNameOptionManager::Impl
100  */
101
102 /*! \internal \brief
103  * Private implemention class for FileNameOptionManager.
104  *
105  * \ingroup module_options
106  */
107 class FileNameOptionManager::Impl
108 {
109     public:
110         Impl() : bInputCheckingDisabled_(false) {}
111
112         //! Global default file name, if set.
113         std::string     defaultFileName_;
114         //! Whether input option processing has been disabled.
115         bool            bInputCheckingDisabled_;
116 };
117
118 /********************************************************************
119  * FileNameOptionManager
120  */
121
122 FileNameOptionManager::FileNameOptionManager()
123     : impl_(new Impl())
124 {
125 }
126
127 FileNameOptionManager::~FileNameOptionManager()
128 {
129 }
130
131 void FileNameOptionManager::disableInputOptionChecking(bool bDisable)
132 {
133     impl_->bInputCheckingDisabled_ = bDisable;
134 }
135
136 void FileNameOptionManager::addDefaultFileNameOption(
137         Options *options, const char *name)
138 {
139     options->addOption(
140             StringOption(name).store(&impl_->defaultFileName_)
141                 .description("Set the default filename for all file options"));
142 }
143
144 std::string FileNameOptionManager::completeFileName(
145         const std::string &value, const FileNameOptionInfo &option)
146 {
147     const bool bInput = option.isInputFile() || option.isInputOutputFile();
148     // Currently, directory options are simple, and don't need any
149     // special processing.
150     // TODO: Consider splitting them into a separate DirectoryOption.
151     if (option.isDirectoryOption())
152     {
153         if (!impl_->bInputCheckingDisabled_ && bInput && !Directory::exists(value))
154         {
155             std::string message
156                 = formatString("Directory '%s' does not exist or is not accessible.",
157                                value.c_str());
158             // TODO: Get actual errno value from the attempt to open the file
159             // to provide better feedback to the user.
160             GMX_THROW(InvalidInputError(message));
161         }
162         return value;
163     }
164     const int fileType = fn2ftp(value.c_str());
165     if (bInput && !impl_->bInputCheckingDisabled_)
166     {
167         if (fileType == efNR && File::exists(value))
168         {
169             ConstArrayRef<const char *>                 compressedExtensions(c_compressedExtensions);
170             ConstArrayRef<const char *>::const_iterator ext;
171             for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext)
172             {
173                 if (endsWith(value, *ext))
174                 {
175                     std::string newValue = value.substr(0, value.length() - std::strlen(*ext));
176                     if (option.isValidType(fn2ftp(newValue.c_str())))
177                     {
178                         return newValue;
179                     }
180                     else
181                     {
182                         return std::string();
183                     }
184                 }
185             }
186             // VMD plugins may be able to read the file.
187             if (option.isInputFile() && option.isTrajectoryOption())
188             {
189                 return value;
190             }
191         }
192         else if (fileType == efNR)
193         {
194             std::string processedValue = findExistingExtension(value, option);
195             if (!processedValue.empty())
196             {
197                 return processedValue;
198             }
199             if (option.isLibraryFile())
200             {
201                 // TODO: Treat also library files here.
202                 return value + option.defaultExtension();
203             }
204             else
205             {
206                 std::string message
207                     = formatString("File '%s' does not exist or is not accessible.\n"
208                                    "The following extensions were tried to complete the file name:\n  %s",
209                                    value.c_str(), joinStrings(option.extensions(), ", ").c_str());
210                 GMX_THROW(InvalidInputError(message));
211             }
212         }
213         else if (option.isValidType(fileType))
214         {
215             if (option.isLibraryFile())
216             {
217                 // TODO: Treat also library files.
218             }
219             else if (!File::exists(value))
220             {
221                 std::string message
222                     = formatString("File '%s' does not exist or is not accessible.",
223                                    value.c_str());
224                 // TODO: Get actual errno value from the attempt to open the file
225                 // to provide better feedback to the user.
226                 GMX_THROW(InvalidInputError(message));
227             }
228             return value;
229         }
230     }
231     else // Not an input file
232     {
233         if (fileType == efNR)
234         {
235             return value + option.defaultExtension();
236         }
237         else if (option.isValidType(fileType))
238         {
239             return value;
240         }
241     }
242     return std::string();
243 }
244
245 std::string FileNameOptionManager::completeDefaultFileName(
246         const std::string &prefix, const FileNameOptionInfo &option)
247 {
248     if (option.isDirectoryOption() || impl_->bInputCheckingDisabled_)
249     {
250         return std::string();
251     }
252     const bool        bInput = option.isInputFile() || option.isInputOutputFile();
253     const std::string realPrefix
254         = !impl_->defaultFileName_.empty() ? impl_->defaultFileName_ : prefix;
255     if (bInput)
256     {
257         std::string completedName = findExistingExtension(realPrefix, option);
258         if (!completedName.empty())
259         {
260             return completedName;
261         }
262         if (option.isLibraryFile())
263         {
264             // TODO: Treat also library files here.
265             return realPrefix + option.defaultExtension();
266         }
267         else if (option.isSet())
268         {
269             std::string message
270                 = formatString("No file name was provided, and the default file "
271                                "'%s' does not exist or is not accessible.\n"
272                                "The following extensions were tried to complete the file name:\n  %s",
273                                prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
274             GMX_THROW(InvalidInputError(message));
275         }
276         else if (option.isRequired())
277         {
278             std::string message
279                 = formatString("Required option was not provided, and the default file "
280                                "'%s' does not exist or is not accessible.\n"
281                                "The following extensions were tried to complete the file name:\n  %s",
282                                prefix.c_str(), joinStrings(option.extensions(), ", ").c_str());
283             GMX_THROW(InvalidInputError(message));
284         }
285         // We get here with the legacy optional behavior.
286     }
287     return realPrefix + option.defaultExtension();
288 }
289
290 } // namespace gmx