From e88e0408e7a5863480ec7a5bd5ef697f6cde19c0 Mon Sep 17 00:00:00 2001 From: bergmann Date: Tue, 26 Nov 2019 00:16:11 +0100 Subject: [PATCH] * Initial commit * Implemented 'path' and 'directory' classes (including unit tests) --- .gitmodules | 3 + CMakeLists.txt | 65 +++++++ README.md | 3 + cmake/cppfs-config.cmake | 9 + cmake/cppfs-const.cmake | 28 +++ cmake/cppfs-options.cmake | 11 ++ cmake/cppfs-var.cmake | 32 ++++ cmake/modules | 1 + include/cppfs.h | 6 + include/cppfs/config.h | 26 +++ include/cppfs/directory.h | 11 ++ include/cppfs/directory/directory.h | 125 ++++++++++++ include/cppfs/directory/directory.inl | 81 ++++++++ include/cppfs/directory/directory.linux.inl | 199 ++++++++++++++++++++ include/cppfs/file.h | 10 + include/cppfs/file/file.h | 39 ++++ include/cppfs/file/file.inl | 21 +++ include/cppfs/file/file.linux.inl | 21 +++ include/cppfs/misc.h | 7 + include/cppfs/misc/misc.h | 65 +++++++ include/cppfs/misc/misc.linux.inl | 46 +++++ include/cppfs/path.h | 11 ++ include/cppfs/path/path.h | 149 +++++++++++++++ include/cppfs/path/path.inl | 122 ++++++++++++ include/cppfs/path/path.linux.inl | 118 ++++++++++++ src/CMakeLists.txt | 35 ++++ test/CMakeLists.txt | 59 ++++++ test/cppfs/directory_tests.cpp | 36 ++++ test/cppfs/path_tests.cpp | 112 +++++++++++ 29 files changed, 1451 insertions(+) create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/cppfs-config.cmake create mode 100644 cmake/cppfs-const.cmake create mode 100644 cmake/cppfs-options.cmake create mode 100644 cmake/cppfs-var.cmake create mode 160000 cmake/modules create mode 100644 include/cppfs.h create mode 100644 include/cppfs/config.h create mode 100644 include/cppfs/directory.h create mode 100644 include/cppfs/directory/directory.h create mode 100644 include/cppfs/directory/directory.inl create mode 100644 include/cppfs/directory/directory.linux.inl create mode 100644 include/cppfs/file.h create mode 100644 include/cppfs/file/file.h create mode 100644 include/cppfs/file/file.inl create mode 100644 include/cppfs/file/file.linux.inl create mode 100644 include/cppfs/misc.h create mode 100644 include/cppfs/misc/misc.h create mode 100644 include/cppfs/misc/misc.linux.inl create mode 100644 include/cppfs/path.h create mode 100644 include/cppfs/path/path.h create mode 100644 include/cppfs/path/path.inl create mode 100644 include/cppfs/path/path.linux.inl create mode 100644 src/CMakeLists.txt create mode 100644 test/CMakeLists.txt create mode 100644 test/cppfs/directory_tests.cpp create mode 100644 test/cppfs/path_tests.cpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fee9922 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cmake/modules"] + path = cmake/modules + url = https://git.bergmann89.de/cpp/CMakeModules.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..94dc14a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,65 @@ +# Initialize CMake ################################################################################ + +CMake_Minimum_Required ( VERSION 3.12.0 FATAL_ERROR ) + +# Set CMAKE_BUILD_TYPE +If ( NOT CMAKE_BUILD_TYPE ) + Set ( CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build!" FORCE ) +EndIf ( NOT CMAKE_BUILD_TYPE ) +Set_Property ( CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel ) + +# Set CMAKE_MODULE_PATH +If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) + Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) +EndIf ( ) +If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/cmake" ) + Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/cmake" ) +EndIf ( ) + +# Project ######################################################################################### + +Include ( GNUInstallDirs ) +Include ( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cppfs-options.cmake ) +Include ( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cppfs-const.cmake ) +Include ( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cppfs-var.cmake ) +Project ( ${CPPFS_PROJECT_NAME} + DESCRIPTION "${CPPFS_PROJECT_DESCRIPTION}" + VERSION "${CPPFS_VERSION}" ) +Include ( CTest ) + +Add_Compile_Options ( $<$,$>:-D_GLIBCXX_DEBUG> ) + +# Subdirectories +Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/src ) +Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/test ) + +# Install +If ( NOT CPPFS_HAS_EXPORT + OR NOT CPPFS_INSTALL_PACKAGE ) + Return ( ) +EndIf ( ) + +Include ( CMakePackageConfigHelpers ) +Write_Basic_Package_Version_File ( "${CMAKE_CURRENT_BINARY_DIR}/cmake/cppfs-config-version.cmake" + VERSION ${CPPFS_VERSION} + COMPATIBILITY AnyNewerVersion ) +Configure_File ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cppfs-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/cppfs-config.cmake" + @ONLY ) + +Set ( ConfigPackageLocation "${CPPFS_INSTALL_DIR_SHARE}/cmake" ) +Install ( EXPORT + cppfs + NAMESPACE + cppfs:: + DESTINATION + ${ConfigPackageLocation} ) +Install ( FILES + "${CMAKE_CURRENT_BINARY_DIR}/cmake/cppfs-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/cppfs-config-version.cmake" + DESTINATION + ${ConfigPackageLocation} + COMPONENT + Devel ) diff --git a/README.md b/README.md new file mode 100644 index 0000000..54ab61d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# cppfs + +Readme of the project. diff --git a/cmake/cppfs-config.cmake b/cmake/cppfs-config.cmake new file mode 100644 index 0000000..bfdc840 --- /dev/null +++ b/cmake/cppfs-config.cmake @@ -0,0 +1,9 @@ +# cppfs-config.cmake - package configuration file + +# Include ( CMakeFindDependencyMacro ) +# Find_Dependency ( ) + +Include ( FindPackageHandleStandardArgs ) +Set ( ${CMAKE_FIND_PACKAGE_NAME}_CONFIG ${CMAKE_CURRENT_LIST_FILE} ) +Find_Package_Handle_Standard_Args ( cppfs CONFIG_MODE ) +Include ( "${CMAKE_CURRENT_LIST_DIR}/cppfs.cmake") diff --git a/cmake/cppfs-const.cmake b/cmake/cppfs-const.cmake new file mode 100644 index 0000000..2e59f75 --- /dev/null +++ b/cmake/cppfs-const.cmake @@ -0,0 +1,28 @@ +# This file contains constant variables that are fixed to this project + +# Version +Set ( CPPFS_VERSION_MAJOR 1 ) +Set ( CPPFS_VERSION_MINOR 0 ) +Set ( CPPFS_VERSION_PATCH 0 ) +Set ( CPPFS_VERSION_BUILD 0 ) +Set ( CPPFS_VERSION_HASH "" ) +Set ( CPPFS_VERSION_BEHIND 0 ) +Set ( CPPFS_VERSION_DIRTY 0 ) + +# Names +Set ( CPPFS_PROJECT_NAME "cppfs" ) +Set ( CPPFS_PROJECT_DESCRIPTION "A simple interface library" ) + +# Include generated variables for further usage +Include ( ${CMAKE_CURRENT_LIST_DIR}/cppfs-var.cmake ) + +# Install directories +Set ( CPPFS_INSTALL_DIR_INCLUDE "${CMAKE_INSTALL_INCLUDEDIR}/${CPPFS_NAME}" ) +Set ( CPPFS_INSTALL_DIR_LIB "${CMAKE_INSTALL_LIBDIR}" ) +Set ( CPPFS_INSTALL_DIR_SHARE "${CMAKE_INSTALL_DATAROOTDIR}/${CPPFS_NAME}" ) + +# C Standard +Set ( CMAKE_C_STANDARD 11 ) +Set ( CMAKE_CXX_STANDARD 17 ) +Set ( CMAKE_C_STANDARD_REQUIRED ON ) +Set ( CMAKE_CXX_STANDARD_REQUIRED ON ) diff --git a/cmake/cppfs-options.cmake b/cmake/cppfs-options.cmake new file mode 100644 index 0000000..8449dda --- /dev/null +++ b/cmake/cppfs-options.cmake @@ -0,0 +1,11 @@ +# This file contains options that can be passed to the cmake command + +Option ( CPPFS_INSTALL_HEADER + "Install headers of cppfs." + ON ) +Option ( CPPFS_INSTALL_PACKAGE + "Install the cmake package of cppfs." + ON ) +Option ( CPPFS_USE_GIT_VERSION + "Read the git tags to get the version of cppfs" + ON ) diff --git a/cmake/cppfs-var.cmake b/cmake/cppfs-var.cmake new file mode 100644 index 0000000..ea5e45f --- /dev/null +++ b/cmake/cppfs-var.cmake @@ -0,0 +1,32 @@ +# This file contains generated variables that are needed for the project + +# Git Version +If ( CPPFS_USE_GIT_VERSION ) + Include ( git_helper OPTIONAL RESULT_VARIABLE HAS_GIT_HELPER ) + If ( HAS_GIT_HELPER ) + GitGetVersion ( ${CMAKE_CURRENT_LIST_DIR}/.. + CPPFS_VERSION_MAJOR + CPPFS_VERSION_MINOR + CPPFS_VERSION_PATCH + CPPFS_VERSION_BUILD + CPPFS_VERSION_HASH + CPPFS_VERSION_BEHIND + CPPFS_VERSION_DIRTY ) + EndIf ( ) +EndIf ( ) + +# Strings +Set ( CPPFS_VERSION_SHORT + "${CPPFS_VERSION_MAJOR}.${CPPFS_VERSION_MINOR}" ) +Set ( CPPFS_VERSION + "${CPPFS_VERSION_SHORT}.${CPPFS_VERSION_PATCH}.${CPPFS_VERSION_BUILD}" ) +Set ( CPPFS_VERSION_COMPLETE + "${CPPFS_VERSION}" ) +Set ( CPPFS_NAME + "${CPPFS_PROJECT_NAME}-${CPPFS_VERSION_SHORT}" ) +Set ( CPPFS_OUTPUTNAME + "${CPPFS_PROJECT_NAME}" ) +If ( CPPFS_VERSION_BEHIND ) + Set ( CPPFS_VERSION_COMPLETE + "${CPPFS_VERSION_COMPLETE}+${CPPFS_VERSION_BEHIND}" ) +EndIf ( ) diff --git a/cmake/modules b/cmake/modules new file mode 160000 index 0000000..165bf7c --- /dev/null +++ b/cmake/modules @@ -0,0 +1 @@ +Subproject commit 165bf7c7702ec184b46b046889b62ef4a63afccf diff --git a/include/cppfs.h b/include/cppfs.h new file mode 100644 index 0000000..25a0c95 --- /dev/null +++ b/include/cppfs.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include +#include +#include diff --git a/include/cppfs/config.h b/include/cppfs/config.h new file mode 100644 index 0000000..2903e77 --- /dev/null +++ b/include/cppfs/config.h @@ -0,0 +1,26 @@ +#pragma once + +#define cppfs_os_linux 1 +#define cppfs_os_windows 2 +#define cppfs_os_ios 3 + +#if defined(__linux__) + #define cppfs_os cppfs_os_linux +#else + #error "Unknown or unsupported operation system!" +#endif + +namespace cppfs +{ + +#if cppfs_os == cppfs_os_linux + struct constants + { + static constexpr char path_delimiter = '/'; + static constexpr const char * temp_dir = "/tmp"; + }; + + using fd_handle = int; +#endif + +} diff --git a/include/cppfs/directory.h b/include/cppfs/directory.h new file mode 100644 index 0000000..96f32ee --- /dev/null +++ b/include/cppfs/directory.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "directory/directory.h" + +#include "directory/directory.inl" + +#if cppfs_os == cppfs_os_linux + #include "directory/directory.linux.inl" +#endif diff --git a/include/cppfs/directory/directory.h b/include/cppfs/directory/directory.h new file mode 100644 index 0000000..6b18467 --- /dev/null +++ b/include/cppfs/directory/directory.h @@ -0,0 +1,125 @@ +#pragma once + +#include +#include +#include + +#if cppfs_os == cppfs_os_linux + #include +#endif + +#include "../path.h" +#include "../misc.h" + +namespace cppfs +{ + + struct directory + { + public: + struct entry + { + public: + file_type type; + std::string name; + }; + + struct iterator + : public std::iterator + { + private: + const directory * _owner; + entry _entry; + + public: + inline iterator( + const directory& p_owner, + bool p_end); + + inline iterator( + iterator&& p_other); + + inline iterator( + const iterator& p_other); + + inline iterator& operator=(iterator&& p_other); + inline iterator& operator=(const iterator& p_other); + + inline iterator& operator++(); + inline iterator operator++(int); + + inline bool operator==(const iterator& p_other) const; + inline bool operator!=(const iterator& p_other) const; + + inline reference operator* () const; + inline pointer operator->() const; + + #if cppfs_os == cppfs_os_linux + private: + using dir_ptr_u = std::unique_ptr; + + dir_ptr_u _dir; + struct dirent * _dirent; + + inline void next(); + inline long offset() const; + #endif + }; + + public: + /** + * @brief Get the default file permissions for directories. + */ + static inline const file_permissions& default_permissions(); + + private: + cppfs::path _path; + + public: + /** + * @brief Constructor. + */ + template + inline directory(T_args&&... p_args); + + /** + * @brief Get the path of the directory. + */ + inline const cppfs::path& path() const; + + /** + * @brief Check if the directory exsists. + */ + inline bool exists() const; + + /** + * @brief Create the directory. + */ + inline const directory& create( + bool p_parent, + const file_permissions& p_perm = default_permissions()) const; + + /** + * @brief Remove the directory (and all it's content if the parameter is set to true) + */ + inline const directory& remove( + bool p_recursive) const; + + /** + * @brief Create a begin iterator. + */ + inline iterator begin() const; + + /** + * @brief Create a end iterator. + */ + inline iterator end() const; + + private: + /** + * @brief Remove the directory if it's empty. + */ + inline void remove_dir() const; + }; + +} diff --git a/include/cppfs/directory/directory.inl b/include/cppfs/directory/directory.inl new file mode 100644 index 0000000..39d452f --- /dev/null +++ b/include/cppfs/directory/directory.inl @@ -0,0 +1,81 @@ +#pragma once + +#include "directory.h" +#include "../misc.h" +#include "../file.h" + +namespace cppfs +{ + + /* directory */ + + const file_permissions& directory::default_permissions() + { + static const file_permissions value({ + file_permission::user_read, + file_permission::user_write, + file_permission::user_execute, + + file_permission::group_read, + file_permission::group_write, + file_permission::group_execute, + + file_permission::others_read, + file_permission::others_execute, + }); + + return value; + } + + template + directory::directory(T_args&&... p_args) + : _path(std::forward(p_args)...) + { } + + const cppfs::path& directory::path() const + { return _path; } + + bool directory::exists() const + { return _path.is_directory(); } + + inline const directory& directory::remove( + bool p_recursive) const + { + if (p_recursive) + { + for (auto& e : *this) + { + auto p = path().join(e.name); + switch (e.type) + { + case file_type::directory: + directory(p).remove(true); + break; + + case file_type::file: + case file_type::symbolic_link: + case file_type::hard_link: + case file_type::block_device: + case file_type::char_device: + case file_type::named_pipe: + case file_type::socket: + file(p).remove(); + break; + + case file_type::unknown: + throw cppcore::exception("Unknown file type: " + p.str()); + } + } + } + + remove_dir(); + return *this; + } + + directory::iterator directory::begin() const + { return iterator(*this, false); } + + directory::iterator directory::end() const + { return iterator(*this, true); } + +} diff --git a/include/cppfs/directory/directory.linux.inl b/include/cppfs/directory/directory.linux.inl new file mode 100644 index 0000000..3d05a76 --- /dev/null +++ b/include/cppfs/directory/directory.linux.inl @@ -0,0 +1,199 @@ +#pragma once + +#include + +#include "directory.h" + +namespace cppfs +{ + + /* directory::iterator */ + + directory::iterator::iterator(const directory& p_owner, bool p_end) + : _owner (&p_owner) + , _entry () + , _dir (nullptr, &closedir) + , _dirent (nullptr) + { + if (!p_end) + { + assert(_owner); + auto s = _owner->path().str(); + _dir.reset(opendir(s.c_str())); + if (!_dir) + throw cppcore::error_exception("Unable to open directory", errno); + next(); + } + } + + directory::iterator::iterator(iterator&& p_other) + : _owner (std::move(p_other)._owner) + , _entry (std::move(p_other)._entry) + , _dir (std::move(p_other)._dir) + , _dirent (std::move(p_other)._dirent) + { } + + directory::iterator::iterator(const iterator& p_other) + : _owner (p_other._owner) + , _entry () + , _dir (nullptr, &closedir) + , _dirent (nullptr) + { this->operator=(p_other); } + + directory::iterator& directory::iterator::operator=(iterator&& p_other) + { + _owner = std::move(p_other)._owner; + _entry = std::move(p_other)._entry; + _dir = std::move(p_other)._dir; + _dirent = std::move(p_other)._dirent; + + return *this; + } + + directory::iterator& directory::iterator::operator=(const iterator& p_other) + { + _owner = p_other._owner; + _entry = entry { }; + _dirent = nullptr; + _dir.reset(); + + assert(_owner); + auto s = _owner->path().str(); + _dir.reset(opendir(s.c_str())); + if (!_dir) + throw cppcore::error_exception("Unable to open directory", errno); + + seekdir(_dir.get(), p_other.offset()); + next(); + + return *this; + } + + directory::iterator& directory::iterator::operator++() + { + next(); + return *this; + } + + directory::iterator directory::iterator::operator++(int) + { + auto it = *this; + next(); + return it; + } + + bool directory::iterator::operator==(const iterator& p_other) const + { + return _owner + && p_other._owner + && _owner == p_other._owner + && ( (!_dirent && !p_other._dirent) + || offset() == p_other.offset()); + } + + bool directory::iterator::operator!=(const iterator& p_other) const + { return !this->operator==(p_other); } + + directory::iterator::reference directory::iterator::operator* () const + { + assert(_dirent); + return _entry; + } + + directory::iterator::pointer directory::iterator::operator->() const + { + assert(_dirent); + return &_entry; + } + + void directory::iterator::next() + { + do + { + _dirent = readdir(_dir.get()); + } + while ( + _dirent + && ( ( _dirent->d_name[0] == '.' + && _dirent->d_name[1] == '.' + && _dirent->d_name[2] == '\0') + || ( _dirent->d_name[0] == '.' + && _dirent->d_name[1] == '\0'))); + + if (_dirent) + { + _entry.name = &_dirent->d_name[0]; + switch (_dirent->d_type) + { + case DT_BLK: _entry.type = file_type::block_device; break; + case DT_CHR: _entry.type = file_type::char_device; break; + case DT_DIR: _entry.type = file_type::directory; break; + case DT_FIFO: _entry.type = file_type::named_pipe; break; + case DT_LNK: _entry.type = file_type::symbolic_link; break; + case DT_REG: _entry.type = file_type::file; break; + case DT_SOCK: _entry.type = file_type::socket; break; + default: _entry.type = file_type::unknown; break; + } + } + } + + long directory::iterator::offset() const + { + long ret = -1; + if (_dir) + { + ret = telldir(_dir.get()); + if (ret < 0) + throw cppcore::error_exception("Unable to get current position in directory", errno); + } + return ret; + } + + /* directory */ + + const directory& directory::create( + bool p_parent, + const file_permissions& p_perm) const + { + using namespace std::string_literals; + + auto perm = file_permissions::to_unix(p_perm); + + if (p_parent) + { + std::ostringstream os; + + auto& parts = _path.parts(); + for (auto& p : parts) + { + os << '/' << p; + + struct stat st; + auto s = os.str(); + if (!stat(s.c_str(), &st) && S_ISDIR(st.st_mode)) + continue; + + if (mkdir(s.c_str(), perm)) + throw cppcore::error_exception("Unable to create directory: "s + s, errno); + } + } + else + { + auto& s = _path.str(); + if (mkdir(s.c_str(), perm)) + throw cppcore::error_exception("Unable to create directory: "s + s, errno); + } + + return *this; + } + + void directory::remove_dir() const + { + using namespace std::string_literals; + + auto& p = _path.str(); + if (rmdir(p.c_str())) + throw cppcore::error_exception("Unable to remove directory: "s + p, errno); + } + +} diff --git a/include/cppfs/file.h b/include/cppfs/file.h new file mode 100644 index 0000000..d1dcffa --- /dev/null +++ b/include/cppfs/file.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "file/file.h" + +#include "file/file.inl" +#if cppfs_os == cppfs_os_linux + #include "file/file.linux.inl" +#endif diff --git a/include/cppfs/file/file.h b/include/cppfs/file/file.h new file mode 100644 index 0000000..120d7d5 --- /dev/null +++ b/include/cppfs/file/file.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "../path.h" +#include "../misc.h" + +namespace cppfs +{ + + struct file + { + private: + cppfs::path _path; + + public: + /** + * @brief Constructor. + */ + template + inline file(T_args&&... p_args); + + /** + * @brief Get the path of the file. + */ + inline const cppfs::path& path() const; + + /** + * @brief Check if the file exsists. + */ + inline bool exists() const; + + /** + * @brief Remove the file. + */ + inline const file& remove() const; + }; + +} diff --git a/include/cppfs/file/file.inl b/include/cppfs/file/file.inl new file mode 100644 index 0000000..7e53d3f --- /dev/null +++ b/include/cppfs/file/file.inl @@ -0,0 +1,21 @@ +#pragma once + +#include "file.h" + +namespace cppfs +{ + + /* file */ + + template + file::file(T_args&&... p_args) + : _path(std::forward(p_args)...) + { } + + const cppfs::path& file::path() const + { return _path; } + + bool file::exists() const + { return _path.is_file(); } + +} diff --git a/include/cppfs/file/file.linux.inl b/include/cppfs/file/file.linux.inl new file mode 100644 index 0000000..ac84e97 --- /dev/null +++ b/include/cppfs/file/file.linux.inl @@ -0,0 +1,21 @@ +#pragma once + +#include "file.h" + +namespace cppfs +{ + + /* file */ + + const file& file::remove() const + { + using namespace std::string_literals; + + auto& s = _path.str(); + if (unlink(s.c_str())) + throw cppcore::error_exception("Unable to remove file: "s + _path.str(), errno); + + return *this; + } + +} diff --git a/include/cppfs/misc.h b/include/cppfs/misc.h new file mode 100644 index 0000000..d99baac --- /dev/null +++ b/include/cppfs/misc.h @@ -0,0 +1,7 @@ +#pragma once + +#include "misc/misc.h" + +#if cppfs_os == cppfs_os_linux + #include "misc/misc.linux.inl" +#endif diff --git a/include/cppfs/misc/misc.h b/include/cppfs/misc/misc.h new file mode 100644 index 0000000..2480209 --- /dev/null +++ b/include/cppfs/misc/misc.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +#if cppfs_os == cppfs_os_linux + #include +#endif + +namespace cppfs +{ + + enum class file_type + { + unknown = 0, + directory, + file, + symbolic_link, + hard_link, + block_device, + char_device, + named_pipe, + socket, + }; + + enum class file_permission + { + unknown = 0, + + user_read, + user_write, + user_execute, + + group_read, + group_write, + group_execute, + + others_read, + others_write, + others_execute, + }; + + struct file_permissions + : public cppcore::shifted_flags + { + public: + using base_type = cppcore::shifted_flags; + + public: + using base_type::base_type; + + #if cppfs_os == cppfs_os_linux + public: + /** + * @brief Convert file permissions to unix permissions. + */ + static inline mode_t to_unix(const file_permissions& p_perm); + + /** + * @brief Convert unix permissions to file permissions. + */ + static inline file_permissions from_unix(mode_t p_perm); + #endif + }; + +} diff --git a/include/cppfs/misc/misc.linux.inl b/include/cppfs/misc/misc.linux.inl new file mode 100644 index 0000000..08d7526 --- /dev/null +++ b/include/cppfs/misc/misc.linux.inl @@ -0,0 +1,46 @@ +#pragma once + +#include "misc.h" + +namespace cppfs +{ + + mode_t file_permissions::to_unix(const file_permissions& p_perm) + { + mode_t perm = 0; + + if (p_perm.is_set(file_permission::user_read)) perm |= S_IRUSR; + if (p_perm.is_set(file_permission::user_write)) perm |= S_IWUSR; + if (p_perm.is_set(file_permission::user_execute)) perm |= S_IXUSR; + + if (p_perm.is_set(file_permission::group_read)) perm |= S_IRGRP; + if (p_perm.is_set(file_permission::group_write)) perm |= S_IWGRP; + if (p_perm.is_set(file_permission::group_execute)) perm |= S_IXGRP; + + if (p_perm.is_set(file_permission::others_read)) perm |= S_IROTH; + if (p_perm.is_set(file_permission::others_write)) perm |= S_IWOTH; + if (p_perm.is_set(file_permission::others_execute)) perm |= S_IXOTH; + + return perm; + } + + file_permissions file_permissions::from_unix(mode_t p_perm) + { + file_permissions perm; + + if (p_perm & S_IRUSR) perm.set(file_permission::user_read); + if (p_perm & S_IWUSR) perm.set(file_permission::user_write); + if (p_perm & S_IXUSR) perm.set(file_permission::user_execute); + + if (p_perm & S_IRGRP) perm.set(file_permission::group_read); + if (p_perm & S_IWGRP) perm.set(file_permission::group_write); + if (p_perm & S_IXGRP) perm.set(file_permission::group_execute); + + if (p_perm & S_IROTH) perm.set(file_permission::others_read); + if (p_perm & S_IWOTH) perm.set(file_permission::others_write); + if (p_perm & S_IXOTH) perm.set(file_permission::others_execute); + + return perm; + } + +} diff --git a/include/cppfs/path.h b/include/cppfs/path.h new file mode 100644 index 0000000..a9107c1 --- /dev/null +++ b/include/cppfs/path.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "path/path.h" + +#include "path/path.inl" + +#if cppfs_os == cppfs_os_linux + #include "path/path.linux.inl" +#endif diff --git a/include/cppfs/path/path.h b/include/cppfs/path/path.h new file mode 100644 index 0000000..6819ca0 --- /dev/null +++ b/include/cppfs/path/path.h @@ -0,0 +1,149 @@ +#pragma once + +#include +#include + +#include "../misc.h" + +namespace cppfs +{ + + enum class path_status + { + none = 0, + absoulte, + relative, + }; + + struct path + { + public: + using string_vector = std::vector; + + public: + /** + * @brief Get the path of the current working directory. + */ + static inline path current(); + + private: + path_status _status; + mutable std::string _path; + mutable string_vector _parts; + + public: + /** + * @brief Default construtor. + */ + inline path(); + + /** + * @brief Value construtor. + */ + inline path(const std::string& p_path); + + /** + * @brief Value construtor. + */ + inline path( + string_vector&& p_parts, + path_status p_status); + + /** + * @brief Value construtor. + */ + inline path( + const string_vector& p_parts, + path_status p_status = path_status::none); + + /** + * @brief Value construtor. + */ + inline path( + const std::initializer_list& p_parts, + path_status p_status = path_status::none); + + /** + * @brief Move constructor. + */ + inline path(path&&) = default; + + /** + * @brief Copy constructor. + */ + inline path(const path&) = default; + + /** + * @brief Move assignment constructor. + */ + inline path& operator=(path&&) = default; + + /** + * @brief Copy assignment constructor. + */ + inline path& operator=(const path&) = default; + + public: + /** + * @brief Get the path status. + */ + inline path_status status() const; + + /** + * @brief Get the type of the path. + */ + inline file_type type() const; + + /** + * @brief Get the stored path as string. + */ + inline const std::string& str() const; + + /** + * @brief Get the parts of the path. + */ + inline const string_vector& parts() const; + + /** + * @brief Returns true if the path exists. + */ + inline bool exsists() const; + + /** + * @brief Returns true if the path is a normal file. + */ + inline bool is_file() const; + + /** + * @brief Returns true if the path is a normal directory. + */ + inline bool is_directory() const; + + /** + * @brief Returns the base path of the current stored path. + */ + inline path base() const; + + /** + * @brief Returns the normalized path. + */ + inline path normalize() const; + + /** + * @brief Returns file extension. + */ + inline std::string extension() const; + + /** + * @brief Combines two paths. + */ + inline path join(const path& other) const; + + private: + /** + * @brief Create a path from the parts vector. + */ + std::string make_path() const; + }; + +} diff --git a/include/cppfs/path/path.inl b/include/cppfs/path/path.inl new file mode 100644 index 0000000..d96adfd --- /dev/null +++ b/include/cppfs/path/path.inl @@ -0,0 +1,122 @@ +#pragma once + +#include "path.h" + +namespace cppfs +{ + + /* path */ + + path::path() + : _status (path_status::absoulte) + , _path () + , _parts () + { } + + path::path( + string_vector&& p_parts, + path_status p_status) + : _status (p_status) + , _path () + , _parts (std::move(p_parts)) + { } + + path::path( + const string_vector& p_parts, + path_status p_status) + : _status (p_status) + , _path () + , _parts (p_parts) + { } + + path::path( + const std::initializer_list& p_parts, + path_status p_status) + : _status (p_status) + , _path () + , _parts (std::begin(p_parts), std::end(p_parts)) + { } + + path_status path::status() const + { return _status; } + + const std::string& path::str() const + { + if (_path.empty()) + _path = make_path(); + + return _path; + } + + const path::string_vector& path::parts() const + { return _parts; } + + bool path::exsists() const + { return type() != file_type::unknown; } + + bool path::is_file() const + { + auto t = type(); + return t != file_type::unknown + && t != file_type::directory; + } + + bool path::is_directory() const + { return type() == file_type::directory; } + + path path::base() const + { + auto ret = normalize(); + + if (!ret._parts.empty()) + ret._parts.pop_back(); + + return ret; + } + + path path::normalize() const + { + string_vector parts; + if (_status != path_status::absoulte) + parts = current().parts(); + + for (auto& p : _parts) + { + if (p == ".." && !parts.empty()) + parts.pop_back(); + else if (p != ".") + parts.emplace_back(p); + } + + return path(std::move(parts), path_status::absoulte); + } + + std::string path::extension() const + { + if (_parts.empty()) + return std::string(); + + auto& s = _parts.back(); + auto p = s.find_last_of('.'); + if (p == std::string::npos || p == 0) + return std::string(); + + return s.substr(p + 1); + } + + path path::join(const path& other) const + { + switch (other._status) + { + case path_status::absoulte: + return other; + + default: + auto parts = _parts; + for (auto& p : other.parts()) + parts.emplace_back(p); + return path(std::move(parts), _status); + } + } + +} diff --git a/include/cppfs/path/path.linux.inl b/include/cppfs/path/path.linux.inl new file mode 100644 index 0000000..34e3794 --- /dev/null +++ b/include/cppfs/path/path.linux.inl @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +#include "path.h" + +namespace cppfs +{ + + path path::current() + { + char buf[PATH_MAX + 1]; + + if (!getcwd(&buf[0], sizeof(buf)-1)) + throw cppcore::error_exception(errno); + + return path(&buf[0]); + } + + path::path(const std::string& p_path) + : _status (path_status::none) + , _path (p_path) + , _parts () + { + if (!_path.empty()) + { + if (_path[0] == '.') + { + _status = path_status::relative; + } + else if (_path[0] == '/' || _path[0] == '\\') + { + _status = path_status::absoulte; + } + } + + bool ok = cppcore::string_split( + _path, + [](char c) { + return c == '\\' + || c == '/'; + }, + [this](auto&& s){ + if (_parts.empty() && s.empty()) + return true; + + if (s != ".") + _parts.emplace_back(std::move(s)); + + return true; + }); + + if (!ok) + throw cppcore::exception("Error while splitting path into parts"); + } + + file_type path::type() const + { + struct stat st; + auto& s = str(); + + if (stat(s.c_str(), &st)) + return file_type::unknown; + + switch (st.st_mode & S_IFMT) + { + case S_IFSOCK: return file_type::socket; + case S_IFLNK: return file_type::symbolic_link; + case S_IFREG: return file_type::file; + case S_IFBLK: return file_type::block_device; + case S_IFDIR: return file_type::directory; + case S_IFCHR: return file_type::char_device; + case S_IFIFO: return file_type::named_pipe; + default: return file_type::unknown; + } + } + + std::string path::make_path() const + { + std::ostringstream ss; + + switch (_status) + { + case path_status::none: + break; + + case path_status::absoulte: + ss << constants::path_delimiter; + break; + + case path_status::relative: + ss << '.' << constants::path_delimiter; + break; + } + + bool first = true; + for (auto& part : _parts) + { + if (first) + first = false; + else + ss << constants::path_delimiter; + + ss << part; + } + + + return ss.str(); + } + +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..f7fe906 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,35 @@ +# Initialize ###################################################################################### + +Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE ) +Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC ) +Include ( strip_symbols OPTIONAL RESULT_VARIABLE HAS_STRIP_SYMBOLS ) + +FInd_Package ( cppcore REQUIRED ) + +# Interface Library ############################################################################### + +Set ( CPPFS_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include ) +Add_Library ( cppfs INTERFACE ) +Target_Include_Directories ( cppfs + INTERFACE + $ + $ ) +Target_Link_Libraries ( cppfs + INTERFACE + cppcore::cppcore ) + +# Install ######################################################################################### + +Set ( CPPFS_HAS_EXPORT False PARENT_SCOPE ) + +# Header +If ( CPPFS_INSTALL_HEADER ) + Set ( CPPFS_HAS_EXPORT True PARENT_SCOPE ) + Install ( FILES ${CPPFS_INCLUDE_DIR}/cppfs.h + DESTINATION ${CPPFS_INSTALL_DIR_INCLUDE} ) + Install ( DIRECTORY ${CPPFS_INCLUDE_DIR}/cppfs + DESTINATION ${CPPFS_INSTALL_DIR_INCLUDE} ) + Install ( TARGETS cppfs + EXPORT cppfs + DESTINATION ${CPPFS_INSTALL_DIR_INCLUDE} ) +EndIf ( ) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..c97425f --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,59 @@ +# Initialize ###################################################################################### + +Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE ) +Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC ) +Include ( cmake_tests OPTIONAL RESULT_VARIABLE HAS_CMAKE_TESTS ) + +Find_Package ( Sanitizers QUIET ) + +# Test ############################################################################################ + +Find_Package ( GTest ) +If ( NOT "${GTest_FOUND}" ) + Return ( ) +EndIf ( ) + +File ( GLOB_RECURSE CPPFS_TEST_HEADER_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/*.h ) +File ( GLOB_RECURSE CPPFS_TEST_INLINE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/*.inl ) +File ( GLOB_RECURSE CPPFS_TEST_SOURCE_FILES + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ) + +ForEach ( FILE IN LISTS CPPFS_TEST_SOURCE_FILES ) + # add test + Get_Filename_Component ( TEST_DIR ${FILE} DIRECTORY ) + Get_Filename_Component ( TEST_NAME ${FILE} NAME_WE ) + Set ( TEST_NAME "${TEST_DIR}/${TEST_NAME}" ) + String ( REPLACE "\\" "-" TEST_NAME "${TEST_NAME}" ) + String ( REPLACE "/" "-" TEST_NAME "${TEST_NAME}" ) + String ( REPLACE "_" "-" TEST_NAME "${TEST_NAME}" ) + Set ( TEST_NAME "test-${TEST_NAME}" ) + Add_Executable ( ${TEST_NAME} + EXCLUDE_FROM_ALL + ${CPPFS_TEST_HEADER_FILES} + ${CPPFS_TEST_INLINE_FILES} + ${FILE} ) + Target_Link_Libraries ( ${TEST_NAME} + PUBLIC + cppfs + GTest::Main ) + + # Sanitizers + If ( Sanitizers_FOUND ) + Add_Sanitizers ( ${TEST_NAME} ) + EndIf ( ) + + # pedantic + If ( HAS_PEDANTIC ) + Pedantic_Apply_Flags_Target ( ${TEST_NAME} ALL ) + EndIf ( ) + + # test + If ( HAS_CMAKE_TESTS ) + Add_CMake_Test ( NAME ${TEST_NAME} TARGET ${TEST_NAME} GROUP cppfs ) + Else ( ) + Add_Test ( NAME ${TEST_NAME} COMMAND ${TEST_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) + EndIf ( ) +EndForEach ( ) diff --git a/test/cppfs/directory_tests.cpp b/test/cppfs/directory_tests.cpp new file mode 100644 index 0000000..bfc738d --- /dev/null +++ b/test/cppfs/directory_tests.cpp @@ -0,0 +1,36 @@ +#include + +#include + +using namespace ::testing; +using namespace ::cppfs; + +TEST(directory_tests, create) +{ + EXPECT_ANY_THROW(directory("/fuu/bar/baz").create(false)); + + EXPECT_FALSE(directory("/tmp/fuu/bar/baz").exists()); + directory("/tmp/fuu/bar/baz").create(true).remove(false); +} + +TEST(directory_tests, exists) +{ + EXPECT_TRUE (directory("./cppfs").exists()); + EXPECT_FALSE(directory("./cppfs/directory_tests.cpp").exists()); +} + +TEST(directory_tests, iterator) +{ + directory dir("./cppfs"); + + std::vector files; + std::transform(dir.begin(), dir.end(), std::back_inserter(files), [](auto& e) { return e.name; }); + std::sort(files.begin(), files.end()); + + EXPECT_EQ( + files, + std::vector({ + "directory_tests.cpp", + "path_tests.cpp", + })); +} diff --git a/test/cppfs/path_tests.cpp b/test/cppfs/path_tests.cpp new file mode 100644 index 0000000..73b5c68 --- /dev/null +++ b/test/cppfs/path_tests.cpp @@ -0,0 +1,112 @@ +#include + +#include + +using namespace ::testing; +using namespace ::cppfs; + +TEST(path_tests, ctor_string) +{ + path p("some/test/path"); + + EXPECT_EQ(p.status(), path_status::none); + EXPECT_EQ(p.parts(), path::string_vector({ "some", "test", "path" })); +} + +TEST(path_tests, ctor_string_relative) +{ + path p0("./some/test/path"); + + EXPECT_EQ(p0.status(), path_status::relative); + EXPECT_EQ(p0.parts(), path::string_vector({ "some", "test", "path" })); + + path p1("../some/test/path"); + + EXPECT_EQ(p1.status(), path_status::relative); + EXPECT_EQ(p1.parts(), path::string_vector({ "..", "some", "test", "path" })); +} + +TEST(path_tests, ctor_string_absolute) +{ + path p("/some/test/path"); + + EXPECT_EQ(p.status(), path_status::absoulte); + EXPECT_EQ(p.parts(), path::string_vector({ "some", "test", "path" })); +} + +TEST(path_tests, ctor_parts) +{ + EXPECT_EQ("some/test/path", path({ "some", "test", "path" }, path_status::none).str()); + EXPECT_EQ("/some/test/path", path({ "some", "test", "path" }, path_status::absoulte).str()); + EXPECT_EQ("./some/test/path", path({ "some", "test", "path" }, path_status::relative).str()); +} + +TEST(path_tests, exsists) +{ + EXPECT_TRUE (path("./cppfs") + .exsists()); + EXPECT_TRUE (path("./cppfs/path_tests.cpp") + .exsists()); + EXPECT_TRUE (path("./cppfs/directory_tests.cpp") + .exsists()); + EXPECT_FALSE(path("./cppfs/asd") + .exsists()); +} + +TEST(path_tests, is_file) +{ + EXPECT_FALSE(path("./cppfs") + .is_file()); + EXPECT_TRUE (path("./cppfs/path_tests.cpp") + .is_file()); + EXPECT_TRUE (path("./cppfs/directory_tests.cpp") + .is_file()); + EXPECT_FALSE(path("./cppfs/asd") + .is_file()); +} + +TEST(path_tests, is_directory) +{ + EXPECT_TRUE (path("./cppfs") + .is_directory()); + EXPECT_FALSE(path("./cppfs/path_tests.cpp") + .is_directory()); + EXPECT_FALSE(path("./cppfs/directory_tests.cpp") + .is_directory()); + EXPECT_FALSE(path("./cppfs/asd") + .is_directory()); +} + +TEST(path_tests, base) +{ + auto cwd = path::current().str(); + + EXPECT_EQ(path("./cppfs/fuuu/bar").base().str(), cwd + "/cppfs/fuuu"); + EXPECT_EQ(path("/fuu/bar/../baz").base().str(), "/fuu"); +} + +TEST(path_tests, normalize) +{ + auto cwd = path::current().str(); + + EXPECT_EQ(path("fuu/../bar/./biz/baz/..").normalize().str(), cwd + "/bar/biz"); + EXPECT_EQ(path("fuu/../bar/./biz/baz/..").normalize().str(), cwd + "/bar/biz"); + EXPECT_EQ(path("/fuu/../bar/./biz/baz/..").normalize().str(), "/bar/biz"); +} + +TEST(path_tests, extension) +{ + EXPECT_EQ(path("fuu/fuu").extension(), ""); + EXPECT_EQ(path("fuu/.fuu").extension(), ""); + EXPECT_EQ(path("fuu/fuu.bar").extension(), "bar"); + EXPECT_EQ(path("fuu/fuu.bar.biz").extension(), "biz"); +} + +TEST(path_tests, join) +{ + using namespace std::string_literals; + + EXPECT_EQ(path("fuu/fuu").join("/bar"s).str(), "/bar"); + EXPECT_EQ(path("fuu/fuu").join("./bar"s).str(), "fuu/fuu/bar"); + EXPECT_EQ(path("fuu/fuu").join("bar"s).str(), "fuu/fuu/bar"); +}