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 265 : getPath(std::string path, const std::optional<std::string> & base)
23 : {
24 265 : const auto & data_paths = Registry::getRegistry().getDataFilePaths();
25 :
26 : // Search for "<name>:" prefix which is a data name to limit the search to
27 265 : std::optional<std::string> data_name;
28 265 : std::smatch match;
29 265 : if (std::regex_search(path, match, std::regex("(?:(\\w+):)?(.*)")))
30 : {
31 265 : if (match[1].matched)
32 : {
33 6 : data_name = match[1];
34 6 : if (!data_paths.count(*data_name))
35 1 : mooseError("Data from '", *data_name, "' is not registered to be searched");
36 : }
37 264 : path = match[2];
38 : }
39 : else
40 0 : mooseError("Failed to parse path '", path, "'");
41 :
42 264 : const std::filesystem::path value_path = std::filesystem::path(path);
43 :
44 : // File is absolute, no need to search
45 264 : if (std::filesystem::path(path).is_absolute())
46 : {
47 26 : if (data_name)
48 1 : mooseError("Should not specify an absolute path along with a data name to search (requested "
49 : "to search in '",
50 : *data_name,
51 : "')");
52 25 : if (MooseUtils::checkFileReadable(path, false, false, false))
53 24 : return {MooseUtils::canonicalPath(path), Context::ABSOLUTE};
54 1 : mooseError("The absolute path '", path, "' does not exist or is not readable.");
55 : }
56 :
57 : // Keep track of what was was searched for error context
58 238 : std::map<std::string, std::string> not_found;
59 :
60 : // Relative to the base, if provided
61 238 : if (base)
62 : {
63 186 : const auto relative_to_base = MooseUtils::pathjoin(*base, path);
64 186 : if (MooseUtils::checkFileReadable(relative_to_base, false, false, false))
65 151 : return {MooseUtils::canonicalPath(relative_to_base), Context::RELATIVE};
66 35 : not_found.emplace("working directory", MooseUtils::canonicalPath(*base));
67 186 : }
68 :
69 : // See if we should skip searching data
70 87 : std::optional<std::string> skip_data_reason;
71 : // Path starts with ./ so don't search data
72 87 : if (path.size() > 1 && path.substr(0, 2) == "./")
73 : {
74 1 : skip_data_reason = "begins with './'";
75 : }
76 : else
77 : {
78 : // Path resolves outside of . so don't search data
79 86 : const std::string proximate = std::filesystem::proximate(path).c_str();
80 86 : if (proximate.size() > 1 && proximate.substr(0, 2) == "..")
81 : {
82 1 : skip_data_reason = "resolves behind '.'";
83 : }
84 86 : }
85 :
86 : // Search data if we don't have a reason not to
87 87 : std::map<std::string, std::string> found;
88 87 : if (!skip_data_reason)
89 179 : for (const auto & [name, data_path] : data_paths)
90 : {
91 : // Explicit search, name doesn't match requested name
92 94 : if (data_name && name != *data_name) // explicit search
93 4 : continue;
94 90 : const auto file_path = MooseUtils::pathjoin(data_path, path);
95 90 : if (MooseUtils::checkFileReadable(file_path, false, false, false))
96 76 : found.emplace(name, MooseUtils::canonicalPath(file_path));
97 : else
98 14 : not_found.emplace(name + " data", data_path);
99 90 : }
100 :
101 : // Found exactly one
102 87 : if (found.size() == 1)
103 : {
104 74 : const auto & [name, data_path] = *found.begin();
105 74 : return {MooseUtils::canonicalPath(data_path), Context::DATA, name};
106 : }
107 :
108 13 : std::stringstream oss;
109 : // Found multiple
110 13 : if (found.size() > 1)
111 : {
112 1 : oss << "Multiple files were found when searching for the data file '" << path << "':\n\n";
113 3 : for (const auto & [name, data_path] : found)
114 2 : oss << " " << name << ": " << data_path << "\n";
115 1 : const auto & first_name = found.begin()->first;
116 : oss << "\nYou can resolve this ambiguity by appending a prefix with the desired data name, for "
117 : "example:\n\n "
118 1 : << first_name << ":" << path;
119 : }
120 : // Found none
121 : else
122 : {
123 12 : oss << "Unable to find the data file '" << path << "' anywhere.\n";
124 12 : if (not_found.size())
125 : {
126 10 : oss << "\nPaths searched:\n";
127 27 : for (const auto & [name, data_path] : not_found)
128 17 : oss << " " << name << ": " << data_path << "\n";
129 : }
130 12 : if (skip_data_reason)
131 2 : oss << "\nData path(s) were not searched because search path " << *skip_data_reason << ".\n";
132 : }
133 :
134 26 : mooseError(oss.str());
135 348 : }
136 :
137 : Moose::DataFileUtils::Path
138 2 : getPathExplicit(const std::string & data_name,
139 : const std::string & path,
140 : const std::optional<std::string> & base)
141 : {
142 4 : return getPath(data_name + ":" + path, base);
143 : }
144 : }
|