LCOV - code coverage report
Current view: top level - src/utils - DataFileUtils.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 81 81 100.0 %
Date: 2026-05-29 20:35:17 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          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, &not_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             : }

Generated by: LCOV version 1.14