Compare commits
35 commits
dynamic-lo
...
master
Author | SHA1 | Date | |
---|---|---|---|
1523f83a6b | |||
92ac6586de | |||
c8e6f51e1b | |||
1aea5cd924 | |||
293ee6e1cc | |||
5d9856c658 | |||
65053bbcb9 | |||
c1506c4547 | |||
c6e0e92db2 | |||
e6a6e9268e | |||
7141e67e14 | |||
70ae623a1d | |||
89dd5186f5 | |||
a7737533c2 | |||
df9c9d48cc | |||
f80dc1decd | |||
8873e7930b | |||
a6fb93a484 | |||
fe4a5f4460 | |||
c3d9dd6360 | |||
24f1407ed9 | |||
12afa628d0 | |||
6a7e98dbf6 | |||
4c1c5bf00f | |||
189be660f5 | |||
f5cddbc0f3 | |||
4945bbd45c | |||
645e93a03c | |||
6c8f0cb6bb | |||
6525de9303 | |||
221a6afe2a | |||
d9cbf47036 | |||
cdb3775eb6 | |||
9230a606ae | |||
7058c326c6 |
63 changed files with 1555 additions and 714 deletions
169
.clang-format
169
.clang-format
|
@ -1 +1,168 @@
|
|||
BasedOnStyle: Chromium
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentCaseLabels: true
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
|
|
81
.drone.yml
81
.drone.yml
|
@ -3,17 +3,38 @@ type: docker
|
|||
name: default
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: arminfriedl/xwim-build
|
||||
- name: build-shared
|
||||
image: arminfriedl/xwim-build:shared
|
||||
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
|
||||
- meson wrap install gtest || true
|
||||
- meson target/shared
|
||||
- ninja -C target/shared
|
||||
- mv target/shared/src/xwim xwim-x86_64-glibc-linux-shared
|
||||
|
||||
- name: build-static
|
||||
image: arminfriedl/xwim-build:static
|
||||
commands:
|
||||
- meson wrap install gtest || true
|
||||
- meson --default-library=static target/static
|
||||
- ninja -C target/static
|
||||
- mv target/static/src/xwim xwim-x86_64-musl-linux-static
|
||||
|
||||
- name: publish-binaries
|
||||
image: appleboy/drone-scp
|
||||
settings:
|
||||
host: friedl.net
|
||||
username:
|
||||
from_secret: deploy_user
|
||||
password:
|
||||
from_secret: deploy_password
|
||||
port: 22
|
||||
target: /var/services/dirlist/repo/cicd/xwim/${DRONE_COMMIT_SHA:0:8}/
|
||||
source:
|
||||
- xwim-x86_64-glibc-linux-shared
|
||||
- xwim-x86_64-musl-linux-static
|
||||
depends_on:
|
||||
- build-shared
|
||||
- build-static
|
||||
|
||||
trigger:
|
||||
event:
|
||||
|
@ -26,21 +47,34 @@ type: docker
|
|||
name: release
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: arminfriedl/xwim-build
|
||||
- name: build-shared
|
||||
image: arminfriedl/xwim-build:shared
|
||||
commands:
|
||||
- meson wrap install gtest
|
||||
- meson --buildtype=release build
|
||||
- ninja -C build
|
||||
- mkdir xwim-${DRONE_TAG}-x86_64-glibc-linux
|
||||
- mv build/src/xwim xwim-${DRONE_TAG}-x86_64-glibc-linux
|
||||
- meson wrap install gtest || true
|
||||
- meson --buildtype=release target/shared
|
||||
- ninja -C target/shared
|
||||
- strip target/shared/src/xwim
|
||||
- mkdir xwim-${DRONE_TAG}-x86_64-glibc-linux-shared
|
||||
- mv target/shared/src/xwim xwim-${DRONE_TAG}-x86_64-glibc-linux-shared
|
||||
|
||||
- name: build-static
|
||||
image: arminfriedl/xwim-build:static
|
||||
commands:
|
||||
- meson wrap install gtest || true
|
||||
- meson --buildtype=release --default-library=static target/static
|
||||
- ninja -C target/static
|
||||
- strip target/static/src/xwim
|
||||
- mkdir xwim-${DRONE_TAG}-x86_64-musl-linux-static
|
||||
- mv target/static/src/xwim xwim-${DRONE_TAG}-x86_64-musl-linux-static
|
||||
|
||||
- name: package
|
||||
image: arminfriedl/xwim-build
|
||||
commands:
|
||||
- tar cjf xwim-${DRONE_TAG}-x86_64-glibc-linux.tar.bz2 xwim-${DRONE_TAG}-x86_64-glibc-linux/xwim
|
||||
- tar czf xwim-${DRONE_TAG}-x86_64-glibc-linux.tar.gz xwim-${DRONE_TAG}-x86_64-glibc-linux/xwim
|
||||
- zip -r xwim-${DRONE_TAG}-x86_64-glibc-linux.zip xwim-${DRONE_TAG}-x86_64-glibc-linux
|
||||
- tar czf xwim-${DRONE_TAG}-x86_64-glibc-linux-shared.tar.gz xwim-${DRONE_TAG}-x86_64-glibc-linux-shared/xwim
|
||||
- tar czf xwim-${DRONE_TAG}-x86_64-musl-linux-static.tar.gz xwim-${DRONE_TAG}-x86_64-musl-linux-static/xwim
|
||||
depends_on:
|
||||
- build-shared
|
||||
- build-static
|
||||
|
||||
- name: publish
|
||||
image: plugins/gitea-release
|
||||
|
@ -49,13 +83,14 @@ steps:
|
|||
api_key:
|
||||
from_secret: gitea_token
|
||||
files:
|
||||
- xwim-${DRONE_TAG}-x86_64-glibc-linux.tar.bz2
|
||||
- xwim-${DRONE_TAG}-x86_64-glibc-linux.tar.gz
|
||||
- xwim-${DRONE_TAG}-x86_64-glibc-linux.zip
|
||||
- xwim-${DRONE_TAG}-x86_64-glibc-linux-shared.tar.gz
|
||||
- xwim-${DRONE_TAG}-x86_64-musl-linux-static.tar.gz
|
||||
title: xwim ${DRONE_TAG}
|
||||
checksum:
|
||||
- md5
|
||||
- sha256
|
||||
depends_on:
|
||||
- package
|
||||
|
||||
trigger:
|
||||
event:
|
||||
|
|
247
.gitignore
vendored
247
.gitignore
vendored
|
@ -2,11 +2,11 @@
|
|||
build/
|
||||
target/
|
||||
compile_commands.json
|
||||
.vscode
|
||||
.ccls-cache
|
||||
.idea/codeStyles/**
|
||||
|
||||
# Created by https://www.gitignore.io/api/vim,c++,emacs,ninja
|
||||
# Edit at https://www.gitignore.io/?templates=vim,c++,emacs,ninja
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/c++,vim,emacs,linux,macos,ninja,windows,jetbrains+all,clion+all,visualstudiocode
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=c++,vim,emacs,linux,macos,ninja,windows,jetbrains+all,clion+all,visualstudiocode
|
||||
|
||||
### C++ ###
|
||||
# Prerequisites
|
||||
|
@ -42,6 +42,94 @@ compile_commands.json
|
|||
*.out
|
||||
*.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 ###
|
||||
# -*- mode: gitignore; -*-
|
||||
*~
|
||||
|
@ -93,6 +181,108 @@ flycheck_*.el
|
|||
/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_deps
|
||||
.ninja_log
|
||||
|
@ -100,6 +290,7 @@ flycheck_*.el
|
|||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
!*.svg # comment out if you don't need vector files
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
|
@ -111,14 +302,54 @@ Sessionx.vim
|
|||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
# Coc configuration directory
|
||||
.vim
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# End of https://www.gitignore.io/api/vim,c++,emacs,ninja
|
||||
# Local History for Visual Studio Code
|
||||
.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
|
||||
|
|
12
Makefile
Normal file
12
Makefile
Normal file
|
@ -0,0 +1,12 @@
|
|||
all: compile_commands.json
|
||||
cd build && ninja
|
||||
|
||||
compile_commands.json:
|
||||
cd build && ninja -t compdb > compile_commands.json
|
||||
|
||||
clean:
|
||||
cd build && ninja -t clean
|
||||
|
||||
.PHONY:
|
||||
compile_commands.json
|
||||
clean
|
91
README.md
91
README.md
|
@ -5,7 +5,9 @@ Do What I Mean Extractor
|
|||
|
||||
![https://xkcd.com/1168/](https://imgs.xkcd.com/comics/tar.png)
|
||||
|
||||
Continuing the emacs tradition of "Do What I Mean" tools, xwim is a replacement
|
||||
[xkcd-1168](https://xkcd.com/1168/)
|
||||
|
||||
Continuing the emacs tradition of "Do What I Mean" tools, xwim is replacement
|
||||
for the excellent, but unfortunately unmaintained,
|
||||
[dtrx](https://github.com/brettcs/dtrx). xwim is a command line tool that
|
||||
targets two problems with archives:
|
||||
|
@ -15,6 +17,27 @@ considerably between formats
|
|||
- Inconsiderately packaged archives tend to spill their content over the
|
||||
directory they are extracted to
|
||||
|
||||
`dtrx` is a Python script that sets up the command line and calls appropriate
|
||||
archiving binaries (if installed). In contrast `xwim` is a compiled binary based
|
||||
directly on archiving libraries, which some may appreciate. It can optionally be
|
||||
statically linked if you want it entirely self-contained.
|
||||
|
||||
# Install
|
||||
`xwim` currently released for Linux only. There are two flavers: statically
|
||||
linked and dynamically linked. The releases can be downloaded from
|
||||
https://git.friedl.net/incubator/xwim/releases and should run on most 64-bit
|
||||
GNU/Linux distributions.
|
||||
|
||||
For the dynamically linked version, the following dependencies have to be
|
||||
installed:
|
||||
- [spdlog](https://github.com/gabime/spdlog)
|
||||
- [fmt](https://github.com/fmtlib/fmt)
|
||||
- [libarchive](https://github.com/libarchive/libarchive)
|
||||
|
||||
Windows support is planned for the first stable release. Packaging for various
|
||||
distributions is also planned once `xwim` stabilizes. Please reach out if you
|
||||
can help.
|
||||
|
||||
# Usage
|
||||
Invoking `xwim` is as simple as:
|
||||
|
||||
|
@ -23,8 +46,25 @@ xwim archive.tar.gz
|
|||
```
|
||||
|
||||
This will extract the archive to the current folder. If the archive contains a
|
||||
single root folder it is just extracted as is. Otherwise xwim first creates a
|
||||
folder named after the archive and extracts the contents there.
|
||||
single root folder it is just extracted as is. Otherwise xwim creates a folder
|
||||
named after the archive and extracts the contents there.
|
||||
|
||||
|
||||
```shell
|
||||
xwim /home/user/
|
||||
```
|
||||
|
||||
This will create an archive in the "platform native" format (zip on windows,
|
||||
tar.gz on unix) in the current working directory. The archive contains a single
|
||||
root folder `user` and is itself named `user.zip` or `user.tar.gz`.
|
||||
|
||||
```shell
|
||||
xwim /home/user/file.txt
|
||||
```
|
||||
|
||||
This will create an archive in the "platform native" format (zip on windows,
|
||||
tar.gz on unix) in the current working directory. The archive contains a single
|
||||
entry `file.txt` and is itself named `file.zip` or `file.tar.gz`.
|
||||
|
||||
# Examples
|
||||
|
||||
|
@ -58,26 +98,13 @@ xwim will create a folder `archive` in the current directory and extract the
|
|||
archive contents there.
|
||||
|
||||
# Supported formats
|
||||
xwim supports most formats supported by [libarchive](https://libarchive.org/):
|
||||
Currently `xwim` supports `tar.gz` and `zip` archives. However, this will
|
||||
rapidly expand to many more formats until a stable release is officially
|
||||
announced.
|
||||
|
||||
- 7-zip: 7z, 7zip
|
||||
- zip: jar, zip
|
||||
- bzip2: bz2, bzip2
|
||||
- gzip: gz, gzip
|
||||
- xzip: xz
|
||||
- rar: rar
|
||||
- tar with compression: tgz, tar.gz, tar.bz2, tar.xz
|
||||
|
||||
# Install
|
||||
xwim is currently released as a dynamically linked glibc binary only. The
|
||||
releases can be downloaded from https://git.friedl.net/incubator/xwim/releases
|
||||
and should run on most glibc based GNU/Linux distributions. The following
|
||||
dependencies have to be installed:
|
||||
- [spdlog](https://github.com/gabime/spdlog)
|
||||
- [fmt](https://github.com/fmtlib/fmt)
|
||||
- [libarchive](https://github.com/libarchive/libarchive)
|
||||
|
||||
Approaching the first stable release we will release for more platforms.
|
||||
Take a look `Archiver.hpp` if you want to help and have some time for testing.
|
||||
Most formats can readily be added if they are supported by libarchive. For other
|
||||
formats you have to add an `Archiver` implementation.
|
||||
|
||||
# Build
|
||||
xwim is built with [meson](https://mesonbuild.com/). To compile xwim from source
|
||||
|
@ -126,14 +153,28 @@ Per default xwim chooses an appropriate log level according to your build type
|
|||
- off
|
||||
|
||||
# Contributing
|
||||
While xwim is still in incubator phase (i.e. before version 1.0) it's main
|
||||
While xwim is still in incubator phase (i.e. before version 1.0) its main
|
||||
repository is hosted on https://git.friedl.net/incubator/xwim with a mirror on
|
||||
https://github.com/arminfriedl/xwim. With the first stable release it will most
|
||||
likely move to GitHub as it's main repository.
|
||||
likely move to GitHub as its main repository.
|
||||
|
||||
If you want to contribute, you can either issue a pull request on it's Github
|
||||
If you want to contribute, you can either issue a pull request on its Github
|
||||
mirror (will be cherry picked into the main repository) or send patches to
|
||||
dev[at]friedl[dot]net.
|
||||
|
||||
If you are interested in a long-term co-maintainership you can also drop me a
|
||||
mail for an account on https://git.friedl.net.
|
||||
|
||||
# Known Issues
|
||||
|
||||
- <strong>Parsing filters is unsupported</strong>
|
||||
There is a somewhat long standing
|
||||
[bug](https://github.com/libarchive/libarchive/issues/373) in libarchive. rar
|
||||
files might fail with `Parsing filters is unsupported`. This is because `rar`
|
||||
is a proprietary format and `libarchive` does not implement the full machinery
|
||||
necessary to support `rar` completely. `xwim` is all about convenience. If you
|
||||
want to help with supporting `rar`, please keep in mind that this means we
|
||||
have we want to take the [official `unrar`
|
||||
library](https://www.rarlab.com/rar_add.htm) if possible. This is also a
|
||||
licensing issue as `unrar` is proprietary and its license seemingly not GPL
|
||||
compatible.
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
project('xwim', 'cpp',
|
||||
version: '0.2',
|
||||
version: '0.4',
|
||||
default_options: ['cpp_std=c++17',
|
||||
'warning_level=3',
|
||||
'b_coverage=true'])
|
||||
'b_ndebug=if-release'])
|
||||
|
||||
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('doc')
|
||||
|
|
147
src/Archiver.cpp
Normal file
147
src/Archiver.cpp
Normal file
|
@ -0,0 +1,147 @@
|
|||
#include "Archiver.hpp"
|
||||
#include "Formats.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "util/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;
|
||||
|
||||
// Extract longest known extension from path
|
||||
fs::path archive_extension(const fs::path& path) {
|
||||
// TODO: creates lots of paths, refactor
|
||||
fs::path ext;
|
||||
fs::path tmp_ext;
|
||||
fs::path tmp_path;
|
||||
|
||||
// cater for trailing `/` which is represented
|
||||
// as empty path element
|
||||
for (auto p : path) {
|
||||
if (!p.empty()) {
|
||||
tmp_path /= p;
|
||||
}
|
||||
}
|
||||
|
||||
while (tmp_path.has_extension()) {
|
||||
tmp_ext = tmp_path.extension() += tmp_ext;
|
||||
Format format = find_extension_format(tmp_ext);
|
||||
|
||||
if (format != Format::UNKNOWN) {
|
||||
// (Combined) extension known. Remember as `ext` and keep
|
||||
// looking for even longer extensions.
|
||||
ext = tmp_ext;
|
||||
} // else: (Combined) extension not known, keep `ext` as-is but try
|
||||
// longer extensions
|
||||
|
||||
tmp_path = tmp_path.stem();
|
||||
}
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
// Strip longest known extension from path
|
||||
fs::path strip_archive_extension(const fs::path& path) {
|
||||
// TODO: creates lots of paths, refactor
|
||||
int longest_ext = 0;
|
||||
int tmp_longest_ext = 0;
|
||||
fs::path tmp_ext;
|
||||
fs::path tmp_path;
|
||||
fs::path stem_path;
|
||||
|
||||
// cater for trailing `/` which is represented
|
||||
// as empty path element
|
||||
for(auto p: path) {
|
||||
if(!p.empty()) {
|
||||
tmp_path /= p;
|
||||
}
|
||||
}
|
||||
stem_path = tmp_path;
|
||||
|
||||
spdlog::debug("Checking {} extensions", tmp_path);
|
||||
|
||||
while (tmp_path.has_extension()) {
|
||||
tmp_ext = tmp_path.extension() += tmp_ext;
|
||||
spdlog::debug("Looking for {} in known extensions", tmp_ext);
|
||||
|
||||
Format format = find_extension_format(tmp_ext);
|
||||
tmp_longest_ext++;
|
||||
if (format != Format::UNKNOWN) {
|
||||
// (Combined) extension known. Remember as `longest_ext` and keep
|
||||
// looking for even longer extensions.
|
||||
longest_ext = tmp_longest_ext;
|
||||
} // else: (Combined) extension not known, keep `longest_ext` as-is but try
|
||||
// longer extensions
|
||||
|
||||
spdlog::debug("Stemming {} to {}", tmp_path, tmp_path.stem());
|
||||
tmp_path = tmp_path.stem();
|
||||
}
|
||||
|
||||
spdlog::debug("Found {} extensions", longest_ext);
|
||||
tmp_path = stem_path;
|
||||
for (int i = 0; i < longest_ext; i++) tmp_path = tmp_path.stem();
|
||||
|
||||
spdlog::debug("Stripped path is {} ", tmp_path);
|
||||
return tmp_path;
|
||||
}
|
||||
|
||||
std::filesystem::path default_archive(const std::filesystem::path& base) {
|
||||
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);
|
||||
if (format_extensions.find(ext.string()) != format_extensions.end()) {
|
||||
spdlog::debug("Found {} in known formats", ext);
|
||||
return true;
|
||||
}
|
||||
|
||||
spdlog::debug("Could not find {} in known formats", ext);
|
||||
return false;
|
||||
}
|
||||
|
||||
Format parse_format(const fs::path& path) {
|
||||
spdlog::debug("Looking for path {}", path);
|
||||
fs::path ext = archive_extension(path);
|
||||
spdlog::debug("Looking for ext {}", ext);
|
||||
Format format = find_extension_format(ext);
|
||||
|
||||
if (format == Format::UNKNOWN) {
|
||||
throw XwimError{"No known archiver for {}", path};
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
unique_ptr<Archiver> make_archiver(const string& archive_name) {
|
||||
switch (parse_format(archive_name)) {
|
||||
case Format::TAR_GZIP: case Format::TAR_BZIP2:
|
||||
case Format::TAR_COMPRESS: case Format::TAR_LZIP:
|
||||
case Format::TAR_XZ: case Format::TAR_ZSTD:
|
||||
case Format::ZIP:
|
||||
return make_unique<LibArchiver>();
|
||||
default:
|
||||
throw XwimError{
|
||||
"Cannot construct archiver for {}. `extension_format` surjection "
|
||||
"invariant violated?",
|
||||
archive_name};
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace xwim
|
43
src/Archiver.hpp
Normal file
43
src/Archiver.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include "util/Common.hpp"
|
||||
#include "Formats.hpp"
|
||||
|
||||
namespace xwim {
|
||||
|
||||
class Archiver {
|
||||
public:
|
||||
virtual void compress(std::set<std::filesystem::path> ins,
|
||||
std::filesystem::path archive_out) = 0;
|
||||
|
||||
virtual void extract(std::filesystem::path archive_in,
|
||||
std::filesystem::path out) = 0;
|
||||
|
||||
virtual ~Archiver() = default;
|
||||
};
|
||||
|
||||
class LibArchiver : public Archiver {
|
||||
public:
|
||||
void compress(std::set<std::filesystem::path> ins,
|
||||
std::filesystem::path archive_out);
|
||||
|
||||
void extract(std::filesystem::path archive_in, std::filesystem::path out);
|
||||
};
|
||||
|
||||
std::filesystem::path 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);
|
||||
bool can_handle_archive(const std::filesystem::path& path);
|
||||
|
||||
std::unique_ptr<Archiver> make_archiver(const std::string& archive_name);
|
||||
|
||||
} // namespace xwim
|
49
src/Formats.hpp
Normal file
49
src/Formats.hpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#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;
|
||||
}
|
||||
}
|
226
src/UserIntent.cpp
Normal file
226
src/UserIntent.cpp
Normal file
|
@ -0,0 +1,226 @@
|
|||
#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
|
93
src/UserIntent.hpp
Normal file
93
src/UserIntent.hpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#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
|
52
src/UserOpt.cpp
Normal file
52
src/UserOpt.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#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
|
31
src/UserOpt.hpp
Normal file
31
src/UserOpt.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#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
|
121
src/archive.cpp
121
src/archive.cpp
|
@ -1,121 +0,0 @@
|
|||
#include <spdlog/spdlog.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace logger = spdlog;
|
||||
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "archive_sys.hpp"
|
||||
#include "archive.hpp"
|
||||
#include "spec.hpp"
|
||||
#include "fileformats.hpp"
|
||||
|
||||
namespace xwim {
|
||||
|
||||
static void _spec_is_root_filename(ArchiveSpec* spec,
|
||||
ArchiveEntryView entry,
|
||||
std::filesystem::path* filepath) {
|
||||
auto entry_path = entry.path();
|
||||
auto norm_stem = filepath->filename();
|
||||
norm_stem = xwim::stem(norm_stem);
|
||||
|
||||
if (*entry_path.begin() != norm_stem) {
|
||||
logger::debug("Archive root does not match archive name");
|
||||
spec->is_root_filename = false;
|
||||
} else {
|
||||
logger::debug("Archive root matches archive name");
|
||||
spec->is_root_filename = true;
|
||||
}
|
||||
logger::debug("\t-> Archive root: {}", entry_path.begin()->string());
|
||||
logger::debug("\t-> Archive stem: {}", norm_stem.string());
|
||||
}
|
||||
|
||||
static void _spec_is_root_dir(ArchiveSpec* spec, ArchiveEntryView entry) {
|
||||
if (entry.is_directory()) {
|
||||
logger::debug("Archive root is directory");
|
||||
spec->is_root_dir = true;
|
||||
} else {
|
||||
logger::debug("Archive root is not a directory");
|
||||
spec->is_root_dir = false;
|
||||
}
|
||||
logger::debug("\t-> Archive mode_t: {0:o}", entry.file_type());
|
||||
}
|
||||
|
||||
static void _spec_has_single_root(ArchiveSpec* spec,
|
||||
ArchiveEntryView first_entry,
|
||||
ArchiveReaderSys& archive_reader) {
|
||||
std::filesystem::path first_entry_root = *(first_entry.path().begin());
|
||||
logger::trace("Testing roots");
|
||||
|
||||
spec->has_single_root = true;
|
||||
|
||||
while (archive_reader.advance()) {
|
||||
ArchiveEntryView entry = archive_reader.cur();
|
||||
|
||||
auto next_entry = entry.path();
|
||||
logger::trace("Path: {}, Root: {}", next_entry.string(),
|
||||
next_entry.begin()->string());
|
||||
|
||||
if (first_entry_root != *next_entry.begin()) {
|
||||
logger::debug("Archive has multiple roots");
|
||||
logger::debug("\t-> Archive root I: {}",
|
||||
first_entry_root.begin()->string());
|
||||
logger::debug("\t-> Archive root II: {}", next_entry.begin()->string());
|
||||
|
||||
spec->has_single_root = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (spec->has_single_root)
|
||||
logger::debug("Archive has single root: {}", first_entry_root.string());
|
||||
}
|
||||
|
||||
Archive::Archive(std::filesystem::path path) : path{path} {}
|
||||
|
||||
ArchiveSpec Archive::check() {
|
||||
logger::trace("Creating archive spec for {}", this->path.string());
|
||||
|
||||
ArchiveReaderSys archive_reader {this->path};
|
||||
|
||||
ArchiveSpec archive_spec;
|
||||
|
||||
if (!archive_reader.advance()) { // can't advance even once, archive is empty
|
||||
logger::debug("Archive is empty");
|
||||
return {false, false, false};
|
||||
}
|
||||
|
||||
ArchiveEntryView first_entry = archive_reader.cur();
|
||||
|
||||
logger::trace("Found archive entry {}", first_entry.path_name());
|
||||
|
||||
_spec_is_root_filename(&archive_spec, first_entry, &this->path);
|
||||
_spec_is_root_dir(&archive_spec, first_entry);
|
||||
_spec_has_single_root(&archive_spec, first_entry, archive_reader);
|
||||
|
||||
return archive_spec;
|
||||
}
|
||||
|
||||
void Archive::extract(ExtractSpec extract_spec) {
|
||||
std::filesystem::path abs_path = std::filesystem::absolute(this->path);
|
||||
|
||||
std::unique_ptr<ArchiveExtractorSys> extractor;
|
||||
|
||||
if(extract_spec.make_dir) {
|
||||
logger::trace("Creating extract directory {}", extract_spec.dirname.string());
|
||||
extractor = std::unique_ptr<ArchiveExtractorSys>(new ArchiveExtractorSys{extract_spec.dirname});
|
||||
} else {
|
||||
extractor = std::unique_ptr<ArchiveExtractorSys>(new ArchiveExtractorSys{});
|
||||
}
|
||||
|
||||
ArchiveReaderSys reader{abs_path};
|
||||
extractor->extract_all(reader);
|
||||
}
|
||||
|
||||
} // namespace xwim
|
|
@ -1,50 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <archive.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "spec.hpp"
|
||||
|
||||
namespace xwim {
|
||||
|
||||
/** Class for interacting with archives */
|
||||
class Archive {
|
||||
private:
|
||||
std::filesystem::path path;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
class ArchiveException : public std::exception {
|
||||
private:
|
||||
std::string _what;
|
||||
|
||||
public:
|
||||
ArchiveException(std::string what, archive* archive) {
|
||||
if (archive_error_string(archive)) {
|
||||
_what = fmt::format("{}: {}", what, archive_error_string(archive));
|
||||
} else {
|
||||
_what = fmt::format("{}", what);
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* what() const noexcept
|
||||
{ return this->_what.c_str(); }
|
||||
};
|
||||
|
||||
} // namespace xwim
|
|
@ -1,142 +0,0 @@
|
|||
#include <archive_entry.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
namespace logger = spdlog;
|
||||
|
||||
#include "archive_sys.hpp"
|
||||
|
||||
#include <archive.h>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
bool xwim::ArchiveEntryView::is_empty() {
|
||||
return (this->ae == nullptr);
|
||||
}
|
||||
|
||||
std::string xwim::ArchiveEntryView::path_name() {
|
||||
if (!this->ae) throw ArchiveSysException{"Access to invalid archive entry"};
|
||||
|
||||
return archive_entry_pathname(this->ae);
|
||||
}
|
||||
|
||||
std::filesystem::path xwim::ArchiveEntryView::path() {
|
||||
if (!this->ae) throw ArchiveSysException{"Access to invalid archive entry"};
|
||||
return std::filesystem::path{this->path_name()};
|
||||
}
|
||||
|
||||
mode_t xwim::ArchiveEntryView::file_type() {
|
||||
if (!this->ae) throw ArchiveSysException{"Access to invalid archive entry"};
|
||||
return archive_entry_filetype(this->ae);
|
||||
}
|
||||
|
||||
bool xwim::ArchiveEntryView::is_directory() {
|
||||
return S_ISDIR(this->file_type());
|
||||
}
|
||||
|
||||
xwim::ArchiveReaderSys::ArchiveReaderSys(std::filesystem::path& path) {
|
||||
int r; // libarchive error handling
|
||||
|
||||
logger::trace("Setting up archive reader");
|
||||
this->ar = archive_read_new();
|
||||
archive_read_support_filter_all(this->ar);
|
||||
archive_read_support_format_all(this->ar);
|
||||
|
||||
logger::trace("Reading archive at {}", path.c_str());
|
||||
r = archive_read_open_filename(this->ar, path.c_str(), 10240);
|
||||
if (r != ARCHIVE_OK)
|
||||
throw ArchiveSysException{"Could not open archive file", this->ar};
|
||||
|
||||
logger::trace("Archive read succesfully");
|
||||
}
|
||||
|
||||
xwim::ArchiveReaderSys::~ArchiveReaderSys() {
|
||||
logger::trace("Destructing ArchiveReaderSys");
|
||||
|
||||
if (this->ar) archive_read_free(this->ar);
|
||||
}
|
||||
|
||||
bool xwim::ArchiveReaderSys::advance() {
|
||||
int r; // libarchive error handling
|
||||
logger::trace("Advancing reader to next archive entry");
|
||||
|
||||
r = archive_read_next_header(this->ar, &this->ae);
|
||||
if (r == ARCHIVE_EOF) { this->ae = nullptr; return false; }
|
||||
if (r != ARCHIVE_OK) throw(ArchiveSysException{"Could not list archive", this->ar});
|
||||
|
||||
logger::trace("Got entry {}", archive_entry_pathname(ae));
|
||||
return true;
|
||||
}
|
||||
|
||||
const xwim::ArchiveEntryView xwim::ArchiveReaderSys::cur() {
|
||||
return ArchiveEntryView{this->ae};
|
||||
}
|
||||
|
||||
xwim::ArchiveExtractorSys::ArchiveExtractorSys(std::filesystem::path& root) {
|
||||
logger::trace("Constructing ArchiveExtractorSys with path {}", root.string());
|
||||
|
||||
std::filesystem::create_directories(root);
|
||||
std::filesystem::current_path(root);
|
||||
|
||||
this->writer = archive_write_disk_new();
|
||||
archive_write_disk_set_standard_lookup(this->writer);
|
||||
|
||||
logger::trace("Constructed ArchiveExtractorSys at {:p}", (void*) this->writer);
|
||||
}
|
||||
|
||||
xwim::ArchiveExtractorSys::ArchiveExtractorSys() {
|
||||
logger::trace("Construction ArchiveExtractorSys without root");
|
||||
|
||||
this->writer = archive_write_disk_new();
|
||||
archive_write_disk_set_standard_lookup(this->writer);
|
||||
logger::trace("Constructed ArchiveExtractorSys at {:p}", (void*) this->writer);
|
||||
}
|
||||
|
||||
void xwim::ArchiveExtractorSys::extract_all(xwim::ArchiveReaderSys& reader) {
|
||||
while(reader.advance()) {
|
||||
this->extract_entry(reader);
|
||||
}
|
||||
}
|
||||
|
||||
// forward declared
|
||||
static int copy_data(struct archive* ar, struct archive* aw);
|
||||
|
||||
void xwim::ArchiveExtractorSys::extract_entry(xwim::ArchiveReaderSys& reader) {
|
||||
int r;
|
||||
r = archive_write_header(this->writer, reader.ae);
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw(ArchiveSysException("Could not extract entry", reader.ar));
|
||||
}
|
||||
|
||||
r = copy_data(reader.ar, this->writer);
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw(ArchiveSysException("Could not extract entry", reader.ar));
|
||||
}
|
||||
}
|
||||
|
||||
xwim::ArchiveExtractorSys::~ArchiveExtractorSys(){
|
||||
logger::trace("Destructing ArchiveExtractorSys at {:p}", (void*) this->writer);
|
||||
if(this->writer) {
|
||||
archive_write_close(this->writer);
|
||||
archive_write_free(this->writer);
|
||||
}
|
||||
}
|
||||
|
||||
static int copy_data(struct archive* ar, struct archive* aw) {
|
||||
int r;
|
||||
const void* buff;
|
||||
size_t size;
|
||||
int64_t offset;
|
||||
|
||||
for (;;) {
|
||||
r = archive_read_data_block(ar, &buff, &size, &offset);
|
||||
if (r == ARCHIVE_EOF) {
|
||||
return (ARCHIVE_OK);
|
||||
}
|
||||
if (r != ARCHIVE_OK) {
|
||||
return (r);
|
||||
}
|
||||
r = archive_write_data_block(aw, buff, size, offset);
|
||||
if (r != ARCHIVE_OK) {
|
||||
return (r);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <archive.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <fmt/format.h>
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
ArchiveEntryView() = default;
|
||||
ArchiveEntryView(archive_entry* entry) : ae{entry} {}
|
||||
|
||||
bool is_empty();
|
||||
std::string path_name();
|
||||
std::filesystem::path path();
|
||||
mode_t file_type();
|
||||
bool is_directory();
|
||||
};
|
||||
|
||||
/** A reader for archive files
|
||||
*
|
||||
* Shim for `libarchive`. Iterates through
|
||||
* entries of an archive with `next()`
|
||||
*/
|
||||
class ArchiveReaderSys {
|
||||
private:
|
||||
archive* ar;
|
||||
archive_entry* ae;
|
||||
friend class ArchiveExtractorSys;
|
||||
|
||||
public:
|
||||
ArchiveReaderSys(std::filesystem::path& path);
|
||||
~ArchiveReaderSys();
|
||||
|
||||
/** Advances the internal entry pointer
|
||||
*
|
||||
* @return true if the pointer advanced to the next entry
|
||||
* false if the end of the archive was reached
|
||||
*/
|
||||
bool advance();
|
||||
|
||||
/** Returns a non-owning view of the current entry
|
||||
*
|
||||
* ArchiveEntryView is a non-owning view of the currently
|
||||
* active entry in this reader. A retrieved archive entry
|
||||
* may not be used after another call to advance in the
|
||||
* same reader.
|
||||
*
|
||||
* @return a view to the archive entry this reader currently
|
||||
* points to
|
||||
*/
|
||||
const ArchiveEntryView cur();
|
||||
};
|
||||
|
||||
/** A extractor for archive files
|
||||
*
|
||||
* Shim for `libarchive`.
|
||||
*/
|
||||
class ArchiveExtractorSys {
|
||||
private:
|
||||
archive* writer;
|
||||
|
||||
public:
|
||||
ArchiveExtractorSys(std::filesystem::path& root);
|
||||
ArchiveExtractorSys();
|
||||
~ArchiveExtractorSys();
|
||||
|
||||
void extract_all(ArchiveReaderSys& reader);
|
||||
void extract_entry(ArchiveReaderSys& reader);
|
||||
};
|
||||
|
||||
class ArchiveSysException : public std::exception {
|
||||
private:
|
||||
std::string _what;
|
||||
|
||||
public:
|
||||
ArchiveSysException(std::string what, archive* archive) {
|
||||
if (archive_error_string(archive)) {
|
||||
_what = fmt::format("{}: {}", what, archive_error_string(archive));
|
||||
} else {
|
||||
_what = fmt::format("{}", what);
|
||||
}
|
||||
}
|
||||
ArchiveSysException(std::string what) { _what = fmt::format("{}", what); }
|
||||
|
||||
virtual const char* what() const noexcept { return this->_what.c_str(); }
|
||||
};
|
||||
|
||||
} // namespace xwim
|
167
src/archiver/LibArchiver.cpp
Normal file
167
src/archiver/LibArchiver.cpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "../Archiver.hpp"
|
||||
#include "../util/Common.hpp"
|
||||
|
||||
namespace xwim {
|
||||
using namespace std;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static int copy_data(shared_ptr<archive> reader, shared_ptr<archive> writer);
|
||||
|
||||
void LibArchiver::compress(set<fs::path> ins, fs::path archive_out) {
|
||||
spdlog::debug("Compressing to {}", archive_out);
|
||||
int r; // libarchive error handling
|
||||
static char buff[16384]; // read buffer
|
||||
|
||||
// cannot use unique_ptr here since unique_ptr requires a
|
||||
// complete type. `archive` is forward declared only.
|
||||
shared_ptr<archive> writer;
|
||||
writer = shared_ptr<archive>(archive_write_new(), archive_write_free);
|
||||
// archive_write_add_filter_gzip(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());
|
||||
|
||||
shared_ptr<archive> reader;
|
||||
|
||||
shared_ptr<archive_entry> entry = shared_ptr<archive_entry>(archive_entry_new(), archive_entry_free);
|
||||
|
||||
for (auto in : ins) {
|
||||
spdlog::debug("Compressing {}", in);
|
||||
reader = shared_ptr<archive>(archive_read_disk_new(), archive_read_free);
|
||||
archive_read_disk_set_standard_lookup(reader.get());
|
||||
|
||||
r = archive_read_disk_open(reader.get(), in.c_str());
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw XwimError{"Failed opening {}. {}", in,
|
||||
archive_error_string(reader.get())};
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
r = archive_read_next_header2(reader.get(), entry.get());
|
||||
|
||||
if (r == ARCHIVE_EOF) break;
|
||||
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw XwimError{"Failed compressing archive entry. {}",
|
||||
archive_error_string(reader.get())};
|
||||
}
|
||||
|
||||
spdlog::debug("Adding {} to archive", archive_entry_pathname(entry.get()));
|
||||
r = archive_write_header(writer.get(), entry.get());
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw XwimError{"Failed writing archive entry. {}",
|
||||
archive_error_string(writer.get())};
|
||||
}
|
||||
|
||||
/* For now, we use a simpler loop to copy data
|
||||
* into the target archive. */
|
||||
int fd = open(archive_entry_sourcepath(entry.get()), O_RDONLY);
|
||||
ssize_t len = read(fd, buff, sizeof(buff));
|
||||
while (len > 0) {
|
||||
archive_write_data(writer.get(), buff, len);
|
||||
len = read(fd, buff, sizeof(buff));
|
||||
}
|
||||
close(fd);
|
||||
|
||||
archive_entry_clear(entry.get());
|
||||
archive_read_disk_descend(reader.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LibArchiver::extract(fs::path archive_in, fs::path out) {
|
||||
spdlog::debug("Extracting archive {} to {}", archive_in, out);
|
||||
int r; // libarchive error handling
|
||||
|
||||
// cannot use unique_ptr here since unique_ptr requires a
|
||||
// complete type. `archive` is forward declared only.
|
||||
shared_ptr<archive> reader;
|
||||
reader = shared_ptr<archive>(archive_read_new(), archive_read_free);
|
||||
archive_read_support_filter_all(reader.get());
|
||||
archive_read_support_format_all(reader.get());
|
||||
r = archive_read_open_filename(reader.get(), archive_in.c_str(), 10240);
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw XwimError{"Failed opening archive {}. {}", archive_in,
|
||||
archive_error_string(reader.get())};
|
||||
}
|
||||
|
||||
shared_ptr<archive> writer;
|
||||
writer = shared_ptr<archive>(archive_write_disk_new(), archive_write_free);
|
||||
archive_write_disk_set_standard_lookup(writer.get());
|
||||
|
||||
fs::create_directories(out);
|
||||
fs::path cur_path = fs::current_path();
|
||||
fs::current_path(out);
|
||||
|
||||
archive_entry *entry;
|
||||
for (;;) {
|
||||
r = archive_read_next_header(reader.get(), &entry);
|
||||
if (r == ARCHIVE_EOF) break;
|
||||
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw XwimError{"Failed extracting archive entry. {}",
|
||||
archive_error_string(reader.get())};
|
||||
}
|
||||
|
||||
r = archive_write_header(writer.get(), entry);
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw XwimError{"Failed writing archive entry header. {}",
|
||||
archive_error_string(writer.get())};
|
||||
}
|
||||
|
||||
if (archive_entry_size(entry) > 0) {
|
||||
r = copy_data(reader, writer);
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw XwimError{"Failed writing archive entry data. {}",
|
||||
archive_error_string(writer.get())};
|
||||
}
|
||||
}
|
||||
|
||||
r = archive_write_finish_entry(writer.get());
|
||||
if (r != ARCHIVE_OK) {
|
||||
throw XwimError{"Failed finishing archive entry data. {}",
|
||||
archive_error_string(writer.get())};
|
||||
}
|
||||
}
|
||||
|
||||
if (r != ARCHIVE_OK && r != ARCHIVE_EOF) {
|
||||
throw XwimError{"Failed extracting archive {}. {}", archive_in,
|
||||
archive_error_string(reader.get())};
|
||||
}
|
||||
|
||||
fs::current_path(cur_path);
|
||||
}
|
||||
|
||||
static int copy_data(shared_ptr<archive> reader, shared_ptr<archive> writer) {
|
||||
int r;
|
||||
const void *buff;
|
||||
size_t size;
|
||||
int64_t offset;
|
||||
|
||||
for (;;) {
|
||||
r = archive_read_data_block(reader.get(), &buff, &size, &offset);
|
||||
if (r == ARCHIVE_EOF) {
|
||||
return (ARCHIVE_OK);
|
||||
}
|
||||
if (r != ARCHIVE_OK) {
|
||||
return (r);
|
||||
}
|
||||
r = archive_write_data_block(writer.get(), buff, size, offset);
|
||||
if (r != ARCHIVE_OK) {
|
||||
return (r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xwim
|
|
@ -1,46 +0,0 @@
|
|||
/** @file fileformats.hpp
|
||||
* @brief Handle archive extensions
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
namespace logger = spdlog;
|
||||
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace xwim {
|
||||
|
||||
/** Common archive formats understood by xwim
|
||||
*
|
||||
* The underlying libarchive backend retrieves format information by a process
|
||||
* called `bidding`. Hence, this information is mainly used to strip extensions.
|
||||
*
|
||||
* 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<std::string> fileformats{".7z", ".7zip", ".jar", ".tgz",
|
||||
".bz2", ".bzip2", ".gz", ".gzip",
|
||||
".rar", ".tar", "xz", ".zip"};
|
||||
|
||||
/** 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());
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
logger::trace("Finished stemming {}", p_stem.string());
|
||||
return p_stem;
|
||||
}
|
||||
|
||||
} // namespace xwim
|
55
src/main.cpp
55
src/main.cpp
|
@ -1,45 +1,26 @@
|
|||
#include <spdlog/common.h>
|
||||
namespace logger = spdlog;
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
|
||||
#include "util/log.hpp"
|
||||
#include "archive.hpp"
|
||||
#include "spec.hpp"
|
||||
#include "fileformats.hpp"
|
||||
#include "UserIntent.hpp"
|
||||
#include "UserOpt.hpp"
|
||||
#include "util/Common.hpp"
|
||||
#include "util/Log.hpp"
|
||||
|
||||
using namespace xwim;
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
xwim::log::init();
|
||||
log::init();
|
||||
UserOpt user_opt = UserOpt{argc, argv};
|
||||
log::init(user_opt.verbosity);
|
||||
|
||||
try {
|
||||
std::filesystem::path filepath{argv[1]};
|
||||
xwim::Archive archive{filepath};
|
||||
xwim::ArchiveSpec archive_spec = archive.check();
|
||||
|
||||
logger::info("{}", archive_spec);
|
||||
|
||||
xwim::ExtractSpec extract_spec{};
|
||||
|
||||
if (!archive_spec.has_single_root || !archive_spec.is_root_filename) {
|
||||
extract_spec.make_dir = true;
|
||||
|
||||
std::filesystem::path stem = xwim::stem(filepath);
|
||||
|
||||
extract_spec.dirname = stem;
|
||||
}
|
||||
|
||||
if (archive_spec.has_subarchive) {
|
||||
extract_spec.extract_subarchive = true;
|
||||
}
|
||||
|
||||
logger::info("{}", extract_spec);
|
||||
|
||||
archive.extract(extract_spec);
|
||||
|
||||
} catch (xwim::ArchiveException& ae) {
|
||||
logger::error("{}", ae.what());
|
||||
unique_ptr<UserIntent> user_intent = make_intent(user_opt);
|
||||
user_intent->execute();
|
||||
} catch (XwimError& e) {
|
||||
spdlog::error(e.what());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
xwim_src = ['main.cpp',
|
||||
'archive.cpp',
|
||||
'archive_sys.cpp']
|
||||
xwim_src = ['main.cpp', 'Archiver.cpp', 'UserOpt.cpp', 'UserIntent.cpp']
|
||||
|
||||
xwim_libs = [dependency('libarchive', required: true),
|
||||
dependency('fmt', required: true),
|
||||
dependency('spdlog', required: true)]
|
||||
xwim_archiver = ['archiver/LibArchiver.cpp']
|
||||
|
||||
executable('xwim', xwim_src, dependencies: xwim_libs)
|
||||
is_static = get_option('default_library')=='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('tclap', required: true, static: is_static)]
|
||||
|
||||
executable('xwim', xwim_src+xwim_archiver, dependencies: xwim_libs)
|
||||
|
|
80
src/spec.hpp
80
src/spec.hpp
|
@ -1,80 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <archive.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
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; /** 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; /** 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<char> format_parse_context;
|
||||
#endif
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<xwim::ArchiveSpec> {
|
||||
constexpr auto parse(format_parse_context & ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const xwim::ArchiveSpec& spec, FormatContext& ctx) {
|
||||
return format_to(ctx.out(),
|
||||
"Archive["
|
||||
" .has_single_root={},"
|
||||
" .is_root_filename={}"
|
||||
" .is_root_dir={}"
|
||||
" .has_subarchive={}"
|
||||
" ]",
|
||||
spec.has_single_root, spec.is_root_filename,
|
||||
spec.is_root_dir, spec.has_subarchive);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<xwim::ExtractSpec> {
|
||||
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const xwim::ExtractSpec& spec, FormatContext& ctx) {
|
||||
return format_to(ctx.out(),
|
||||
"Extract["
|
||||
" .make_dir={},"
|
||||
" .dirname={}"
|
||||
" .extract_subarchive={}"
|
||||
" ]",
|
||||
spec.make_dir, spec.dirname.string(),
|
||||
spec.extract_subarchive);
|
||||
}
|
||||
};
|
30
src/util/Common.hpp
Normal file
30
src/util/Common.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <random>
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<std::filesystem::path> {
|
||||
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::filesystem::path& path, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), path.string());
|
||||
}
|
||||
};
|
||||
|
||||
class XwimError : public std::runtime_error {
|
||||
public:
|
||||
template <typename... Args>
|
||||
XwimError(const std::string& fmt, const Args... args)
|
||||
: std::runtime_error(fmt::format(fmt, args...)){}
|
||||
};
|
||||
|
||||
inline int rand_int(int from, int to) {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<> distrib(from, to);
|
||||
return distrib(gen);
|
||||
}
|
|
@ -1,17 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <spdlog/common.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <cstdlib>
|
||||
#ifdef NDEBUG
|
||||
#define XWIM_LOGLEVEL SPDLOG_LEVEL_ERROR
|
||||
#else
|
||||
#define XWIM_LOGLEVEL SPDLOG_LEVEL_DEBUG
|
||||
#endif
|
||||
|
||||
namespace xwim {
|
||||
namespace log {
|
||||
namespace xwim::log {
|
||||
|
||||
/**
|
||||
* Get log level from XWIM_LOGLEVEL environment variable.
|
||||
|
@ -34,7 +32,7 @@ spdlog::level::level_enum _init_from_env() {
|
|||
}
|
||||
|
||||
return lvl;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get log level from compile time definition.
|
||||
|
@ -60,7 +58,27 @@ spdlog::level::level_enum _init_from_compile() {
|
|||
* The determined level is then set for the default logger via
|
||||
* `spdlog::set_level`.
|
||||
*/
|
||||
void init(spdlog::level::level_enum level = spdlog::level::level_enum::off) {
|
||||
void init(int verbosity = -1,
|
||||
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) {
|
||||
spdlog::set_level(level);
|
||||
return;
|
||||
|
@ -72,8 +90,7 @@ void init(spdlog::level::level_enum level = spdlog::level::level_enum::off) {
|
|||
return;
|
||||
}
|
||||
|
||||
return spdlog::set_level(_init_from_compile());
|
||||
spdlog::set_level(_init_from_compile());
|
||||
}
|
||||
|
||||
} // namespace log
|
||||
} // namespace xwim
|
||||
} // namespace xwim::log
|
|
@ -1,11 +0,0 @@
|
|||
#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,34 +0,0 @@
|
|||
#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"});
|
||||
}
|
|
@ -2,22 +2,10 @@
|
|||
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],
|
||||
# subdir('archives')
|
||||
user_opt_test_exe = executable('user_opt_test_exe',
|
||||
sources: ['user_opt_test.cpp', '../src/UserOpt.cpp'],
|
||||
include_directories: ['../src'],
|
||||
dependencies: [gtest_dep, xwim_libs])
|
||||
dependencies: [gtest_dep])
|
||||
|
||||
|
||||
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)
|
||||
test('user opt parsing test', user_opt_test_exe)
|
||||
|
|
90
test/user_opt_test.cpp
Normal file
90
test/user_opt_test.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#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);
|
||||
}
|
BIN
tests/ahaahm.tar.gz
Normal file
BIN
tests/ahaahm.tar.gz
Normal file
Binary file not shown.
1
tests/ahaahm/äh🦔~öhm/ähä~öhm.txt
Normal file
1
tests/ahaahm/äh🦔~öhm/ähä~öhm.txt
Normal file
|
@ -0,0 +1 @@
|
|||
äahääm
|
1
tests/ahaahm/äh🦔~öhm/ähä~öhm/ähä~öhm.txt
Normal file
1
tests/ahaahm/äh🦔~öhm/ähä~öhm/ähä~öhm.txt
Normal file
|
@ -0,0 +1 @@
|
|||
äahääm
|
BIN
tests/root.tar.gz
Normal file
BIN
tests/root.tar.gz
Normal file
Binary file not shown.
17
tests/runtests/run.py
Executable file
17
tests/runtests/run.py
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/python3
|
||||
|
||||
import os;
|
||||
import sys;
|
||||
import subprocess;
|
||||
from fnmatch import fnmatch;
|
||||
|
||||
for root, dirs, files in os.walk('../../'):
|
||||
for f in files:
|
||||
if len(sys.argv) > 1 and not fnmatch(f, sys.argv[1]):
|
||||
continue;
|
||||
|
||||
print(f"Running {f}")
|
||||
print(f"{os.path.join(root,f)}")
|
||||
r = subprocess.run(["../../../target/src/xwim", os.path.join(root, f)], capture_output=True, encoding='utf-8')
|
||||
print(f"{r.stdout}")
|
||||
print(f"{r.stderr}", file=sys.stderr)
|
BIN
tests/test-1.23.7z
Normal file
BIN
tests/test-1.23.7z
Normal file
Binary file not shown.
BIN
tests/test-1.23.arj
Normal file
BIN
tests/test-1.23.arj
Normal file
Binary file not shown.
BIN
tests/test-1.23.cpio
Normal file
BIN
tests/test-1.23.cpio
Normal file
Binary file not shown.
BIN
tests/test-1.23.lzh
Normal file
BIN
tests/test-1.23.lzh
Normal file
Binary file not shown.
BIN
tests/test-1.23.rar
Normal file
BIN
tests/test-1.23.rar
Normal file
Binary file not shown.
BIN
tests/test-1.23.tar
Normal file
BIN
tests/test-1.23.tar
Normal file
Binary file not shown.
BIN
tests/test-1.23.tar.bz2
Normal file
BIN
tests/test-1.23.tar.bz2
Normal file
Binary file not shown.
BIN
tests/test-1.23.tar.gz
Normal file
BIN
tests/test-1.23.tar.gz
Normal file
Binary file not shown.
BIN
tests/test-1.23.tar.lrz
Normal file
BIN
tests/test-1.23.tar.lrz
Normal file
Binary file not shown.
BIN
tests/test-1.23.tar.lzma
Normal file
BIN
tests/test-1.23.tar.lzma
Normal file
Binary file not shown.
BIN
tests/test-1.23.zip
Normal file
BIN
tests/test-1.23.zip
Normal file
Binary file not shown.
BIN
tests/test-1.23_all.deb
Normal file
BIN
tests/test-1.23_all.deb
Normal file
Binary file not shown.
BIN
tests/test-2_all.deb
Normal file
BIN
tests/test-2_all.deb
Normal file
Binary file not shown.
BIN
tests/test-deep-recursion.tar
Normal file
BIN
tests/test-deep-recursion.tar
Normal file
Binary file not shown.
BIN
tests/test-dot-first-bomb.tar.gz
Normal file
BIN
tests/test-dot-first-bomb.tar.gz
Normal file
Binary file not shown.
BIN
tests/test-dot-first-onedir.tar.gz
Normal file
BIN
tests/test-dot-first-onedir.tar.gz
Normal file
Binary file not shown.
BIN
tests/test-empty.tar.bz2
Normal file
BIN
tests/test-empty.tar.bz2
Normal file
Binary file not shown.
BIN
tests/test-one-archive.tar.gz
Normal file
BIN
tests/test-one-archive.tar.gz
Normal file
Binary file not shown.
BIN
tests/test-onedir.tar.bz2
Normal file
BIN
tests/test-onedir.tar.bz2
Normal file
Binary file not shown.
BIN
tests/test-onedir.tar.gz
Normal file
BIN
tests/test-onedir.tar.gz
Normal file
Binary file not shown.
BIN
tests/test-onefile.tar.gz
Normal file
BIN
tests/test-onefile.tar.gz
Normal file
Binary file not shown.
BIN
tests/test-recursive-badperms.tar.bz2
Normal file
BIN
tests/test-recursive-badperms.tar.bz2
Normal file
Binary file not shown.
BIN
tests/test-recursive-no-prompt.tar.bz2
Normal file
BIN
tests/test-recursive-no-prompt.tar.bz2
Normal file
Binary file not shown.
BIN
tests/test-tar-with-node.tar.gz
Normal file
BIN
tests/test-tar-with-node.tar.gz
Normal file
Binary file not shown.
BIN
tests/test-text.bz2
Normal file
BIN
tests/test-text.bz2
Normal file
Binary file not shown.
BIN
tests/test-text.gz
Normal file
BIN
tests/test-text.gz
Normal file
Binary file not shown.
BIN
tests/test-text.lrz
Normal file
BIN
tests/test-text.lrz
Normal file
Binary file not shown.
BIN
tests/test-text.lz
Normal file
BIN
tests/test-text.lz
Normal file
Binary file not shown.
BIN
tests/test-text.xz
Normal file
BIN
tests/test-text.xz
Normal file
Binary file not shown.
BIN
tests/xwim-0.5.0-x86_64-linux.tar.gz
Normal file
BIN
tests/xwim-0.5.0-x86_64-linux.tar.gz
Normal file
Binary file not shown.
Loading…
Reference in a new issue