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