Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #include "DataFileUtils.h"
11 : #include "Moose.h"
12 : #include "MooseError.h"
13 : #include "MooseUtils.h"
14 : #include "Registry.h"
15 :
16 : #include <filesystem>
17 : #include <regex>
18 :
19 : namespace Moose::DataFileUtils
20 : {
21 : Moose::DataFileUtils::Path
22 21088 : getPath(std::string path, const GetPathOptions & options)
23 : {
24 21088 : const auto & data_paths = Registry::getRegistry().getDataFilePaths();
25 :
26 : // Search for "<name>:" prefix which is an explicit data name to search
27 21088 : std::optional<std::pair<std::string, std::string>> explicit_data;
28 21088 : if (std::smatch match; std::regex_search(path, match, std::regex("(?:([a-z0-9_]+):)?(.*)")))
29 : {
30 : // Has an explicit data name
31 21088 : if (match[1].matched)
32 : {
33 48 : if (const auto it = data_paths.find(match[1].str()); it != data_paths.end())
34 42 : explicit_data = *it;
35 : else
36 6 : mooseError("Data from '", match[1], "' is not registered to be searched");
37 : }
38 21082 : path = match[2];
39 : }
40 : else
41 21088 : mooseError("Failed to parse path '", path, "'");
42 :
43 21082 : const std::filesystem::path value_path = std::filesystem::path(path);
44 :
45 : // Helper for erroring if the data name is explicitly set
46 208 : const auto if_data_name_error = [&explicit_data](const auto & message)
47 : {
48 208 : if (explicit_data)
49 18 : mooseError("Cannot use ",
50 : message,
51 : " along with a data name to search (requested "
52 : "to search in '",
53 18 : explicit_data->first,
54 : "')");
55 21272 : };
56 :
57 : // File is absolute, no need to search
58 21082 : if (std::filesystem::path(path).is_absolute())
59 : {
60 : // Shouldn't provide an absolute path and an explicit data name
61 192 : if_data_name_error("an absolute path");
62 :
63 : // File exists, we're good to go
64 186 : if (MooseUtils::checkFileReadable(path, false, false, false))
65 22 : return {MooseUtils::canonicalPath(path), Context::ABSOLUTE};
66 :
67 : // If absolute and graceful, return the bad absolute path
68 164 : if (options.graceful)
69 160 : return {MooseUtils::canonicalPath(path), Context::ABSOLUTE_NOT_FOUND};
70 :
71 : // If absolute and not graceful, nothing else to do
72 4 : mooseError("The absolute path '", path, "' does not exist or is not readable.");
73 : }
74 :
75 : // Keep track of what was was searched for error context
76 20890 : std::map<std::string, std::string> not_found;
77 :
78 : // Because no explicit data name is set, we always want to prefer
79 : // checking relative paths first before searching the data
80 20890 : if (!explicit_data)
81 : {
82 20903 : const std::string base = options.base ? *options.base : std::filesystem::current_path().c_str();
83 20854 : const auto relative_to_base = MooseUtils::pathjoin(base, path);
84 :
85 : // Relative path exists
86 20854 : if (MooseUtils::checkFileReadable(relative_to_base, false, false, false))
87 19425 : return {MooseUtils::canonicalPath(relative_to_base), Context::RELATIVE};
88 :
89 : // Without an explicit data name set, if we're set to not search all data
90 : // there's nothing else to check here so we'll avoid the error at the bottom
91 : // and exit immediately
92 1429 : if (!options.search_all_data && options.graceful)
93 1346 : return {MooseUtils::canonicalPath(relative_to_base), Context::RELATIVE_NOT_FOUND};
94 :
95 : // Mark that we searched the working directory
96 83 : not_found.emplace("working directory", MooseUtils::canonicalPath(base));
97 41625 : }
98 :
99 : // See if we should skip searching data
100 119 : std::optional<std::string> skip_data_reason;
101 : // Path starts with ./ so don't search data
102 119 : if (path.size() > 1 && path.substr(0, 2) == "./")
103 : {
104 : // Shouldn't provide a relative path and an explicit data name
105 8 : if_data_name_error("a path that starts with './'");
106 :
107 : // Mark that we're not checking data because it is a relative path
108 2 : skip_data_reason = "begins with './'";
109 : }
110 : // Path resolves outside of . so don't search data
111 111 : else if (const std::string proximate = std::filesystem::proximate(path).c_str();
112 111 : (proximate.size() > 1 && proximate.substr(0, 2) == ".."))
113 : {
114 : // Shouldn't provide a relative path and an explicit data name
115 8 : if_data_name_error("a relative path");
116 :
117 : // Mark that we're not checking data because it's out of the cwd
118 2 : skip_data_reason = "resolves behind '.'";
119 111 : }
120 :
121 : // Search the data if we found a reason not to and it's set
122 : // to search data or we have an explicit data name to search
123 107 : std::map<std::string, std::string> found;
124 107 : if (!skip_data_reason && (options.search_all_data || explicit_data))
125 : {
126 182 : const auto check_data_path = [&found, ¬_found, &path](const auto & entry)
127 : {
128 182 : const auto & [name, data_path] = entry;
129 182 : const auto file_path = MooseUtils::pathjoin(data_path, path);
130 182 : if (MooseUtils::checkFileReadable(file_path, false, false, false))
131 89 : found.emplace(name, MooseUtils::canonicalPath(file_path));
132 : else
133 93 : not_found.emplace(name + " data", data_path);
134 285 : };
135 :
136 : // Explicit search, just searching one
137 103 : if (explicit_data)
138 : {
139 24 : check_data_path(*explicit_data);
140 24 : if (found.empty())
141 6 : mooseError("The path '", path, "' was not found in data from '", explicit_data->first, "'");
142 : }
143 : // Search all data paths
144 : else
145 : {
146 237 : for (const auto & entry : data_paths)
147 158 : check_data_path(entry);
148 : }
149 : }
150 :
151 : // Found exactly one
152 101 : if (found.size() == 1)
153 : {
154 85 : const auto & [name, data_path] = *found.begin();
155 85 : return {MooseUtils::canonicalPath(data_path), Context::DATA, name};
156 : }
157 :
158 16 : std::stringstream oss;
159 : // Found multiple
160 16 : if (found.size() > 1)
161 : {
162 2 : oss << "Multiple files were found when searching for the data file '" << path << "':\n\n";
163 6 : for (const auto & [name, data_path] : found)
164 4 : oss << " " << name << ": " << data_path << "\n";
165 2 : const auto & first_name = found.begin()->first;
166 : oss << "\nYou can resolve this ambiguity by appending a prefix with the desired data name, for "
167 : "example:\n\n "
168 2 : << first_name << ":" << path;
169 : }
170 : // Found none
171 : else
172 : {
173 14 : oss << "Unable to find the data file '" << path << "'.\n";
174 14 : if (not_found.size())
175 : {
176 14 : oss << "\nPaths searched:\n";
177 48 : for (const auto & [name, data_path] : not_found)
178 34 : oss << " " << name << ": " << data_path << "\n";
179 : }
180 14 : if (skip_data_reason)
181 4 : oss << "\nData path(s) were not searched because search path " << *skip_data_reason << ".\n";
182 : }
183 :
184 32 : mooseError(oss.str());
185 21238 : }
186 :
187 : Moose::DataFileUtils::Path
188 4 : getPathExplicit(const std::string & data_name,
189 : const std::string & path,
190 : const std::optional<std::string> & base)
191 : {
192 4 : GetPathOptions options;
193 4 : options.base = base;
194 8 : return getPath(data_name + ":" + path, options);
195 4 : }
196 : }
|