Skip to content

Commit

Permalink
Add ability to resolve ambiguity
Browse files Browse the repository at this point in the history
  • Loading branch information
loganharbour committed Nov 28, 2024
1 parent afed0e3 commit a121ded
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 23 deletions.
21 changes: 16 additions & 5 deletions framework/include/utils/DataFileUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,23 @@ struct Path
/**
* Get the data path for a given path, searching the registered data
*
* @param path - The path; can be prefixed with <name>: to search only data from <name>
* @param base - The base by which to search for the file relative to (optional)
*/
Path getPath(std::string path,
const std::optional<std::string> & base = std::optional<std::string>());

/**
* Get the data path for a given path, searching the registered data given an explicit
* data search path.
*
* This exists primarily so that you don't need to call getPath("moose:file").
*
* @param data_name - The registered data name
* @param path - The path
* @param base - The base by which to search for the file relative to (optional)
* @param data_name - The specific registered data name to seach for (optional,
* otherwise search all)
*/
Path getPath(const std::string & path,
const std::optional<std::string> & base = std::optional<std::string>(),
const std::optional<std::string> & data_name = std::optional<std::string>());
Path getPathExplicit(const std::string & data_name,
const std::string & path,
const std::optional<std::string> & base = std::optional<std::string>());
}
3 changes: 3 additions & 0 deletions framework/src/base/Registry.C
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ Registry::addKnownLabel(const std::string & label)
void
Registry::addDataFilePath(const std::string & name, const std::string & in_tree_path)
{
if (!std::regex_search(name, std::regex("\\w+")))
mooseError("Unallowed characters in '", name, "'");

// Enforce that the folder is called "data", because we rely on the installed path
// to be within PREFIX/share/<name>/data (see determineDataFilePath())
const auto folder = MooseUtils::shortName(in_tree_path);
Expand Down
47 changes: 39 additions & 8 deletions framework/src/utils/DataFileUtils.C
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,41 @@
#include "Registry.h"

#include <filesystem>
#include <regex>

namespace Moose::DataFileUtils
{
Moose::DataFileUtils::Path
getPath(const std::string & path,
const std::optional<std::string> & base,
const std::optional<std::string> & data_name)
getPath(std::string path, const std::optional<std::string> & base)
{
const auto & data_paths = Registry::getRegistry().getDataFilePaths();

// Search for "<name>:" prefix which is a data name to limit the search to
std::optional<std::string> data_name;
std::smatch match;
if (std::regex_search(path, match, std::regex("(?:(\\w+):)?(.*)")))
{
if (match[1].matched)
{
data_name = match[1];
if (!data_paths.count(*data_name))
mooseError("Data from '", *data_name, "' is not registered to be searched");
}
path = match[2];
}
else
mooseError("Failed to parse path '", path, "'");

const std::filesystem::path value_path = std::filesystem::path(path);

// File is absolute, no need to search
if (std::filesystem::path(path).is_absolute())
{
if (data_name)
mooseError("Should not specify an absolute path along with a data name to search (requested "
"to search in '",
*data_name,
"')");
if (MooseUtils::checkFileReadable(path, false, false, false))
return {MooseUtils::absolutePath(path), Context::ABSOLUTE};
mooseError("The absolute path '", path, "' does not exist or is not readable.");
Expand All @@ -45,9 +67,9 @@ getPath(const std::string & path,
}

// Search each registered data path for the relative path
for (const auto & [name, data_path] : Registry::getRegistry().getDataFilePaths())
for (const auto & [name, data_path] : data_paths)
{
if (data_name && name != *data_name)
if (data_name && name != *data_name) // explicit search
continue;
const auto file_path = MooseUtils::pathjoin(data_path, path);
if (MooseUtils::checkFileReadable(file_path, false, false, false))
Expand All @@ -65,14 +87,15 @@ getPath(const std::string & path,

std::stringstream oss;
// Found multiple
// TODO: Eventually, we could support a special syntax here that will allow a user
// to specify where to get data from to resolve ambiguity. For example, something like
// solid_mechancs:path/to/data
if (found.size() > 1)
{
oss << "Multiple files were found when searching for the data file '" << path << "':\n\n";
for (const auto & [name, data_path] : found)
oss << " " << name << ": " << data_path << "\n";
const auto & first_name = found.begin()->first;
oss << "\nYou can resolve this ambiguity by appending a prefix with the desired data name, for "
"example:\n\n "
<< first_name << ":" << path;
}
// Found none
else
Expand All @@ -88,4 +111,12 @@ getPath(const std::string & path,

mooseError(oss.str());
}

Moose::DataFileUtils::Path
getPathExplicit(const std::string & data_name,
const std::string & path,
const std::optional<std::string> & base)
{
return getPath(data_name + ":" + path, base);
}
}
66 changes: 56 additions & 10 deletions unit/src/DataFileUtilsTest.C
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,22 @@ DataFileUtilsTest::testAbsolute(const Moose::DataFileUtils::Path & path,
EXPECT_FALSE(path.data_name);
}

TEST_F(DataFileUtilsTest, getDataUnique)
TEST_F(DataFileUtilsTest, getPathUnique)
{
// Files that are unique to each data path
testData(Moose::DataFileUtils::getPath("testdata0"), 0, "testdata0");
testData(Moose::DataFileUtils::getPath("testdata1"), 1, "testdata1");
}

TEST_F(DataFileUtilsTest, getDataNotUnique)
TEST_F(DataFileUtilsTest, getPathExplicit)
{
testData(Moose::DataFileUtils::getPath("testdata", {}, _names[0]), 0, "testdata");
testData(Moose::DataFileUtils::getPath("testdata", {}, _names[1]), 1, "testdata");
testData(Moose::DataFileUtils::getPath(_names[0] + ":testdata"), 0, "testdata");
testData(Moose::DataFileUtils::getPath(_names[1] + ":testdata"), 1, "testdata");
testData(Moose::DataFileUtils::getPathExplicit(_names[0], "testdata"), 0, "testdata");
testData(Moose::DataFileUtils::getPathExplicit(_names[1], "testdata"), 1, "testdata");
}

TEST_F(DataFileUtilsTest, getDataMissing)
TEST_F(DataFileUtilsTest, getPathMissing)
{
const std::string file = "missingdata";
const std::string cwd = std::filesystem::current_path().c_str();
Expand Down Expand Up @@ -99,7 +101,7 @@ TEST_F(DataFileUtilsTest, getDataMissing)
std::exception);
}

TEST_F(DataFileUtilsTest, getDataAmbiguous)
TEST_F(DataFileUtilsTest, getPathAmbiguous)
{
const std::string file = "testdata";
// testdata exists in both registered data
Expand All @@ -114,14 +116,17 @@ TEST_F(DataFileUtilsTest, getDataAmbiguous)
EXPECT_EQ(std::string(e.what()),
"Multiple files were found when searching for the data file 'testdata':\n\n " +
_names[0] + ": " + _abs_paths[0] + "/" + file + "\n " + _names[1] + ": " +
_abs_paths[1] + "/" + file + "\n");
_abs_paths[1] + "/" + file +
"\n\nYou can resolve this ambiguity by appending a prefix with the desired "
"data name, for example:\n\n " +
_names[0] + ":" + file);
throw;
}
},
std::exception);
}

