Compare commits

..

1 Commits

Author SHA1 Message Date
Martchus a5b0b0b1e1 Add archiving utilities using libarchive 2024-03-03 20:53:11 +01:00
18 changed files with 415 additions and 192 deletions

View File

@ -96,6 +96,10 @@ set(CMAKE_TEMPLATE_FILES
cmake/templates/global.h.in
cmake/templates/version.h.in
cmake/templates/template.pc.in)
set(SCRIPT_FILES)
if (MINGW)
list(APPEND SCRIPT_FILES scripts/wine.sh)
endif ()
if (WIN32)
list(APPEND CMAKE_TEMPLATE_FILES cmake/templates/windows.rc.in cmake/templates/windows-cli-wrapper.rc.in
cmake/templates/cli-wrapper.cpp)
@ -117,7 +121,7 @@ set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
set(META_APP_DESCRIPTION "Useful C++ classes and routines such as argument parser, IO and conversion utilities")
set(META_VERSION_MAJOR 5)
set(META_VERSION_MINOR 24)
set(META_VERSION_PATCH 9)
set(META_VERSION_PATCH 7)
# find required 3rd party libraries
include(3rdParty)
@ -187,13 +191,27 @@ if (REQUIRED_BOOST_COMPONENTS)
endif ()
# configure required libraries for std::filesystem
option(USE_STANDARD_FILESYSTEM "uses std::filesystem; if disabled Bash completion for files and directories is not working"
option(USE_STANDARD_FILESYSTEM "uses std::filesystem; if disabled Bash completion for files and directories and archiving utilities are disabled"
ON)
if (USE_STANDARD_FILESYSTEM)
list(APPEND META_PRIVATE_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_STANDARD_FILESYSTEM)
use_standard_filesystem()
else ()
message(WARNING "The use of std::filesystem has been disabled. Bash completion for files and directories will not work.")
message(WARNING "The use of std::filesystem has been disabled. Bash completion for files and directories will not work and archiving utilities are disabled.")
endif ()
# configure usage of libarchive
option(USE_LIBARCHIVE "uses libarchive; if disabled archiving utilities will not be available" OFF)
if (USE_LIBARCHIVE)
if (NOT USE_STANDARD_FILESYSTEM)
message(FATAL_ERROR "Unable to use USE_LIBARCHIVE without USE_STANDARD_FILESYSTEM.")
endif ()
use_package(TARGET_NAME LibArchive::LibArchive PACKAGE_NAME LibArchive)
list(APPEND HEADER_FILES io/archive.h)
list(APPEND SRC_FILES io/archive.cpp)
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_LIBARCHIVE)
else ()
set(EXCLUDED_FILES io/archive.h io/archive.cpp)
endif ()
# configure whether escape codes should be enabled by default

View File

