LCOV - code coverage report
Current view: top level - EnergyPlus - FileSystem.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 68.9 % 132 91
Test Date: 2025-05-22 16:09:37 Functions: 90.9 % 22 20

            Line data    Source code
       1              : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
       2              : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3              : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4              : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5              : // contributors. All rights reserved.
       6              : //
       7              : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8              : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9              : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10              : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11              : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12              : //
      13              : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14              : // provided that the following conditions are met:
      15              : //
      16              : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17              : //     conditions and the following disclaimer.
      18              : //
      19              : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20              : //     conditions and the following disclaimer in the documentation and/or other materials
      21              : //     provided with the distribution.
      22              : //
      23              : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24              : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25              : //     used to endorse or promote products derived from this software without specific prior
      26              : //     written permission.
      27              : //
      28              : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29              : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30              : //     reference solely to the software portion of its product, Licensee must refer to the
      31              : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32              : //     obtained under this License and may not use a different name for the software. Except as
      33              : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34              : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35              : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36              : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37              : //
      38              : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39              : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40              : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41              : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42              : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43              : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44              : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45              : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46              : // POSSIBILITY OF SUCH DAMAGE.
      47              : 
      48              : // Standard C++ library
      49              : #include <cerrno>
      50              : #include <cstdio>
      51              : #include <cstdlib>
      52              : #include <fstream>
      53              : #include <iostream>
      54              : #include <sys/stat.h>
      55              : #include <sys/types.h>
      56              : #include <type_traits>
      57              : 
      58              : #ifdef _WIN32
      59              : #    include <Shlwapi.h>
      60              : #    include <windows.h>
      61              : #else
      62              : #    include <unistd.h>
      63              : #endif
      64              : 
      65              : #ifdef __APPLE__
      66              : #    include <mach-o/dyld.h>
      67              : #endif
      68              : 
      69              : // EnergyPlus Headers
      70              : #include <EnergyPlus/DataStringGlobals.hh>
      71              : #include <EnergyPlus/FileSystem.hh>
      72              : #include <EnergyPlus/UtilityRoutines.hh>
      73              : 
      74              : #include <CLI/CLI11.hpp>
      75              : 
      76              : namespace EnergyPlus {
      77              : 
      78              : namespace FileSystem {
      79              : 
      80              : #ifdef _WIN32
      81              :     std::string const exeExtension(".exe");
      82              : #else
      83              :     std::string const exeExtension;
      84              : #endif
      85              : 
      86              :     static constexpr std::array<std::string_view, static_cast<std::size_t>(FileTypes::Num)> FileTypesExt{
      87              :         "epJSON", "json", "glhe", "cbor", "msgpack", "ubjson", "bson", "idf", "imf", "csv", "tsv", "txt", "eso", "mtr", "ddy"};
      88              :     static constexpr std::array<std::string_view, static_cast<std::size_t>(FileTypes::Num)> FileTypesExtUC{
      89              :         "EPJSON", "JSON", "GLHE", "CBOR", "MSGPACK", "UBJSON", "BSON", "IDF", "IMF", "CSV", "TSV", "TXT", "ESO", "MTR", "DDY"};
      90              : 
      91              :     static_assert(FileTypesExt.size() == static_cast<std::size_t>(FileTypes::Num), "Mismatched FileTypes enum and FileTypesExt array.");
      92              :     static_assert(FileTypesExtUC.size() == static_cast<std::size_t>(FileTypes::Num), "Mismatched FileTypes enum and FileTypesExtUC array.");
      93              : 
      94              :     static_assert(!FileTypesExt.back().empty(), "Likely missing an enum from FileTypes in FileTypesExt array.");
      95              :     static_assert(!FileTypesExtUC.back().empty(), "Likely missing an enum from FileTypes in FileTypesExtUC array.");
      96              : 
      97          214 :     fs::path makeNativePath(fs::path const &path)
      98              :     {
      99              :         // path.make_preferred() on windows will change "/" to "\\", because '/' is a fallback separator
     100              :         // but on Unix it will *not* change "\\" to "/", because POSIX doesn't define it as a fallback separator. In fact, it's even allowed in a
     101              :         // filename. Do we really need that though?
     102              :         // path.make_preferred();
     103          214 :         fs::path result = path;
     104              : #ifdef _WIN32
     105              :         result.make_preferred();
     106              : #else
     107          214 :         std::string tempPathAsStr = result.make_preferred().string();
     108          214 :         std::replace(tempPathAsStr.begin(), tempPathAsStr.end(), DataStringGlobals::altpathChar, DataStringGlobals::pathChar);
     109          214 :         result = fs::path(tempPathAsStr);
     110              : #endif
     111          428 :         return result;
     112          214 :     }
     113              : 
     114              :     // TODO: remove as providing little benefit over calling fs::path::filename directly?
     115           55 :     fs::path getFileName(fs::path const &filePath)
     116              :     {
     117           55 :         return filePath.filename();
     118              :     }
     119              : 
     120          135 :     fs::path getParentDirectoryPath(fs::path const &path)
     121              :     {
     122              :         // Note: this is needed because "/a/b/c".parent_path() = "/a/b/c/"
     123              : #ifdef _WIN32
     124              :         std::wstring pathStr = path.native();
     125              :         if (!pathStr.empty()) {
     126              :             while ((pathStr.back() == DataStringGlobals::pathChar) || (pathStr.back() == DataStringGlobals::altpathChar)) {
     127              :                 pathStr.erase(pathStr.size() - 1);
     128              :             }
     129              :         }
     130              : #else
     131          135 :         std::string pathStr = path.string();
     132          135 :         if (!pathStr.empty()) {
     133          129 :             while ((pathStr.back() == DataStringGlobals::pathChar) || (pathStr.back() == DataStringGlobals::altpathChar)) {
     134            1 :                 pathStr.erase(pathStr.size() - 1);
     135              :             }
     136              :         }
     137              : #endif
     138              :         // If empty, return "./" instead
     139          135 :         fs::path parent_path = fs::path(pathStr).parent_path();
     140          135 :         if (parent_path.empty()) {
     141           34 :             parent_path = "./";
     142              :         }
     143          270 :         return parent_path;
     144          135 :     }
     145              : 
     146          120 :     fs::path getAbsolutePath(fs::path const &path)
     147              :     {
     148              :         //        /*
     149              :         //         * Returns the absolute path for a given relative path.
     150              :         //         *
     151              :         //         * If the relative path points to a symlink, the symlink will
     152              :         //         * be resolved, and this function will return the absolute path
     153              :         //         * of the link.
     154              :         //         */
     155              : 
     156              :         // Not available in experimental/filesystem
     157              :         // return fs::weakly_canonical(fs::absolute(p));
     158              : 
     159          120 :         fs::path p = fs::absolute(path);
     160              : 
     161          121 :         while (fs::is_symlink(p)) {
     162            1 :             fs::path linkpath = fs::read_symlink(p);
     163            1 :             if (linkpath.is_absolute()) {
     164            0 :                 p = linkpath;
     165              :             } else {
     166              :                 // Note: temp will end up absolute but not canonical yet
     167              :                 // eg:
     168              :                 // temp="/home/a_folder/a_symlink"
     169              :                 // linkpath="../another_folder/a_file"
     170            1 :                 p = p.parent_path() / linkpath;
     171              :                 // eg: temp ="/home/a_folder/../another_folder/a_file"
     172              :             }
     173            1 :         }
     174              : 
     175          120 :         fs::path result;
     176              :         // `p` now is absolute, but it isn't necessarilly canonical.
     177              :         // If you have <filesystem>, you can use `fs::weakly_canonical`. <experimental/filesystem> does **not** have `weakly_canonical` though
     178              :         // This block resolves a canonical path, even if it doesn't exist (yet?) on disk.
     179         1070 :         for (fs::path::iterator it = p.begin(); it != p.end(); ++it) {
     180          950 :             if (*it == fs::path("..")) {
     181            1 :                 if (fs::is_symlink(result) || (result.filename() == fs::path(".."))) {
     182            0 :                     result /= *it;
     183              :                 } else {
     184            1 :                     result = result.parent_path();
     185              :                 }
     186          949 :             } else if (*it != fs::path(".")) {
     187          933 :                 result /= *it;
     188              :             }
     189              :         }
     190              : 
     191              :         // Workaround to maintain std::string & backward compat
     192              :         // TODO: is this wanted?
     193              :         // The problem is really only for unit tests, if you try to compare getAbsolutePath("sandbox/") == getAbsolutePath("sandbox") you get false
     194              :         // because one has the trailing sep, the other doesn't. Both paths have the same components though (if iterated on), and as long as you use
     195              :         // path operations (such as operator/) and not string concatenation, this works just fine.
     196              :         // std::string s = result.string();
     197              :         // if (fs::is_directory(s)) {
     198              :         // s += '/';
     199              :         //}
     200          240 :         return result;
     201          120 :     }
     202              : 
     203           59 :     fs::path getProgramPath()
     204              :     {
     205              :         // /*
     206              :         // * Returns the relative path to the executable file (including symlinks).
     207              :         // *
     208              :         // * To resolve symlinks, wrap this call in getAbsolutePath().
     209              :         // */
     210              :         char executableRelativePath[1024];
     211              : 
     212              : #ifdef __APPLE__
     213              :         uint32_t pathSize = sizeof(executableRelativePath);
     214              :         _NSGetExecutablePath(executableRelativePath, &pathSize);
     215              : #elif __linux__
     216           59 :         ssize_t len = readlink("/proc/self/exe", executableRelativePath, sizeof(executableRelativePath) - 1);
     217           59 :         if (len == -1) {
     218            0 :             std::cout << "ERROR: Unable to locate executable." << std::endl;
     219            0 :             std::exit(EXIT_FAILURE);
     220              :         } else {
     221           59 :             executableRelativePath[len] = '\0';
     222              :         }
     223              : #elif _WIN32
     224              :         GetModuleFileName(NULL, executableRelativePath, sizeof(executableRelativePath));
     225              : #endif
     226              : 
     227          118 :         return executableRelativePath;
     228              :     }
     229              : 
     230              :     // TODO: remove? seems like fs::path::extension would do fine. It's only used in CommandLineInterface to check the input file type, so we could
     231              :     // just compare to ".EPJSON" instead of "EPJSON"...
     232            3 :     fs::path getFileExtension(fs::path const &filePath)
     233              :     {
     234            3 :         std::string pext = toString(filePath.extension());
     235            3 :         if (!pext.empty()) {
     236              :             // remove '.'
     237            2 :             pext = std::string(++pext.begin(), pext.end());
     238              :         }
     239            6 :         return fs::path{pext};
     240            3 :     }
     241              : 
     242           67 :     FileTypes getFileType(fs::path const &filePath)
     243              :     {
     244           67 :         std::string stringExtension = toString(filePath.extension());
     245           67 :         stringExtension = stringExtension.substr(stringExtension.rfind('.') + 1);
     246          134 :         return static_cast<FileTypes>(getEnumValue(FileTypesExtUC, Util::makeUPPER(stringExtension)));
     247           67 :     }
     248              : 
     249              :     // TODO: remove for fs::path::replace_extension directly? Note that replace_extension mutates the object
     250            3 :     fs::path removeFileExtension(fs::path const &filePath)
     251              :     {
     252              :         // return fs::path(filePath).stem().string();
     253            3 :         return fs::path(filePath).replace_extension();
     254              :     }
     255              : 
     256            5 :     fs::path replaceFileExtension(fs::path const &filePath, fs::path const &ext)
     257              :     {
     258              :         // return fs::path(filePath).stem().string();
     259            5 :         return fs::path(filePath).replace_extension(ext);
     260              :     }
     261              : 
     262              :     // TODO: remove? `fs::create_directory` for a single or `fs::create_directories` for nested directory creation
     263           32 :     void makeDirectory(fs::path const &directoryPath)
     264              :     {
     265              :         // Create a directory if doesn't already exist
     266           32 :         if (pathExists(directoryPath)) { // path already exists
     267           25 :             if (!directoryExists(directoryPath)) {
     268            0 :                 std::cout << "ERROR: " << toString(getAbsolutePath(directoryPath)) << " already exists and is not a directory." << std::endl;
     269            0 :                 std::exit(EXIT_FAILURE);
     270              :             }
     271              :         } else { // directory does not already exist
     272              :             // Create_directories is recursive, create_directory isn't. I don't see why we wouldn't want recursive
     273            7 :             fs::create_directories(directoryPath);
     274              :         }
     275           32 :     }
     276              : 
     277              :     // TODO: remove?
     278           43 :     bool pathExists(fs::path const &path)
     279              :     {
     280           43 :         return fs::exists(path);
     281              :     }
     282              : 
     283              :     // TODO: I think we want to keep this one
     284           35 :     bool directoryExists(fs::path const &directoryPath)
     285              :     {
     286           35 :         return fs::exists(directoryPath) && fs::is_directory(directoryPath);
     287              :     }
     288              : 
     289        19300 :     bool fileExists(fs::path const &filePath)
     290              :     {
     291        19300 :         return fs::exists(filePath) && !fs::is_directory(filePath);
     292              :     }
     293              : 
     294            4 :     void moveFile(fs::path const &filePath, fs::path const &destination)
     295              :     {
     296              :         // TODO: should we throw? Or return false?
     297            4 :         if (!fileExists(filePath)) {
     298            0 :             return;
     299              :         }
     300              : 
     301              :         // rename would fail if copying across devices
     302              :         try {
     303            4 :             fs::rename(fs::path(filePath), destination);
     304            0 :         } catch (fs::filesystem_error &) {
     305            0 :             fs::copy(filePath, destination, fs::copy_options::update_existing);
     306            0 :             fs::remove(filePath);
     307            0 :         }
     308              :     }
     309              : 
     310            6 :     int systemCall(std::string const &command)
     311              :     {
     312              : #ifdef _WIN32
     313              :         // Wrap in double quotes and pass that to system
     314              :         // Note: on Windows, system(command) will already send the command through "cmd /C command"
     315              :         // cf C:\Program Files (x86)\Windows Kits\10\Source\10.0.17763.0\ucrt\exec
     316              :         // Ends up calling something that looks like the following:
     317              :         // cmd /C ""C:\path\to\ReadVarsESO.exe" "A folder with spaces\1ZoneUncontrolled.mvi" unlimited"
     318              :         return system(("\"" + command + "\"").c_str());
     319              : #else
     320            6 :         return system(command.c_str());
     321              : #endif
     322              :     }
     323              : 
     324        18815 :     bool removeFile(fs::path const &filePath)
     325              :     {
     326        18815 :         if (!fileExists(filePath)) {
     327        18776 :             return false;
     328              :         }
     329              : 
     330           39 :         return fs::remove(filePath);
     331              :     }
     332              : 
     333            0 :     void linkFile(fs::path const &filePath, fs::path const &linkPath)
     334              :     {
     335            0 :         if (!fileExists(filePath)) {
     336            0 :             return;
     337              :         }
     338              : 
     339              : #ifdef _WIN32
     340              :         fs::copy(filePath, linkPath, fs::copy_options::update_existing);
     341              : #else
     342              :         // we could return bool?
     343            0 :         fs::create_symlink(filePath, linkPath);
     344              : #endif
     345              :     }
     346              : 
     347           34 :     std::string readFile(fs::path const &filePath, std::ios_base::openmode mode)
     348              :     {
     349              :         // Shenanigans would not be needed with fmt 10+ (maybe earlier), because fmt has native fs::path support
     350           34 :         if (!fileExists(filePath)) {
     351            0 :             throw FatalError(fmt::format("File does not exists: {}", filePath));
     352              :         }
     353              : 
     354              :         // Can only be 'r', 'b' or 'rb'
     355           34 :         if ((mode & (std::ios_base::in | std::ios_base::binary)) == 0) {
     356            0 :             throw FatalError("ERROR - readFile: Bad openmode argument. Must be std::ios_base::in or std::ios_base::binary");
     357              :         }
     358              : 
     359           34 :         const std::uintmax_t file_size = fs::file_size(filePath);
     360           34 :         std::ifstream file(filePath, mode);
     361           34 :         if (!file.is_open()) {
     362            0 :             throw FatalError(fmt::format("Could not open file: {}", filePath));
     363              :         }
     364           34 :         std::string result(file_size, '\0');
     365           34 :         file.read(result.data(), file_size);
     366           68 :         return result;
     367           34 :     }
     368              : 
     369            0 :     nlohmann::json readJSON(fs::path const &filePath, std::ios_base::openmode mode)
     370              :     {
     371              : 
     372              :         // Shenanigans would not be needed with fmt 10+ (maybe earlier), because fmt has native fs::path support
     373            0 :         if (!fileExists(filePath)) {
     374            0 :             throw FatalError(fmt::format("File does not exists: {}", filePath));
     375              :         }
     376              : 
     377              :         // Can only be 'r', 'b' or 'rb'
     378            0 :         if ((mode & (std::ios_base::in | std::ios_base::binary)) == 0) {
     379            0 :             throw FatalError("ERROR - readFile: Bad openmode argument. Must be std::ios_base::in or std::ios_base::binary");
     380              :         }
     381              : 
     382            0 :         std::ifstream file(filePath, mode);
     383            0 :         if (!file.is_open()) {
     384            0 :             throw FatalError(fmt::format("Could not open file: {}", filePath));
     385              :         }
     386              : 
     387            0 :         FileTypes const ext = getFileType(filePath);
     388            0 :         switch (ext) {
     389            0 :         case FileTypes::EpJSON:
     390              :         case FileTypes::JSON:
     391              :         case FileTypes::GLHE:
     392            0 :             return nlohmann::json::parse(file, nullptr, true, true);
     393            0 :         case FileTypes::CBOR:
     394            0 :             return nlohmann::json::from_cbor(file);
     395            0 :         case FileTypes::MsgPack:
     396            0 :             return nlohmann::json::from_msgpack(file);
     397            0 :         case FileTypes::UBJSON:
     398            0 :             return nlohmann::json::from_ubjson(file);
     399            0 :         case FileTypes::BSON:
     400            0 :             return nlohmann::json::from_bson(file);
     401            0 :         default:
     402            0 :             throw FatalError("Invalid file extension. Must be epJSON, JSON, or other experimental extensions");
     403              :         }
     404            0 :     }
     405              : 
     406          125 :     std::string toString(fs::path const &p)
     407              :     {
     408              :         if constexpr (std::is_same_v<typename fs::path::value_type, wchar_t>) {
     409              :             return CLI::narrow(p.wstring());
     410              :         } else {
     411          125 :             return p.string();
     412              :         }
     413              :     }
     414              : 
     415          231 :     std::string toGenericString(fs::path const &p)
     416              :     {
     417              :         if constexpr (std::is_same_v<typename fs::path::value_type, wchar_t>) {
     418              :             return CLI::narrow(p.generic_wstring());
     419              :         } else {
     420          231 :             return p.generic_string();
     421              :         }
     422              :     }
     423              : 
     424         3728 :     fs::path appendSuffixToPath(fs::path const &outputFilePrefixFullPath, const std::string &suffix)
     425              :     {
     426              :         if constexpr (std::is_same_v<typename fs::path::value_type, wchar_t>) {
     427              :             return {outputFilePrefixFullPath.wstring() + CLI::widen(suffix)};
     428              :         } else {
     429         3728 :             return {outputFilePrefixFullPath.string() + suffix};
     430              :         }
     431              :     }
     432              : 
     433              : } // namespace FileSystem
     434              : } // namespace EnergyPlus
        

Generated by: LCOV version 2.0-1