Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
io.h
1#ifndef SOURCEMETA_CORE_IO_H_
2#define SOURCEMETA_CORE_IO_H_
3
4#ifndef SOURCEMETA_CORE_IO_EXPORT
5#include <sourcemeta/core/io_export.h>
6#endif
7
8// NOLINTBEGIN(misc-include-cleaner)
9#include <sourcemeta/core/io_atomic.h>
10#include <sourcemeta/core/io_binary.h>
11#include <sourcemeta/core/io_bytestream.h>
12#include <sourcemeta/core/io_error.h>
13#include <sourcemeta/core/io_fileview.h>
14#include <sourcemeta/core/io_temporary.h>
15// NOLINTEND(misc-include-cleaner)
16
17#include <cstddef> // std::byte
18#include <filesystem> // std::filesystem
19#include <fstream> // std::basic_ifstream
20#include <functional> // std::function
21#include <ios> // std::ios, std::streamoff, std::streampos, std::streamsize
22#include <iostream> // std::cin
23#include <istream> // std::basic_istream
24#include <limits> // std::numeric_limits
25#include <ostream> // std::ostream
26#include <span> // std::span
27#include <sstream> // std::basic_ostringstream
28#include <string> // std::basic_string, std::char_traits, std::string
29#include <string_view> // std::string_view
30#include <system_error> // std::error_code
31
40
41namespace sourcemeta::core {
42
55SOURCEMETA_CORE_IO_EXPORT
56auto canonical(const std::filesystem::path &path) -> std::filesystem::path;
57
71SOURCEMETA_CORE_IO_EXPORT
72auto weakly_canonical(const std::filesystem::path &path)
73 -> std::filesystem::path;
74
86SOURCEMETA_CORE_IO_EXPORT
87auto is_under_path(const std::filesystem::path &path,
88 const std::filesystem::path &prefix) -> bool;
89
101SOURCEMETA_CORE_IO_EXPORT
102auto is_lexically_under_path(const std::filesystem::path &path,
103 const std::filesystem::path &prefix) -> bool;
104
117SOURCEMETA_CORE_IO_EXPORT
118auto strip_path_prefix(const std::filesystem::path &path,
119 const std::filesystem::path &prefix)
120 -> std::filesystem::path;
121
134template <typename CharT = char, typename Traits = std::char_traits<CharT>>
135auto read_file(const std::filesystem::path &path)
136 -> std::basic_ifstream<CharT, Traits> {
137 if (std::filesystem::is_directory(path)) {
138 throw IOIsADirectoryError{path};
139 }
140
141 const auto canonical_path{sourcemeta::core::canonical(path)};
142 // Text mode translates line endings on some platforms, which desynchronises
143 // character offsets from byte offsets and makes the stream impossible to
144 // reposition by arithmetic
145 std::basic_ifstream<CharT, Traits> stream{canonical_path, std::ios::binary};
146 if (!stream.is_open()) {
147 throw IOFilePermissionError{canonical_path};
148 }
149
150 stream.exceptions(std::basic_ifstream<CharT, Traits>::badbit);
151 return stream;
152}
153
167template <typename CharT = char, typename Traits = std::char_traits<CharT>>
168auto read_to_string(std::basic_istream<CharT, Traits> &stream)
169 -> std::basic_string<CharT, Traits> {
170 const auto start{stream.tellg()};
171 if (start != static_cast<std::streampos>(-1)) {
172 stream.seekg(0, std::ios::end);
173 const auto end{stream.tellg()};
174 stream.seekg(start);
175 if (end > start) {
176 std::basic_string<CharT, Traits> result;
177 result.resize(static_cast<std::size_t>(end - start));
178 stream.read(result.data(), static_cast<std::streamsize>(result.size()));
179 // Text-mode reads may return fewer characters than the byte count
180 // (i.e. CRLF collapses to LF on Windows), so trim to actual.
181 result.resize(static_cast<std::size_t>(stream.gcount()));
182 return result;
183 }
184 }
185
186 std::basic_ostringstream<CharT, Traits> buffer;
187 buffer << stream.rdbuf();
188 return buffer.str();
189}
190
208template <typename CharT = char, typename Traits = std::char_traits<CharT>>
209auto resume_stream(std::basic_istream<CharT, Traits> &stream,
210 const std::streampos start, const std::streamsize count)
211 -> void {
212 if (start == static_cast<std::streampos>(-1)) {
213 return;
214 }
215
216 stream.clear();
217 stream.seekg(start + static_cast<std::streamoff>(count));
218}
219
231template <typename CharT = char, typename Traits = std::char_traits<CharT>>
232auto read_file_to_string(const std::filesystem::path &path)
233 -> std::basic_string<CharT, Traits> {
234 if (std::filesystem::is_directory(path)) {
235 throw IOIsADirectoryError{path};
236 }
237
238 const auto canonical_path{sourcemeta::core::canonical(path)};
239 // Binary, so that the result is the bytes the file holds. Text mode collapses
240 // CRLF to LF on some platforms, which silently makes the contents, its size
241 // and every offset into it disagree with the file itself
242 std::basic_ifstream<CharT, Traits> stream{canonical_path, std::ios::binary};
243 if (!stream.is_open()) {
244 throw IOFilePermissionError{canonical_path};
245 }
246
247 stream.exceptions(std::basic_ifstream<CharT, Traits>::badbit);
248
249 // `file_size` only works on regular files, so when it cannot determine a
250 // size (FIFOs from process substitution, sockets, pipes, or any other
251 // stat failure) fall back to a streaming read on the already-open stream.
252 std::error_code file_size_error;
253 const auto size{std::filesystem::file_size(canonical_path, file_size_error)};
254 if (file_size_error) {
255 return read_to_string<CharT, Traits>(stream);
256 }
257
258 // On 32-bit targets the file size can exceed what an in-memory string can
259 // hold, in which case fall back to a streaming read that grows as needed
260 if constexpr (std::numeric_limits<decltype(size)>::max() >
261 std::numeric_limits<std::size_t>::max()) {
262 if (size > std::numeric_limits<std::size_t>::max()) {
263 return read_to_string<CharT, Traits>(stream);
264 }
265 }
266
267 std::basic_string<CharT, Traits> result;
268 result.resize(static_cast<std::size_t>(size));
269 stream.read(result.data(), static_cast<std::streamsize>(result.size()));
270 // The size was taken before the read, so a file that shrank in between hands
271 // back fewer bytes than were asked for
272 result.resize(static_cast<std::size_t>(stream.gcount()));
273 return result;
274}
275
285inline auto read_stdin() -> std::string { return read_to_string(std::cin); }
286
305template <typename Callback>
306auto for_each_line(std::istream &stream, Callback callback) -> void {
307 std::string line;
308 while (std::getline(stream, line)) {
309 std::string_view view{line};
310 if (!view.empty() && view.back() == '\r') {
311 view.remove_suffix(1);
312 }
313
314 callback(view);
315 }
316}
317
330SOURCEMETA_CORE_IO_EXPORT
331auto hardlink_directory(const std::filesystem::path &source,
332 const std::filesystem::path &destination) -> void;
333
343SOURCEMETA_CORE_IO_EXPORT
344auto write_file(const std::filesystem::path &path,
345 const std::string_view contents) -> void;
346
359SOURCEMETA_CORE_IO_EXPORT
360auto write_file(const std::filesystem::path &path,
361 const std::span<const std::byte> contents) -> void;
362
377SOURCEMETA_CORE_IO_EXPORT
378auto write_file(const std::filesystem::path &path,
379 const std::function<void(std::ostream &)> &writer) -> void;
380
391SOURCEMETA_CORE_IO_EXPORT
392auto flush(const std::filesystem::path &path) -> void;
393
394} // namespace sourcemeta::core
395
396#endif
SOURCEMETA_CORE_IO_EXPORT auto weakly_canonical(const std::filesystem::path &path) -> std::filesystem::path
SOURCEMETA_CORE_IO_EXPORT auto is_under_path(const std::filesystem::path &path, const std::filesystem::path &prefix) -> bool
auto read_file(const std::filesystem::path &path) -> std::basic_ifstream< CharT, Traits >
Definition io.h:135
auto resume_stream(std::basic_istream< CharT, Traits > &stream, const std::streampos start, const std::streamsize count) -> void
Definition io.h:209
auto read_to_string(std::basic_istream< CharT, Traits > &stream) -> std::basic_string< CharT, Traits >
Definition io.h:168
auto for_each_line(std::istream &stream, Callback callback) -> void
Definition io.h:306
auto read_file_to_string(const std::filesystem::path &path) -> std::basic_string< CharT, Traits >
Definition io.h:232
SOURCEMETA_CORE_IO_EXPORT auto is_lexically_under_path(const std::filesystem::path &path, const std::filesystem::path &prefix) -> bool
SOURCEMETA_CORE_IO_EXPORT auto canonical(const std::filesystem::path &path) -> std::filesystem::path
SOURCEMETA_CORE_IO_EXPORT auto flush(const std::filesystem::path &path) -> void
SOURCEMETA_CORE_IO_EXPORT auto strip_path_prefix(const std::filesystem::path &path, const std::filesystem::path &prefix) -> std::filesystem::path
SOURCEMETA_CORE_IO_EXPORT auto hardlink_directory(const std::filesystem::path &source, const std::filesystem::path &destination) -> void
auto read_stdin() -> std::string
Definition io.h:285
SOURCEMETA_CORE_IO_EXPORT auto write_file(const std::filesystem::path &path, const std::string_view contents) -> void