@ -22,23 +22,14 @@
}
},
{
"name": "clang",
"name": "libc++",
"inherits": "default",
"displayName": "Use clang/clang++",
"description": "Enforces use of clang/clang++ even when it is not the system default",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-clang",
"displayName": "Use clang++ and libc++",
"description": "Enforces use of clang++ and libc++ even when it is not the system default",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-no-webview",
"cacheVariables": {
"CMAKE_C_COMPILER": {"type": "STRING", "value": "clang"},
"CMAKE_CXX_COMPILER": {"type": "STRING", "value": "clang++"}
}
},
{
"name": "libc++",
"inherits": "clang",
"displayName": "Use clang/clang++ and libc++",
"description": "Enforces use of clang/clang++ and libc++ even when it is not the system default",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-clang-libc++",
"cacheVariables": {
"CMAKE_CXX_COMPILER": {"type": "STRING", "value": "clang++"},
"CMAKE_CXX_FLAGS": {"type": "STRING", "value": "$env{CXXFLAGS} -stdlib=libc++"}
}
},
@ -90,13 +81,6 @@
"CONFIGURATION_TARGET_SUFFIX": {"type": "STRING", "value": "devel"}
}
},
{
"name": "devel-clang",
"inherits": ["devel", "clang"],
"displayName": "Development config using clang",
"description": "Combination of devel and libc++",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-clang"
},
{
"name": "devel-libc++",
"inherits": ["devel", "libc++"],
@ -132,13 +116,6 @@
"CMAKE_UNITY_BUILD": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "devel-clang-qt6",
"inherits": ["qt6", "devel-clang"],
"displayName": "Development config using clang and Qt 6",
"description": "Combination of qt6 and devel-clang",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-clang-qt6"
},
{
"name": "devel-libc++-qt6",
"inherits": ["qt6", "devel-libc++"],
@ -185,42 +162,9 @@
"CMAKE_INSTALL_PREFIX": {"type": "PATH", "value": "$env{KDE_INSTALL_DIR}"}
}
},
{
"name": "arch-*-w64-mingw32",
"inherits": ["no-webview", "no-kde"],
"environment": {
"CPPFLAGS": "-D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS",
"CFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"CXXFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"LDFLAGS": "-Wl,-O1,--sort-common,--as-needed -fstack-protector"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"},
"VERSIONED_MINGW_LIBRARIES": {"type": "BOOL", "value": "ON"},
"ENABLE_TARGETS_FOR_MINGW_CROSS_PACKAGING": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "arch-i686-w64-mingw32",
"inherits": "arch-*-w64-mingw32",
"displayName": "Target i686-w64-mingw32 using Arch Linux's mingw-w64 packaging",
"description": "Build targeting i686-w64-mingw32, paths and flags are specific to Arch Linux's mingw-w64 packaging",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32",
"toolchainFile": "/usr/share/mingw/toolchain-i686-w64-mingw32.cmake",
"environment": {
"CROSS_TOOL_PREFIX": "i686-w64-mingw32-",
"CROSS_INSTALL_PREFIX": "/usr/i686-w64-mingw32",
"PATH": "$env{CROSS_INSTALL_PREFIX}/bin:$penv{PATH}"
},
"cacheVariables": {
"CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CROSSCOMPILING_EMULATOR": {"type": "PATH", "value": "/usr/bin/i686-w64-mingw32-wine"}
}
},
{
"name": "arch-x86_64-w64-mingw32",
"inherits": "arch-*-w64-mingw32",
"inherits": ["no-webview", "no-kde"],
"displayName": "Target x86_64-w64-mingw32 using Arch Linux's mingw-w64 packaging",
"description": "Build targeting x86_64-w64-mingw32, paths and flags are specific to Arch Linux's mingw-w64 packaging",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32",
@ -228,28 +172,20 @@
"environment": {
"CROSS_TOOL_PREFIX": "x86_64-w64-mingw32-",
"CROSS_INSTALL_PREFIX": "/usr/x86_64-w64-mingw32",
"CPPFLAGS": "-D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS",
"CFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"CXXFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"LDFLAGS": "-Wl,-O1,--sort-common,--as-needed -fstack-protector",
"PATH": "$env{CROSS_INSTALL_PREFIX}/bin:$penv{PATH}"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"},
"VERSIONED_MINGW_LIBRARIES": {"type": "BOOL", "value": "ON"},
"CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CROSSCOMPILING_EMULATOR": {"type": "PATH", "value": "/usr/bin/x86_64-w64-mingw32-wine"}
}
},
{
"name": "arch-i686-w64-mingw32-static",
"inherits": "arch-i686-w64-mingw32",
"displayName": "Target i686-w64-mingw32 using Arch Linux's mingw-w64 packaging (static)",
"description": "Build targeting i686-w64-mingw32, paths and flags are specific to Arch Linux's mingw-w64 packaging",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-static",
"toolchainFile": "/usr/share/mingw/toolchain-i686-w64-mingw32-static.cmake",
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
"CMAKE_FIND_LIBRARY_SUFFIXES": {"type": "STRING", "value": ".a;.lib"},
"STATIC_LIBRARY_LINKAGE": {"type": "BOOL", "value": "ON"},
"STATIC_LINKAGE": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "arch-x86_64-w64-mingw32-static",
"inherits": "arch-x86_64-w64-mingw32",
@ -258,19 +194,9 @@
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-static",
"toolchainFile": "/usr/share/mingw/toolchain-x86_64-w64-mingw32-static.cmake",
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
"CMAKE_FIND_LIBRARY_SUFFIXES": {"type": "STRING", "value": ".a;.lib"},
"STATIC_LIBRARY_LINKAGE": {"type": "BOOL", "value": "ON"},
"STATIC_LINKAGE": {"type": "BOOL", "value": "ON"}
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"}
}
},
{
"name": "arch-i686-w64-mingw32-qt6",
"inherits": ["qt6", "arch-i686-w64-mingw32"],
"displayName": "Combination of qt6 and arch-i686-w64-mingw32",
"description": "See description of qt6 and arch-i686-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-qt6"
},
{
"name": "arch-x86_64-w64-mingw32-qt6",
"inherits": ["qt6", "arch-x86_64-w64-mingw32"],
@ -278,13 +204,6 @@
"description": "See description of qt6 and arch-x86_64-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-qt6"
},
{
"name": "arch-i686-w64-mingw32-static-qt6",
"inherits": ["qt6", "arch-i686-w64-mingw32-static"],
"displayName": "Combination of qt6 and arch-i686-w64-mingw32-static",
"description": "See description of qt6 and arch-i686-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-static-qt6"
},
{
"name": "arch-x86_64-w64-mingw32-static-qt6",
"inherits": ["qt6", "arch-x86_64-w64-mingw32-static"],
@ -292,13 +211,6 @@
"description": "See description of qt6 and arch-x86_64-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-static-qt6"
},
{
"name": "arch-i686-w64-mingw32-devel",
"inherits": ["devel", "arch-i686-w64-mingw32"],
"displayName": "Combination of devel and arch-i686-w64-mingw32",
"description": "See descriptions of devel and arch-i686-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-devel"
},
{
"name": "arch-x86_64-w64-mingw32-devel",
"inherits": ["devel", "arch-x86_64-w64-mingw32"],
@ -306,34 +218,6 @@
"description": "See descriptions of devel and arch-x86_64-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-devel"
},
{
"name": "arch-i686-w64-mingw32-devel-qt6",
"inherits": ["qt6", "devel", "arch-i686-w64-mingw32"],
"displayName": "Combination of qt6, devel and arch-i686-w64-mingw32",
"description": "See descriptions of devel and arch-i686-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-devel"
},
{
"name": "arch-x86_64-w64-mingw32-devel-qt6",
"inherits": ["qt6", "devel", "arch-x86_64-w64-mingw32"],
"displayName": "Combination of qt6, devel and arch-x86_64-w64-mingw32",
"description": "See descriptions of devel and arch-x86_64-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-devel"
},
{
"name": "arch-i686-w64-mingw32-static-devel-qt6",
"inherits": ["qt6", "devel", "arch-i686-w64-mingw32-static"],
"displayName": "Combination of qt6, devel and arch-i686-w64-mingw32-static",
"description": "See descriptions of devel and arch-i686-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-devel"
},
{
"name": "arch-x86_64-w64-mingw32-static-devel-qt6",
"inherits": ["qt6", "devel", "arch-x86_64-w64-mingw32-static"],
"displayName": "Combination of qt6, devel and arch-x86_64-w64-mingw32-static",
"description": "See descriptions of devel and arch-x86_64-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-devel"
},
{
"name": "arch-static-compat",
"inherits": ["no-webview", "no-kde", "qt6"],
@ -382,10 +266,10 @@
"description": "Build on Windows targeting x64-windows-static using MSVC, Qt 6 (for Qt libs and CMake/Ninja), vcpkg (for other dependencies) and MSYS2 (for Perl, Go, ffmpeg and other tools)",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/win-x64-msvc-static",
"environment": {
"INCLUDE": "$env{MSVC_ROOT}/include;$env{MSVC_ROOT}/ATLMFC/include;$env{WIN_KITS_ROOT}/include/$env{WIN_KITS_VERSION}/ucrt;$env{WIN_KITS_ROOT}//include/$env{WIN_KITS_VERSION}//um;$env{WIN_KITS_ROOT}//include/$env{WIN_KITS_VERSION}//shared;$env{WIN_KITS_ROOT}/include/$env{WIN_KITS_VERSION}//winrt;$env{WIN_KITS_ROOT}/include/$env{WIN_KITS_VERSION}//cppwinrt",
"LIB": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/ucrt/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}//um/x64",
"LIBPATH": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/ucrt/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/um/x64",
"GOROOT": "$env{MSYS2_ROOT}/mingw64/lib/go"
"LIBPATH": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/ucrt/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/um/x64"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
@ -410,6 +294,7 @@
"DOXYGEN_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/doxygen.exe"},
"CLANG_FORMAT_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/clang-format.exe"},
"GO_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/go.exe"},
"GOROOT": {"type": "PATH", "value": "$env{MSYS2_ROOT}/mingw64/lib/go"},
"FFMPEG_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/ffmpeg.exe"},
"REALPATH_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/usr/bin/realpath.exe"},
"FORCE_EXTERNAL_ICONV": {"type": "BOOL", "value": "ON"},
@ -449,30 +334,19 @@
{"name": "libc++", "configurePreset": "libc++"},
{"name": "qt6", "configurePreset": "qt6"},
{"name": "devel", "configurePreset": "devel"},
{"name": "devel-clang", "configurePreset": "devel-clang"},
{"name": "devel-libc++", "configurePreset": "devel-libc++"},
{"name": "devel-qt6", "configurePreset": "devel-qt6"},
{"name": "devel-unity", "configurePreset": "devel-unity"},
{"name": "devel-clang-qt6", "configurePreset": "devel-clang-qt6"},
{"name": "devel-libc++-qt6", "configurePreset": "devel-libc++-qt6"},
{"name": "debug", "configurePreset": "debug"},
{"name": "debug-qt6", "configurePreset": "debug-qt6"},
{"name": "debug-kde", "configurePreset": "debug-kde"},
{"name": "debug-kde-custom", "configurePreset": "debug-kde-custom"},
{"name": "arch-i686-w64-mingw32", "configurePreset": "arch-i686-w64-mingw32"},
{"name": "arch-x86_64-w64-mingw32", "configurePreset": "arch-x86_64-w64-mingw32"},
{"name": "arch-i686-w64-mingw32-static", "configurePreset": "arch-i686-w64-mingw32-static"},
{"name": "arch-x86_64-w64-mingw32-static", "configurePreset": "arch-x86_64-w64-mingw32-static"},
{"name": "arch-i686-w64-mingw32-qt6", "configurePreset": "arch-i686-w64-mingw32-qt6"},
{"name": "arch-x86_64-w64-mingw32-qt6", "configurePreset": "arch-x86_64-w64-mingw32-qt6"},
{"name": "arch-i686-w64-mingw32-static-qt6", "configurePreset": "arch-i686-w64-mingw32-static-qt6"},
{"name": "arch-x86_64-w64-mingw32-static-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-qt6"},
{"name": "arch-i686-w64-mingw32-devel", "configurePreset": "arch-i686-w64-mingw32-devel"},
{"name": "arch-x86_64-w64-mingw32-devel", "configurePreset": "arch-x86_64-w64-mingw32-devel"},
{"name": "arch-i686-w64-mingw32-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-devel-qt6"},
{"name": "arch-x86_64-w64-mingw32-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-devel-qt6"},
{"name": "arch-i686-w64-mingw32-static-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-static-devel-qt6"},
{"name": "arch-x86_64-w64-mingw32-static-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-devel-qt6"},
{"name": "arch-static-compat", "configurePreset": "arch-static-compat"},
{"name": "arch-static-compat-devel", "configurePreset": "arch-static-compat-devel"},
{"name": "win-x64-msvc-static", "configurePreset": "win-x64-msvc-static"},

