LCOV - code coverage report
Current view: top level - EnergyPlus - FileSystem.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 89 132 67.4 %
Date: 2024-08-24 18:31:18 Functions: 19 22 86.4 %

          Line data    Source code
       1             : // EnergyPlus, Copyright (c) 1996-2024, 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        3335 :     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        3335 :         fs::path result = path;
     104             : #ifdef _WIN32
     105             :         result.make_preferred();
     106             : #else
     107        3335 :         std::string tempPathAsStr = result.make_preferred().string();
     108        3335 :         std::replace(tempPathAsStr.begin(), tempPathAsStr.end(), DataStringGlobals::altpathChar, DataStringGlobals::pathChar);
     109        3335 :         result = fs::path(tempPathAsStr);
     110             : #endif
     111        6670 :         return result;
     112        3335 :     }
     113             : 
     114             :     // TODO: remove as providing little benefit over calling fs::path::filename directly?
     115         796 :     fs::path getFileName(fs::path const &filePath)
     116             :     {
     117         796 :         return filePath.filename();
     118             :     }
     119             : 
     120        1783 :     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        1783 :         std::string pathStr = path.string();
     132        1783 :         if (!pathStr.empty()) {
     133        1762 :             while ((pathStr.back() == DataStringGlobals::pathChar) || (pathStr.back() == DataStringGlobals::altpathChar)) {
     134           0 :                 pathStr.erase(pathStr.size() - 1);
     135             :             }
     136             :         }
     137             : #endif
     138             :         // If empty, return "./" instead
     139        1783 :         fs::path parent_path = fs::path(pathStr).parent_path();
     140        1783 :         if (parent_path.empty()) {
     141          21 :             parent_path = "./";
     142             :         }
     143        3566 :         return parent_path;
     144        1783 :     }
     145             : 
     146        1380 :     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        1380 :         fs::path p = fs::absolute(path);
     160             : 
     161        1380 :         while (fs::is_symlink(p)) {
     162           0 :             fs::path linkpath = fs::read_symlink(p);
     163           0 :             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           0 :                 p = p.parent_path() / linkpath;
     171             :                 // eg: temp ="/home/a_folder/../another_folder/a_file"
     172             :             }
     173           0 :         }
     174             : 
     175        1380 :         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       12678 :         for (fs::path::iterator it = p.begin(); it != p.end(); ++it) {
     180       11298 :             if (*it == fs::path("..")) {
     181          30 :                 if (fs::is_symlink(result) || (result.filename() == fs::path(".."))) {
     182           0 :                     result /= *it;
     183             :                 } else {
     184          30 :                     result = result.parent_path();
     185             :                 }
     186       11268 :             } else if (*it != fs::path(".")) {
     187       11268 :                 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        2760 :         return result;
     201        1380 :     }
     202             : 
     203         818 :     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         818 :         ssize_t len = readlink("/proc/self/exe", executableRelativePath, sizeof(executableRelativePath) - 1);
     217         818 :         if (len == -1) {
     218           0 :             std::cout << "ERROR: Unable to locate executable." << std::endl;
     219           0 :             std::exit(EXIT_FAILURE);
     220             :         } else {
     221         818 :             executableRelativePath[len] = '\0';
     222             :         }
     223             : #elif _WIN32
     224             :         GetModuleFileName(NULL, executableRelativePath, sizeof(executableRelativePath));
     225             : #endif
     226             : 
     227        1636 :         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           0 :     fs::path getFileExtension(fs::path const &filePath)
     233             :     {
     234           0 :         std::string pext = toString(filePath.extension());
     235           0 :         if (!pext.empty()) {
     236             :             // remove '.'
     237           0 :             pext = std::string(++pext.begin(), pext.end());
     238             :         }
     239           0 :         return fs::path{pext};
     240           0 :     }
     241             : 
     242         807 :     FileTypes getFileType(fs::path const &filePath)
     243             :     {
     244         807 :         std::string stringExtension = toString(filePath.extension());
     245         807 :         stringExtension = stringExtension.substr(stringExtension.rfind('.') + 1);
     246        1614 :         return static_cast<FileTypes>(getEnumValue(FileTypesExtUC, Util::makeUPPER(stringExtension)));
     247         807 :     }
     248             : 
     249             :     // TODO: remove for fs::path::replace_extension directly? Note that replace_extension mutates the object
     250           0 :     fs::path removeFileExtension(fs::path const &filePath)
     251             :     {
     252             :         // return fs::path(filePath).stem().string();
     253           0 :         return fs::path(filePath).replace_extension();
     254             :     }
     255             : 
     256           0 :     fs::path replaceFileExtension(fs::path const &filePath, fs::path const &ext)
     257             :     {
     258             :         // return fs::path(filePath).stem().string();
     259           0 :         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         796 :     void makeDirectory(fs::path const &directoryPath)
     264             :     {
     265             :         // Create a directory if doesn't already exist
     266         796 :         if (pathExists(directoryPath)) { // path already exists
     267         796 :             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           0 :             fs::create_directories(directoryPath);
     274             :         }
     275         796 :     }
     276             : 
     277             :     // TODO: remove?
     278         796 :     bool pathExists(fs::path const &path)
     279             :     {
     280         796 :         return fs::exists(path);
     281             :     }
     282             : 
     283             :     // TODO: I think we want to keep this one
     284         796 :     bool directoryExists(fs::path const &directoryPath)
     285             :     {
     286         796 :         return fs::exists(directoryPath) && fs::is_directory(directoryPath);
     287             :     }
     288             : 
     289       13768 :     bool fileExists(fs::path const &filePath)
     290             :     {
     291       13768 :         return fs::exists(filePath) && !fs::is_directory(filePath);
     292             :     }
     293             : 
     294         854 :     void moveFile(fs::path const &filePath, fs::path const &destination)
     295             :     {
     296             :         // TODO: should we throw? Or return false?
     297         854 :         if (!fileExists(filePath)) {
     298          26 :             return;
     299             :         }
     300             : 
     301             :         // rename would fail if copying across devices
     302             :         try {
     303         828 :             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        1618 :     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        1618 :         return system(command.c_str());
     321             : #endif
     322             :     }
     323             : 
     324        4480 :     bool removeFile(fs::path const &filePath)
     325             :     {
     326        4480 :         if (!fileExists(filePath)) {
     327           2 :             return false;
     328             :         }
     329             : 
     330        4478 :         return fs::remove(filePath);
     331             :     }
     332             : 
     333          59 :     void linkFile(fs::path const &filePath, fs::path const &linkPath)
     334             :     {
     335          59 :         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          59 :         fs::create_symlink(filePath, linkPath);
     344             : #endif
     345             :     }
     346             : 
     347         805 :     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         805 :         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         805 :         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         805 :         const std::uintmax_t file_size = fs::file_size(filePath);
     360         805 :         std::ifstream file(filePath, mode);
     361         805 :         if (!file.is_open()) {
     362           0 :             throw FatalError(fmt::format("Could not open file: {}", filePath));
     363             :         }
     364         805 :         std::string result(file_size, '\0');
     365         805 :         file.read(result.data(), file_size);
     366        1610 :         return result;
     367         805 :     }
     368             : 
     369           1 :     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           1 :         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           1 :         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           1 :         std::ifstream file(filePath, mode);
     383           1 :         if (!file.is_open()) {
     384           0 :             throw FatalError(fmt::format("Could not open file: {}", filePath));
     385             :         }
     386             : 
     387           1 :         FileTypes const ext = getFileType(filePath);
     388           1 :         switch (ext) {
     389           1 :         case FileTypes::EpJSON:
     390             :         case FileTypes::JSON:
     391             :         case FileTypes::GLHE:
     392           1 :             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           1 :     }
     405             : 
     406        7253 :     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        7253 :             return p.string();
     412             :         }
     413             :     }
     414             : 
     415         246 :     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         246 :             return p.generic_string();
     421             :         }
     422             :     }
     423             : 
     424      136485 :     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      272970 :             return {outputFilePrefixFullPath.string() + suffix};
     430             :         }
     431             :     }
     432             : 
     433             : } // namespace FileSystem
     434             : } // namespace EnergyPlus

Generated by: LCOV version 1.14