Compare commits
2 commits
master
...
separate-p
Author | SHA1 | Date | |
---|---|---|---|
d95f3d0f32 | |||
0663f42aec |
20 changed files with 375 additions and 874 deletions
|
@ -6,7 +6,6 @@ steps:
|
||||||
- name: build-shared
|
- name: build-shared
|
||||||
image: arminfriedl/xwim-build:shared
|
image: arminfriedl/xwim-build:shared
|
||||||
commands:
|
commands:
|
||||||
- meson wrap install gtest || true
|
|
||||||
- meson target/shared
|
- meson target/shared
|
||||||
- ninja -C target/shared
|
- ninja -C target/shared
|
||||||
- mv target/shared/src/xwim xwim-x86_64-glibc-linux-shared
|
- mv target/shared/src/xwim xwim-x86_64-glibc-linux-shared
|
||||||
|
@ -14,7 +13,6 @@ steps:
|
||||||
- name: build-static
|
- name: build-static
|
||||||
image: arminfriedl/xwim-build:static
|
image: arminfriedl/xwim-build:static
|
||||||
commands:
|
commands:
|
||||||
- meson wrap install gtest || true
|
|
||||||
- meson --default-library=static target/static
|
- meson --default-library=static target/static
|
||||||
- ninja -C target/static
|
- ninja -C target/static
|
||||||
- mv target/static/src/xwim xwim-x86_64-musl-linux-static
|
- mv target/static/src/xwim xwim-x86_64-musl-linux-static
|
||||||
|
@ -50,7 +48,6 @@ steps:
|
||||||
- name: build-shared
|
- name: build-shared
|
||||||
image: arminfriedl/xwim-build:shared
|
image: arminfriedl/xwim-build:shared
|
||||||
commands:
|
commands:
|
||||||
- meson wrap install gtest || true
|
|
||||||
- meson --buildtype=release target/shared
|
- meson --buildtype=release target/shared
|
||||||
- ninja -C target/shared
|
- ninja -C target/shared
|
||||||
- strip target/shared/src/xwim
|
- strip target/shared/src/xwim
|
||||||
|
@ -60,7 +57,6 @@ steps:
|
||||||
- name: build-static
|
- name: build-static
|
||||||
image: arminfriedl/xwim-build:static
|
image: arminfriedl/xwim-build:static
|
||||||
commands:
|
commands:
|
||||||
- meson wrap install gtest || true
|
|
||||||
- meson --buildtype=release --default-library=static target/static
|
- meson --buildtype=release --default-library=static target/static
|
||||||
- ninja -C target/static
|
- ninja -C target/static
|
||||||
- strip target/static/src/xwim
|
- strip target/static/src/xwim
|
||||||
|
|
248
.gitignore
vendored
248
.gitignore
vendored
|
@ -1,12 +1,13 @@
|
||||||
.clangd/
|
.clangd/
|
||||||
|
.cache/
|
||||||
build/
|
build/
|
||||||
target/
|
target/
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
.vscode
|
||||||
.ccls-cache
|
.ccls-cache
|
||||||
.idea/codeStyles/**
|
|
||||||
|
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/c++,vim,emacs,linux,macos,ninja,windows,jetbrains+all,clion+all,visualstudiocode
|
# Created by https://www.gitignore.io/api/vim,c++,emacs,ninja
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=c++,vim,emacs,linux,macos,ninja,windows,jetbrains+all,clion+all,visualstudiocode
|
# Edit at https://www.gitignore.io/?templates=vim,c++,emacs,ninja
|
||||||
|
|
||||||
### C++ ###
|
### C++ ###
|
||||||
# Prerequisites
|
# Prerequisites
|
||||||
|
@ -42,94 +43,6 @@ compile_commands.json
|
||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
|
||||||
### CLion+all ###
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### CLion+all Patch ###
|
|
||||||
# Ignore everything but code style settings and run configurations
|
|
||||||
# that are supposed to be shared within teams.
|
|
||||||
|
|
||||||
.idea/*
|
|
||||||
|
|
||||||
!.idea/codeStyles
|
|
||||||
!.idea/runConfigurations
|
|
||||||
|
|
||||||
### Emacs ###
|
### Emacs ###
|
||||||
# -*- mode: gitignore; -*-
|
# -*- mode: gitignore; -*-
|
||||||
*~
|
*~
|
||||||
|
@ -181,108 +94,6 @@ flycheck_*.el
|
||||||
/network-security.data
|
/network-security.data
|
||||||
|
|
||||||
|
|
||||||
### JetBrains+all ###
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
|
|
||||||
### JetBrains+all Patch ###
|
|
||||||
# Ignore everything but code style settings and run configurations
|
|
||||||
# that are supposed to be shared within teams.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Linux ###
|
|
||||||
|
|
||||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
|
||||||
.fuse_hidden*
|
|
||||||
|
|
||||||
# KDE directory preferences
|
|
||||||
.directory
|
|
||||||
|
|
||||||
# Linux trash folder which might appear on any partition or disk
|
|
||||||
.Trash-*
|
|
||||||
|
|
||||||
# .nfs files are created when an open file is removed but is still being accessed
|
|
||||||
.nfs*
|
|
||||||
|
|
||||||
### macOS ###
|
|
||||||
# General
|
|
||||||
.DS_Store
|
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Icon must end with two \r
|
|
||||||
Icon
|
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
|
||||||
|
|
||||||
# Files that might appear in the root of a volume
|
|
||||||
.DocumentRevisions-V100
|
|
||||||
.fseventsd
|
|
||||||
.Spotlight-V100
|
|
||||||
.TemporaryItems
|
|
||||||
.Trashes
|
|
||||||
.VolumeIcon.icns
|
|
||||||
.com.apple.timemachine.donotpresent
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
|
||||||
.AppleDB
|
|
||||||
.AppleDesktop
|
|
||||||
Network Trash Folder
|
|
||||||
Temporary Items
|
|
||||||
.apdisk
|
|
||||||
|
|
||||||
### macOS Patch ###
|
|
||||||
# iCloud generated files
|
|
||||||
*.icloud
|
|
||||||
|
|
||||||
### Ninja ###
|
### Ninja ###
|
||||||
.ninja_deps
|
.ninja_deps
|
||||||
.ninja_log
|
.ninja_log
|
||||||
|
@ -290,7 +101,6 @@ Temporary Items
|
||||||
### Vim ###
|
### Vim ###
|
||||||
# Swap
|
# Swap
|
||||||
[._]*.s[a-v][a-z]
|
[._]*.s[a-v][a-z]
|
||||||
!*.svg # comment out if you don't need vector files
|
|
||||||
[._]*.sw[a-p]
|
[._]*.sw[a-p]
|
||||||
[._]s[a-rt-v][a-z]
|
[._]s[a-rt-v][a-z]
|
||||||
[._]ss[a-gi-z]
|
[._]ss[a-gi-z]
|
||||||
|
@ -302,54 +112,14 @@ Sessionx.vim
|
||||||
|
|
||||||
# Temporary
|
# Temporary
|
||||||
.netrwhist
|
.netrwhist
|
||||||
|
|
||||||
# Auto-generated tag files
|
# Auto-generated tag files
|
||||||
tags
|
tags
|
||||||
|
|
||||||
# Persistent undo
|
# Persistent undo
|
||||||
[._]*.un~
|
[._]*.un~
|
||||||
|
|
||||||
### VisualStudioCode ###
|
# Coc configuration directory
|
||||||
.vscode/*
|
.vim
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
!.vscode/*.code-snippets
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
# End of https://www.gitignore.io/api/vim,c++,emacs,ninja
|
||||||
.history/
|
|
||||||
|
|
||||||
# Built Visual Studio Code Extensions
|
|
||||||
*.vsix
|
|
||||||
|
|
||||||
### VisualStudioCode Patch ###
|
|
||||||
# Ignore all local history of files
|
|
||||||
.history
|
|
||||||
.ionide
|
|
||||||
|
|
||||||
### Windows ###
|
|
||||||
# Windows thumbnail cache files
|
|
||||||
Thumbs.db
|
|
||||||
Thumbs.db:encryptable
|
|
||||||
ehthumbs.db
|
|
||||||
ehthumbs_vista.db
|
|
||||||
|
|
||||||
# Dump file
|
|
||||||
*.stackdump
|
|
||||||
|
|
||||||
# Folder config file
|
|
||||||
[Dd]esktop.ini
|
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
# Windows Installer files
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msix
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# Windows shortcuts
|
|
||||||
*.lnk
|
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/c++,vim,emacs,linux,macos,ninja,windows,jetbrains+all,clion+all,visualstudiocode
|
|
||||||
|
|
|
@ -5,9 +5,7 @@ project('xwim', 'cpp',
|
||||||
'b_ndebug=if-release'])
|
'b_ndebug=if-release'])
|
||||||
|
|
||||||
add_global_arguments('-DVERSION='+meson.version(), language: 'cpp')
|
add_global_arguments('-DVERSION='+meson.version(), language: 'cpp')
|
||||||
add_global_arguments('-DSPDLOG_FMT_EXTERNAL', language: 'cpp')
|
|
||||||
add_global_arguments('-DFMT_HEADER_ONLY', language: 'cpp')
|
|
||||||
|
|
||||||
subdir('src')
|
subdir('src')
|
||||||
subdir('doc')
|
subdir('doc')
|
||||||
subdir('test')
|
# subdir('test')
|
||||||
|
|
|
@ -1,21 +1,13 @@
|
||||||
#include "Archiver.hpp"
|
#include "Archiver.hpp"
|
||||||
#include "Formats.hpp"
|
|
||||||
|
|
||||||
|
#include <spdlog/common.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "util/Common.hpp"
|
#include "Common.hpp"
|
||||||
|
|
||||||
#if defined(unix) || defined(__unix__) || defined(__unix)
|
|
||||||
std::string default_extension = ".tar.gz";
|
|
||||||
#elif defined(_win32) || defined(__win32__) || defined(__windows__)
|
|
||||||
std::string default_extension = ".zip";
|
|
||||||
#else
|
|
||||||
std::string default_extension = ".zip";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace xwim {
|
namespace xwim {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -38,9 +30,9 @@ fs::path archive_extension(const fs::path& path) {
|
||||||
|
|
||||||
while (tmp_path.has_extension()) {
|
while (tmp_path.has_extension()) {
|
||||||
tmp_ext = tmp_path.extension() += tmp_ext;
|
tmp_ext = tmp_path.extension() += tmp_ext;
|
||||||
Format format = find_extension_format(tmp_ext);
|
auto search = extensions_format.find(tmp_ext);
|
||||||
|
|
||||||
if (format != Format::UNKNOWN) {
|
if (search != extensions_format.end()) {
|
||||||
// (Combined) extension known. Remember as `ext` and keep
|
// (Combined) extension known. Remember as `ext` and keep
|
||||||
// looking for even longer extensions.
|
// looking for even longer extensions.
|
||||||
ext = tmp_ext;
|
ext = tmp_ext;
|
||||||
|
@ -77,9 +69,9 @@ fs::path strip_archive_extension(const fs::path& path) {
|
||||||
tmp_ext = tmp_path.extension() += tmp_ext;
|
tmp_ext = tmp_path.extension() += tmp_ext;
|
||||||
spdlog::debug("Looking for {} in known extensions", tmp_ext);
|
spdlog::debug("Looking for {} in known extensions", tmp_ext);
|
||||||
|
|
||||||
Format format = find_extension_format(tmp_ext);
|
auto search = extensions_format.find(tmp_ext);
|
||||||
tmp_longest_ext++;
|
tmp_longest_ext++;
|
||||||
if (format != Format::UNKNOWN) {
|
if (search != extensions_format.end()) {
|
||||||
// (Combined) extension known. Remember as `longest_ext` and keep
|
// (Combined) extension known. Remember as `longest_ext` and keep
|
||||||
// looking for even longer extensions.
|
// looking for even longer extensions.
|
||||||
longest_ext = tmp_longest_ext;
|
longest_ext = tmp_longest_ext;
|
||||||
|
@ -98,14 +90,7 @@ fs::path strip_archive_extension(const fs::path& path) {
|
||||||
return tmp_path;
|
return tmp_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path default_archive(const std::filesystem::path& base) {
|
bool can_extract(const fs::path& path) {
|
||||||
string base_s = base.string();
|
|
||||||
string ext_s = default_extension;
|
|
||||||
|
|
||||||
return fs::path{fmt::format("{}{}", base_s, ext_s)};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool can_handle_archive(const fs::path& path) {
|
|
||||||
fs::path ext = archive_extension(path);
|
fs::path ext = archive_extension(path);
|
||||||
if (format_extensions.find(ext.string()) != format_extensions.end()) {
|
if (format_extensions.find(ext.string()) != format_extensions.end()) {
|
||||||
spdlog::debug("Found {} in known formats", ext);
|
spdlog::debug("Found {} in known formats", ext);
|
||||||
|
@ -120,22 +105,19 @@ Format parse_format(const fs::path& path) {
|
||||||
spdlog::debug("Looking for path {}", path);
|
spdlog::debug("Looking for path {}", path);
|
||||||
fs::path ext = archive_extension(path);
|
fs::path ext = archive_extension(path);
|
||||||
spdlog::debug("Looking for ext {}", ext);
|
spdlog::debug("Looking for ext {}", ext);
|
||||||
Format format = find_extension_format(ext);
|
auto search = extensions_format.find(ext);
|
||||||
|
if (search == extensions_format.end()) {
|
||||||
if (format == Format::UNKNOWN) {
|
|
||||||
throw XwimError{"No known archiver for {}", path};
|
throw XwimError{"No known archiver for {}", path};
|
||||||
}
|
}
|
||||||
|
|
||||||
return format;
|
return search->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<Archiver> make_archiver(const string& archive_name) {
|
unique_ptr<Archiver> make_archiver(const string& archive_name) {
|
||||||
switch (parse_format(archive_name)) {
|
switch (parse_format(archive_name)) {
|
||||||
case Format::TAR_GZIP: case Format::TAR_BZIP2:
|
case Format::TAR_GZ:
|
||||||
case Format::TAR_COMPRESS: case Format::TAR_LZIP:
|
case Format::ZIP:
|
||||||
case Format::TAR_XZ: case Format::TAR_ZSTD:
|
return make_unique<LibArchiver>();
|
||||||
case Format::ZIP:
|
|
||||||
return make_unique<LibArchiver>();
|
|
||||||
default:
|
default:
|
||||||
throw XwimError{
|
throw XwimError{
|
||||||
"Cannot construct archiver for {}. `extension_format` surjection "
|
"Cannot construct archiver for {}. `extension_format` surjection "
|
||||||
|
@ -143,5 +125,4 @@ unique_ptr<Archiver> make_archiver(const string& archive_name) {
|
||||||
archive_name};
|
archive_name};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace xwim
|
} // namespace xwim
|
||||||
|
|
|
@ -7,11 +7,18 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include "util/Common.hpp"
|
#include "Common.hpp"
|
||||||
#include "Formats.hpp"
|
|
||||||
|
|
||||||
namespace xwim {
|
namespace xwim {
|
||||||
|
|
||||||
|
// Invariant:
|
||||||
|
// `extensions_format` defines a surjection from `format_extensions`
|
||||||
|
// to `Formats`
|
||||||
|
const std::set<std::string> format_extensions{".tar.gz", ".zip"};
|
||||||
|
enum class Format { TAR_GZ, ZIP };
|
||||||
|
const std::map<std::string, Format> extensions_format{
|
||||||
|
{".tar.gz", Format::TAR_GZ}, {".zip", Format::ZIP}};
|
||||||
|
|
||||||
class Archiver {
|
class Archiver {
|
||||||
public:
|
public:
|
||||||
virtual void compress(std::set<std::filesystem::path> ins,
|
virtual void compress(std::set<std::filesystem::path> ins,
|
||||||
|
@ -33,10 +40,8 @@ class LibArchiver : public Archiver {
|
||||||
|
|
||||||
std::filesystem::path archive_extension(const std::filesystem::path& path);
|
std::filesystem::path archive_extension(const std::filesystem::path& path);
|
||||||
std::filesystem::path strip_archive_extension(const std::filesystem::path& path);
|
std::filesystem::path strip_archive_extension(const std::filesystem::path& path);
|
||||||
std::filesystem::path default_archive(const std::filesystem::path& base);
|
|
||||||
|
|
||||||
Format parse_format(const std::filesystem::path& path);
|
Format parse_format(const std::filesystem::path& path);
|
||||||
bool can_handle_archive(const std::filesystem::path& path);
|
bool can_extract(const std::filesystem::path& path);
|
||||||
|
|
||||||
std::unique_ptr<Archiver> make_archiver(const std::string& archive_name);
|
std::unique_ptr<Archiver> make_archiver(const std::string& archive_name);
|
||||||
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace xwim {
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
// Invariant:
|
|
||||||
// `extensions_format` defines a surjection from `format_extensions`
|
|
||||||
// to `Formats`
|
|
||||||
enum class Format {
|
|
||||||
UNKNOWN,
|
|
||||||
TAR_BZIP2, TAR_GZIP, TAR_LZIP, TAR_XZ, TAR_COMPRESS, TAR_ZSTD,
|
|
||||||
ZIP
|
|
||||||
};
|
|
||||||
|
|
||||||
const set<string> format_extensions{
|
|
||||||
// tar formats see: https://en.wikipedia.org/wiki/Tar_(computing)#Suffixes_for_compressed_files
|
|
||||||
|
|
||||||
/* bzip2 */ ".tar.bz2", ".tb2", ".tbz", ".tbz2", ".tz2",
|
|
||||||
/* gzip */ ".tar.gz", ".taz", ".tgz",
|
|
||||||
/* lzip */ ".tar.lz",
|
|
||||||
/* xz */ ".tar.xz", ".txz",
|
|
||||||
/* compress */ ".tar.Z", ".tZ", ".taZ",
|
|
||||||
/* zstd */ ".tar.zst", ".tzst",
|
|
||||||
|
|
||||||
/* zip */ ".zip"
|
|
||||||
};
|
|
||||||
|
|
||||||
const map<set<string>, Format> extensions_format{
|
|
||||||
{{".tar.bz2", ".tb2", ".tbz", ".tbz2", ".tz2"}, Format::TAR_BZIP2},
|
|
||||||
{{".tar.gz", ".taz", ".tgz"}, Format::TAR_GZIP},
|
|
||||||
{{".tar.lz"}, Format::TAR_LZIP},
|
|
||||||
{{".tar.xz", ".txz"}, Format::TAR_XZ},
|
|
||||||
{{".tar.Z", ".tZ", ".taZ"}, Format::TAR_COMPRESS},
|
|
||||||
{{".tar.zst", ".tzst"}, Format::TAR_ZSTD},
|
|
||||||
|
|
||||||
{{".zip"}, Format::ZIP}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline Format find_extension_format(const string& ext) {
|
|
||||||
for(auto ef: extensions_format) {
|
|
||||||
auto f = ef.first.find(ext);
|
|
||||||
if(f != ef.first.end()) {
|
|
||||||
return ef.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Format::UNKNOWN;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <spdlog/common.h>
|
#include <spdlog/common.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#ifdef NDEBUG
|
#ifdef NDEBUG
|
||||||
#define XWIM_LOGLEVEL SPDLOG_LEVEL_ERROR
|
#define XWIM_LOGLEVEL SPDLOG_LEVEL_ERROR
|
||||||
|
@ -58,27 +57,7 @@ spdlog::level::level_enum _init_from_compile() {
|
||||||
* The determined level is then set for the default logger via
|
* The determined level is then set for the default logger via
|
||||||
* `spdlog::set_level`.
|
* `spdlog::set_level`.
|
||||||
*/
|
*/
|
||||||
void init(int verbosity = -1,
|
void init(spdlog::level::level_enum level = spdlog::level::level_enum::off) {
|
||||||
spdlog::level::level_enum level = spdlog::level::level_enum::off) {
|
|
||||||
if (verbosity != -1) {
|
|
||||||
switch (verbosity) {
|
|
||||||
case 0:
|
|
||||||
spdlog::set_level(spdlog::level::off);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
spdlog::set_level(spdlog::level::info);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
spdlog::set_level(spdlog::level::debug);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
default:
|
|
||||||
spdlog::set_level(spdlog::level::trace);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spdlog::level::level_enum::off != level) {
|
if (spdlog::level::level_enum::off != level) {
|
||||||
spdlog::set_level(level);
|
spdlog::set_level(level);
|
||||||
return;
|
return;
|
46
src/Opt.hpp
Normal file
46
src/Opt.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace xwim {
|
||||||
|
|
||||||
|
enum class Action { EXTRACT, COMPRESS };
|
||||||
|
|
||||||
|
struct UserOpt {
|
||||||
|
std::optional<Action> action;
|
||||||
|
bool interactive;
|
||||||
|
std::optional<std::filesystem::path> out;
|
||||||
|
std::set<std::filesystem::path> paths;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct XwimConfig {
|
||||||
|
Action action;
|
||||||
|
std::filesystem::path output;
|
||||||
|
std::set<std::filesystem::path> input;
|
||||||
|
};
|
||||||
|
|
||||||
|
XwimConfig guess_wim(UserOpt user_opt);
|
||||||
|
void do_wim(XwimConfig);
|
||||||
|
|
||||||
|
} // namespace xwim
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<xwim::Action> {
|
||||||
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const xwim::Action& action, FormatContext& ctx) {
|
||||||
|
switch (action) {
|
||||||
|
case xwim::Action::EXTRACT:
|
||||||
|
return format_to(ctx.out(), "EXTRACT");
|
||||||
|
case xwim::Action::COMPRESS:
|
||||||
|
return format_to(ctx.out(), "COMPRESS");
|
||||||
|
};
|
||||||
|
return format_to(ctx.out(), "");
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,226 +0,0 @@
|
||||||
#include "UserIntent.hpp"
|
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include "Archiver.hpp"
|
|
||||||
|
|
||||||
namespace xwim {
|
|
||||||
unique_ptr<UserIntent> make_compress_intent(const UserOpt &userOpt) {
|
|
||||||
if (userOpt.paths.size() == 1) {
|
|
||||||
return make_unique<CompressSingleIntent>(
|
|
||||||
CompressSingleIntent{*userOpt.paths.begin(), userOpt.out});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userOpt.out.has_value()) {
|
|
||||||
throw XwimError("Cannot guess output for multiple targets");
|
|
||||||
}
|
|
||||||
|
|
||||||
return make_unique<CompressManyIntent>(
|
|
||||||
CompressManyIntent{userOpt.paths, userOpt.out.value()});
|
|
||||||
}
|
|
||||||
|
|
||||||
unique_ptr<UserIntent> make_extract_intent(const UserOpt &userOpt) {
|
|
||||||
for (const path &p : userOpt.paths) {
|
|
||||||
if (!can_handle_archive(p)) {
|
|
||||||
throw XwimError("Cannot extract path {}", p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return make_unique<ExtractIntent>(ExtractIntent{userOpt.paths, userOpt.out});
|
|
||||||
}
|
|
||||||
|
|
||||||
unique_ptr<UserIntent> try_infer_compress_intent(const UserOpt &userOpt) {
|
|
||||||
if (!userOpt.out.has_value()) {
|
|
||||||
spdlog::debug("No <out> provided");
|
|
||||||
if (userOpt.paths.size() != 1) {
|
|
||||||
spdlog::debug(
|
|
||||||
"Not a single-path compression. Cannot guess <out> for many-path "
|
|
||||||
"compression");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::debug("Only one <path> provided. Assume single-path compression.");
|
|
||||||
return make_unique<CompressSingleIntent>(
|
|
||||||
CompressSingleIntent{*userOpt.paths.begin(), userOpt.out});
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::debug("<out> provided: {}", userOpt.out.value());
|
|
||||||
if (can_handle_archive(userOpt.out.value())) {
|
|
||||||
spdlog::debug("{} given and a known archive format, assume compression",
|
|
||||||
userOpt.out.value());
|
|
||||||
return make_compress_intent(userOpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::debug(
|
|
||||||
"Cannot compress multiple paths without a user-provided output archive");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
unique_ptr<UserIntent> try_infer_extract_intent(const UserOpt &userOpt) {
|
|
||||||
bool can_extract_all =
|
|
||||||
std::all_of(userOpt.paths.begin(), userOpt.paths.end(),
|
|
||||||
[](const path &path) { return can_handle_archive(path); });
|
|
||||||
|
|
||||||
if (!can_extract_all) {
|
|
||||||
spdlog::debug(
|
|
||||||
"Cannot extract all provided <paths>. Assume this is not an "
|
|
||||||
"extraction.");
|
|
||||||
for (const path &p : userOpt.paths) {
|
|
||||||
if (!can_handle_archive(p)) {
|
|
||||||
spdlog::debug("Cannot handle {}", p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userOpt.out.has_value() && can_handle_archive(userOpt.out.value())) {
|
|
||||||
spdlog::debug(
|
|
||||||
"Could extract all provided <paths>. But also {} looks like an "
|
|
||||||
"archive. Ambiguous intent. Assume this is not an extraction.",
|
|
||||||
userOpt.out.value());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::debug(
|
|
||||||
"Could extract all provided <paths>. But also <out> looks like an "
|
|
||||||
"archive. Ambiguous intent. Assume this is not an extraction.");
|
|
||||||
return make_extract_intent(userOpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
unique_ptr<UserIntent> make_intent(const UserOpt &userOpt) {
|
|
||||||
if (userOpt.wants_compress() && userOpt.wants_extract()) {
|
|
||||||
throw XwimError("Cannot compress and extract simultaneously");
|
|
||||||
}
|
|
||||||
if (userOpt.paths.empty()) {
|
|
||||||
throw XwimError("No input given...");
|
|
||||||
}
|
|
||||||
|
|
||||||
// explicitly specified intent
|
|
||||||
if (userOpt.wants_compress()) return make_compress_intent(userOpt);
|
|
||||||
if (userOpt.wants_extract()) return make_extract_intent(userOpt);
|
|
||||||
|
|
||||||
spdlog::info("Intent not explicitly provided, trying to infer intent");
|
|
||||||
|
|
||||||
if (auto intent = try_infer_extract_intent(userOpt)) {
|
|
||||||
spdlog::info("Extraction intent inferred");
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
spdlog::info("Cannot infer extraction intent");
|
|
||||||
|
|
||||||
if (auto intent = try_infer_compress_intent(userOpt)) {
|
|
||||||
spdlog::info("Compression intent inferred");
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
spdlog::info("Cannot infer compression intent");
|
|
||||||
|
|
||||||
throw XwimError("Cannot guess intent");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExtractIntent::dwim_reparent(const path &out) {
|
|
||||||
// move extraction if extraction resulted in only one entry and that entries
|
|
||||||
// name is already the stripped archive name, i.e. reduce unnecessary nesting
|
|
||||||
auto dit = std::filesystem::directory_iterator(out);
|
|
||||||
auto dit_path = dit->path();
|
|
||||||
|
|
||||||
if (dit == std::filesystem::directory_iterator()) {
|
|
||||||
spdlog::debug(
|
|
||||||
"Cannot flatten extraction folder: extraction folder is empty");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_directory(dit_path)) {
|
|
||||||
spdlog::debug("Cannot flatten extraction folder: {} is not a directory",
|
|
||||||
dit_path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next(dit) != std::filesystem::directory_iterator()) {
|
|
||||||
spdlog::debug("Cannot flatten extraction folder: multiple items extracted");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!std::filesystem::equivalent(dit_path.filename(), out.filename())) {
|
|
||||||
spdlog::debug(
|
|
||||||
"Cannot flatten extraction folder: archive entry differs from archive "
|
|
||||||
"name [extraction folder: {}, archive entry: {}]",
|
|
||||||
out.filename(), dit_path.filename());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::debug("Output folder [{}] is equivalent to archive entry [{}]", out,
|
|
||||||
dit_path);
|
|
||||||
spdlog::info("Flattening extraction folder");
|
|
||||||
|
|
||||||
int i = rand_int(0, 100000);
|
|
||||||
path tmp_out = path{out};
|
|
||||||
tmp_out.concat(fmt::format(".xwim{}", i));
|
|
||||||
spdlog::debug("Move {} to {}", dit_path, tmp_out);
|
|
||||||
std::filesystem::rename(dit_path, tmp_out);
|
|
||||||
spdlog::debug("Remove parent path {}", out);
|
|
||||||
std::filesystem::remove(out);
|
|
||||||
spdlog::debug("Moving {} to {}", tmp_out, out);
|
|
||||||
std::filesystem::rename(tmp_out, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
path ExtractIntent::out_path(const path &p) {
|
|
||||||
if (!this->out.has_value()) {
|
|
||||||
// not out path given, create from archive name
|
|
||||||
path out = std::filesystem::current_path() / strip_archive_extension(p);
|
|
||||||
create_directories(out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->archives.size() == 1) {
|
|
||||||
// out given and only one archive to extract, just extract into `out`
|
|
||||||
create_directories(this->out.value());
|
|
||||||
return this->out.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
// out given and multiple archives to extract, create subfolder
|
|
||||||
// for each archive
|
|
||||||
create_directories(this->out.value());
|
|
||||||
path out = this->out.value() / strip_archive_extension(p);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExtractIntent::execute() {
|
|
||||||
for (const path &p : this->archives) {
|
|
||||||
std::unique_ptr<Archiver> archiver = make_archiver(p);
|
|
||||||
path out = this->out_path(p);
|
|
||||||
archiver->extract(p, out);
|
|
||||||
this->dwim_reparent(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
path CompressSingleIntent::out_path() {
|
|
||||||
if (this->out.has_value()) {
|
|
||||||
if (!can_handle_archive(this->out.value())) {
|
|
||||||
throw XwimError("Unknown archive format {}", this->out.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->out.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
return default_archive(strip_archive_extension(this->in).stem());
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressSingleIntent::execute() {
|
|
||||||
path out = this->out_path();
|
|
||||||
unique_ptr<Archiver> archiver = make_archiver(out);
|
|
||||||
set<path> ins{this->in};
|
|
||||||
archiver->compress(ins, out);
|
|
||||||
};
|
|
||||||
|
|
||||||
void CompressManyIntent::execute() {
|
|
||||||
if (!can_handle_archive(this->out)) {
|
|
||||||
throw XwimError("Unknown archive format {}", this->out);
|
|
||||||
}
|
|
||||||
|
|
||||||
unique_ptr<Archiver> archiver = make_archiver(this->out);
|
|
||||||
archiver->compress(this->in_paths, this->out);
|
|
||||||
}
|
|
||||||
} // namespace xwim
|
|
|
@ -1,93 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
#include "util/Common.hpp"
|
|
||||||
#include "UserOpt.hpp"
|
|
||||||
|
|
||||||
namespace xwim {
|
|
||||||
using namespace std;
|
|
||||||
using std::filesystem::path;
|
|
||||||
|
|
||||||
class UserIntent {
|
|
||||||
public:
|
|
||||||
virtual void execute() = 0;
|
|
||||||
virtual ~UserIntent() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Factory method to construct a UserIntent which implements `execute()` */
|
|
||||||
unique_ptr<UserIntent> make_intent(const UserOpt& userOpt);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extraction intent
|
|
||||||
*
|
|
||||||
* Extracts one or multiple archives. Optionally extracts them to given `out` folder. Otherwise extracts them to the
|
|
||||||
* current working directory.
|
|
||||||
*/
|
|
||||||
class ExtractIntent: public UserIntent {
|
|
||||||
private:
|
|
||||||
set<path> archives;
|
|
||||||
optional<path> out;
|
|
||||||
|
|
||||||
void dwim_reparent(const path& out);
|
|
||||||
path out_path(const path& p);
|
|
||||||
|
|
||||||
public:
|
|
||||||
ExtractIntent(set<path> archives, optional<path> out): archives(archives), out(out) {};
|
|
||||||
~ExtractIntent() override = default;
|
|
||||||
|
|
||||||
void execute() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compress intent for a single file or folder.
|
|
||||||
*
|
|
||||||
* Compresses a single path which may be a file or a folder.
|
|
||||||
*
|
|
||||||
* No `out` path given:
|
|
||||||
* - derives the archive name from the input path
|
|
||||||
* - uses the default archive format for the platform
|
|
||||||
*
|
|
||||||
* `out` path given:
|
|
||||||
* - `out` path must be a path with a valid archive name (including extension)
|
|
||||||
* - tries to compress the input to the out archive
|
|
||||||
* - if the `out` base name is different from the input base name, puts the input into a new folder
|
|
||||||
* with base name inside the archive (archive base name is always the name of the archive content)
|
|
||||||
*/
|
|
||||||
class CompressSingleIntent : public UserIntent {
|
|
||||||
private:
|
|
||||||
path in;
|
|
||||||
optional<path> out;
|
|
||||||
|
|
||||||
path out_path();
|
|
||||||
|
|
||||||
public:
|
|
||||||
CompressSingleIntent(path in, optional<path> out) : UserIntent(), in(in), out(out) {};
|
|
||||||
~CompressSingleIntent() override = default;
|
|
||||||
|
|
||||||
void execute() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compress intent for multiple files and/or folders.
|
|
||||||
*
|
|
||||||
* Compresses multiple files and/or folders to a single archive as given by the `out` path. Since `out` cannot be
|
|
||||||
* guessed from the input in this case it is mandatory.
|
|
||||||
*
|
|
||||||
* A new, single root folder with base name equal to base name of the `out` archive is created inside the archive. All
|
|
||||||
* input files are put into this root folder.
|
|
||||||
*/
|
|
||||||
class CompressManyIntent: public UserIntent {
|
|
||||||
private:
|
|
||||||
set<path> in_paths;
|
|
||||||
path out;
|
|
||||||
|
|
||||||
public:
|
|
||||||
CompressManyIntent(set<path> in_paths, path out): UserIntent(), in_paths(in_paths), out(out) {};
|
|
||||||
~CompressManyIntent() override = default;
|
|
||||||
|
|
||||||
void execute() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace xwim
|
|
|
@ -1,52 +0,0 @@
|
||||||
#include "UserOpt.hpp"
|
|
||||||
|
|
||||||
#include <tclap/CmdLine.h>
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct TCLAP::ArgTraits<std::filesystem::path> {
|
|
||||||
// We use `operator=` here for path construction
|
|
||||||
// because `operator>>` (`ValueLike`) causes a split at
|
|
||||||
// whitespace
|
|
||||||
typedef StringLike ValueCategory;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace xwim {
|
|
||||||
UserOpt::UserOpt(int argc, char** argv) {
|
|
||||||
// clang-format off
|
|
||||||
TCLAP::CmdLine cmd
|
|
||||||
{"xwim - Do What I Mean Extractor", ' ', "0.3.0"};
|
|
||||||
|
|
||||||
TCLAP::SwitchArg arg_compress
|
|
||||||
{"c", "compress", "Compress <files>", cmd, false};
|
|
||||||
|
|
||||||
TCLAP::SwitchArg arg_extract
|
|
||||||
{"x", "extract", "Extract <file>", cmd, false};
|
|
||||||
|
|
||||||
TCLAP::SwitchArg arg_noninteractive
|
|
||||||
{"i", "non-interactive", "Non-interactive, fail on ambiguity", cmd, false};
|
|
||||||
|
|
||||||
TCLAP::ValueArg<fs::path> arg_outfile
|
|
||||||
{"o", "out", "Out <file-or-path>", false, fs::path{}, "A path on the filesystem", cmd};
|
|
||||||
|
|
||||||
TCLAP::MultiSwitchArg arg_verbose
|
|
||||||
{"v", "verbose", "Verbosity level", cmd, 0};
|
|
||||||
|
|
||||||
TCLAP::UnlabeledMultiArg<fs::path> arg_paths
|
|
||||||
{"files", "Archive(s) to extract or file(s) to compress", true, "A path on the filesystem", cmd};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
cmd.parse(argc, argv);
|
|
||||||
|
|
||||||
if (arg_compress.isSet()) this->compress = arg_compress.getValue();
|
|
||||||
if (arg_extract.isSet()) this->extract = arg_extract.getValue();
|
|
||||||
if (arg_outfile.isSet()) this->out = arg_outfile.getValue();
|
|
||||||
|
|
||||||
this->verbosity = arg_verbose.getValue();
|
|
||||||
this->interactive = !arg_noninteractive.getValue();
|
|
||||||
|
|
||||||
if (arg_paths.isSet()) {
|
|
||||||
this->paths =
|
|
||||||
set<fs::path>{arg_paths.getValue().begin(), arg_paths.getValue().end()};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace xwim
|
|
|
@ -1,31 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
#include "util/Common.hpp"
|
|
||||||
|
|
||||||
namespace xwim {
|
|
||||||
using namespace std;
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
struct UserOpt {
|
|
||||||
optional<bool> compress;
|
|
||||||
optional<bool> extract;
|
|
||||||
bool interactive;
|
|
||||||
int verbosity;
|
|
||||||
std::optional<fs::path> out;
|
|
||||||
std::set<fs::path> paths;
|
|
||||||
|
|
||||||
UserOpt(int argc, char** argv);
|
|
||||||
|
|
||||||
bool wants_compress() const {
|
|
||||||
return this->compress.has_value() && this->compress.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool wants_extract() const {
|
|
||||||
return this->extract.has_value() && this->extract.value();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace xwim
|
|
165
src/Xwim.cpp
Normal file
165
src/Xwim.cpp
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
#include "Xwim.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <ios>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "Archiver.hpp"
|
||||||
|
#include "Common.hpp"
|
||||||
|
|
||||||
|
#if defined(unix) || defined(__unix__) || defined(__unix)
|
||||||
|
std::string default_extension = ".tar.gz";
|
||||||
|
#elif defined(_win32) || defined(__win32__) || defined(__windows__)
|
||||||
|
std::string default_extension = ".zip";
|
||||||
|
#else
|
||||||
|
std::string default_extension = ".zip";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace xwim {
|
||||||
|
using namespace std;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
Xwim::Xwim() : action{Action::UNKNOWN} {}
|
||||||
|
|
||||||
|
void Xwim::try_infer() {
|
||||||
|
infer_action();
|
||||||
|
infer_output();
|
||||||
|
|
||||||
|
if (action == Action::COMPRESS) {
|
||||||
|
archiver = make_archiver(out.string());
|
||||||
|
} else if (action == Action::EXTRACT) {
|
||||||
|
// we can only handle one archive for extraction at a time.
|
||||||
|
// Checked in `infer_extraction_output`
|
||||||
|
archiver = make_archiver(ins.begin()->string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Xwim::dwim() {
|
||||||
|
switch (action) {
|
||||||
|
case Action::COMPRESS:
|
||||||
|
this->archiver->compress(ins, out);
|
||||||
|
break;
|
||||||
|
case Action::EXTRACT:
|
||||||
|
this->archiver->extract(*ins.begin(), out);
|
||||||
|
sanitize_output();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
spdlog::error("Unknown action");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Xwim::sanitize_output() {
|
||||||
|
fs::path in_stripped = xwim::strip_archive_extension(*ins.begin());
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
fs::directory_entry first_entry;
|
||||||
|
for(auto& e: fs::directory_iterator(out)) {
|
||||||
|
count++;
|
||||||
|
if(first_entry.path().empty()) {
|
||||||
|
first_entry = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count >= 2) {
|
||||||
|
spdlog::debug("Found multiple entries in extraction directory. Moving {} to {}", out, in_stripped);
|
||||||
|
fs::rename(out, in_stripped);
|
||||||
|
} else {
|
||||||
|
if(first_entry.is_directory()) {
|
||||||
|
spdlog::debug("Found single directory in extraction directory. Moving {} to {}",
|
||||||
|
first_entry.path(), in_stripped);
|
||||||
|
fs::rename(first_entry, in_stripped);
|
||||||
|
fs::remove(out);
|
||||||
|
} else {
|
||||||
|
spdlog::debug(
|
||||||
|
"Found single file in extraction directory. Moving {} to {}", out, in_stripped);
|
||||||
|
fs::rename(out, in_stripped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Xwim::infer_action() {
|
||||||
|
if (action != Action::UNKNOWN) return;
|
||||||
|
|
||||||
|
if (ins.size() == 1 && can_extract(*ins.begin())) {
|
||||||
|
action = Action::EXTRACT;
|
||||||
|
} else {
|
||||||
|
action = Action::COMPRESS;
|
||||||
|
}
|
||||||
|
spdlog::debug("Inferred action: {}", action);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Xwim::infer_output() {
|
||||||
|
if (!out.empty()) return;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case Action::COMPRESS:
|
||||||
|
infer_compression_output();
|
||||||
|
break;
|
||||||
|
case Action::EXTRACT:
|
||||||
|
infer_extraction_output();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw XwimError{"Cannot infer output, action is unknown"};
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::debug("Inferred out: {}", out.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Xwim::infer_compression_output() {
|
||||||
|
if (ins.size() == 1) {
|
||||||
|
// archive name is just the name of the input with default archive
|
||||||
|
// extension
|
||||||
|
fs::path archive_stem = xwim::strip_archive_extension(*ins.begin());
|
||||||
|
archive_stem += default_extension;
|
||||||
|
out = archive_stem;
|
||||||
|
} else {
|
||||||
|
// We cannot guess the name of the output archive
|
||||||
|
|
||||||
|
// TODO use readline/lineoise/editline for path completion
|
||||||
|
cout << "Archive name: ";
|
||||||
|
cin >> out;
|
||||||
|
out = fs::path(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Xwim::infer_extraction_output() {
|
||||||
|
if (ins.size() > 1) {
|
||||||
|
throw XwimError{"Cannot extract more than one archive at a time"};
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a temporary path for extraction
|
||||||
|
fs::path archive_stem = xwim::strip_archive_extension(*ins.begin());
|
||||||
|
|
||||||
|
// note: we use here what is considered an `extensions` by `fs::path` so that
|
||||||
|
// we can strip it again easily later on
|
||||||
|
archive_stem += ".";
|
||||||
|
archive_stem += to_string(rand_int(999, 99999));
|
||||||
|
archive_stem += ".tmp";
|
||||||
|
this->out = archive_stem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Xwim::setCompress() {
|
||||||
|
this->action = Action::COMPRESS;
|
||||||
|
spdlog::debug("Set action to {}", this->action);
|
||||||
|
}
|
||||||
|
void Xwim::setExtract() {
|
||||||
|
this->action = Action::EXTRACT;
|
||||||
|
spdlog::debug("Set action to {}", this->action);
|
||||||
|
}
|
||||||
|
void Xwim::setOut(fs::path path) {
|
||||||
|
this->out = path;
|
||||||
|
spdlog::debug("Set out to {}", this->out);
|
||||||
|
}
|
||||||
|
void Xwim::setIns(vector<fs::path> ins) {
|
||||||
|
this->ins.insert(ins.begin(), ins.end());
|
||||||
|
if (this->ins.size() != ins.size()) {
|
||||||
|
spdlog::warn("Duplicate input files found. Removed {} duplicate(s).",
|
||||||
|
(ins.size() - this->ins.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace xwim
|
46
src/Xwim.hpp
Normal file
46
src/Xwim.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "Common.hpp"
|
||||||
|
#include "Opt.hpp"
|
||||||
|
#include "Archiver.hpp"
|
||||||
|
|
||||||
|
namespace xwim {
|
||||||
|
using namespace std;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
Xwim xwim(UserOpt user_opt);
|
||||||
|
|
||||||
|
class Xwim {
|
||||||
|
private:
|
||||||
|
Action action;
|
||||||
|
fs::path out;
|
||||||
|
set<fs::path> ins;
|
||||||
|
unique_ptr<Archiver> archiver;
|
||||||
|
|
||||||
|
void infer_action();
|
||||||
|
void infer_output();
|
||||||
|
void infer_compression_output();
|
||||||
|
void infer_extraction_output();
|
||||||
|
void sanitize_output();
|
||||||
|
|
||||||
|
public:
|
||||||
|
Xwim();
|
||||||
|
|
||||||
|
void try_infer();
|
||||||
|
void dwim();
|
||||||
|
|
||||||
|
void setCompress();
|
||||||
|
void setExtract();
|
||||||
|
void setOut(fs::path);
|
||||||
|
void setIns(vector<fs::path> ins);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace xwim
|
|
@ -9,8 +9,8 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "../Archiver.hpp"
|
#include "Archiver.hpp"
|
||||||
#include "../util/Common.hpp"
|
#include "Common.hpp"
|
||||||
|
|
||||||
namespace xwim {
|
namespace xwim {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -27,9 +27,8 @@ void LibArchiver::compress(set<fs::path> ins, fs::path archive_out) {
|
||||||
// complete type. `archive` is forward declared only.
|
// complete type. `archive` is forward declared only.
|
||||||
shared_ptr<archive> writer;
|
shared_ptr<archive> writer;
|
||||||
writer = shared_ptr<archive>(archive_write_new(), archive_write_free);
|
writer = shared_ptr<archive>(archive_write_new(), archive_write_free);
|
||||||
// archive_write_add_filter_gzip(writer.get());
|
archive_write_add_filter_gzip(writer.get());
|
||||||
// archive_write_set_format_pax_restricted(writer.get());
|
archive_write_set_format_pax_restricted(writer.get());
|
||||||
archive_write_set_format_filter_by_ext(writer.get(), archive_out.c_str());
|
|
||||||
archive_write_open_filename(writer.get(), archive_out.c_str());
|
archive_write_open_filename(writer.get(), archive_out.c_str());
|
||||||
|
|
||||||
shared_ptr<archive> reader;
|
shared_ptr<archive> reader;
|
||||||
|
|
67
src/main.cpp
67
src/main.cpp
|
@ -1,26 +1,69 @@
|
||||||
#include <spdlog/common.h>
|
#include <spdlog/common.h>
|
||||||
|
#include <spdlog/logger.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <tclap/ArgException.h>
|
||||||
|
#include <tclap/CmdLine.h>
|
||||||
|
#include <tclap/StdOutput.h>
|
||||||
|
#include <tclap/SwitchArg.h>
|
||||||
|
#include <tclap/UnlabeledMultiArg.h>
|
||||||
|
#include <tclap/ValueArg.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "UserIntent.hpp"
|
#include "Common.hpp"
|
||||||
#include "UserOpt.hpp"
|
#include "Log.hpp"
|
||||||
#include "util/Common.hpp"
|
#include "Opt.hpp"
|
||||||
#include "util/Log.hpp"
|
#include "Xwim.hpp"
|
||||||
|
|
||||||
using namespace xwim;
|
using namespace xwim;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct TCLAP::ArgTraits<std::filesystem::path> {
|
||||||
|
typedef ValueLike ValueCategory;
|
||||||
|
};
|
||||||
|
|
||||||
|
UserOpt parse_args(int argc, char** argv) {
|
||||||
|
// clang-format off
|
||||||
|
TCLAP::CmdLine cmd_line
|
||||||
|
{"xwim - Do What I Mean Extractor", ' ', "0.5.0"};
|
||||||
|
|
||||||
|
TCLAP::SwitchArg arg_compress
|
||||||
|
{"c", "compress", "Compress <files>", cmd_line, false};
|
||||||
|
|
||||||
|
TCLAP::SwitchArg arg_extract
|
||||||
|
{"x", "extract", "Extract <files>", cmd_line, false};
|
||||||
|
|
||||||
|
TCLAP::SwitchArg arg_noninteractive
|
||||||
|
{"ni", "noninteractive", "Fail if action cannot be determined", cmd_line, false};
|
||||||
|
|
||||||
|
TCLAP::ValueArg<std::filesystem::path> arg_out
|
||||||
|
{"o", "out", "Out <path>", false, std::filesystem::path{}, "A path on the filesystem", cmd_line};
|
||||||
|
|
||||||
|
TCLAP::UnlabeledMultiArg<std::filesystem::path> arg_paths
|
||||||
|
{"Paths", "Filesystem paths to extract or compress", true, "A path on the filesystem", cmd_line};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
cmd_line.parse(argc, argv);
|
||||||
|
|
||||||
|
UserOpt user_opt;
|
||||||
|
|
||||||
|
if(arg_compress.getValue()) user_opt.action = Action::COMPRESS;
|
||||||
|
if(arg_extract.getValue()) user_opt.action = Action::EXTRACT;
|
||||||
|
if(arg_out.isSet()) user_opt.out = arg_out.getValue();
|
||||||
|
user_opt.interactive = !arg_noninteractive.getValue();
|
||||||
|
user_opt.paths = std::set<fs::path>{arg_paths.getValue().begin(), arg_paths.getValue().end()};
|
||||||
|
|
||||||
|
return user_opt;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
log::init();
|
log::init();
|
||||||
UserOpt user_opt = UserOpt{argc, argv};
|
|
||||||
log::init(user_opt.verbosity);
|
|
||||||
|
|
||||||
try {
|
UserOpt user_opt = parse_args(argc, argv);
|
||||||
unique_ptr<UserIntent> user_intent = make_intent(user_opt);
|
XwimConfig xwim_config = guess_wim(user_opt);
|
||||||
user_intent->execute();
|
do_wim(xwim_config);
|
||||||
} catch (XwimError& e) {
|
|
||||||
spdlog::error(e.what());
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
xwim_src = ['main.cpp', 'Archiver.cpp', 'UserOpt.cpp', 'UserIntent.cpp']
|
xwim_src = ['main.cpp', 'Xwim.cpp', 'Archiver.cpp']
|
||||||
|
xwim_dwim = ['dwim/Opt.cpp']
|
||||||
xwim_archiver = ['archiver/LibArchiver.cpp']
|
xwim_archiver = ['archiver/LibArchiver.cpp']
|
||||||
|
|
||||||
is_static = get_option('default_library')=='static'
|
is_static = get_option('default_library')=='static'
|
||||||
|
|
||||||
xwim_libs = [dependency('libarchive', required: true, static: is_static),
|
xwim_libs = [dependency('libarchive', required: true, static: is_static),
|
||||||
dependency('spdlog', required: true, static: is_static),
|
|
||||||
dependency('fmt', required: true, static: is_static),
|
dependency('fmt', required: true, static: is_static),
|
||||||
|
dependency('spdlog', required: true, static: is_static),
|
||||||
dependency('tclap', required: true, static: is_static)]
|
dependency('tclap', required: true, static: is_static)]
|
||||||
|
|
||||||
executable('xwim', xwim_src+xwim_archiver, dependencies: xwim_libs)
|
executable('xwim',
|
||||||
|
xwim_src+xwim_archiver+xwim_dwim,
|
||||||
|
dependencies: xwim_libs)
|
||||||
|
|
|
@ -2,10 +2,22 @@
|
||||||
gtest_proj = subproject('gtest')
|
gtest_proj = subproject('gtest')
|
||||||
gtest_dep = gtest_proj.get_variable('gtest_main_dep')
|
gtest_dep = gtest_proj.get_variable('gtest_main_dep')
|
||||||
|
|
||||||
# subdir('archives')
|
xwim_src = ['../src/archive.cpp',
|
||||||
user_opt_test_exe = executable('user_opt_test_exe',
|
'../src/archive_sys.cpp']
|
||||||
sources: ['user_opt_test.cpp', '../src/UserOpt.cpp'],
|
|
||||||
include_directories: ['../src'],
|
|
||||||
dependencies: [gtest_dep])
|
|
||||||
|
|
||||||
test('user opt parsing test', user_opt_test_exe)
|
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)
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
#include <gtest/gtest-death-test.h>
|
|
||||||
#include "gtest/gtest.h"
|
|
||||||
#include <filesystem>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "UserOpt.hpp"
|
|
||||||
|
|
||||||
TEST(UserOpt, compress) {
|
|
||||||
using namespace xwim;
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
char* args[] = {
|
|
||||||
const_cast<char*>("xwim"),
|
|
||||||
const_cast<char*>("-c"),
|
|
||||||
const_cast<char*>("mandator_paths"),
|
|
||||||
nullptr};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
UserOpt uo = UserOpt{3, args};
|
|
||||||
ASSERT_TRUE(uo.compress);
|
|
||||||
ASSERT_FALSE(uo.extract);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UserOpt, exclusive_actions) {
|
|
||||||
using namespace xwim;
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
char* args[] = {
|
|
||||||
const_cast<char*>("xwim"),
|
|
||||||
const_cast<char*>("-c"),
|
|
||||||
const_cast<char*>("-x"),
|
|
||||||
const_cast<char*>("mandatory_paths"),
|
|
||||||
nullptr};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
UserOpt uo = UserOpt{4, args};
|
|
||||||
ASSERT_TRUE(uo.compress);
|
|
||||||
ASSERT_TRUE(uo.extract);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UserOpt, whitespace_in_path) {
|
|
||||||
using namespace xwim;
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
char* args[] = {
|
|
||||||
const_cast<char*>("xwim"),
|
|
||||||
const_cast<char*>("-c"),
|
|
||||||
const_cast<char*>("/foo/bar baz/a file"),
|
|
||||||
nullptr};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
UserOpt uo = UserOpt{3, args};
|
|
||||||
ASSERT_TRUE(uo.paths.find(std::filesystem::path("/foo/bar baz/a file")) !=
|
|
||||||
uo.paths.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UserOpt, mixed_output_and_paths) {
|
|
||||||
using namespace xwim;
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
char* args[] = {
|
|
||||||
const_cast<char*>("xwim"),
|
|
||||||
const_cast<char*>("-o"),
|
|
||||||
const_cast<char*>("/foo/bar baz/output"),
|
|
||||||
const_cast<char*>("/foo/bar baz/a path"),
|
|
||||||
const_cast<char*>("/foo/bar baz/another path"),
|
|
||||||
nullptr};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
UserOpt uo = UserOpt{5, args};
|
|
||||||
ASSERT_TRUE(uo.paths.find(std::filesystem::path("/foo/bar baz/a path")) !=
|
|
||||||
uo.paths.end());
|
|
||||||
ASSERT_TRUE(uo.paths.find(std::filesystem::path("/foo/bar baz/another path")) !=
|
|
||||||
uo.paths.end());
|
|
||||||
ASSERT_TRUE(uo.out == std::filesystem::path("/foo/bar baz/output"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UserOpt, output_defaults_to_nullopt) {
|
|
||||||
using namespace xwim;
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
char* args[] = {
|
|
||||||
const_cast<char*>("xwim"),
|
|
||||||
const_cast<char*>("/foo/bar"),
|
|
||||||
nullptr};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
UserOpt uo = UserOpt{2, args};
|
|
||||||
ASSERT_FALSE(uo.out);
|
|
||||||
}
|
|
Loading…
Reference in a new issue