TEST_F(DataFileUtilsTest, getDataRelative)
TEST_F(DataFileUtilsTest, getPathRelative)
{
const std::string relative_path = "files/data_file_tests/unregistered_data";

Expand All @@ -148,7 +153,7 @@ TEST_F(DataFileUtilsTest, getDataRelative)
std::exception);
}

TEST_F(DataFileUtilsTest, getDataAbsolute)
TEST_F(DataFileUtilsTest, getPathAbsolute)
{
const auto absolute_path = MooseUtils::absolutePath("files/data_file_tests/unregistered_data");

Expand All @@ -157,7 +162,28 @@ TEST_F(DataFileUtilsTest, getDataAbsolute)
testAbsolute(Moose::DataFileUtils::getPath(absolute_path, _cwd), absolute_path);
}

TEST_F(DataFileUtilsTest, getDataAbsoluteMissing)
TEST_F(DataFileUtilsTest, getPathAbsoluteExplicit)
{
// Warning when specifying a data name with an absolute path
EXPECT_THROW(
{
try
{
Moose::DataFileUtils::getPath(_names[0] + ":" + "/absolute/path");
}
catch (const std::exception & e)
{
EXPECT_EQ(std::string(e.what()),
"Should not specify an absolute path along with a data name to search "
"(requested to search in '" +
_names[0] + "')");
throw;
}
},
std::exception);
}

TEST_F(DataFileUtilsTest, getPathAbsoluteMissing)
{
const auto absolute_path = MooseUtils::absolutePath("files/data_file_tests/foo");

Expand All @@ -177,3 +203,23 @@ TEST_F(DataFileUtilsTest, getDataAbsoluteMissing)
},
std::exception);
}

TEST_F(DataFileUtilsTest, getPathNameUnregistered)
{
const std::string name = "unregistered_name";
// Absolute and just doesn't exist
EXPECT_THROW(
{
try
{
Moose::DataFileUtils::getPath(name + ":file");
}
catch (const std::exception & e)
{
EXPECT_EQ(std::string(e.what()),
"Data from '" + name + "' is not registered to be searched");
throw;
}
},
std::exception);
}
17 changes: 17 additions & 0 deletions unit/src/RegistryTest.C
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,23 @@ TEST(RegistryTest, addDataFilePathMismatch)
std::exception);
}

TEST(RegistryTest, addDataFilePathUnallowedName)
{
EXPECT_THROW(
{
try
{
Registry::addDataFilePath("!", "unused");
}
catch (const std::exception & e)
{
EXPECT_EQ(std::string(e.what()), "Unallowed characters in '!'");
throw;
}
},
std::exception);
}

TEST(RegistryTest, getDataPath)
{
const std::string name = "data_working";
Expand Down

0 comments on commit a121ded

Please sign in to comment.