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
|