diff --git a/.drone.yml b/.drone.yml index c69f3d5..c7f5fc4 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,8 +6,14 @@ steps: - name: build image: arminfriedl/xwim-build commands: + - meson wrap install gtest - meson build - ninja -C build + - ninja -C build test && ninja -C build coverage + - echo "******** TEST LOGS ***********" + - cat build/meson-logs/testlog.txt + - echo "****** COVERAGE LOGS *********" + - cat build/meson-logs/coverage.txt trigger: event: @@ -23,6 +29,7 @@ steps: - name: build image: arminfriedl/xwim-build commands: + - meson wrap install gtest - meson --buildtype=release build - ninja -C build - mkdir xwim-${DRONE_TAG}-x86_64-glibc-linux diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 52fe1c4..7a7dd59 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -790,8 +790,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = "@PROJECT_SRCDIR@" \ - "@PROJECT_BINDIR@" +INPUT = "@PROJECT_SRCDIR@" # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -864,7 +863,7 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = "@PROJECT_EXAMPLESDIR@" +EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -2020,8 +2019,7 @@ SEARCH_INCLUDES = YES # preprocessor. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = "@PROJECT_SRCDIR@" \ - "@PROJECT_BINDIR@" +INCLUDE_PATH = "@PROJECT_SRCDIR@" # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the diff --git a/doc/meson.build b/doc/meson.build index 9c17a2c..28fa3d0 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -14,7 +14,7 @@ else endif cdata.set('PROJECT_NAME', meson.project_name()) -cdata.set('PROJECT_SRCDIR', join_paths(meson.current_source_dir(),'..','src',meson.project_name())) +cdata.set('PROJECT_SRCDIR', join_paths(meson.current_source_dir(),'..','src')) cdata.set('PROJECT_DOCDIR', meson.current_source_dir()) cdata.set('PROJECT_TESTDIR', join_paths(meson.current_source_dir(),'..','test')) diff --git a/meson.build b/meson.build index aa1a0d1..d382a12 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,9 @@ project('xwim', 'cpp', version: '0.2', - default_options: ['cpp_std=c++17']) + default_options: ['cpp_std=c++17', + 'warning_level=3', + 'b_coverage=true']) subdir('src') +subdir('doc') +subdir('test') diff --git a/src/archive.hpp b/src/archive.hpp index 0e3b494..ca76979 100644 --- a/src/archive.hpp +++ b/src/archive.hpp @@ -12,6 +12,7 @@ namespace xwim { +/** Class for interacting with archives */ class Archive { private: std::filesystem::path path; @@ -19,7 +20,13 @@ class Archive { public: explicit Archive(std::filesystem::path path); + /** Generate an ArchiveSpec by analysing the archive at `path` + * + * @returns ArchiveSpec for the archive + */ ArchiveSpec check(); + + /** Extract the archive at `path` according to given ExtractSpec */ void extract(ExtractSpec extract_spec); }; diff --git a/src/archive_sys.hpp b/src/archive_sys.hpp index b255117..a9d6556 100644 --- a/src/archive_sys.hpp +++ b/src/archive_sys.hpp @@ -4,9 +4,16 @@ #include #include +#include namespace xwim { +/** A view into an archive entry + * + * The view is non-owning and the caller must guarantee + * that the parent archive entry is valid when the view + * is accessed. + */ class ArchiveEntryView { private: archive_entry* ae; diff --git a/src/fileformats.hpp b/src/fileformats.hpp index 4dc3562..b7a2dcd 100644 --- a/src/fileformats.hpp +++ b/src/fileformats.hpp @@ -1,8 +1,17 @@ +/** @file fileformats.hpp + * @brief Handle archive extensions + */ #pragma once #include namespace logger = spdlog; +#include +#include +#include + +namespace xwim { + /** Common archive formats understood by xwim * * The underlying libarchive backend retrieves format information by a process @@ -11,22 +20,21 @@ namespace logger = spdlog; * Stripping extensions via `std::filesystem::path` does not work reliably since * it gets easily confused by dots in the regular file name. */ +const std::set fileformats{".7z", ".7zip", ".jar", ".tgz", + ".bz2", ".bzip2", ".gz", ".gzip", + ".rar", ".tar", "xz", ".zip"}; -#include -#include -#include - -namespace xwim { - - const std::set fileformats{ - ".7z", ".7zip", ".jar", ".tgz", ".bz2", ".bzip2", ".gz", - ".gzip", ".rar", ".tar", ".tar.gz", ".tar.bz2", ".tar.xz", ".zip"}; - -inline std::filesystem::path stem(std::filesystem::path& path) { - std::filesystem::path p_stem {path}; +/** Strip archive extensions from a path + * + * @returns Base filename without archive extensions + */ +inline std::filesystem::path stem(const std::filesystem::path& path) { + std::filesystem::path p_stem{path}; logger::trace("Stemming {}", p_stem.string()); - while( fileformats.find(p_stem.extension().string()) != fileformats.end() ) { + p_stem = p_stem.filename(); + + while (fileformats.find(p_stem.extension().string()) != fileformats.end()) { p_stem = p_stem.stem(); logger::trace("Stemmed to {}", p_stem.string()); } diff --git a/src/meson.build b/src/meson.build index b9f9f7e..a7d650e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,14 +1,14 @@ -src = ['main.cpp', - 'archive.cpp', - 'archive_sys.cpp'] +xwim_src = ['main.cpp', + 'archive.cpp', + 'archive_sys.cpp'] -inc = ['archive.hpp', - 'spec.hpp', - 'archive_sys.hpp', - 'fileformats.hpp'] +xwim_inc = ['archive.hpp', + 'spec.hpp', + 'archive_sys.hpp', + 'fileformats.hpp'] -libs = [dependency('libarchive', required: true), - dependency('fmt', required: true, static: true), - dependency('spdlog', required: true, static: true)] +xwim_libs = [dependency('libarchive', required: true), + dependency('fmt', required: true), + dependency('spdlog', required: true)] -executable('xwim', src, inc, dependencies: libs) +executable('xwim', xwim_src, xwim_inc, dependencies: xwim_libs) diff --git a/src/spec.hpp b/src/spec.hpp index e9ab8d2..f6fdacd 100644 --- a/src/spec.hpp +++ b/src/spec.hpp @@ -1,35 +1,57 @@ #pragma once #include -#include +#include #include #include namespace xwim { +/** Properties of an archive + * + * These properties can be retrieved by analyzing the + * archive. There is no outside-knowledge. All information + * is in the archive. + */ struct ArchiveSpec { - bool has_single_root = false; - bool is_root_filename = false; - bool is_root_dir = false; - bool has_subarchive = false; + bool has_single_root = false; /** There is only a single file xor a single + folder at the archive's root */ + bool is_root_filename = false; /** the name of the (single) root is the same + as the stemmed archive file name. Cannot be + true if `has_single_root` is false */ + bool is_root_dir = false; /** The (single) root is a folder. Cnnot be true if + `has_single_root` is false */ + bool has_subarchive = false; /** Whether the archive contains sub-archives */ }; +/** Properties influencing the extraction process + * + * These properties can be set to influence the extraction + * process accordingly. + */ struct ExtractSpec { - bool make_dir = false; - std::filesystem::path dirname{}; - bool extract_subarchive = false; + bool make_dir = false; /** Create a new directory for extraction at `dirname` */ + std::filesystem::path dirname{}; /** The path to a directory for extraction */ + bool extract_subarchive = false; /** Recursively extract sub-archives */ }; } // namespace xwim +#if FMT_VERSION < 50300 +typedef fmt::basic_parse_context format_parse_context; +#endif + template <> struct fmt::formatter { - constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } + constexpr auto parse(format_parse_context & ctx) { + return ctx.begin(); + } template auto format(const xwim::ArchiveSpec& spec, FormatContext& ctx) { - return format_to(ctx.out(),"Archive[" + return format_to(ctx.out(), + "Archive[" " .has_single_root={}," " .is_root_filename={}" " .is_root_dir={}" @@ -46,11 +68,13 @@ struct fmt::formatter { template auto format(const xwim::ExtractSpec& spec, FormatContext& ctx) { - return format_to(ctx.out(), "Extract[" + return format_to(ctx.out(), + "Extract[" " .make_dir={}," " .dirname={}" " .extract_subarchive={}" " ]", - spec.make_dir, spec.dirname.string(), spec.extract_subarchive); + spec.make_dir, spec.dirname.string(), + spec.extract_subarchive); } }; diff --git a/subprojects/.gitignore b/subprojects/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/subprojects/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/test/archive_test.cpp b/test/archive_test.cpp new file mode 100644 index 0000000..2450fb0 --- /dev/null +++ b/test/archive_test.cpp @@ -0,0 +1,11 @@ +#include + +#include +#include + +TEST(ArchiveTest, ArchiveSpecDetectsSingleRoot) { + xwim::Archive archive("test/archives/root.tar.gz"); + + xwim::ArchiveSpec spec = archive.check(); + ASSERT_TRUE(spec.has_single_root); +} diff --git a/test/archives/meson.build b/test/archives/meson.build new file mode 100644 index 0000000..206746a --- /dev/null +++ b/test/archives/meson.build @@ -0,0 +1 @@ +configure_file(input: 'root.tar.gz', output: 'root.tar.gz', copy: true) diff --git a/test/archives/root.tar.gz b/test/archives/root.tar.gz new file mode 100644 index 0000000..79ced2b Binary files /dev/null and b/test/archives/root.tar.gz differ diff --git a/test/fileformats_test.cpp b/test/fileformats_test.cpp new file mode 100644 index 0000000..d0fcebd --- /dev/null +++ b/test/fileformats_test.cpp @@ -0,0 +1,34 @@ +#include + +#include +#include + +TEST(FileformatsTest, StemStripsSingleKnownExtension) { + std::filesystem::path archive_path {"/some/path/to/file.rar"}; + + ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"file"}); +} + +TEST(FileformatsTest, StemStripsMultipleKnownExtensions) { + std::filesystem::path archive_path{"/some/path/to/file.tar.rar.gz.7z.rar"}; + + ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"file"}); +} + +TEST(FileformatsTest, StemStripsOnlyKnownExtension) { + std::filesystem::path archive_path{"/some/path/to/file.ukn.rar"}; + + ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"file.ukn"}); +} + +TEST(FileformatsTest, StemStripsNothingWithoutKnownExtension) { + std::filesystem::path archive_path{"/some/path/to/file.ukn"}; + + ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"file.ukn"}); +} + +TEST(FileformatsTest, StemStripsNothingWithoutExtension) { + std::filesystem::path archive_path{"/some/path/to/filerar"}; + + ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"filerar"}); +} diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..e6f5101 --- /dev/null +++ b/test/meson.build @@ -0,0 +1,23 @@ +# Run tests+coverage with `ninja clean && ninja test && ninja coverage` +gtest_proj = subproject('gtest') +gtest_dep = gtest_proj.get_variable('gtest_main_dep') + +xwim_src = ['../src/archive.cpp', + '../src/archive_sys.cpp'] + +subdir('archives') + +archive_test_exe = executable('archive_test_exe', + sources: ['archive_test.cpp', xwim_src], + include_directories: ['../src'], + dependencies: [gtest_dep, xwim_libs]) + + +test('archive test', archive_test_exe) + +fileformats_test_exe = executable('fileformats_test_exe', + sources: ['fileformats_test.cpp', xwim_src], + include_directories: ['../src'], + dependencies: [gtest_dep, xwim_libs]) + +test('fileformats test', fileformats_test_exe)