Merge release-5-0 into master
[alexxy/gromacs.git] / src / gromacs / options / filenameoption.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,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 classes in filenameoption.h and filenameoptionstorage.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_options
41  */
42 #include "filenameoption.h"
43 #include "filenameoptionstorage.h"
44
45 #include <cstring>
46
47 #include <string>
48 #include <vector>
49
50 #include "gromacs/fileio/filenm.h"
51 #include "gromacs/options/filenameoptionmanager.h"
52 #include "gromacs/options/optionmanagercontainer.h"
53 #include "gromacs/utility/arrayref.h"
54 #include "gromacs/utility/file.h"
55 #include "gromacs/utility/gmxassert.h"
56 #include "gromacs/utility/stringutil.h"
57
58 namespace gmx
59 {
60
61 namespace
62 {
63
64 //! \addtogroup module_options
65 //! \{
66
67 /*! \brief
68  * Mapping from OptionFileType to a file type in filenm.h.
69  */
70 struct FileTypeMapping
71 {
72     //! OptionFileType value to map.
73     OptionFileType optionType;
74     //! Corresponding file type from filenm.h.
75     int            fileType;
76 };
77
78 //! Mappings from OptionFileType to file types in filenm.h.
79 const FileTypeMapping c_fileTypeMapping[] =
80 {
81     { eftTopology,    efTPS },
82     { eftTrajectory,  efTRX },
83     { eftPDB,         efPDB },
84     { eftIndex,       efNDX },
85     { eftPlot,        efXVG },
86     { eftGenericData, efDAT }
87 };
88
89 //! Extensions that are recognized as compressed files.
90 const char *const c_compressedExtensions[] =
91 { ".gz", ".Z" };
92
93 /********************************************************************
94  * FileTypeHandler
95  */
96
97 /*! \internal
98  * \brief
99  * Handles a single file type known to FileNameOptionStorage.
100  *
101  * Methods in this class do not throw, except for a possible std::bad_alloc
102  * when constructing std::string return values.
103  */
104 class FileTypeHandler
105 {
106     public:
107         /*! \brief
108          * Returns a handler for a single file type.
109          *
110          * \param[in] fileType  File type (from filenm.h) to use.
111          */
112         explicit FileTypeHandler(int fileType);
113
114         //! Returns the number of acceptable extensions for this file type.
115         int extensionCount() const;
116         //! Returns the extension with the given index.
117         const char *extension(int i) const;
118
119         //! Returns whether \p filename has a valid extension for this type.
120         bool hasKnownExtension(const std::string &filename) const;
121         //! Adds a default extension for this type to \p filename.
122         std::string addExtension(const std::string &filename) const;
123         /*! \brief
124          * Adds an extension to \p filename if it results in an existing file.
125          *
126          * Tries to add each extension for this file type to \p filename and
127          * checks whether this results in an existing file.
128          * The first match is returned.
129          * Returns an empty string if no existing file is found.
130          */
131         std::string findFileWithExtension(const std::string &filename) const;
132
133     private:
134         /*! \brief
135          * File type (from filenm.h) represented by this handler.
136          *
137          * -1 represents an unknown file type.
138          */
139         int        fileType_;
140         //! Number of different extensions this type supports.
141         int        extensionCount_;
142         /*! \brief
143          * List of simple file types that are included in this type.
144          *
145          * If `fileType_` represents a generic type in filenm.h, i.e., a type
146          * that accepts multiple different types of files, then this is an
147          * array of `extensionCount_` elements, each element specifying one
148          * non-generic file type that this option accepts.
149          * `NULL` for single-extension types.
150          */
151         const int *genericTypes_;
152 };
153
154 FileTypeHandler::FileTypeHandler(int fileType)
155     : fileType_(fileType), extensionCount_(0), genericTypes_(NULL)
156 {
157     if (fileType_ >= 0)
158     {
159         const int genericTypeCount = ftp2generic_count(fileType_);
160         if (genericTypeCount > 0)
161         {
162             extensionCount_ = genericTypeCount;
163             genericTypes_   = ftp2generic_list(fileType_);
164         }
165         else if (ftp2ext_with_dot(fileType_)[0] != '\0')
166         {
167             extensionCount_ = 1;
168         }
169     }
170 }
171
172 int FileTypeHandler::extensionCount() const
173 {
174     return extensionCount_;
175 }
176
177 const char *FileTypeHandler::extension(int i) const
178 {
179     GMX_ASSERT(i >= 0 && i < extensionCount_, "Invalid extension index");
180     if (genericTypes_ != NULL)
181     {
182         return ftp2ext_with_dot(genericTypes_[i]);
183     }
184     return ftp2ext_with_dot(fileType_);
185 }
186
187 bool
188 FileTypeHandler::hasKnownExtension(const std::string &filename) const
189 {
190     for (int i = 0; i < extensionCount(); ++i)
191     {
192         if (endsWith(filename, extension(i)))
193         {
194             return true;
195         }
196     }
197     return false;
198 }
199
200 std::string
201 FileTypeHandler::addExtension(const std::string &filename) const
202 {
203     if (extensionCount() == 0)
204     {
205         return filename;
206     }
207     return filename + extension(0);
208 }
209
210 std::string
211 FileTypeHandler::findFileWithExtension(const std::string &filename) const
212 {
213     for (int i = 0; i < extensionCount(); ++i)
214     {
215         std::string testFilename(filename + extension(i));
216         if (File::exists(testFilename))
217         {
218             return testFilename;
219         }
220     }
221     return std::string();
222 }
223
224 /*! \brief
225  * Helper method to complete a file name provided to a file name option.
226  *
227  * \param[in] value       Value provided to the file name option.
228  * \param[in] typeHandler Handler for the file type.
229  * \param[in] bCompleteToExisting
230  *     Whether to check existing files when completing the extension.
231  * \returns   \p value with possible extension added.
232  */
233 std::string completeFileName(const std::string     &value,
234                              const FileTypeHandler &typeHandler,
235                              bool                   bCompleteToExisting)
236 {
237     if (bCompleteToExisting && File::exists(value))
238     {
239         // TODO: This may not work as expected if the value is passed to a
240         // function that uses fn2ftp() to determine the file type and the input
241         // file has an unrecognized extension.
242         ConstArrayRef<const char *>                 compressedExtensions(c_compressedExtensions);
243         ConstArrayRef<const char *>::const_iterator ext;
244         for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext)
245         {
246             if (endsWith(value, *ext))
247             {
248                 return value.substr(0, value.length() - std::strlen(*ext));
249             }
250         }
251         return value;
252     }
253     if (typeHandler.hasKnownExtension(value))
254     {
255         return value;
256     }
257     if (bCompleteToExisting)
258     {
259         std::string newValue = typeHandler.findFileWithExtension(value);
260         if (!newValue.empty())
261         {
262             return newValue;
263         }
264     }
265     return typeHandler.addExtension(value);
266 }
267
268 //! \}
269
270 }   // namespace
271
272 /********************************************************************
273  * FileNameOptionStorage
274  */
275
276 FileNameOptionStorage::FileNameOptionStorage(const FileNameOption  &settings,
277                                              FileNameOptionManager *manager)
278     : MyBase(settings), info_(this), manager_(manager), fileType_(-1),
279       bRead_(settings.bRead_), bWrite_(settings.bWrite_),
280       bLibrary_(settings.bLibrary_)
281 {
282     GMX_RELEASE_ASSERT(!hasFlag(efOption_MultipleTimes),
283                        "allowMultiple() is not supported for file name options");
284     if (settings.optionType_ == eftUnknown && settings.legacyType_ >= 0)
285     {
286         fileType_ = settings.legacyType_;
287     }
288     else
289     {
290         ConstArrayRef<FileTypeMapping>                 map(c_fileTypeMapping);
291         ConstArrayRef<FileTypeMapping>::const_iterator i;
292         for (i = map.begin(); i != map.end(); ++i)
293         {
294             if (i->optionType == settings.optionType_)
295             {
296                 fileType_ = i->fileType;
297                 break;
298             }
299         }
300     }
301     if (settings.defaultBasename_ != NULL)
302     {
303         std::string defaultValue(settings.defaultBasename_);
304         defaultValue.append(defaultExtension());
305         setDefaultValueIfSet(defaultValue);
306         if (isRequired() || settings.bLegacyOptionalBehavior_)
307         {
308             setDefaultValue(defaultValue);
309         }
310     }
311 }
312
313 std::string FileNameOptionStorage::typeString() const
314 {
315     FileTypeHandler typeHandler(fileType_);
316     std::string     result;
317     int             count;
318     for (count = 0; count < 2 && count < typeHandler.extensionCount(); ++count)
319     {
320         if (count > 0)
321         {
322             result.append("/");
323         }
324         result.append(typeHandler.extension(count));
325     }
326     if (count < typeHandler.extensionCount())
327     {
328         result.append("/...");
329     }
330     if (result.empty())
331     {
332         if (isDirectoryOption())
333         {
334             result = "dir";
335         }
336         else
337         {
338             result = "file";
339         }
340     }
341     return result;
342 }
343
344 std::string FileNameOptionStorage::formatExtraDescription() const
345 {
346     FileTypeHandler typeHandler(fileType_);
347     std::string     result;
348     if (typeHandler.extensionCount() > 2)
349     {
350         result.append(":");
351         for (int i = 0; i < typeHandler.extensionCount(); ++i)
352         {
353             result.append(" ");
354             // Skip the dot.
355             result.append(typeHandler.extension(i) + 1);
356         }
357     }
358     return result;
359 }
360
361 std::string FileNameOptionStorage::formatSingleValue(const std::string &value) const
362 {
363     return value;
364 }
365
366 void FileNameOptionStorage::convertValue(const std::string &value)
367 {
368     const bool      bInput = isInputFile() || isInputOutputFile();
369     FileTypeHandler typeHandler(fileType_);
370     addValue(completeFileName(value, typeHandler, bInput));
371 }
372
373 void FileNameOptionStorage::processAll()
374 {
375     FileTypeHandler typeHandler(fileType_);
376     if (hasFlag(efOption_HasDefaultValue) && typeHandler.extensionCount() > 0)
377     {
378         const bool  bInput      = isInputFile() || isInputOutputFile();
379         ValueList  &valueList   = values();
380         GMX_RELEASE_ASSERT(valueList.size() == 1,
381                            "There should be only one default value");
382         const bool  bGlobalDefault =
383             (manager_ != NULL && !manager_->defaultFileName().empty());
384         if (!valueList[0].empty() && (typeHandler.extensionCount() > 1 || bGlobalDefault))
385         {
386             const std::string &oldValue = valueList[0];
387             GMX_ASSERT(endsWith(oldValue, defaultExtension()),
388                        "Default value does not have the expected extension");
389             std::string prefix = stripSuffixIfPresent(oldValue, defaultExtension());
390             if (bGlobalDefault)
391             {
392                 prefix = manager_->defaultFileName();
393             }
394             std::string newValue = completeFileName(prefix, typeHandler, bInput);
395             if (newValue != oldValue)
396             {
397                 valueList[0] = newValue;
398                 refreshValues();
399             }
400         }
401     }
402 }
403
404 bool FileNameOptionStorage::isDirectoryOption() const
405 {
406     return fileType_ == efRND;
407 }
408
409 const char *FileNameOptionStorage::defaultExtension() const
410 {
411     FileTypeHandler typeHandler(fileType_);
412     if (typeHandler.extensionCount() == 0)
413     {
414         return "";
415     }
416     return typeHandler.extension(0);
417 }
418
419 std::vector<const char *> FileNameOptionStorage::extensions() const
420 {
421     FileTypeHandler           typeHandler(fileType_);
422     std::vector<const char *> result;
423     result.reserve(typeHandler.extensionCount());
424     for (int i = 0; i < typeHandler.extensionCount(); ++i)
425     {
426         result.push_back(typeHandler.extension(i));
427     }
428     return result;
429 }
430
431 /********************************************************************
432  * FileNameOptionInfo
433  */
434
435 FileNameOptionInfo::FileNameOptionInfo(FileNameOptionStorage *option)
436     : OptionInfo(option)
437 {
438 }
439
440 const FileNameOptionStorage &FileNameOptionInfo::option() const
441 {
442     return static_cast<const FileNameOptionStorage &>(OptionInfo::option());
443 }
444
445 bool FileNameOptionInfo::isInputFile() const
446 {
447     return option().isInputFile();
448 }
449
450 bool FileNameOptionInfo::isOutputFile() const
451 {
452     return option().isOutputFile();
453 }
454
455 bool FileNameOptionInfo::isInputOutputFile() const
456 {
457     return option().isInputOutputFile();
458 }
459
460 bool FileNameOptionInfo::isLibraryFile() const
461 {
462     return option().isLibraryFile();
463 }
464
465 bool FileNameOptionInfo::isDirectoryOption() const
466 {
467     return option().isDirectoryOption();
468 }
469
470 const char *FileNameOptionInfo::defaultExtension() const
471 {
472     return option().defaultExtension();
473 }
474
475 FileNameOptionInfo::ExtensionList FileNameOptionInfo::extensions() const
476 {
477     return option().extensions();
478 }
479
480 /********************************************************************
481  * FileNameOption
482  */
483
484 AbstractOptionStoragePointer
485 FileNameOption::createStorage(const OptionManagerContainer &managers) const
486 {
487     return AbstractOptionStoragePointer(
488             new FileNameOptionStorage(*this, managers.get<FileNameOptionManager>()));
489 }
490
491 } // namespace gmx