View File

@ -78,6 +78,7 @@ These build instructions apply to `c++utilities` but also to my other projects u
* glibc with iconv support or standalone iconv library
* libstdc++ or Boost.Iostreams for `NativeFileStream` (optional, use `USE_NATIVE_FILE_BUFFER=OFF` to disable)
* Boost.Process for `execApp()` test helper under Windows (optional, use `USE_BOOST_PROCESS=OFF` to disable)
* libarchive (optional, for archiving utilities only, use `USE_LIBARCHIVE=ON` to enable)
* My other projects have further dependencies such as Qt. Checkout the README of these
projects for further details.
@ -117,8 +118,9 @@ building on Windows.
* If thread local storage is not supported by your compiler/platform (might be the case on MacOS), you can
disable making use of it via `ENABLE_THREAD_LOCAL=OFF`.
* To disable use of `std::filesystem`, set `USE_STANDARD_FILESYSTEM=OFF`. Note that the Bash completion will
not be able to suggest files and directories with `USE_STANDARD_FILESYSTEM=OFF`. Note that this will only
help with `c++utilities` itself. My other projects might use `std::filesystem` unconditionally.
not be able to suggest files and directories and the archiving utilities cannot be enabled with
`USE_STANDARD_FILESYSTEM=OFF`. Note that this will only help with `c++utilities` itself. My other projects
might use `std::filesystem` unconditionally.
* To disable `NativeFileStream` (and make it just a regular `std::fstream`), set `USE_NATIVE_FILE_BUFFER=OFF`.
Note that handling paths with non-ASCII characters will then cease to work on Windows.
* The Qt-based applications support bundeling icon themes by specifying e.g.
@ -202,7 +204,7 @@ Run the following commands to build one of my applications and its `c++utilities
in one go (in this example Syncthing Tray):
```
# install dependencies; you may strip down this list depending on the application and features to enable
pacman -Syu git perl-YAML mingw-w64-x86_64-gcc mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-cppunit mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-declarative mingw-w64-x86_64-qt6-tools mingw-w64-x86_64-qt6-svg mingw-w64-x86_64-clang-tools-extra mingw-w64-x86_64-doxygen mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-go
pacman -Syu git perl-YAML mingw-w64-x86_64-gcc mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-cppunit mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-declarative mingw-w64-x86_64-qt6-tools mingw-w64-x86_64-qt6-svg mingw-w64-x86_64-clang-tools-extra mingw-w64-x86_64-doxygen mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-go mingw-w64-x86_64-libarchive
# clone repositories as mentioned under "Building this straight" in the application's README file
cd /path/to/store/sources
@ -268,7 +270,7 @@ various additional environment variables to be set and you need to install depen
* `QT_TOOLS`: for additional build tools provided by the official Qt installer, e.g. `D:/programming/qt/Tools`
* `VCPKG_ROOT`: directory of VCPKG checkout used for other dependencies; install the following packages:
```
vcpkg install boost-system:x64-windows-static boost-iostreams:x64-windows-static boost-filesystem:x64-windows-static boost-hana:x64-windows-static boost-process:x64-windows-static boost-asio:x64-windows-static libiconv:x64-windows-static zlib:x64-windows-static openssl:x64-windows-static cppunit:x64-windows-static
vcpkg install boost-system:x64-windows-static boost-iostreams:x64-windows-static boost-filesystem:x64-windows-static boost-hana:x64-windows-static boost-process:x64-windows-static boost-asio:x64-windows-static libiconv:x64-windows-static zlib:x64-windows-static openssl:x64-windows-static cppunit:x64-windows-static libarchive'[bzip2,crypto,zstd]':x64-windows-static
```
When building with MSVC, do *not* use any of the MSYS2 shells. The environment of those shells leads to

