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 : #ifndef FileSystem_hh_INCLUDED
49 : #define FileSystem_hh_INCLUDED
50 :
51 : #include <algorithm>
52 : #include <fmt/format.h>
53 : #include <fmt/os.h>
54 : #include <fmt/ostream.h>
55 : #include <fmt/ranges.h>
56 : #include <nlohmann/json.hpp>
57 : #include <string>
58 : #ifndef __cppcheck__
59 : # if __has_include(<filesystem>)
60 : # include <filesystem>
61 : namespace fs = std::filesystem;
62 : # elif __has_include(<experimental/filesystem>)
63 : # include <experimental/filesystem>
64 : namespace fs = std::experimental::filesystem;
65 : # else
66 : // cppcheck-suppress preprocessorErrorDirective
67 : # error "no filesystem support"
68 : # endif
69 : #endif
70 :
71 : #include <EnergyPlus/EnergyPlus.hh>
72 :
73 : // If we want to allow this kind of stuff
74 : // fs::path p = "folder/eplus";
75 : // std::string suffixStr = "out.audit";
76 : //
77 : // fs::path filePath = p + suffixStr; => folder/eplusout.audit (would throw)
78 : // std::string message = "Cannot find " + p + "." => would throw now, need p.string() instead
79 :
80 : // inline fs::path operator+(fs::path const &left, fs::path const &right) {
81 : // return fs::path(left)+=right;
82 : // }
83 :
84 : namespace EnergyPlus {
85 :
86 : namespace FileSystem {
87 :
88 : extern std::string const exeExtension;
89 :
90 : enum class FileTypes
91 : {
92 : Invalid = -1,
93 : // JSON types should go first,
94 : EpJSON,
95 : JSON,
96 : GLHE,
97 : last_json_type = GLHE,
98 : CBOR,
99 : MsgPack,
100 : UBJSON,
101 : BSON,
102 : last_binary_json_type = BSON,
103 : IDF,
104 : IMF,
105 : CSV,
106 : TSV,
107 : TXT,
108 : ESO,
109 : MTR,
110 : last_flat_file_type = MTR,
111 : DDY,
112 : Num
113 : };
114 :
115 60 : inline constexpr bool is_all_json_type(FileTypes t)
116 : {
117 60 : return t > FileTypes::Invalid && t <= FileTypes::last_binary_json_type;
118 : }
119 : inline constexpr bool is_json_type(FileTypes t)
120 : {
121 : return t > FileTypes::Invalid && t <= FileTypes::last_json_type;
122 : }
123 : inline constexpr bool is_binary_json_type(FileTypes t)
124 : {
125 : return t > FileTypes::last_json_type && t <= FileTypes::last_binary_json_type;
126 : }
127 5 : inline constexpr bool is_idf_type(FileTypes t)
128 : {
129 5 : return t == FileTypes::IDF || t == FileTypes::IMF;
130 : }
131 6 : inline constexpr bool is_flat_file_type(FileTypes t)
132 : {
133 6 : return t > FileTypes::last_binary_json_type && t <= FileTypes::last_flat_file_type;
134 : }
135 :
136 : // Similar to fs::path::make_preferred, but also does '\\' => '/' conversion on POSIX, which make_preferred does not do
137 : [[nodiscard]] fs::path makeNativePath(fs::path const &path);
138 :
139 : [[nodiscard]] fs::path getFileName(fs::path const &filePath);
140 :
141 : // Returns the parent directory of a path. This implementation differs from filesystem::path::parent_path because it treats trailing separators
142 : // differently.
143 : // | s | getParentDirectoryPath(s) | fs::path(s).parent_path() |
144 : // |--------|---------------------------|---------------------------|
145 : // | a/b/c | "a/b" | "a/b" |
146 : // | a/b/c/ | "a/b" | "a/b/c" |
147 : // | a.idf | "./" | "" |
148 : [[nodiscard]] fs::path getParentDirectoryPath(fs::path const &filePath);
149 :
150 : [[nodiscard]] fs::path getAbsolutePath(fs::path const &filePath);
151 :
152 : [[nodiscard]] fs::path getProgramPath();
153 :
154 : // For `a/b/c.txt.idf` it returns `idf`, i.e. anything after last dot, **not including the dot** (unlike fs::path::extension() which includes it)
155 : [[nodiscard]] fs::path getFileExtension(fs::path const &gc);
156 :
157 : // Returns the FileType by looking at its extension.
158 : [[nodiscard]] FileTypes getFileType(fs::path const &filePath);
159 :
160 : // Turns a/b/c.txt.idf into a/b/c.txt, **without mutating the original object** unlike fs::path::replace_extension
161 : [[nodiscard]] fs::path removeFileExtension(fs::path const &filePath);
162 :
163 : // Replace (or append) an extension to a path **without mutating the original object** unlike fs::path::replace_extension
164 : [[nodiscard]] fs::path replaceFileExtension(fs::path const &filePath, fs::path const &ext);
165 :
166 : // Creates a directory if it doesn't already exists
167 : void makeDirectory(fs::path const &directoryPath);
168 :
169 : bool pathExists(fs::path const &path);
170 :
171 : bool directoryExists(fs::path const &directoryPath);
172 :
173 : bool fileExists(fs::path const &filePath);
174 :
175 : // Checks that fileExists(filePath), if so tries to rename to destination, falling back on copy+remove if failed (if trying to do move accross
176 : // devices for eg)
177 : void moveFile(fs::path const &filePath, fs::path const &destinationPath);
178 :
179 : int systemCall(std::string const &command);
180 :
181 : // Returns false if not fileExists(filePath), or if filePath cannot be removed
182 : bool removeFile(fs::path const &filePath);
183 :
184 : // On Windows, this just copies the file. On Unix, it creates a symlink
185 : // Starts by checking that fileExists(filePath) is true
186 : void linkFile(fs::path const &filePath, fs::path const &linkPath);
187 :
188 : // Reads the full file if it exists
189 : // On Windows, this must be binary input to have \r\n in the read file otherwise it will be converted to \n
190 : std::string readFile(fs::path const &filePath, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::binary);
191 :
192 : // Reads the full json file if it exists
193 : nlohmann::json readJSON(fs::path const &filePath, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::binary);
194 :
195 5 : template <FileTypes fileType> std::string getJSON(const nlohmann::json &data, int const indent = 4)
196 : {
197 : if constexpr (is_json_type(fileType)) {
198 5 : return data.dump(indent, ' ', false, nlohmann::json::error_handler_t::replace);
199 : } else if constexpr (is_binary_json_type(fileType)) {
200 0 : std::string binary_data;
201 : if constexpr (fileType == FileTypes::CBOR) {
202 0 : nlohmann::json::to_cbor(data, binary_data);
203 : } else if constexpr (fileType == FileTypes::MsgPack) {
204 0 : nlohmann::json::to_msgpack(data, binary_data);
205 : } else if constexpr (fileType == FileTypes::BSON) {
206 0 : nlohmann::json::to_bson(data, binary_data);
207 : } else if constexpr (fileType == FileTypes::UBJSON) {
208 0 : nlohmann::json::to_ubjson(data, binary_data);
209 : }
210 0 : return binary_data;
211 0 : } else {
212 : static_assert(is_all_json_type(fileType), "Must be a JSON type");
213 : }
214 : }
215 :
216 : template <class T, class... Ts> struct is_any : std::disjunction<std::is_same<std::remove_cv_t<T>, std::remove_cv_t<Ts>>...>
217 : {
218 : };
219 :
220 : template <class T>
221 : inline constexpr bool enable_unique_ptr_v =
222 : is_any<T, std::unique_ptr<fs::path>, std::unique_ptr<fmt::ostream>, std::unique_ptr<std::ostream>, std::unique_ptr<FILE *>>::value;
223 :
224 : template <class T, FileTypes fileType>
225 : inline constexpr bool enable_json_v = is_all_json_type(fileType) && is_any<T, nlohmann::json>::value &&
226 : !is_any<T, std::string_view, std::string, char *>::value;
227 :
228 5 : template <FileTypes fileType> void writeFile(fs::path const &filePath, const std::string_view data)
229 : {
230 : static_assert(is_all_json_type(fileType) || is_flat_file_type(fileType), "Must be a valid file type");
231 : #ifdef _WIN32
232 : auto filePathStr = filePath.string();
233 : auto path = filePathStr.c_str();
234 : #else
235 5 : auto path = filePath.c_str();
236 : #endif
237 :
238 : if constexpr (is_json_type(fileType) || is_flat_file_type(fileType)) {
239 5 : auto f = fmt::output_file(path, fmt::buffer_size = (2 << 17));
240 5 : f.print("{}", data);
241 5 : } else if constexpr (is_binary_json_type(fileType)) {
242 0 : auto close_file = [](FILE *f) { fclose(f); };
243 0 : auto holder = std::unique_ptr<FILE, decltype(close_file)>(fopen(path, "wb"), close_file);
244 0 : if (!holder) {
245 0 : throw FatalError(fmt::format("Could not open file: {}", path));
246 : }
247 :
248 0 : auto f = holder.get();
249 0 : fmt::print(f, "{}", data);
250 0 : }
251 5 : }
252 :
253 : template <FileTypes fileType> void writeFile(fmt::ostream &os, const std::string_view data)
254 : {
255 : static_assert(fileType > FileTypes::Invalid, "Must be a valid file type");
256 : os.print("{}", data);
257 : }
258 :
259 : template <FileTypes fileType> void writeFile(std::ostream &os, const std::string_view data)
260 : {
261 : static_assert(fileType > FileTypes::Invalid, "Must be a valid file type");
262 : fmt::print(os, "{}", data);
263 : }
264 :
265 : template <FileTypes fileType> void writeFile(FILE *f, const std::string_view data)
266 : {
267 : static_assert(fileType > FileTypes::Invalid, "Must be a valid file type");
268 : fmt::print(f, "{}", data);
269 : }
270 :
271 : template <class T, FileTypes fileType, typename = std::enable_if_t<enable_unique_ptr_v<T>>> void writeFile(T &os, const std::string_view data)
272 : {
273 : static_assert(fileType > FileTypes::Invalid, "Must be a valid file type");
274 : if (os) {
275 : writeFile<fileType>(*os, data);
276 : }
277 : }
278 :
279 : template <FileTypes fileType, class T, typename = std::enable_if_t<enable_json_v<T, fileType>>>
280 5 : void writeFile(fs::path const &filePath, T &data, int const indent = 4)
281 : {
282 5 : auto const json_str = getJSON<fileType>(data, indent);
283 5 : writeFile<fileType>(filePath, std::string_view(json_str));
284 5 : }
285 :
286 : template <FileTypes fileType, class T, typename = std::enable_if_t<enable_json_v<T, fileType>>>
287 : void writeFile(fmt::ostream &os, T &data, int const indent = 4)
288 : {
289 : auto const json_str = getJSON<fileType>(data, indent);
290 : writeFile<fileType>(os, std::string_view(json_str));
291 : }
292 :
293 : template <FileTypes fileType, class T, typename = std::enable_if_t<enable_json_v<T, fileType>>>
294 : void writeFile(std::ostream &os, T &data, int const indent = 4)
295 : {
296 : auto const json_str = getJSON<fileType>(data, indent);
297 : writeFile<fileType>(os, std::string_view(json_str));
298 : }
299 :
300 : template <FileTypes fileType, class T, typename = std::enable_if_t<enable_json_v<T, fileType>>>
301 : void writeFile(FILE *f, T &data, int const indent = 4)
302 : {
303 : auto const json_str = getJSON<fileType>(data, indent);
304 : writeFile<fileType>(f, std::string_view(json_str));
305 : }
306 :
307 : template <FileTypes fileType, class T, class T2, typename = std::enable_if_t<enable_json_v<T2, fileType> && enable_unique_ptr_v<T>>>
308 : void writeFile(T &os, T2 &data, int const indent = 4)
309 : {
310 : if (os) {
311 : auto const json_str = getJSON<fileType>(data, indent);
312 : writeFile<fileType>(*os, std::string_view(json_str));
313 : }
314 : }
315 :
316 : std::string toString(fs::path const &p);
317 :
318 : std::string toGenericString(fs::path const &p);
319 :
320 : fs::path appendSuffixToPath(fs::path const &outputFilePrefixFullPath, const std::string &suffix);
321 :
322 : } // namespace FileSystem
323 : } // namespace EnergyPlus
324 :
325 : // Add a custom formatter for fmt
326 : template <> struct fmt::formatter<fs::path>
327 : {
328 : // Presentation format: 's' - string, 'g' - generic_string.
329 : char presentation = 's';
330 :
331 : // Parses format specifications of the form ['s' | 'g'].
332 29 : constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin())
333 : {
334 : // Parse the presentation format and store it in the formatter:
335 29 : auto it = ctx.begin(), end = ctx.end();
336 29 : if (it != end && (*it == 's' || *it == 'g')) {
337 1 : presentation = *it++;
338 : }
339 :
340 : // Check if reached the end of the range:
341 29 : if (it != end && *it != '}') {
342 0 : throw format_error("invalid format");
343 : };
344 :
345 : // Return an iterator past the end of the parsed range:
346 29 : return it;
347 : }
348 :
349 29 : template <typename FormatContext> auto format(const fs::path &p, FormatContext &ctx) -> decltype(ctx.out())
350 : {
351 87 : return format_to(ctx.out(), "{}", presentation == 'g' ? EnergyPlus::FileSystem::toGenericString(p) : EnergyPlus::FileSystem::toString(p));
352 : }
353 : };
354 :
355 : #endif
|