Apply clang-format to source tree
[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,2015,2016,2017,2019, 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 "gmxpre.h"
43
44 #include "filenameoption.h"
45
46 #include <cstring>
47
48 #include <string>
49 #include <vector>
50
51 #include "gromacs/fileio/filetypes.h"
52 #include "gromacs/options/filenameoptionmanager.h"
53 #include "gromacs/options/optionmanagercontainer.h"
54 #include "gromacs/utility/arrayref.h"
55 #include "gromacs/utility/exceptions.h"
56 #include "gromacs/utility/gmxassert.h"
57 #include "gromacs/utility/stringutil.h"
58
59 #include "filenameoptionstorage.h"
60
61 namespace gmx
62 {
63
64 namespace
65 {
66
67 //! \addtogroup module_options
68 //! \{
69
70 /*! \brief
71  * Mapping from OptionFileType to a file type in filetypes.h.
72  */
73 struct FileTypeMapping
74 {
75     //! OptionFileType value to map.
76     OptionFileType optionType;
77     //! Corresponding file type from filetypes.h.
78     int fileType;
79 };
80
81 //! Mappings from OptionFileType to file types in filetypes.h.
82 const FileTypeMapping c_fileTypeMapping[] = { { eftTopology, efTPS },   { eftRunInput, efTPR },
83                                               { eftTrajectory, efTRX }, { eftEnergy, efEDR },
84                                               { eftPDB, efPDB },        { eftIndex, efNDX },
85                                               { eftPlot, efXVG },       { eftGenericData, efDAT } };
86
87 /********************************************************************
88  * FileTypeHandler
89  */
90
91 /*! \internal
92  * \brief
93  * Handles a single file type known to FileNameOptionStorage.
94  *
95  * Methods in this class do not throw, except for a possible std::bad_alloc
96  * when constructing std::string return values.
97  */
98 class FileTypeHandler
99 {
100 public:
101     /*! \brief
102      * Returns a handler for a single file type.
103      *
104      * \param[in] fileType  File type (from filetypes.h) to use.
105      */
106     explicit FileTypeHandler(int fileType);
107
108     //! Returns the number of acceptable extensions for this file type.
109     int extensionCount() const;
110     //! Returns the extension with the given index.
111     const char* extension(int i) const;
112
113     //! Returns whether \p fileType (from filetypes.h) is accepted for this type.
114     bool isValidType(int fileType) const;
115
116 private:
117     /*! \brief
118      * File type (from filetypes.h) represented by this handler.
119      *
120      * -1 represents an unknown file type.
121      */
122     int fileType_;
123     //! Number of different extensions this type supports.
124     int extensionCount_;
125     /*! \brief
126      * List of simple file types that are included in this type.
127      *
128      * If `fileType_` represents a generic type in filetypes.h, i.e., a type
129      * that accepts multiple different types of files, then this is an
130      * array of `extensionCount_` elements, each element specifying one
131      * non-generic file type that this option accepts.
132      * `NULL` for single-extension types.
133      */
134     const int* genericTypes_;
135 };
136
137 FileTypeHandler::FileTypeHandler(int fileType) :
138     fileType_(fileType),
139     extensionCount_(0),
140     genericTypes_(nullptr)
141 {
142     if (fileType_ >= 0)
143     {
144         const int genericTypeCount = ftp2generic_count(fileType_);
145         if (genericTypeCount > 0)
146         {
147             extensionCount_ = genericTypeCount;
148             genericTypes_   = ftp2generic_list(fileType_);
149         }
150         else if (ftp2ext_with_dot(fileType_)[0] != '\0')
151         {
152             extensionCount_ = 1;
153         }
154     }
155 }
156
157 int FileTypeHandler::extensionCount() const
158 {
159     return extensionCount_;
160 }
161
162 const char* FileTypeHandler::extension(int i) const
163 {
164     GMX_ASSERT(i >= 0 && i < extensionCount_, "Invalid extension index");
165     if (genericTypes_ != nullptr)
166     {
167         return ftp2ext_with_dot(genericTypes_[i]);
168     }
169     return ftp2ext_with_dot(fileType_);
170 }
171
172 bool FileTypeHandler::isValidType(int fileType) const
173 {
174     if (genericTypes_ != nullptr)
175     {
176         for (int i = 0; i < extensionCount(); ++i)
177         {
178             if (fileType == genericTypes_[i])
179             {
180                 return true;
181             }
182         }
183         return false;
184     }
185     else
186     {
187         return fileType == fileType_;
188     }
189 }
190
191 //! \}
192
193 } // namespace
194
195 /********************************************************************
196  * FileNameOptionStorage
197  */
198
199 FileNameOptionStorage::FileNameOptionStorage(const FileNameOption& settings, FileNameOptionManager* manager) :
200     MyBase(settings),
201     info_(this),
202     manager_(manager),
203     fileType_(-1),
204     defaultExtension_(""),
205     bRead_(settings.bRead_),
206     bWrite_(settings.bWrite_),
207     bLibrary_(settings.bLibrary_),
208     bAllowMissing_(settings.bAllowMissing_)
209 {
210     GMX_RELEASE_ASSERT(!hasFlag(efOption_MultipleTimes),
211                        "allowMultiple() is not supported for file name options");
212     if (settings.optionType_ == eftUnknown && settings.legacyType_ >= 0)
213     {
214         fileType_ = settings.legacyType_;
215     }
216     else
217     {
218         ArrayRef<const FileTypeMapping>                 map(c_fileTypeMapping);
219         ArrayRef<const FileTypeMapping>::const_iterator i;
220         for (i = map.begin(); i != map.end(); ++i)
221         {
222             if (i->optionType == settings.optionType_)
223             {
224                 fileType_ = i->fileType;
225                 break;
226             }
227         }
228     }
229     FileTypeHandler typeHandler(fileType_);
230     if (settings.defaultType_ >= 0 && settings.defaultType_ < efNR)
231     {
232         // This also assures that the default type is not a generic type.
233         GMX_RELEASE_ASSERT(typeHandler.isValidType(settings.defaultType_),
234                            "Default type for a file option is not an accepted "
235                            "type for the option");
236         FileTypeHandler defaultHandler(settings.defaultType_);
237         defaultExtension_ = defaultHandler.extension(0);
238     }
239     else if (typeHandler.extensionCount() > 0)
240     {
241         defaultExtension_ = typeHandler.extension(0);
242     }
243     if (settings.defaultBasename_ != nullptr)
244     {
245         std::string defaultValue(settings.defaultBasename_);
246         int         type = fn2ftp(settings.defaultBasename_);
247         GMX_RELEASE_ASSERT(type == efNR || type == settings.defaultType_,
248                            "Default basename has an extension that does not "
249                            "match the default type");
250         if (type == efNR)
251         {
252             defaultValue.append(defaultExtension());
253         }
254         setDefaultValueIfSet(defaultValue);
255         if (isRequired() || settings.bLegacyOptionalBehavior_)
256         {
257             setDefaultValue(defaultValue);
258         }
259     }
260 }
261
262 std::string FileNameOptionStorage::typeString() const
263 {
264     FileTypeHandler typeHandler(fileType_);
265     std::string     result;
266     int             count;
267     for (count = 0; count < 2 && count < typeHandler.extensionCount(); ++count)
268     {
269         if (count > 0)
270         {
271             result.append("/");
272         }
273         result.append(typeHandler.extension(count));
274     }
275     if (count < typeHandler.extensionCount())
276     {
277         result.append("/...");
278     }
279     if (result.empty())
280     {
281         if (isDirectoryOption())
282         {
283             result = "dir";
284         }
285         else
286         {
287             result = "file";
288         }
289     }
290     return result;
291 }
292
293 std::string FileNameOptionStorage::formatExtraDescription() const
294 {
295     FileTypeHandler typeHandler(fileType_);
296     std::string     result;
297     if (typeHandler.extensionCount() > 2)
298     {
299         result.append(":");
300         for (int i = 0; i < typeHandler.extensionCount(); ++i)
301         {
302             result.append(" [REF]");
303             // Skip the dot.
304             result.append(typeHandler.extension(i) + 1);
305             result.append("[ref]");
306         }
307     }
308     return result;
309 }
310
311 std::string FileNameOptionStorage::formatSingleValue(const std::string& value) const
312 {
313     return value;
314 }
315
316 void FileNameOptionStorage::initConverter(ConverterType* /*converter*/) {}
317
318 std::string FileNameOptionStorage::processValue(const std::string& value) const
319 {
320     if (manager_ != nullptr)
321     {
322         std::string processedValue = manager_->completeFileName(value, info_);
323         if (!processedValue.empty())
324         {
325             // If the manager returns a value, use it without further checks,
326             // except for sanity checking.
327             if (!isDirectoryOption())
328             {
329                 const int fileType = fn2ftp(processedValue.c_str());
330                 if (fileType == efNR)
331                 {
332                     // If the manager returned an invalid file name, assume
333                     // that it knows what it is doing.  But assert that it
334                     // only does that for the only case that it is currently
335                     // required for: VMD plugins.
336                     GMX_ASSERT(isInputFile() && isTrajectoryOption(),
337                                "Manager returned an invalid file name");
338                 }
339                 else
340                 {
341                     GMX_ASSERT(isValidType(fileType), "Manager returned an invalid file name");
342                 }
343             }
344             return processedValue;
345         }
346     }
347     // Currently, directory options are simple, and don't need any
348     // special processing.
349     // TODO: Consider splitting them into a separate DirectoryOption.
350     if (isDirectoryOption())
351     {
352         return value;
353     }
354     const int fileType = fn2ftp(value.c_str());
355     if (fileType == efNR)
356     {
357         std::string message = formatString(
358                 "File '%s' cannot be used by GROMACS because it "
359                 "does not have a recognizable extension.\n"
360                 "The following extensions are possible for this option:\n  %s",
361                 value.c_str(), joinStrings(extensions(), ", ").c_str());
362         GMX_THROW(InvalidInputError(message));
363     }
364     else if (!isValidType(fileType))
365     {
366         std::string message = formatString(
367                 "File name '%s' cannot be used for this option.\n"
368                 "Only the following extensions are possible:\n  %s",
369                 value.c_str(), joinStrings(extensions(), ", ").c_str());
370         GMX_THROW(InvalidInputError(message));
371     }
372     return value;
373 }
374
375 void FileNameOptionStorage::processAll()
376 {
377     if (manager_ != nullptr && hasFlag(efOption_HasDefaultValue))
378     {
379         ArrayRef<std::string> valueList = values();
380         GMX_RELEASE_ASSERT(valueList.size() == 1, "There should be only one default value");
381         if (!valueList[0].empty())
382         {
383             const std::string& oldValue = valueList[0];
384             GMX_ASSERT(endsWith(oldValue, defaultExtension()),
385                        "Default value does not have the expected extension");
386             const std::string prefix   = stripSuffixIfPresent(oldValue, defaultExtension());
387             const std::string newValue = manager_->completeDefaultFileName(prefix, info_);
388             if (!newValue.empty() && newValue != oldValue)
389             {
390                 GMX_ASSERT(isValidType(fn2ftp(newValue.c_str())),
391                            "Manager returned an invalid default value");
392                 valueList[0] = newValue;
393             }
394         }
395     }
396 }
397
398 bool FileNameOptionStorage::isDirectoryOption() const
399 {
400     return fileType_ == efRND;
401 }
402
403 bool FileNameOptionStorage::isTrajectoryOption() const
404 {
405     return fileType_ == efTRX;
406 }
407
408 const char* FileNameOptionStorage::defaultExtension() const
409 {
410     return defaultExtension_;
411 }
412
413 std::vector<const char*> FileNameOptionStorage::extensions() const
414 {
415     FileTypeHandler          typeHandler(fileType_);
416     std::vector<const char*> result;
417     result.reserve(typeHandler.extensionCount());
418     for (int i = 0; i < typeHandler.extensionCount(); ++i)
419     {
420         result.push_back(typeHandler.extension(i));
421     }
422     return result;
423 }
424
425 bool FileNameOptionStorage::isValidType(int fileType) const
426 {
427     FileTypeHandler typeHandler(fileType_);
428     return typeHandler.isValidType(fileType);
429 }
430
431 ArrayRef<const int> FileNameOptionStorage::fileTypes() const
432 {
433     if (fileType_ < 0)
434     {
435         return ArrayRef<const int>();
436     }
437     const int genericTypeCount = ftp2generic_count(fileType_);
438     if (genericTypeCount > 0)
439     {
440         return constArrayRefFromArray<int>(ftp2generic_list(fileType_), genericTypeCount);
441     }
442     return constArrayRefFromArray<int>(&fileType_, 1);
443 }
444
445 /********************************************************************
446  * FileNameOptionInfo
447  */
448
449 FileNameOptionInfo::FileNameOptionInfo(FileNameOptionStorage* option) : OptionInfo(option) {}
450
451 const FileNameOptionStorage& FileNameOptionInfo::option() const
452 {
453     return static_cast<const FileNameOptionStorage&>(OptionInfo::option());
454 }
455
456 bool FileNameOptionInfo::isInputFile() const
457 {
458     return option().isInputFile();
459 }
460
461 bool FileNameOptionInfo::isOutputFile() const
462 {
463     return option().isOutputFile();
464 }
465
466 bool FileNameOptionInfo::isInputOutputFile() const
467 {
468     return option().isInputOutputFile();
469 }
470
471 bool FileNameOptionInfo::isLibraryFile() const
472 {
473     return option().isLibraryFile();
474 }
475
476 bool FileNameOptionInfo::allowMissing() const
477 {
478     return option().allowMissing();
479 }
480
481 bool FileNameOptionInfo::isDirectoryOption() const
482 {
483     return option().isDirectoryOption();
484 }
485
486 bool FileNameOptionInfo::isTrajectoryOption() const
487 {
488     return option().isTrajectoryOption();
489 }
490
491 const char* FileNameOptionInfo::defaultExtension() const
492 {
493     return option().defaultExtension();
494 }
495
496 FileNameOptionInfo::ExtensionList FileNameOptionInfo::extensions() const
497 {
498     return option().extensions();
499 }
500
501 bool FileNameOptionInfo::isValidType(int fileType) const
502 {
503     return option().isValidType(fileType);
504 }
505
506 ArrayRef<const int> FileNameOptionInfo::fileTypes() const
507 {
508     return option().fileTypes();
509 }
510
511 /********************************************************************
512  * FileNameOption
513  */
514
515 AbstractOptionStorage* FileNameOption::createStorage(const OptionManagerContainer& managers) const
516 {
517     return new FileNameOptionStorage(*this, managers.get<FileNameOptionManager>());
518 }
519
520 } // namespace gmx