View File

@ -1799,7 +1799,7 @@ void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const s
throw ParseError(argumentPath.empty()
? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
: argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
targetTypeName, "\" failed: ", errorMessage));
targetTypeName, "\" failed: ", errorMessage));
}
/*!
@ -1810,7 +1810,7 @@ void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesTo
throw ParseError(path.empty()
? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
: argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
" have been specified."));
" have been specified."));
}
} // namespace CppUtilities

View File

@ -881,7 +881,7 @@ inline void Argument::setFlags(Argument::Flags flags, bool add)
{
m_flags = add ? (m_flags | flags)
: static_cast<Argument::Flags>(static_cast<std::underlying_type<Argument::Flags>::type>(m_flags)
& ~static_cast<std::underlying_type<Argument::Flags>::type>(flags));
& ~static_cast<std::underlying_type<Argument::Flags>::type>(flags));
}
/*!

View File

@ -14,7 +14,7 @@ namespace CppUtilities {
*/
FakeQtConfigArguments::FakeQtConfigArguments()
: m_qtWidgetsGuiArg(
"qt-widgets-gui", 'g', "shows a Qt widgets based graphical user interface (the application has not been built with Qt widgets support)")
"qt-widgets-gui", 'g', "shows a Qt widgets based graphical user interface (the application has not been built with Qt widgets support)")
, m_qtQuickGuiArg(
"qt-quick-gui", 'q', "shows a Qt quick based graphical user interface (the application has not been built with Qt quick support)")
{

View File

@ -63,7 +63,7 @@ function (add_appstream_file)
endif ()
# create appstream desktop file from template
set(APPSTREAM_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_ID}.metainfo.xml")
set(APPSTREAM_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_ID}.appdata.xml")
configure_file("${APP_APPSTREAM_TEMPLATE_FILE}" "${APPSTREAM_FILE}" @ONLY)
# add install for the appstream file

View File

@ -191,39 +191,18 @@ if (NOT META_PROJECT_LICENSE)
endif ()
endif ()
# determine RDNS automatically from other meta-data and allow override
set(${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE
""
CACHE STRING "overrides the RDNS used in AppStream meta-data files for ${META_PROJECT_NAME}")
if (${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE)
set(META_PROJECT_RDNS ${${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE})
endif ()
set(${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE
""
CACHE STRING "overrides the developer ID used in AppStream meta-data files for ${META_PROJECT_NAME}")
if (${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE)
set(META_DEVELOPER_ID ${${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE})
endif ()
if (${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE OR ${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE)
message(
WARNING
"Overriding the RDNS or developer ID is NOT recommended. This feature is only intended to ease "
"transitioning when a change is required and to create alternative packaging for development and private use.")
endif ()
if (NOT META_PROJECT_RDNS OR NOT META_DEVELOPER_ID)
string(TOLOWER "${META_APP_AUTHOR}" META_APP_AUTHOR_LOWER)
# determine RDNS automatically from other meta-data
if (NOT META_PROJECT_RDNS)
if (NOT META_PROJECT_RDNS_BASE)
if (META_APP_URL MATCHES ".*github\\.(com|io).*")
if (META_APP_URL MATCHES ".*github\\.com.*")
set(META_PROJECT_RDNS_BASE "io.github") # assume GitHub pages
else ()
set(META_PROJECT_RDNS_BASE "org")
endif ()
endif ()
string(TOLOWER "${META_APP_AUTHOR}" META_APP_AUTHOR_LOWER)
set(META_PROJECT_RDNS "${META_PROJECT_RDNS_BASE}.${META_APP_AUTHOR_LOWER}.${META_PROJECT_NAME}${TARGET_SUFFIX}")
endif ()
if (NOT META_DEVELOPER_ID)
set(META_DEVELOPER_ID "${META_PROJECT_RDNS_BASE}.${META_APP_AUTHOR_LOWER}")
endif ()
# provide variables for other projects built as part of the same subdirs project to access files from this project
get_directory_property(HAS_PARENT PARENT_DIRECTORY)

View File

@ -59,7 +59,7 @@ function (configure_development_warnings)
option(TREAT_WARNINGS_AS_ERRORS "adds additional compiler flag to treat warnings as errors" "${ENABLE_DEVEL_DEFAULTS}")
if (TREAT_WARNINGS_AS_ERRORS)
if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND COMPILE_OPTIONS_TO_CONFIGURE -Werror -Wno-error=address)
list(APPEND COMPILE_OPTIONS_TO_CONFIGURE -Werror)
else ()
message(AUTHOR_WARNING "Treating warnings as errors is not supported for compiler '${CMAKE_CXX_COMPILER_ID}'.")
endif ()

View File

@ -52,10 +52,9 @@ function (configure_test_target)
PRIVATE "${ARGS_LIBRARIES}" "${PRIVATE_LIBRARIES}")
target_include_directories(
"${TEST_TARGET_NAME}"
PUBLIC $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
$<BUILD_INTERFACE:${TARGET_GENERATED_INCLUDE_DIRECTORY}> $<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}>
${PUBLIC_INCLUDE_DIRS}
PRIVATE ${TEST_INCLUDE_DIRS} ${PRIVATE_INCLUDE_DIRS})
PRIVATE ${TEST_INCLUDE_DIRS} "${PRIVATE_INCLUDE_DIRS}")
target_compile_definitions(
"${TEST_TARGET_NAME}"
PUBLIC "${META_PUBLIC_COMPILE_DEFINITIONS}"

View File

@ -9,7 +9,7 @@
<url type="homepage">@META_APP_URL@</url>
<url type="bugtracker">@META_APP_BUGTRACKER_URL@</url>
<launchable type="desktop-id">@META_ID@.desktop</launchable>
<developer id="@META_DEVELOPER_ID@"><name>@META_APP_AUTHOR@</name></developer>
<developer><name>@META_APP_AUTHOR@</name></developer>
<provides>
<binary>@META_TARGET_NAME@</binary>
</provides>

View File

@ -13,7 +13,6 @@
#define PROJECT_CONFIG_SUFFIX "@META_CONFIG_SUFFIX@"
#define PROJECT_CONFIG_TARGET_SUFFIX "@TARGET_SUFFIX@"
#define APP_NAME "@META_APP_NAME@"
#define APP_ID "@META_ID@"
#define APP_VERSION "@META_APP_VERSION@"
#define APP_VERSION_MAJOR @META_VERSION_MAJOR@
#define APP_VERSION_MINOR @META_VERSION_MINOR@

View File

@ -168,11 +168,11 @@ CPP_UTILITIES_EXPORT inline void getBytes24(std::uint32_t value, char *outputbuf
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[2] = static_cast<char>((value) & 0xFF);
outputbuffer[2] = static_cast<char>((value)&0xFF);
#else
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
#endif
}

View File

@ -16,16 +16,16 @@ template <class StringType, class ViewType> using IsStringViewType = std::is_sam
template <class StringType, class CharType> using IsCharType = std::is_same<typename StringType::value_type, CharType>;
namespace Detail {
template <typename StringType, typename T>
auto IsStringType(
int) -> decltype(std::declval<StringType &>().append(std::declval<const T &>()), std::declval<const T &>().size(), Traits::Bool<true>{});
auto IsStringType(int)
-> decltype(std::declval<StringType &>().append(std::declval<const T &>()), std::declval<const T &>().size(), Traits::Bool<true>{});
template <typename StringType, typename T> Traits::Bool<false> IsStringType(...);
template <typename StringType> void functionTakingConstStringRef(const StringType &str);
template <typename StringType, typename T>
auto IsConvertibleToConstStringRef(int) -> decltype(functionTakingConstStringRef<StringType>(std::declval<const T &>()), Traits::Bool<true>{});
template <typename StringType, typename T> Traits::Bool<false> IsConvertibleToConstStringRef(...);
template <typename StringType, typename T>
auto IsConvertibleToConstStringRefViaNative(
int) -> decltype(functionTakingConstStringRef<StringType>(std::declval<const T &>().native()), Traits::Bool<true>{});
auto IsConvertibleToConstStringRefViaNative(int)
-> decltype(functionTakingConstStringRef<StringType>(std::declval<const T &>().native()), Traits::Bool<true>{});
template <typename StringType, typename T> Traits::Bool<false> IsConvertibleToConstStringRefViaNative(...);
} // namespace Detail
template <typename StringType, typename StringType2>

225
io/archive.cpp Normal file
View File

@ -0,0 +1,225 @@
#include "./archive.h"
#include "../conversion/stringbuilder.h"
#include "../io/misc.h"
#include <archive.h>
#include <archive_entry.h>
#include <filesystem>
using namespace CppUtilities;
namespace CppUtilities {
/*!
* \brief Destroys the ArchiveException.
*/
ArchiveException::~ArchiveException()
{
}
/// \cond
///
struct AddDirectoryToFileMap {
bool operator()(std::string_view path)
{
fileMap[std::string(path)];
return false;
}
FileMap &fileMap;
};
struct AddFileToFileMap {
bool operator()(std::string_view directoryPath, ArchiveFile &&file)
{
fileMap[std::string(directoryPath)].emplace_back(std::move(file));
return false;
}
FileMap &fileMap;
};
void walkThroughArchiveInternal(struct archive *ar, std::string_view archiveName, const FilePredicate &isFileRelevant, FileHandler &&fileHandler,
DirectoryHandler &&directoryHandler)
{
// iterate through all archive entries
struct archive_entry *const entry = archive_entry_new();
auto fileContent = std::string();
while (archive_read_next_header2(ar, entry) == ARCHIVE_OK) {
// check entry type (only dirs, files and symlinks relevant here)
const auto entryType(archive_entry_filetype(entry));
if (entryType != AE_IFDIR && entryType != AE_IFREG && entryType != AE_IFLNK) {
continue;
}
// get file path
const char *filePath = archive_entry_pathname_utf8(entry);
if (!filePath) {
filePath = archive_entry_pathname(entry);
}
if (!filePath) {
continue;
}
// get permissions
const mode_t perm = archive_entry_perm(entry);
// add directories explicitly to get the entire tree though skipping irrelevant files
if (entryType == AE_IFDIR) {
// remove trailing slashes
const char *dirEnd = filePath;
for (const char *i = filePath; *i; ++i) {
if (*i != '/') {
dirEnd = i + 1;
}
}
if (directoryHandler(std::string_view(filePath, static_cast<std::size_t>(dirEnd - filePath)))) {
goto free;
}
continue;
}
// split the path into dir and fileName
const char *fileName = filePath, *dirEnd = filePath;
for (const char *i = filePath; *i; ++i) {
if (*i == '/') {
fileName = i + 1;
dirEnd = i;
}
}
// prevent looking into irrelevant files
if (isFileRelevant && !isFileRelevant(filePath, fileName, perm)) {
continue;
}
// read timestamps
const auto creationTime = DateTime::fromTimeStampGmt(archive_entry_ctime(entry));
const auto modificationTime = DateTime::fromTimeStampGmt(archive_entry_mtime(entry));
// read symlink
if (entryType == AE_IFLNK) {
if (fileHandler(std::string_view(filePath, static_cast<std::string::size_type>(dirEnd - filePath)),
ArchiveFile(fileName, std::string(archive_entry_symlink_utf8(entry)), ArchiveFileType::Link, creationTime, modificationTime))) {
goto free;
}
continue;
}
// determine file size to pre-allocate buffer for file content
const la_int64_t fileSize = archive_entry_size(entry);
fileContent.clear();
if (fileSize > 0) {
fileContent.reserve(static_cast<std::string::size_type>(fileSize));
}
// read file content
const char *buff;
auto size = std::size_t();
auto offset = la_int64_t();
for (;;) {
const auto returnCode = archive_read_data_block(ar, reinterpret_cast<const void **>(&buff), &size, &offset);
if (returnCode == ARCHIVE_EOF || returnCode < ARCHIVE_OK) {
break;
}
fileContent.append(buff, size);
}
// move it to results
if (fileHandler(std::string_view(filePath, static_cast<std::string::size_type>(dirEnd - filePath)),
ArchiveFile(fileName, std::move(fileContent), ArchiveFileType::Regular, creationTime, modificationTime))) {
goto free;
}
}
// free resources used by libarchive
free:
archive_entry_free(entry);
const auto returnCode = archive_read_free(ar);
if (returnCode != ARCHIVE_OK) {
throw ArchiveException(argsToString("Unable to free archive: ", archiveName));
}
}
/// \endcond
/*!
* \brief Invokes callbacks for files and directories in the specified archive.
*/
void walkThroughArchiveFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant,
FileHandler &&fileHandler, DirectoryHandler &&directoryHandler)
{
// refuse opening empty buffer
if (archiveData.empty()) {
throw ArchiveException("Unable to open archive \"" % archiveName + "\": archive data is empty");
}
// open archive buffer using libarchive
struct archive *ar = archive_read_new();
archive_read_support_filter_all(ar);
archive_read_support_format_all(ar);
const auto returnCode = archive_read_open_memory(ar, archiveData.data(), archiveData.size());
if (returnCode != ARCHIVE_OK) {
archive_read_free(ar);
if (const char *const error = archive_error_string(ar)) {
throw ArchiveException("Unable to open/read archive \"" % archiveName % "\": " + error);
} else {
throw ArchiveException("Unable to open/read archive \"" % archiveName + "\": unable to open archive from memory");
}
}
walkThroughArchiveInternal(ar, archiveName, isFileRelevant, std::move(fileHandler), std::move(directoryHandler));
}
/*!
* \brief Extracts the specified archive.
*/
FileMap extractFilesFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant)
{
auto results = FileMap();
walkThroughArchiveFromBuffer(archiveData, archiveName, isFileRelevant, AddFileToFileMap{ results }, AddDirectoryToFileMap{ results });
return results;
}
/*!
* \brief Invokes callbacks for files and directories in the specified archive.
*/
void walkThroughArchive(
std::string_view archivePath, const FilePredicate &isFileRelevant, FileHandler &&fileHandler, DirectoryHandler &&directoryHandler)
{
// open archive file using libarchive
if (archivePath.empty()) {
throw ArchiveException("Unable to open archive: no path specified");
}
auto ec = std::error_code();
auto size = std::filesystem::file_size(archivePath, ec);
if (ec) {
throw ArchiveException("Unable to determine size of \"" % archivePath % "\": " + ec.message());
}
if (!size) {
throw ArchiveException("Unable to open archive \"" % archivePath + "\": file is empty");
}
struct archive *ar = archive_read_new();
archive_read_support_filter_all(ar);
archive_read_support_format_all(ar);
const auto returnCode = archive_read_open_filename(ar, archivePath.data(), 10240);
if (returnCode != ARCHIVE_OK) {
archive_read_free(ar);
if (const char *const error = archive_error_string(ar)) {
throw ArchiveException("Unable to open/read archive \"" % archivePath % "\": " + error);
} else {
throw ArchiveException("Unable to open/read archive \"" % archivePath + "\": unable to open archive from file");
}
}
walkThroughArchiveInternal(ar, archivePath, isFileRelevant, std::move(fileHandler), std::move(directoryHandler));
}
/*!
* \brief Extracts the specified archive.
*/
FileMap extractFiles(std::string_view archivePath, const FilePredicate &isFileRelevant)
{
auto results = FileMap();
walkThroughArchive(archivePath, isFileRelevant, AddFileToFileMap{ results }, AddDirectoryToFileMap{ results });
return results;
}
} // namespace CppUtilities

88
io/archive.h Normal file
View File

@ -0,0 +1,88 @@
#ifndef CPP_UTILITIES_ARCHIVE_H
#define CPP_UTILITIES_ARCHIVE_H
#include "../chrono/datetime.h"
#include "../global.h"
#include <exception>
#include <functional>
#include <map>
#include <string>
#include <string_view>
#include <vector>
namespace CppUtilities {
/*!
* \class ArchiveException
* \brief The ArchiveException class is thrown by the various archiving-related
* functions of this library when a conversion error occurs.
*/
class CPP_UTILITIES_EXPORT ArchiveException : public std::runtime_error {
public:
explicit ArchiveException() noexcept;
explicit ArchiveException(std::string_view what) noexcept;
~ArchiveException() override;
};
/*!
* \brief Constructs a new ArchiveException.
*/
inline ArchiveException::ArchiveException() noexcept
: std::runtime_error("unable to convert")
{
}
/*!
* \brief Constructs a new ArchiveException.
*/
inline ArchiveException::ArchiveException(std::string_view what) noexcept
: std::runtime_error(what.data())
{
}
/*!
* \brief The ArchiveFileType enum specifies the type of a file within an archive.
*/
enum class ArchiveFileType { Regular, Link };
/*!
* \brief The ArchiveFile class holds data about a file within an archive.
*/
struct CPP_UTILITIES_EXPORT ArchiveFile {
explicit ArchiveFile(
std::string &&name, std::string &&content, ArchiveFileType type, CppUtilities::DateTime creationTime, CppUtilities::DateTime modificationTime)
: name(name)
, content(content)
, creationTime(creationTime)
, modificationTime(modificationTime)
, type(type)
{
}
std::string name;
std::string content;
CppUtilities::DateTime creationTime;
CppUtilities::DateTime modificationTime;
ArchiveFileType type;
};
/// \brief A map of files extracted from an archive. Keys represent directories and values files within those directories.
using FileMap = std::map<std::string, std::vector<ArchiveFile>>;
/// \brief A function that is invoked for each file within an archive. If it returns true, the file is considered; otherwise the file is ignored.
using FilePredicate = std::function<bool(const char *, const char *, mode_t)>;
/// \brief A function that is invoked by the walk-through-functions to return a directory.
using DirectoryHandler = std::function<bool(std::string_view path)>;
/// \brief A function that is invoked by the walk-through-functions to return a file.
using FileHandler = std::function<bool(std::string_view path, ArchiveFile &&file)>;
CPP_UTILITIES_EXPORT FileMap extractFiles(std::string_view archivePath, const FilePredicate &isFileRelevant = FilePredicate());
CPP_UTILITIES_EXPORT void walkThroughArchive(std::string_view archivePath, const FilePredicate &isFileRelevant = FilePredicate(),
FileHandler &&fileHandler = FileHandler(), DirectoryHandler &&directoryHandler = DirectoryHandler());
CPP_UTILITIES_EXPORT FileMap extractFilesFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant = FilePredicate());
CPP_UTILITIES_EXPORT void walkThroughArchiveFromBuffer(std::string_view archiveData, std::string_view archiveName,
const FilePredicate &isFileRelevant = FilePredicate(), FileHandler &&fileHandler = FileHandler(),
DirectoryHandler &&directoryHandler = DirectoryHandler());
} // namespace CppUtilities
#endif // CPP_UTILITIES_ARCHIVE_H

BIN
testfiles/test.zip Normal file

Binary file not shown.

View File

@ -1,5 +1,7 @@
#include "./testutils.h"
using namespace CppUtilities;
#include "../conversion/stringconversion.h"
/*!
@ -32,6 +34,10 @@ std::ostream &operator<<(std::ostream &out, const std::wstring &s)
#include "../io/nativefilestream.h"
#include "../io/path.h"
#ifdef CPP_UTILITIES_USE_LIBARCHIVE
#include "../io/archive.h"
#endif
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
@ -50,7 +56,6 @@ std::ostream &operator<<(std::ostream &out, const std::wstring &s)
#endif
using namespace std;
using namespace CppUtilities;
using namespace CppUtilities::Literals;
using namespace CPPUNIT_NS;
@ -73,6 +78,9 @@ class IoTests : public TestFixture {
CPPUNIT_TEST(testAnsiEscapeCodes);
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
CPPUNIT_TEST(testNativeFileStream);
#endif
#ifdef CPP_UTILITIES_USE_LIBARCHIVE
CPPUNIT_TEST(testExtractingArchive);
#endif
CPPUNIT_TEST_SUITE_END();
@ -95,6 +103,9 @@ public:
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
void testNativeFileStream();
#endif
#ifdef CPP_UTILITIES_USE_LIBARCHIVE
void testExtractingArchive();
#endif
};
CPPUNIT_TEST_SUITE_REGISTRATION(IoTests);
@ -769,3 +780,31 @@ void IoTests::testNativeFileStream()
CPPUNIT_ASSERT_EQUAL("barfoo"s, readFile(txtFilePath, 7));
}
#endif
#ifdef CPP_UTILITIES_USE_LIBARCHIVE
void IoTests::testExtractingArchive()
{
const auto archivePath = testFilePath("test.zip");
const auto archiveContents = extractFiles(archivePath);
const auto &root = archiveContents.at(std::string());
const auto &subdir = archiveContents.at("subdir");
const auto &subsubdir = archiveContents.at("subdir/foo");
CPPUNIT_ASSERT_EQUAL(1_st, root.size());
CPPUNIT_ASSERT_EQUAL("test.txt"s, root.at(0).name);
CPPUNIT_ASSERT_EQUAL(ArchiveFileType::Regular, root.at(0).type);
CPPUNIT_ASSERT_EQUAL(DateTime::fromDate(1970, 1, 1), root.at(0).creationTime);
CPPUNIT_ASSERT_EQUAL(DateTime::fromDateAndTime(2024, 3, 3, 19, 46, 42), root.at(0).modificationTime);
CPPUNIT_ASSERT_EQUAL("testfile\n"s, root.at(0).content);
CPPUNIT_ASSERT_EQUAL(1_st, subdir.size());
CPPUNIT_ASSERT_EQUAL("nested-testfile.txt"s, subdir.at(0).name);
CPPUNIT_ASSERT_EQUAL(ArchiveFileType::Regular, subdir.at(0).type);
CPPUNIT_ASSERT_EQUAL("some file\n"s, subdir.at(0).content);
CPPUNIT_ASSERT_EQUAL(1_st, subsubdir.size());
CPPUNIT_ASSERT_EQUAL("bar"s, subsubdir.at(0).name);
CPPUNIT_ASSERT_EQUAL(ArchiveFileType::Regular, subsubdir.at(0).type);
CPPUNIT_ASSERT_EQUAL(std::string(), subsubdir.at(0).content);
}
#endif