diff --git a/CMakeLists.txt b/CMakeLists.txt index e91c005..13a8c36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,7 @@ project(tmp LANGUAGES CXX) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") find_package(Filesystem REQUIRED) -add_library(${PROJECT_NAME} STATIC - lib/directory.cpp - lib/file.cpp - lib/path.cpp) +add_library(${PROJECT_NAME} STATIC src/tmp.cpp) add_library(tmp::tmp ALIAS ${PROJECT_NAME}) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) diff --git a/lib/directory.cpp b/lib/directory.cpp deleted file mode 100644 index d53befa..0000000 --- a/lib/directory.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include - -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace { - -/// Creates a temporary directory with the given prefix in the system's -/// temporary directory, and returns its path -/// @param prefix The prefix to use for the temporary file name -/// @returns A path to the created temporary file -/// @throws fs::filesystem_error if the temporary directory cannot be created -fs::path create(std::string_view prefix) { - std::string pattern = tmp::path::make_pattern(prefix); - if (mkdtemp(pattern.data()) == nullptr) { - std::error_code ec = std::error_code(errno, std::system_category()); - throw fs::filesystem_error("Cannot create temporary directory", ec); - } - - return pattern; -} -} // namespace - -namespace tmp { - -directory::directory(std::string_view prefix) - : path(create(prefix)) {} - -fs::path directory::operator/(std::string_view source) const { - return static_cast(*this) / source; -} - -directory::~directory() noexcept = default; - -directory::directory(directory&&) noexcept = default; -directory& directory::operator=(directory&&) noexcept = default; -} // namespace tmp diff --git a/lib/file.cpp b/lib/file.cpp deleted file mode 100644 index 3743b3b..0000000 --- a/lib/file.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace { - -/// Creates a temporary file with the given prefix in the system's -/// temporary directory, and returns its path -/// @param prefix The prefix to use for the temporary file name -/// @returns A path to the created temporary file -/// @throws fs::filesystem_error if the temporary file cannot be created -fs::path create(std::string_view prefix) { - std::string pattern = tmp::path::make_pattern(prefix); - if (mkstemp(pattern.data()) == -1) { - std::error_code ec = std::error_code(errno, std::system_category()); - throw fs::filesystem_error("Cannot create temporary file", ec); - } - - return pattern; -} - -/// Opens a temporary file for writing and returns an output file stream -/// @param file The file to open -/// @param binary Whether to open the file in binary mode -/// @param append Whether to append to the end of the file -/// @returns An output file stream -std::ofstream stream(const tmp::file& file, bool binary, bool append) noexcept { - std::ios::openmode mode = append ? std::ios::app : std::ios::trunc; - if (binary) { - mode |= std::ios::binary; - } - - return std::ofstream(static_cast(file), mode); -} -} // namespace - -namespace tmp { - -file::file(std::string_view prefix) - : file(prefix, /*binary=*/true) {} - -file::file(std::string_view prefix, bool binary) - : path(create(prefix)), - binary(binary) {} - -file file::text(std::string_view prefix) { - return file(prefix, /*binary=*/false); -} - -std::ifstream file::read() const { - const fs::path& file = *this; - return binary - ? std::ifstream(file, std::ios::binary) - : std::ifstream(file); -} - -std::string file::slurp() const { - std::ifstream stream = read(); - return std::string(std::istreambuf_iterator(stream), {}); -} - -void file::write(std::string_view content) const { - stream(*this, binary, /*append=*/false) << content; -} - -void file::append(std::string_view content) const { - stream(*this, binary, /*append=*/true) << content; -} - -file::~file() noexcept = default; - -file::file(file&&) noexcept = default; -file& file::operator=(file&&) noexcept = default; -} // namespace tmp diff --git a/lib/path.cpp b/lib/path.cpp deleted file mode 100644 index 11466c8..0000000 --- a/lib/path.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include - -#include -#include -#include - -namespace fs = std::filesystem; - -namespace { - -/// Options for recursive overwriting coping -const fs::copy_options copy_options = fs::copy_options::recursive - | fs::copy_options::overwrite_existing; - -/// Creates the parent directory of the given path if it does not exist -/// @param path The path for which the parent directory needs to be created -/// @throws fs::filesystem_error if cannot create the parent -void create_parent(const fs::path& path) { - fs::create_directories(path.parent_path()); -} - -/// Deletes the given path recursively, ignoring any errors -/// @param path The path of the directory to remove -void remove(const tmp::path& path) noexcept { - if (!path->empty()) { - std::error_code ec; - fs::remove_all(path, ec); - } -} - -/// Throws a filesystem error indicating that a temporary resource cannot be -/// moved to the specified path -/// @param to The target path where the resource was intended to be moved -/// @param ec The error code associated with the failure to move the resource -/// @throws fs::filesystem_error when called -[[noreturn]] void throw_move_error(const fs::path& to, std::error_code ec) { - throw fs::filesystem_error("Cannot move temporary resource", to, ec); -} -} // namespace - -namespace tmp { - -path::path(fs::path path) - : underlying(std::move(path)) {} - -path::path(path&& other) noexcept - : underlying(other.release()) {} - -path& path::operator=(path&& other) noexcept { - remove(*this); - underlying = other.release(); - return *this; -} - -path::~path() noexcept { - remove(*this); -} - -path::operator const fs::path&() const noexcept { - return underlying; -} - -const fs::path* path::operator->() const noexcept { - return std::addressof(underlying); -} - -fs::path path::release() noexcept { - fs::path path = std::move(underlying); - underlying.clear(); - return path; -} - -void path::move(const fs::path& to) { - create_parent(to); - - std::error_code ec; - fs::rename(*this, to, ec); - if (ec == std::errc::cross_device_link) { - if (fs::is_regular_file(*this) && fs::is_directory(to)) { - ec = std::make_error_code(std::errc::is_a_directory); - throw_move_error(to, ec); - } - - fs::remove_all(to); - fs::copy(*this, to, copy_options, ec); - } - - if (ec) { - throw_move_error(to, ec); - } - - remove(*this); - release(); -} - -fs::path path::make_pattern(std::string_view prefix) { - fs::path pattern = fs::temp_directory_path() / prefix / "XXXXXX"; - create_parent(pattern); - - return pattern; -} -} // namespace tmp diff --git a/src/tmp.cpp b/src/tmp.cpp new file mode 100644 index 0000000..ad1ef56 --- /dev/null +++ b/src/tmp.cpp @@ -0,0 +1,208 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace { + +/// Options for recursive overwriting coping +const fs::copy_options copy_options = fs::copy_options::recursive + | fs::copy_options::overwrite_existing; + +/// Creates the parent directory of the given path if it does not exist +/// @param path The path for which the parent directory needs to be created +/// @throws fs::filesystem_error if cannot create the parent +void create_parent(const fs::path& path) { + fs::create_directories(path.parent_path()); +} + +/// Deletes the given path recursively, ignoring any errors +/// @param path The path of the directory to remove +void remove(const tmp::path& path) noexcept { + if (!path->empty()) { + std::error_code ec; + fs::remove_all(path, ec); + } +} + +/// Throws a filesystem error indicating that a temporary resource cannot be +/// moved to the specified path +/// @param to The target path where the resource was intended to be moved +/// @param ec The error code associated with the failure to move the resource +/// @throws fs::filesystem_error when called +[[noreturn]] void throw_move_error(const fs::path& to, std::error_code ec) { + throw fs::filesystem_error("Cannot move temporary resource", to, ec); +} + +/// Creates a temporary file with the given prefix in the system's +/// temporary directory, and returns its path +/// @param prefix The prefix to use for the temporary file name +/// @returns A path to the created temporary file +/// @throws fs::filesystem_error if the temporary file cannot be created +fs::path create_file(std::string_view prefix) { + std::string pattern = tmp::path::make_pattern(prefix); + if (mkstemp(pattern.data()) == -1) { + std::error_code ec = std::error_code(errno, std::system_category()); + throw fs::filesystem_error("Cannot create temporary file", ec); + } + + return pattern; +} + +/// Creates a temporary directory with the given prefix in the system's +/// temporary directory, and returns its path +/// @param prefix The prefix to use for the temporary file name +/// @returns A path to the created temporary file +/// @throws fs::filesystem_error if the temporary directory cannot be created +fs::path create_directory(std::string_view prefix) { + std::string pattern = tmp::path::make_pattern(prefix); + if (mkdtemp(pattern.data()) == nullptr) { + std::error_code ec = std::error_code(errno, std::system_category()); + throw fs::filesystem_error("Cannot create temporary directory", ec); + } + + return pattern; +} + +/// Opens a temporary file for writing and returns an output file stream +/// @param file The file to open +/// @param binary Whether to open the file in binary mode +/// @param append Whether to append to the end of the file +/// @returns An output file stream +std::ofstream stream(const tmp::file& file, bool binary, bool append) noexcept { + std::ios::openmode mode = append ? std::ios::app : std::ios::trunc; + if (binary) { + mode |= std::ios::binary; + } + + return std::ofstream(static_cast(file), mode); +} +} // namespace + +namespace tmp { + +path::path(fs::path path) + : underlying(std::move(path)) {} + +path::path(path&& other) noexcept + : underlying(other.release()) {} + +path& path::operator=(path&& other) noexcept { + remove(*this); + underlying = other.release(); + return *this; +} + +path::~path() noexcept { + remove(*this); +} + +path::operator const fs::path&() const noexcept { + return underlying; +} + +const fs::path* path::operator->() const noexcept { + return std::addressof(underlying); +} + +fs::path path::release() noexcept { + fs::path path = std::move(underlying); + underlying.clear(); + return path; +} + +void path::move(const fs::path& to) { + create_parent(to); + + std::error_code ec; + fs::rename(*this, to, ec); + if (ec == std::errc::cross_device_link) { + if (fs::is_regular_file(*this) && fs::is_directory(to)) { + ec = std::make_error_code(std::errc::is_a_directory); + throw_move_error(to, ec); + } + + fs::remove_all(to); + fs::copy(*this, to, copy_options, ec); + } + + if (ec) { + throw_move_error(to, ec); + } + + remove(*this); + release(); +} + +fs::path path::make_pattern(std::string_view prefix) { + fs::path pattern = fs::temp_directory_path() / prefix / "XXXXXX"; + create_parent(pattern); + + return pattern; +} +} // namespace tmp + + +namespace tmp { + +file::file(std::string_view prefix) + : file(prefix, /*binary=*/true) {} + +file::file(std::string_view prefix, bool binary) + : path(create_file(prefix)), + binary(binary) {} + +file file::text(std::string_view prefix) { + return file(prefix, /*binary=*/false); +} + +std::ifstream file::read() const { + const fs::path& file = *this; + return binary + ? std::ifstream(file, std::ios::binary) + : std::ifstream(file); +} + +std::string file::slurp() const { + std::ifstream stream = read(); + return std::string(std::istreambuf_iterator(stream), {}); +} + +void file::write(std::string_view content) const { + stream(*this, binary, /*append=*/false) << content; +} + +void file::append(std::string_view content) const { + stream(*this, binary, /*append=*/true) << content; +} + +file::~file() noexcept = default; + +file::file(file&&) noexcept = default; +file& file::operator=(file&&) noexcept = default; +} // namespace tmp + + +namespace tmp { + +directory::directory(std::string_view prefix) + : path(create_directory(prefix)) {} + +fs::path directory::operator/(std::string_view source) const { + return static_cast(*this) / source; +} + +directory::~directory() noexcept = default; + +directory::directory(directory&&) noexcept = default; +directory& directory::operator=(directory&&) noexcept = default; +} // namespace tmp