Apply clang-format to source tree
[alexxy/gromacs.git] / src / gromacs / utility / datafilefinder.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2014,2015,2016,2017,2018,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 gmx::DataFileFinder.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_utility
41  */
42 #include "gmxpre.h"
43
44 #include "datafilefinder.h"
45
46 #include <cstdlib>
47
48 #include <set>
49 #include <string>
50 #include <vector>
51
52 #include "buildinfo.h"
53 #include "gromacs/utility/directoryenumerator.h"
54 #include "gromacs/utility/exceptions.h"
55 #include "gromacs/utility/filestream.h"
56 #include "gromacs/utility/path.h"
57 #include "gromacs/utility/programcontext.h"
58 #include "gromacs/utility/stringutil.h"
59
60 namespace gmx
61 {
62
63 /********************************************************************
64  * DataFileFinder::Impl
65  */
66
67 class DataFileFinder::Impl
68 {
69 public:
70     static std::string getDefaultPath();
71
72     Impl() : envName_(nullptr), bEnvIsSet_(false) {}
73
74     const char*              envName_;
75     bool                     bEnvIsSet_;
76     std::vector<std::string> searchPath_;
77 };
78
79 std::string DataFileFinder::Impl::getDefaultPath()
80 {
81     const InstallationPrefixInfo installPrefix = getProgramContext().installationPrefix();
82     if (!isNullOrEmpty(installPrefix.path))
83     {
84         const char* const dataPath = installPrefix.bSourceLayout ? "share" : GMX_INSTALL_GMXDATADIR;
85         return Path::join(installPrefix.path, dataPath, "top");
86     }
87     return std::string();
88 }
89
90 /********************************************************************
91  * DataFileFinder
92  */
93
94 DataFileFinder::DataFileFinder() : impl_(nullptr) {}
95
96 DataFileFinder::~DataFileFinder() {}
97
98 void DataFileFinder::setSearchPathFromEnv(const char* envVarName)
99 {
100     if (!impl_)
101     {
102         impl_.reset(new Impl());
103     }
104     impl_->envName_       = envVarName;
105     const char* const lib = getenv(envVarName);
106     if (!isNullOrEmpty(lib))
107     {
108         std::vector<std::string>& path        = impl_->searchPath_; // convenience
109         const std::string         defaultPath = impl_->getDefaultPath();
110         std::vector<std::string>  tmpPath;
111         Path::splitPathEnvironment(lib, &tmpPath);
112         std::set<std::string> pathsSeen;
113         pathsSeen.insert(defaultPath);
114         for (auto& d : tmpPath)
115         {
116             if (!pathsSeen.count(d))
117             {
118                 path.push_back(d);
119                 pathsSeen.insert(d);
120             }
121         }
122         impl_->bEnvIsSet_ = true;
123     }
124 }
125
126 FilePtr DataFileFinder::openFile(const DataFileOptions& options) const
127 {
128     // TODO: There is a small race here, since there is some time between
129     // the exists() calls and actually opening the file.  It would be better
130     // to leave the file open after a successful exists() if the desire is to
131     // actually open the file.
132     std::string filename = findFile(options);
133     if (filename.empty())
134     {
135         return nullptr;
136     }
137 #if 0
138     if (debug)
139     {
140         fprintf(debug, "Opening library file %s\n", fn);
141     }
142 #endif
143     return TextInputFile::openRawHandle(filename);
144 }
145
146 std::string DataFileFinder::findFile(const DataFileOptions& options) const
147 {
148     if (options.bCurrentDir_ && Path::exists(options.filename_))
149     {
150         return options.filename_;
151     }
152     if (impl_ != nullptr)
153     {
154         std::vector<std::string>::const_iterator i;
155         for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
156         {
157             // TODO: Deal with an empty search path entry more reasonably.
158             std::string testPath = Path::join(*i, options.filename_);
159             // TODO: Consider skipping directories.
160             if (Path::exists(testPath))
161             {
162                 return testPath;
163             }
164         }
165     }
166     const std::string& defaultPath = Impl::getDefaultPath();
167     if (!defaultPath.empty())
168     {
169         std::string testPath = Path::join(defaultPath, options.filename_);
170         if (Path::exists(testPath))
171         {
172             return testPath;
173         }
174     }
175     if (options.bThrow_)
176     {
177         const char* const envName   = (impl_ != nullptr ? impl_->envName_ : nullptr);
178         const bool        bEnvIsSet = (impl_ != nullptr ? impl_->bEnvIsSet_ : false);
179         std::string       message(formatString("Library file '%s' not found", options.filename_));
180         if (options.bCurrentDir_)
181         {
182             message.append(" in current dir nor");
183         }
184         if (bEnvIsSet)
185         {
186             message.append(formatString(" in your %s path nor", envName));
187         }
188         message.append(" in the default directories.\nThe following paths were searched:");
189         if (options.bCurrentDir_)
190         {
191             message.append("\n  ");
192             message.append(Path::getWorkingDirectory());
193             message.append(" (current dir)");
194         }
195         if (impl_ != nullptr)
196         {
197             std::vector<std::string>::const_iterator i;
198             for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
199             {
200                 message.append("\n  ");
201                 message.append(*i);
202             }
203         }
204         if (!defaultPath.empty())
205         {
206             message.append("\n  ");
207             message.append(defaultPath);
208             message.append(" (default)");
209         }
210         if (!bEnvIsSet && envName != nullptr)
211         {
212             message.append(
213                     formatString("\nYou can set additional directories to search "
214                                  "with the %s path variable.",
215                                  envName));
216         }
217         GMX_THROW(FileIOError(message));
218     }
219     return std::string();
220 }
221
222 std::vector<DataFileInfo> DataFileFinder::enumerateFiles(const DataFileOptions& options) const
223 {
224     // TODO: Consider if not being able to list one of the directories should
225     // really be a fatal error. Or alternatively, check somewhere else that
226     // paths in GMXLIB are valid.
227     std::vector<DataFileInfo>                result;
228     std::vector<std::string>::const_iterator i;
229     if (options.bCurrentDir_)
230     {
231         std::vector<std::string> files =
232                 DirectoryEnumerator::enumerateFilesWithExtension(".", options.filename_, false);
233         for (i = files.begin(); i != files.end(); ++i)
234         {
235             result.emplace_back(".", *i, false);
236         }
237     }
238     if (impl_ != nullptr)
239     {
240         std::vector<std::string>::const_iterator j;
241         for (j = impl_->searchPath_.begin(); j != impl_->searchPath_.end(); ++j)
242         {
243             std::vector<std::string> files = DirectoryEnumerator::enumerateFilesWithExtension(
244                     j->c_str(), options.filename_, false);
245             for (i = files.begin(); i != files.end(); ++i)
246             {
247                 result.emplace_back(*j, *i, false);
248             }
249         }
250     }
251     const std::string& defaultPath = Impl::getDefaultPath();
252     if (!defaultPath.empty())
253     {
254         std::vector<std::string> files = DirectoryEnumerator::enumerateFilesWithExtension(
255                 defaultPath.c_str(), options.filename_, false);
256         for (i = files.begin(); i != files.end(); ++i)
257         {
258             result.emplace_back(defaultPath, *i, true);
259         }
260     }
261     if (result.empty() && options.bThrow_)
262     {
263         // TODO: Print the search path as is done in findFile().
264         std::string message(
265                 formatString("Could not find any files ending on '%s' in the "
266                              "current directory or the GROMACS library search path",
267                              options.filename_));
268         GMX_THROW(FileIOError(message));
269     }
270     return result;
271 }
272
273 } // namespace gmx