Make FileNameOption behave more like old filenm parser
[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 <string>
46 #include <vector>
47
48 #include "gromacs/fileio/filenm.h"
49
50 #include "gromacs/utility/file.h"
51 #include "gromacs/utility/stringutil.h"
52
53 namespace gmx
54 {
55
56 namespace
57 {
58
59 class FileTypeRegistry;
60
61 //! \addtogroup module_options
62 //! \{
63
64 //! Shorthand for a list of file extensions.
65 typedef std::vector<const char *> ExtensionList;
66
67 /********************************************************************
68  * FileTypeHandler
69  */
70
71 /*! \internal \brief
72  * Handles a single file type known to FileNameOptionStorage.
73  */
74 class FileTypeHandler
75 {
76     public:
77         //! Returns the list of extensions for this file type.
78         const ExtensionList &extensions() const { return extensions_; }
79
80         //! Returns whether \p filename has a valid extension for this type.
81         bool hasKnownExtension(const std::string &filename) const;
82         //! Adds a default extension for this type to \p filename.
83         std::string addExtension(const std::string &filename) const;
84         /*! \brief
85          * Adds an extension to \p filename if it results in an existing file.
86          *
87          * Tries to add each extension for this file type to \p filename and
88          * checks whether this results in an existing file.
89          * The first match is returned.
90          * Returns an empty string if no existing file is found.
91          */
92         std::string findFileWithExtension(const std::string &filename) const;
93
94     private:
95         //! Possible extensions for this file type.
96         ExtensionList extensions_;
97
98         /*! \brief
99          * Needed for initialization; all initialization is handled by
100          * FileTypeRegistry.
101          */
102         friend class FileTypeRegistry;
103 };
104
105 bool
106 FileTypeHandler::hasKnownExtension(const std::string &filename) const
107 {
108     for (size_t i = 0; i < extensions_.size(); ++i)
109     {
110         if (endsWith(filename, extensions_[i]))
111         {
112             return true;
113         }
114     }
115     return false;
116 }
117
118 std::string
119 FileTypeHandler::addExtension(const std::string &filename) const
120 {
121     if (extensions_.empty())
122     {
123         return filename;
124     }
125     return filename + extensions_[0];
126 }
127
128 std::string
129 FileTypeHandler::findFileWithExtension(const std::string &filename) const
130 {
131     for (size_t i = 0; i < extensions_.size(); ++i)
132     {
133         std::string testFilename(filename + extensions_[i]);
134         if (File::exists(testFilename))
135         {
136             return testFilename;
137         }
138     }
139     return std::string();
140 }
141
142 /********************************************************************
143  * FileTypeRegistry
144  */
145
146 /*! \internal \brief
147  * Singleton for managing static file type info for FileNameOptionStorage.
148  */
149 class FileTypeRegistry
150 {
151     public:
152         //! Returns a singleton instance of this class.
153         static const FileTypeRegistry &instance();
154         //! Returns a handler for a single file type.
155         const FileTypeHandler &handlerForType(OptionFileType type) const;
156
157     private:
158         //! Initializes the file type registry.
159         FileTypeRegistry();
160
161         //! Registers a file type that corresponds to a ftp in filenm.h.
162         void registerType(OptionFileType type, int ftp);
163         //! Registers a file type with a single extension.
164         void registerType(OptionFileType type, const char *extension);
165
166         std::vector<FileTypeHandler> filetypes_;
167 };
168
169 // static
170 const FileTypeRegistry &
171 FileTypeRegistry::instance()
172 {
173     static FileTypeRegistry singleton;
174     return singleton;
175 }
176
177 const FileTypeHandler &
178 FileTypeRegistry::handlerForType(OptionFileType type) const
179 {
180     GMX_RELEASE_ASSERT(type >= 0 && static_cast<size_t>(type) < filetypes_.size(),
181                        "Invalid file type");
182     return filetypes_[type];
183 }
184
185 FileTypeRegistry::FileTypeRegistry()
186 {
187     filetypes_.resize(eftOptionFileType_NR);
188     registerType(eftTopology,    efTPS);
189     registerType(eftTrajectory,  efTRX);
190     registerType(eftPDB,         efPDB);
191     registerType(eftIndex,       efNDX);
192     registerType(eftPlot,        efXVG);
193     registerType(eftGenericData, efDAT);
194 }
195
196 void FileTypeRegistry::registerType(OptionFileType type, int ftp)
197 {
198     GMX_RELEASE_ASSERT(type >= 0 && static_cast<size_t>(type) < filetypes_.size(),
199                        "Invalid file type");
200     const int genericTypeCount = ftp2generic_count(ftp);
201     if (genericTypeCount > 0)
202     {
203         const int *const genericTypes = ftp2generic_list(ftp);
204         filetypes_[type].extensions_.clear();
205         filetypes_[type].extensions_.reserve(genericTypeCount);
206         for (int i = 0; i < genericTypeCount; ++i)
207         {
208             filetypes_[type].extensions_.push_back(ftp2ext_with_dot(genericTypes[i]));
209         }
210     }
211     else
212     {
213         registerType(type, ftp2ext_with_dot(ftp));
214     }
215 }
216
217 void FileTypeRegistry::registerType(OptionFileType type,
218                                     const char    *extension)
219 {
220     GMX_RELEASE_ASSERT(type >= 0 && static_cast<size_t>(type) < filetypes_.size(),
221                        "Invalid file type");
222     filetypes_[type].extensions_.assign(1, extension);
223 }
224
225 /*! \brief
226  * Helper method to complete a file name provided to a file name option.
227  *
228  * \param[in] value     Value provided to the file name option.
229  * \param[in] filetype  File type for the option.
230  * \param[in] bCompleteToExisting
231  *      Whether to check existing files when completing the extension.
232  * \returns   \p value with possible extension added.
233  */
234 std::string completeFileName(const std::string &value, OptionFileType filetype,
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         return value;
243     }
244     const FileTypeRegistry &registry    = FileTypeRegistry::instance();
245     const FileTypeHandler  &typeHandler = registry.handlerForType(filetype);
246     if (typeHandler.hasKnownExtension(value))
247     {
248         return value;
249     }
250     if (bCompleteToExisting)
251     {
252         std::string newValue = typeHandler.findFileWithExtension(value);
253         if (!newValue.empty())
254         {
255             return newValue;
256         }
257     }
258     return typeHandler.addExtension(value);
259 }
260
261 //! \}
262
263 }   // namespace
264
265 /********************************************************************
266  * FileNameOptionStorage
267  */
268
269 FileNameOptionStorage::FileNameOptionStorage(const FileNameOption &settings)
270     : MyBase(settings), info_(this), filetype_(settings.filetype_),
271       bRead_(settings.bRead_), bWrite_(settings.bWrite_),
272       bLibrary_(settings.bLibrary_)
273 {
274     if (settings.defaultBasename_ != NULL)
275     {
276         std::string defaultValue =
277             completeFileName(settings.defaultBasename_, filetype_, false);
278         setDefaultValueIfSet(defaultValue);
279         if (isRequired())
280         {
281             setDefaultValue(defaultValue);
282         }
283     }
284 }
285
286 std::string FileNameOptionStorage::typeString() const
287 {
288     const FileTypeRegistry       &registry    = FileTypeRegistry::instance();
289     const FileTypeHandler        &typeHandler = registry.handlerForType(filetype_);
290     const ExtensionList          &extensions  = typeHandler.extensions();
291     std::string                   result;
292     ExtensionList::const_iterator i;
293     int                           count = 0;
294     for (i = extensions.begin(); count < 2 && i != extensions.end(); ++i, ++count)
295     {
296         if (i != extensions.begin())
297         {
298             result.append("/");
299         }
300         result.append(*i);
301     }
302     if (i != extensions.end())
303     {
304         result.append("/...");
305     }
306     if (result.empty())
307     {
308         result = "file";
309     }
310     return result;
311 }
312
313 std::string FileNameOptionStorage::formatExtraDescription() const
314 {
315     const FileTypeRegistry       &registry    = FileTypeRegistry::instance();
316     const FileTypeHandler        &typeHandler = registry.handlerForType(filetype_);
317     const ExtensionList          &extensions  = typeHandler.extensions();
318     std::string                   result;
319     if (extensions.size() > 2)
320     {
321         result.append(":");
322         ExtensionList::const_iterator i;
323         for (i = extensions.begin(); i != extensions.end(); ++i)
324         {
325             result.append(" ");
326             result.append((*i) + 1);
327         }
328     }
329     return result;
330 }
331
332 std::string FileNameOptionStorage::formatSingleValue(const std::string &value) const
333 {
334     return value;
335 }
336
337 void FileNameOptionStorage::convertValue(const std::string &value)
338 {
339     bool bInput = isInputFile() || isInputOutputFile();
340     addValue(completeFileName(value, filetype_, bInput));
341 }
342
343 /********************************************************************
344  * FileNameOptionInfo
345  */
346
347 FileNameOptionInfo::FileNameOptionInfo(FileNameOptionStorage *option)
348     : OptionInfo(option)
349 {
350 }
351
352 const FileNameOptionStorage &FileNameOptionInfo::option() const
353 {
354     return static_cast<const FileNameOptionStorage &>(OptionInfo::option());
355 }
356
357 bool FileNameOptionInfo::isInputFile() const
358 {
359     return option().isInputFile();
360 }
361
362 bool FileNameOptionInfo::isOutputFile() const
363 {
364     return option().isOutputFile();
365 }
366
367 bool FileNameOptionInfo::isInputOutputFile() const
368 {
369     return option().isInputOutputFile();
370 }
371
372 bool FileNameOptionInfo::isLibraryFile() const
373 {
374     return option().isLibraryFile();
375 }
376
377 /********************************************************************
378  * FileNameOption
379  */
380
381 AbstractOptionStoragePointer FileNameOption::createStorage() const
382 {
383     return AbstractOptionStoragePointer(new FileNameOptionStorage(*this));
384 }
385
386 } // namespace gmx