Improve (Selection|FileName)OptionManager handling
[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 class FileTypeRegistry;
65
66 //! \addtogroup module_options
67 //! \{
68
69 //! Extensions that are recognized as compressed files.
70 const char *const c_compressedExtensions[] =
71 { ".gz", ".Z" };
72
73 //! Shorthand for a list of file extensions.
74 typedef std::vector<const char *> ExtensionList;
75
76 /********************************************************************
77  * FileTypeHandler
78  */
79
80 /*! \internal \brief
81  * Handles a single file type known to FileNameOptionStorage.
82  */
83 class FileTypeHandler
84 {
85     public:
86         //! Returns the list of extensions for this file type.
87         const ExtensionList &extensions() const { return extensions_; }
88
89         //! Returns whether \p filename has a valid extension for this type.
90         bool hasKnownExtension(const std::string &filename) const;
91         //! Adds a default extension for this type to \p filename.
92         std::string addExtension(const std::string &filename) const;
93         /*! \brief
94          * Adds an extension to \p filename if it results in an existing file.
95          *
96          * Tries to add each extension for this file type to \p filename and
97          * checks whether this results in an existing file.
98          * The first match is returned.
99          * Returns an empty string if no existing file is found.
100          */
101         std::string findFileWithExtension(const std::string &filename) const;
102
103     private:
104         //! Possible extensions for this file type.
105         ExtensionList extensions_;
106
107         /*! \brief
108          * Needed for initialization; all initialization is handled by
109          * FileTypeRegistry.
110          */
111         friend class FileTypeRegistry;
112 };
113
114 bool
115 FileTypeHandler::hasKnownExtension(const std::string &filename) const
116 {
117     for (size_t i = 0; i < extensions_.size(); ++i)
118     {
119         if (endsWith(filename, extensions_[i]))
120         {
121             return true;
122         }
123     }
124     return false;
125 }
126
127 std::string
128 FileTypeHandler::addExtension(const std::string &filename) const
129 {
130     if (extensions_.empty())
131     {
132         return filename;
133     }
134     return filename + extensions_[0];
135 }
136
137 std::string
138 FileTypeHandler::findFileWithExtension(const std::string &filename) const
139 {
140     for (size_t i = 0; i < extensions_.size(); ++i)
141     {
142         std::string testFilename(filename + extensions_[i]);
143         if (File::exists(testFilename))
144         {
145             return testFilename;
146         }
147     }
148     return std::string();
149 }
150
151 /********************************************************************
152  * FileTypeRegistry
153  */
154
155 /*! \internal \brief
156  * Singleton for managing static file type info for FileNameOptionStorage.
157  */
158 class FileTypeRegistry
159 {
160     public:
161         //! Returns a singleton instance of this class.
162         static const FileTypeRegistry &instance();
163         //! Returns a handler for a single file type.
164         const FileTypeHandler &
165         handlerForType(OptionFileType type, int legacyType) const;
166
167     private:
168         //! Initializes the file type registry.
169         FileTypeRegistry();
170
171         //! Registers a file type that corresponds to a ftp in filenm.h.
172         void registerType(int type, int ftp);
173         //! Registers a file type with a single extension.
174         void registerType(int type, const char *extension);
175
176         std::vector<FileTypeHandler> filetypes_;
177 };
178
179 // static
180 const FileTypeRegistry &
181 FileTypeRegistry::instance()
182 {
183     static FileTypeRegistry singleton;
184     return singleton;
185 }
186
187 const FileTypeHandler &
188 FileTypeRegistry::handlerForType(OptionFileType type, int legacyType) const
189 {
190     int index = type;
191     if (type == eftUnknown && legacyType >= 0)
192     {
193         index = eftOptionFileType_NR + legacyType;
194     }
195     GMX_RELEASE_ASSERT(index >= 0 && static_cast<size_t>(index) < filetypes_.size(),
196                        "Invalid file type");
197     return filetypes_[index];
198 }
199
200 FileTypeRegistry::FileTypeRegistry()
201 {
202     filetypes_.resize(eftOptionFileType_NR + efNR);
203     registerType(eftTopology,    efTPS);
204     registerType(eftTrajectory,  efTRX);
205     registerType(eftPDB,         efPDB);
206     registerType(eftIndex,       efNDX);
207     registerType(eftPlot,        efXVG);
208     registerType(eftGenericData, efDAT);
209     for (int i = 0; i < efNR; ++i)
210     {
211         registerType(eftOptionFileType_NR + i, i);
212     }
213 }
214
215 void FileTypeRegistry::registerType(int type, int ftp)
216 {
217     GMX_RELEASE_ASSERT(type >= 0 && static_cast<size_t>(type) < filetypes_.size(),
218                        "Invalid file type");
219     const int genericTypeCount = ftp2generic_count(ftp);
220     if (genericTypeCount > 0)
221     {
222         const int *const genericTypes = ftp2generic_list(ftp);
223         filetypes_[type].extensions_.clear();
224         filetypes_[type].extensions_.reserve(genericTypeCount);
225         for (int i = 0; i < genericTypeCount; ++i)
226         {
227             filetypes_[type].extensions_.push_back(ftp2ext_with_dot(genericTypes[i]));
228         }
229     }
230     else
231     {
232         registerType(type, ftp2ext_with_dot(ftp));
233     }
234 }
235
236 void FileTypeRegistry::registerType(int type, const char *extension)
237 {
238     GMX_RELEASE_ASSERT(type >= 0 && static_cast<size_t>(type) < filetypes_.size(),
239                        "Invalid file type");
240     filetypes_[type].extensions_.assign(1, extension);
241 }
242
243 /*! \brief
244  * Helper method to complete a file name provided to a file name option.
245  *
246  * \param[in] value      Value provided to the file name option.
247  * \param[in] filetype   File type for the option.
248  * \param[in] legacyType If \p filetype is eftUnknown, this gives the type as
249  *     an enum value from filenm.h.
250  * \param[in] bCompleteToExisting
251  *     Whether to check existing files when completing the extension.
252  * \returns   \p value with possible extension added.
253  */
254 std::string completeFileName(const std::string &value, OptionFileType filetype,
255                              int legacyType, bool bCompleteToExisting)
256 {
257     if (bCompleteToExisting && File::exists(value))
258     {
259         // TODO: This may not work as expected if the value is passed to a
260         // function that uses fn2ftp() to determine the file type and the input
261         // file has an unrecognized extension.
262         ConstArrayRef<const char *>                 compressedExtensions(c_compressedExtensions);
263         ConstArrayRef<const char *>::const_iterator ext;
264         for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext)
265         {
266             if (endsWith(value, *ext))
267             {
268                 return value.substr(0, value.length() - std::strlen(*ext));
269             }
270         }
271         return value;
272     }
273     const FileTypeRegistry &registry    = FileTypeRegistry::instance();
274     const FileTypeHandler  &typeHandler = registry.handlerForType(filetype, legacyType);
275     if (typeHandler.hasKnownExtension(value))
276     {
277         return value;
278     }
279     if (bCompleteToExisting)
280     {
281         std::string newValue = typeHandler.findFileWithExtension(value);
282         if (!newValue.empty())
283         {
284             return newValue;
285         }
286     }
287     return typeHandler.addExtension(value);
288 }
289
290 //! \}
291
292 }   // namespace
293
294 /********************************************************************
295  * FileNameOptionStorage
296  */
297
298 FileNameOptionStorage::FileNameOptionStorage(const FileNameOption  &settings,
299                                              FileNameOptionManager *manager)
300     : MyBase(settings), info_(this), manager_(manager),
301       filetype_(settings.filetype_), legacyType_(settings.legacyType_),
302       bRead_(settings.bRead_), bWrite_(settings.bWrite_),
303       bLibrary_(settings.bLibrary_)
304 {
305     GMX_RELEASE_ASSERT(!hasFlag(efOption_MultipleTimes),
306                        "allowMultiple() is not supported for file name options");
307     if (settings.defaultBasename_ != NULL)
308     {
309         std::string defaultValue =
310             completeFileName(settings.defaultBasename_, filetype_,
311                              legacyType_, false);
312         setDefaultValueIfSet(defaultValue);
313         if (isRequired() || settings.bLegacyOptionalBehavior_)
314         {
315             setDefaultValue(defaultValue);
316         }
317     }
318 }
319
320 std::string FileNameOptionStorage::typeString() const
321 {
322     const FileTypeRegistry       &registry    = FileTypeRegistry::instance();
323     const FileTypeHandler        &typeHandler = registry.handlerForType(filetype_, legacyType_);
324     const ExtensionList          &extensions  = typeHandler.extensions();
325     std::string                   result;
326     ExtensionList::const_iterator i;
327     int                           count = 0;
328     for (i = extensions.begin(); count < 2 && i != extensions.end(); ++i, ++count)
329     {
330         if (i != extensions.begin())
331         {
332             result.append("/");
333         }
334         result.append(*i);
335     }
336     if (i != extensions.end())
337     {
338         result.append("/...");
339     }
340     if (result.empty())
341     {
342         if (legacyType_ == efRND)
343         {
344             result = "dir";
345         }
346         else
347         {
348             result = "file";
349         }
350     }
351     return result;
352 }
353
354 std::string FileNameOptionStorage::formatExtraDescription() const
355 {
356     const FileTypeRegistry       &registry    = FileTypeRegistry::instance();
357     const FileTypeHandler        &typeHandler = registry.handlerForType(filetype_, legacyType_);
358     const ExtensionList          &extensions  = typeHandler.extensions();
359     std::string                   result;
360     if (extensions.size() > 2)
361     {
362         result.append(":");
363         ExtensionList::const_iterator i;
364         for (i = extensions.begin(); i != extensions.end(); ++i)
365         {
366             result.append(" ");
367             result.append((*i) + 1);
368         }
369     }
370     return result;
371 }
372
373 std::string FileNameOptionStorage::formatSingleValue(const std::string &value) const
374 {
375     return value;
376 }
377
378 void FileNameOptionStorage::convertValue(const std::string &value)
379 {
380     bool bInput = isInputFile() || isInputOutputFile();
381     addValue(completeFileName(value, filetype_, legacyType_, bInput));
382 }
383
384 void FileNameOptionStorage::processAll()
385 {
386     if (hasFlag(efOption_HasDefaultValue))
387     {
388         const bool              bInput      = isInputFile() || isInputOutputFile();
389         const FileTypeRegistry &registry    = FileTypeRegistry::instance();
390         const FileTypeHandler  &typeHandler = registry.handlerForType(filetype_, legacyType_);
391         const ExtensionList    &extensions  = typeHandler.extensions();
392         ValueList              &valueList   = values();
393         GMX_RELEASE_ASSERT(valueList.size() == 1,
394                            "There should be only one default value");
395         const bool              bGlobalDefault =
396             (manager_ != NULL && !manager_->defaultFileName().empty());
397         if (!valueList[0].empty() && (extensions.size() > 1 || bGlobalDefault))
398         {
399             std::string oldValue = valueList[0];
400             std::string newValue = stripSuffixIfPresent(oldValue, extensions[0]);
401             if (bGlobalDefault)
402             {
403                 newValue = manager_->defaultFileName();
404             }
405             newValue = completeFileName(newValue, filetype_, legacyType_, bInput);
406             if (newValue != oldValue)
407             {
408                 valueList[0] = newValue;
409                 refreshValues();
410             }
411         }
412     }
413 }
414
415 bool FileNameOptionStorage::isDirectoryOption() const
416 {
417     return legacyType_ == efRND;
418 }
419
420 ConstArrayRef<const char *> FileNameOptionStorage::extensions() const
421 {
422     const FileTypeRegistry &registry    = FileTypeRegistry::instance();
423     const FileTypeHandler  &typeHandler = registry.handlerForType(filetype_, legacyType_);
424     const ExtensionList    &extensions  = typeHandler.extensions();
425     return ConstArrayRef<const char *>(extensions.begin(), extensions.end());
426 }
427
428 /********************************************************************
429  * FileNameOptionInfo
430  */
431
432 FileNameOptionInfo::FileNameOptionInfo(FileNameOptionStorage *option)
433     : OptionInfo(option)
434 {
435 }
436
437 const FileNameOptionStorage &FileNameOptionInfo::option() const
438 {
439     return static_cast<const FileNameOptionStorage &>(OptionInfo::option());
440 }
441
442 bool FileNameOptionInfo::isInputFile() const
443 {
444     return option().isInputFile();
445 }
446
447 bool FileNameOptionInfo::isOutputFile() const
448 {
449     return option().isOutputFile();
450 }
451
452 bool FileNameOptionInfo::isInputOutputFile() const
453 {
454     return option().isInputOutputFile();
455 }
456
457 bool FileNameOptionInfo::isLibraryFile() const
458 {
459     return option().isLibraryFile();
460 }
461
462 bool FileNameOptionInfo::isDirectoryOption() const
463 {
464     return option().isDirectoryOption();
465 }
466
467 FileNameOptionInfo::ExtensionList FileNameOptionInfo::extensions() const
468 {
469     return option().extensions();
470 }
471
472 /********************************************************************
473  * FileNameOption
474  */
475
476 AbstractOptionStoragePointer
477 FileNameOption::createStorage(const OptionManagerContainer &managers) const
478 {
479     return AbstractOptionStoragePointer(
480             new FileNameOptionStorage(*this, managers.get<FileNameOptionManager>()));
481 }
482
483 } // namespace gmx