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
|
name: default
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: build-shared
|
||||||
image: arminfriedl/xwim-build
|
image: arminfriedl/xwim-build:shared
|
||||||
commands:
|
commands:
|
||||||
- meson wrap install gtest
|
- meson wrap install gtest || true
|
||||||
- meson build
|
- meson target/shared
|
||||||
- ninja -C build
|
- ninja -C target/shared
|
||||||
- ninja -C build test && ninja -C build coverage
|
- mv target/shared/src/xwim xwim-x86_64-glibc-linux-shared
|
||||||
- echo "******** TEST LOGS ***********"
|
|
||||||
- cat build/meson-logs/testlog.txt
|
- name: build-static
|
||||||
- echo "****** COVERAGE LOGS *********"
|
image: arminfriedl/xwim-build:static
|
||||||
- cat build/meson-logs/coverage.txt
|
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:
|
trigger:
|
||||||
event:
|
event:
|
||||||
|
@ -26,21 +47,34 @@ type: docker
|
||||||
name: release
|
name: release
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: build-shared
|
||||||
image: arminfriedl/xwim-build
|
image: arminfriedl/xwim-build:shared
|
||||||
commands:
|
commands:
|
||||||
- meson wrap install gtest
|
- meson wrap install gtest || true
|
||||||
- meson --buildtype=release build
|
- meson --buildtype=release target/shared
|
||||||
- ninja -C build
|
- ninja -C target/shared
|
||||||
- mkdir xwim-${DRONE_TAG}-x86_64-glibc-linux
|
- strip target/shared/src/xwim
|
||||||
- mv build/src/xwim xwim-${DRONE_TAG}-x86_64-glibc-linux
|
- 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
|
- name: package
|
||||||
image: arminfriedl/xwim-build
|
image: arminfriedl/xwim-build
|
||||||
commands:
|
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-shared.tar.gz xwim-${DRONE_TAG}-x86_64-glibc-linux-shared/xwim
|
||||||
- tar czf xwim-${DRONE_TAG}-x86_64-glibc-linux.tar.gz xwim-${DRONE_TAG}-x86_64-glibc-linux/xwim
|
- tar czf xwim-${DRONE_TAG}-x86_64-musl-linux-static.tar.gz xwim-${DRONE_TAG}-x86_64-musl-linux-static/xwim
|
||||||
- zip -r xwim-${DRONE_TAG}-x86_64-glibc-linux.zip xwim-${DRONE_TAG}-x86_64-glibc-linux
|
depends_on:
|
||||||
|
- build-shared
|
||||||
|
- build-static
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: plugins/gitea-release
|
image: plugins/gitea-release
|
||||||
|
@ -49,13 +83,14 @@ steps:
|
||||||
api_key:
|
api_key:
|
||||||
from_secret: gitea_token
|
from_secret: gitea_token
|
||||||
files:
|
files:
|
||||||
- xwim-${DRONE_TAG}-x86_64-glibc-linux.tar.bz2
|
- xwim-${DRONE_TAG}-x86_64-glibc-linux-shared.tar.gz
|
||||||
- xwim-${DRONE_TAG}-x86_64-glibc-linux.tar.gz
|
- xwim-${DRONE_TAG}-x86_64-musl-linux-static.tar.gz
|
||||||
- xwim-${DRONE_TAG}-x86_64-glibc-linux.zip
|
|
||||||
title: xwim ${DRONE_TAG}
|
title: xwim ${DRONE_TAG}
|
||||||
checksum:
|
checksum:
|
||||||
- md5
|
- md5
|
||||||
- sha256
|
- sha256
|
||||||
|
depends_on:
|
||||||
|
- package
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
event:
|
event:
|
||||||
|
|
247
.gitignore
vendored
247
.gitignore
vendored
|
@ -2,11 +2,11 @@
|
||||||
build/
|
build/
|
||||||
target/
|
target/
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
.vscode
|
|
||||||
.ccls-cache
|
.ccls-cache
|
||||||
|
.idea/codeStyles/**
|
||||||
|
|
||||||
# Created by https://www.gitignore.io/api/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.gitignore.io/?templates=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
|
||||||
|
|
||||||
### C++ ###
|
### C++ ###
|
||||||
# Prerequisites
|
# Prerequisites
|
||||||
|
@ -42,6 +42,94 @@ 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; -*-
|
||||||
*~
|
*~
|
||||||
|
@ -93,6 +181,108 @@ 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
|
||||||
|
@ -100,6 +290,7 @@ flycheck_*.el
|
||||||
### 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]
|
||||||
|
@ -111,14 +302,54 @@ Sessionx.vim
|
||||||
|
|
||||||
# Temporary
|
# Temporary
|
||||||
.netrwhist
|
.netrwhist
|
||||||
|
|
||||||
# Auto-generated tag files
|
# Auto-generated tag files
|
||||||
tags
|
tags
|
||||||
|
|
||||||
# Persistent undo
|
# Persistent undo
|
||||||
[._]*.un~
|
[._]*.un~
|
||||||
|
|
||||||
# Coc configuration directory
|
### VisualStudioCode ###
|
||||||
.vim
|
.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)
|
![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,
|
for the excellent, but unfortunately unmaintained,
|
||||||
[dtrx](https://github.com/brettcs/dtrx). xwim is a command line tool that
|
[dtrx](https://github.com/brettcs/dtrx). xwim is a command line tool that
|
||||||
targets two problems with archives:
|
targets two problems with archives:
|
||||||
|
@ -15,6 +17,27 @@ considerably between formats
|
||||||
- Inconsiderately packaged archives tend to spill their content over the
|
- Inconsiderately packaged archives tend to spill their content over the
|
||||||
directory they are extracted to
|
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
|
# Usage
|
||||||
Invoking `xwim` is as simple as:
|
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
|
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
|
single root folder it is just extracted as is. Otherwise xwim creates a folder
|
||||||
folder named after the archive and extracts the contents there.
|
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
|
# Examples
|
||||||
|
|
||||||
|
@ -58,26 +98,13 @@ xwim will create a folder `archive` in the current directory and extract the
|
||||||
archive contents there.
|
archive contents there.
|
||||||
|
|
||||||
# Supported formats
|
# 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
|
Take a look `Archiver.hpp` if you want to help and have some time for testing.
|
||||||
- zip: jar, zip
|
Most formats can readily be added if they are supported by libarchive. For other
|
||||||
- bzip2: bz2, bzip2
|
formats you have to add an `Archiver` implementation.
|
||||||
- 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.
|
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
xwim is built with [meson](https://mesonbuild.com/). To compile xwim from source
|
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
|
- off
|
||||||
|
|
||||||
# Contributing
|
# 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
|
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
|
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
|
mirror (will be cherry picked into the main repository) or send patches to
|
||||||
dev[at]friedl[dot]net.
|
dev[at]friedl[dot]net.
|
||||||
|
|
||||||
If you are interested in a long-term co-maintainership you can also drop me a
|
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.
|
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',
|
project('xwim', 'cpp',
|
||||||
version: '0.2',
|
version: '0.4',
|
||||||
default_options: ['cpp_std=c++17',
|
default_options: ['cpp_std=c++17',
|
||||||
'warning_level=3',
|
'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('src')
|
||||||
subdir('doc')
|
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
|
|
53
src/main.cpp
53
src/main.cpp
|
@ -1,45 +1,26 @@
|
||||||
#include <spdlog/common.h>
|
#include <spdlog/common.h>
|
||||||
namespace logger = spdlog;
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <cstdlib>
|
||||||
#include <ostream>
|
#include <filesystem>
|
||||||
#include <string>
|
|
||||||
#include <list>
|
|
||||||
|
|
||||||
#include "util/log.hpp"
|
#include "UserIntent.hpp"
|
||||||
#include "archive.hpp"
|
#include "UserOpt.hpp"
|
||||||
#include "spec.hpp"
|
#include "util/Common.hpp"
|
||||||
#include "fileformats.hpp"
|
#include "util/Log.hpp"
|
||||||
|
|
||||||
|
using namespace xwim;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
xwim::log::init();
|
log::init();
|
||||||
|
UserOpt user_opt = UserOpt{argc, argv};
|
||||||
|
log::init(user_opt.verbosity);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::filesystem::path filepath{argv[1]};
|
unique_ptr<UserIntent> user_intent = make_intent(user_opt);
|
||||||
xwim::Archive archive{filepath};
|
user_intent->execute();
|
||||||
xwim::ArchiveSpec archive_spec = archive.check();
|
} catch (XwimError& e) {
|
||||||
|
spdlog::error(e.what());
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
xwim_src = ['main.cpp',
|
xwim_src = ['main.cpp', 'Archiver.cpp', 'UserOpt.cpp', 'UserIntent.cpp']
|
||||||
'archive.cpp',
|
|
||||||
'archive_sys.cpp']
|
|
||||||
|
|
||||||
xwim_libs = [dependency('libarchive', required: true),
|
xwim_archiver = ['archiver/LibArchiver.cpp']
|
||||||
dependency('fmt', required: true),
|
|
||||||
dependency('spdlog', required: true)]
|
|
||||||
|
|
||||||
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
|
#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
|
||||||
#else
|
#else
|
||||||
#define XWIM_LOGLEVEL SPDLOG_LEVEL_DEBUG
|
#define XWIM_LOGLEVEL SPDLOG_LEVEL_DEBUG
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace xwim {
|
namespace xwim::log {
|
||||||
namespace log {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get log level from XWIM_LOGLEVEL environment variable.
|
* Get log level from XWIM_LOGLEVEL environment variable.
|
||||||
|
@ -34,7 +32,7 @@ spdlog::level::level_enum _init_from_env() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return lvl;
|
return lvl;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get log level from compile time definition.
|
* 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
|
* The determined level is then set for the default logger via
|
||||||
* `spdlog::set_level`.
|
* `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) {
|
if (spdlog::level::level_enum::off != level) {
|
||||||
spdlog::set_level(level);
|
spdlog::set_level(level);
|
||||||
return;
|
return;
|
||||||
|
@ -72,8 +90,7 @@ void init(spdlog::level::level_enum level = spdlog::level::level_enum::off) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return spdlog::set_level(_init_from_compile());
|
spdlog::set_level(_init_from_compile());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace log
|
} // namespace xwim::log
|
||||||
} // namespace xwim
|
|
|
@ -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_proj = subproject('gtest')
|
||||||
gtest_dep = gtest_proj.get_variable('gtest_main_dep')
|
gtest_dep = gtest_proj.get_variable('gtest_main_dep')
|
||||||
|
|
||||||
xwim_src = ['../src/archive.cpp',
|
# subdir('archives')
|
||||||
'../src/archive_sys.cpp']
|
user_opt_test_exe = executable('user_opt_test_exe',
|
||||||
|
sources: ['user_opt_test.cpp', '../src/UserOpt.cpp'],
|
||||||
subdir('archives')
|
|
||||||
|
|
||||||
archive_test_exe = executable('archive_test_exe',
|
|
||||||
sources: ['archive_test.cpp', xwim_src],
|
|
||||||
include_directories: ['../src'],
|
include_directories: ['../src'],
|
||||||
dependencies: [gtest_dep, xwim_libs])
|
dependencies: [gtest_dep])
|
||||||
|
|
||||||
|
test('user opt parsing test', user_opt_test_exe)
|
||||||
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)
|
|
||||||
|
|
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