This commit is contained in:
parent
6a2aa756ca
commit
9cb65ce856
15 changed files with 171 additions and 43 deletions
|
@ -6,8 +6,14 @@ steps:
|
||||||
- name: build
|
- name: build
|
||||||
image: arminfriedl/xwim-build
|
image: arminfriedl/xwim-build
|
||||||
commands:
|
commands:
|
||||||
|
- meson wrap install gtest
|
||||||
- meson build
|
- meson build
|
||||||
- ninja -C 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:
|
trigger:
|
||||||
event:
|
event:
|
||||||
|
@ -23,6 +29,7 @@ steps:
|
||||||
- name: build
|
- name: build
|
||||||
image: arminfriedl/xwim-build
|
image: arminfriedl/xwim-build
|
||||||
commands:
|
commands:
|
||||||
|
- meson wrap install gtest
|
||||||
- meson --buildtype=release build
|
- meson --buildtype=release build
|
||||||
- ninja -C build
|
- ninja -C build
|
||||||
- mkdir xwim-${DRONE_TAG}-x86_64-glibc-linux
|
- mkdir xwim-${DRONE_TAG}-x86_64-glibc-linux
|
||||||
|
|
|
@ -790,8 +790,7 @@ WARN_LOGFILE =
|
||||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||||
# Note: If this tag is empty the current directory is searched.
|
# Note: If this tag is empty the current directory is searched.
|
||||||
|
|
||||||
INPUT = "@PROJECT_SRCDIR@" \
|
INPUT = "@PROJECT_SRCDIR@"
|
||||||
"@PROJECT_BINDIR@"
|
|
||||||
|
|
||||||
# This tag can be used to specify the character encoding of the source files
|
# 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
|
# 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
|
# that contain example code fragments that are included (see the \include
|
||||||
# command).
|
# command).
|
||||||
|
|
||||||
EXAMPLE_PATH = "@PROJECT_EXAMPLESDIR@"
|
EXAMPLE_PATH =
|
||||||
|
|
||||||
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
|
# 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
|
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
|
||||||
|
@ -2020,8 +2019,7 @@ SEARCH_INCLUDES = YES
|
||||||
# preprocessor.
|
# preprocessor.
|
||||||
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
||||||
|
|
||||||
INCLUDE_PATH = "@PROJECT_SRCDIR@" \
|
INCLUDE_PATH = "@PROJECT_SRCDIR@"
|
||||||
"@PROJECT_BINDIR@"
|
|
||||||
|
|
||||||
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
|
# 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
|
# patterns (like *.h and *.hpp) to filter out the header-files in the
|
||||||
|
|
|
@ -14,7 +14,7 @@ else
|
||||||
endif
|
endif
|
||||||
|
|
||||||
cdata.set('PROJECT_NAME', meson.project_name())
|
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_DOCDIR', meson.current_source_dir())
|
||||||
cdata.set('PROJECT_TESTDIR', join_paths(meson.current_source_dir(),'..','test'))
|
cdata.set('PROJECT_TESTDIR', join_paths(meson.current_source_dir(),'..','test'))
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
project('xwim', 'cpp',
|
project('xwim', 'cpp',
|
||||||
version: '0.2',
|
version: '0.2',
|
||||||
default_options: ['cpp_std=c++17'])
|
default_options: ['cpp_std=c++17',
|
||||||
|
'warning_level=3',
|
||||||
|
'b_coverage=true'])
|
||||||
|
|
||||||
subdir('src')
|
subdir('src')
|
||||||
|
subdir('doc')
|
||||||
|
subdir('test')
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
namespace xwim {
|
namespace xwim {
|
||||||
|
|
||||||
|
/** Class for interacting with archives */
|
||||||
class Archive {
|
class Archive {
|
||||||
private:
|
private:
|
||||||
std::filesystem::path path;
|
std::filesystem::path path;
|
||||||
|
@ -19,7 +20,13 @@ class Archive {
|
||||||
public:
|
public:
|
||||||
explicit Archive(std::filesystem::path path);
|
explicit Archive(std::filesystem::path path);
|
||||||
|
|
||||||
|
/** Generate an ArchiveSpec by analysing the archive at `path`
|
||||||
|
*
|
||||||
|
* @returns ArchiveSpec for the archive
|
||||||
|
*/
|
||||||
ArchiveSpec check();
|
ArchiveSpec check();
|
||||||
|
|
||||||
|
/** Extract the archive at `path` according to given ExtractSpec */
|
||||||
void extract(ExtractSpec extract_spec);
|
void extract(ExtractSpec extract_spec);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,16 @@
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
namespace xwim {
|
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 {
|
class ArchiveEntryView {
|
||||||
private:
|
private:
|
||||||
archive_entry* ae;
|
archive_entry* ae;
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
|
/** @file fileformats.hpp
|
||||||
|
* @brief Handle archive extensions
|
||||||
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
namespace logger = spdlog;
|
namespace logger = spdlog;
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace xwim {
|
||||||
|
|
||||||
/** Common archive formats understood by xwim
|
/** Common archive formats understood by xwim
|
||||||
*
|
*
|
||||||
* The underlying libarchive backend retrieves format information by a process
|
* 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
|
* Stripping extensions via `std::filesystem::path` does not work reliably since
|
||||||
* it gets easily confused by dots in the regular file name.
|
* it gets easily confused by dots in the regular file name.
|
||||||
*/
|
*/
|
||||||
|
const std::set<std::string> fileformats{".7z", ".7zip", ".jar", ".tgz",
|
||||||
|
".bz2", ".bzip2", ".gz", ".gzip",
|
||||||
|
".rar", ".tar", "xz", ".zip"};
|
||||||
|
|
||||||
#include <filesystem>
|
/** Strip archive extensions from a path
|
||||||
#include <set>
|
*
|
||||||
#include <string>
|
* @returns Base filename without archive extensions
|
||||||
|
*/
|
||||||
namespace xwim {
|
inline std::filesystem::path stem(const std::filesystem::path& path) {
|
||||||
|
std::filesystem::path p_stem{path};
|
||||||
const std::set<std::string> 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};
|
|
||||||
logger::trace("Stemming {}", p_stem.string());
|
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();
|
p_stem = p_stem.stem();
|
||||||
logger::trace("Stemmed to {}", p_stem.string());
|
logger::trace("Stemmed to {}", p_stem.string());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
src = ['main.cpp',
|
xwim_src = ['main.cpp',
|
||||||
'archive.cpp',
|
'archive.cpp',
|
||||||
'archive_sys.cpp']
|
'archive_sys.cpp']
|
||||||
|
|
||||||
inc = ['archive.hpp',
|
xwim_inc = ['archive.hpp',
|
||||||
'spec.hpp',
|
'spec.hpp',
|
||||||
'archive_sys.hpp',
|
'archive_sys.hpp',
|
||||||
'fileformats.hpp']
|
'fileformats.hpp']
|
||||||
|
|
||||||
libs = [dependency('libarchive', required: true),
|
xwim_libs = [dependency('libarchive', required: true),
|
||||||
dependency('fmt', required: true, static: true),
|
dependency('fmt', required: true),
|
||||||
dependency('spdlog', required: true, static: true)]
|
dependency('spdlog', required: true)]
|
||||||
|
|
||||||
executable('xwim', src, inc, dependencies: libs)
|
executable('xwim', xwim_src, xwim_inc, dependencies: xwim_libs)
|
||||||
|
|
48
src/spec.hpp
48
src/spec.hpp
|
@ -1,35 +1,57 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <archive.h>
|
#include <archive.h>
|
||||||
#include <fmt/core.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace xwim {
|
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 {
|
struct ArchiveSpec {
|
||||||
bool has_single_root = false;
|
bool has_single_root = false; /** There is only a single file xor a single
|
||||||
bool is_root_filename = false;
|
folder at the archive's root */
|
||||||
bool is_root_dir = false;
|
bool is_root_filename = false; /** the name of the (single) root is the same
|
||||||
bool has_subarchive = false;
|
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 {
|
struct ExtractSpec {
|
||||||
bool make_dir = false;
|
bool make_dir = false; /** Create a new directory for extraction at `dirname` */
|
||||||
std::filesystem::path dirname{};
|
std::filesystem::path dirname{}; /** The path to a directory for extraction */
|
||||||
bool extract_subarchive = false;
|
bool extract_subarchive = false; /** Recursively extract sub-archives */
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace xwim
|
} // namespace xwim
|
||||||
|
|
||||||
|
#if FMT_VERSION < 50300
|
||||||
|
typedef fmt::basic_parse_context<char> format_parse_context;
|
||||||
|
#endif
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<xwim::ArchiveSpec> {
|
struct fmt::formatter<xwim::ArchiveSpec> {
|
||||||
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
constexpr auto parse(format_parse_context & ctx) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const xwim::ArchiveSpec& spec, FormatContext& ctx) {
|
auto format(const xwim::ArchiveSpec& spec, FormatContext& ctx) {
|
||||||
return format_to(ctx.out(),"Archive["
|
return format_to(ctx.out(),
|
||||||
|
"Archive["
|
||||||
" .has_single_root={},"
|
" .has_single_root={},"
|
||||||
" .is_root_filename={}"
|
" .is_root_filename={}"
|
||||||
" .is_root_dir={}"
|
" .is_root_dir={}"
|
||||||
|
@ -46,11 +68,13 @@ struct fmt::formatter<xwim::ExtractSpec> {
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const xwim::ExtractSpec& spec, FormatContext& ctx) {
|
auto format(const xwim::ExtractSpec& spec, FormatContext& ctx) {
|
||||||
return format_to(ctx.out(), "Extract["
|
return format_to(ctx.out(),
|
||||||
|
"Extract["
|
||||||
" .make_dir={},"
|
" .make_dir={},"
|
||||||
" .dirname={}"
|
" .dirname={}"
|
||||||
" .extract_subarchive={}"
|
" .extract_subarchive={}"
|
||||||
" ]",
|
" ]",
|
||||||
spec.make_dir, spec.dirname.string(), spec.extract_subarchive);
|
spec.make_dir, spec.dirname.string(),
|
||||||
|
spec.extract_subarchive);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
4
subprojects/.gitignore
vendored
Normal file
4
subprojects/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
11
test/archive_test.cpp
Normal file
11
test/archive_test.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <archive.hpp>
|
||||||
|
#include <spec.hpp>
|
||||||
|
|
||||||
|
TEST(ArchiveTest, ArchiveSpecDetectsSingleRoot) {
|
||||||
|
xwim::Archive archive("test/archives/root.tar.gz");
|
||||||
|
|
||||||
|
xwim::ArchiveSpec spec = archive.check();
|
||||||
|
ASSERT_TRUE(spec.has_single_root);
|
||||||
|
}
|
1
test/archives/meson.build
Normal file
1
test/archives/meson.build
Normal file
|
@ -0,0 +1 @@
|
||||||
|
configure_file(input: 'root.tar.gz', output: 'root.tar.gz', copy: true)
|
BIN
test/archives/root.tar.gz
Normal file
BIN
test/archives/root.tar.gz
Normal file
Binary file not shown.
34
test/fileformats_test.cpp
Normal file
34
test/fileformats_test.cpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <fileformats.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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"});
|
||||||
|
}
|
23
test/meson.build
Normal file
23
test/meson.build
Normal file
|
@ -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)
|
Loading…
Reference in a new issue