excavate 0.3.7 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/post-rake.yml +29 -0
- data/.github/workflows/rake-metanorma.yaml +60 -0
- data/.github/workflows/rake.yml +81 -0
- data/.github/workflows/release.yml +24 -0
- data/.rubocop.yml +11 -10
- data/.rubocop_todo.yml +78 -0
- data/Gemfile +7 -0
- data/README.adoc +480 -52
- data/excavate.gemspec +9 -13
- data/lib/excavate/archive.rb +2 -1
- data/lib/excavate/extractors/ole_extractor.rb +1 -1
- data/lib/excavate/extractors/xz_extractor.rb +58 -0
- data/lib/excavate/extractors.rb +1 -0
- data/lib/excavate/file_magic.rb +8 -3
- data/lib/excavate/utils.rb +1 -1
- data/lib/excavate/version.rb +1 -1
- metadata +9 -46
- data/.github/workflows/test-and-release.yml +0 -99
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1355dad6341565ebea3901156ac04793db80b02eb1057ccb1c94672dddd639af
|
|
4
|
+
data.tar.gz: 308b7b43fa583c7c98a812f7f165c9c455ede142d35250ffc213370c9a10eaf9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 07614c1ae5bbae0964bdd52c6c0fbaf2f639bcb3f49d40b48d0e08685f0e9a38088d600a3eb4f92375f727173c21d8f2f92f670d48ddfba1bff622d6a8d2e4cf
|
|
7
|
+
data.tar.gz: 9bce399bd785eb308aea9cf0f67efb58d78a115a24d76553ef1dc0f95ec59297b8f7bb73d21ff46b985e9b061d22ea1f4d2fc8ad70c5d26742244f6de19e34df
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: post-rake
|
|
2
|
+
|
|
3
|
+
permissions:
|
|
4
|
+
contents: read
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
workflow_run:
|
|
9
|
+
workflows:
|
|
10
|
+
- rake
|
|
11
|
+
- rake-metanorma
|
|
12
|
+
types:
|
|
13
|
+
- completed
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
post-rake:
|
|
17
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' && contains(github.ref, 'refs/tags/v') }}
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- if: contains(github.ref, 'refs/tags/v')
|
|
23
|
+
name: Repository ready for release
|
|
24
|
+
uses: peter-evans/repository-dispatch@v3
|
|
25
|
+
with:
|
|
26
|
+
token: ${{ secrets.FONTIST_CI_PAT_TOKEN }}
|
|
27
|
+
repository: ${{ github.repository }}
|
|
28
|
+
event-type: do-release
|
|
29
|
+
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "type": "do-release"}'
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: rake-metanorma
|
|
2
|
+
|
|
3
|
+
permissions:
|
|
4
|
+
contents: read
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ main ]
|
|
9
|
+
tags: [ 'v*' ]
|
|
10
|
+
pull_request:
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
env:
|
|
17
|
+
# Forcing bundler version to ensure that it is consistent everywhere and
|
|
18
|
+
# does not cause bundler gem reinstalls
|
|
19
|
+
# bundler/rubygems 2.3.22 is a minimal requirement to support gnu/musl differentiation
|
|
20
|
+
# https://github.com/rubygems/rubygems/pull/4488
|
|
21
|
+
BUNDLER_VER: 2.3.24
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
prepare:
|
|
25
|
+
uses: metanorma/ci/.github/workflows/prepare-rake.yml@main
|
|
26
|
+
|
|
27
|
+
metanorma:
|
|
28
|
+
name: Test metanorma on Ruby ${{ matrix.ruby.version }} ${{ matrix.os }}
|
|
29
|
+
runs-on: ${{ matrix.os }}
|
|
30
|
+
|
|
31
|
+
needs: prepare
|
|
32
|
+
if: needs.prepare.outputs.push-for-tag != 'true'
|
|
33
|
+
|
|
34
|
+
continue-on-error: ${{ matrix.ruby.experimental }}
|
|
35
|
+
strategy:
|
|
36
|
+
fail-fast: false
|
|
37
|
+
max-parallel: 5
|
|
38
|
+
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
|
39
|
+
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
with:
|
|
43
|
+
repository: metanorma/metanorma
|
|
44
|
+
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
with:
|
|
47
|
+
path: "excavate"
|
|
48
|
+
|
|
49
|
+
- run: 'echo ''gem "excavate", path: "./excavate"'' > Gemfile.devel'
|
|
50
|
+
|
|
51
|
+
- uses: ruby/setup-ruby@v1
|
|
52
|
+
with:
|
|
53
|
+
ruby-version: ${{ matrix.ruby.version }}
|
|
54
|
+
rubygems: ${{ matrix.ruby.rubygems }}
|
|
55
|
+
bundler: ${{ env.BUNDLER_VER }}
|
|
56
|
+
bundler-cache: true
|
|
57
|
+
|
|
58
|
+
- uses: metanorma/metanorma-build-scripts/inkscape-setup-action@main
|
|
59
|
+
|
|
60
|
+
- run: bundle exec rake
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
name: rake
|
|
2
|
+
|
|
3
|
+
permissions:
|
|
4
|
+
contents: read
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ main ]
|
|
9
|
+
tags: [ 'v*' ]
|
|
10
|
+
paths-ignore:
|
|
11
|
+
- '**.adoc'
|
|
12
|
+
pull_request:
|
|
13
|
+
paths-ignore:
|
|
14
|
+
- '**.adoc'
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
env:
|
|
21
|
+
BUNDLER_VER: 2.3.24
|
|
22
|
+
# Forcing bundler version to ensure that it is consistent everywhere and
|
|
23
|
+
# does not cause bundler gem reinstalls
|
|
24
|
+
# bundler/rubygems 2.3.22 is a minimal requirement to support gnu/musl differentiation
|
|
25
|
+
# https://github.com/rubygems/rubygems/pull/4488
|
|
26
|
+
GOOGLE_FONTS_API_KEY: ${{secrets.FONTIST_CI_GOOGLE_FONTS_API_KEY}}
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
prepare:
|
|
30
|
+
uses: metanorma/ci/.github/workflows/prepare-rake.yml@main
|
|
31
|
+
|
|
32
|
+
test:
|
|
33
|
+
name: Test on Ruby ${{ matrix.ruby.version }} ${{ matrix.os }}
|
|
34
|
+
runs-on: ${{ matrix.os }}
|
|
35
|
+
|
|
36
|
+
needs: prepare
|
|
37
|
+
if: needs.prepare.outputs.push-for-tag != 'true'
|
|
38
|
+
|
|
39
|
+
continue-on-error: ${{ matrix.ruby.experimental }}
|
|
40
|
+
strategy:
|
|
41
|
+
fail-fast: false
|
|
42
|
+
max-parallel: 5
|
|
43
|
+
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
|
|
48
|
+
- uses: ruby/setup-ruby@v1
|
|
49
|
+
with:
|
|
50
|
+
ruby-version: ${{ matrix.ruby.version }}
|
|
51
|
+
rubygems: ${{ matrix.ruby.rubygems }}
|
|
52
|
+
bundler: ${{ env.BUNDLER_VER }}
|
|
53
|
+
bundler-cache: true
|
|
54
|
+
|
|
55
|
+
- run: bundle exec rake
|
|
56
|
+
|
|
57
|
+
archlinux-test:
|
|
58
|
+
name: Test on Arch Linux
|
|
59
|
+
needs: prepare
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
container:
|
|
62
|
+
image: 'archlinux:latest'
|
|
63
|
+
env:
|
|
64
|
+
CI: true
|
|
65
|
+
|
|
66
|
+
steps:
|
|
67
|
+
- name: Setup packages
|
|
68
|
+
run: pacman -Syu --noconfirm git binutils gcc autoconf make libffi libyaml gmp
|
|
69
|
+
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
|
|
72
|
+
- uses: asdf-vm/actions/install@v3
|
|
73
|
+
with:
|
|
74
|
+
tool_versions: ruby ${{ needs.prepare.outputs.default-ruby-version }}
|
|
75
|
+
|
|
76
|
+
- run: |
|
|
77
|
+
gem install bundler
|
|
78
|
+
bundle install
|
|
79
|
+
|
|
80
|
+
- name: Test
|
|
81
|
+
run: bundle exec rake
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
permissions:
|
|
4
|
+
contents: write
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
next_version:
|
|
10
|
+
description: |
|
|
11
|
+
Next release version. Possible values: x.y.z, major, minor, patch (or pre|rc|etc).
|
|
12
|
+
Also, you can pass 'skip' to skip 'git tag' and do 'gem push' for the current version
|
|
13
|
+
required: true
|
|
14
|
+
default: 'skip'
|
|
15
|
+
repository_dispatch:
|
|
16
|
+
types: [ do-release ]
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
release:
|
|
20
|
+
uses: fontist/support/.github/workflows/release.yml@main
|
|
21
|
+
with:
|
|
22
|
+
next_version: ${{ github.event.inputs.next_version }}
|
|
23
|
+
secrets:
|
|
24
|
+
rubygems-api-key: ${{ secrets.FONTIST_CI_RUBYGEMS_API_KEY }}
|
data/.rubocop.yml
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
# Auto-generated by Cimas: Do not edit it manually!
|
|
2
|
+
# See https://github.com/metanorma/cimas
|
|
1
3
|
inherit_from:
|
|
2
|
-
-
|
|
3
|
-
|
|
4
|
+
- https://raw.githubusercontent.com/riboseinc/oss-guides/main/ci/rubocop.yml
|
|
5
|
+
- .rubocop_todo.yml
|
|
4
6
|
|
|
7
|
+
# local repo-specific modifications
|
|
5
8
|
AllCops:
|
|
6
|
-
TargetRubyVersion: 2.7
|
|
7
|
-
SuggestExtensions: false
|
|
8
9
|
Exclude:
|
|
9
|
-
- '
|
|
10
|
-
|
|
11
|
-
Gemspec/RequireMFA:
|
|
12
|
-
Enabled: false
|
|
10
|
+
- 'lib/excavate/extractors/cpio/cpio_old_format.rb'
|
|
11
|
+
- 'vendor/**/*'
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
plugins:
|
|
14
|
+
- rubocop-performance
|
|
15
|
+
- rubocop-rake
|
|
16
|
+
- rubocop-rspec
|
data/.rubocop_todo.yml
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# This configuration was generated by
|
|
2
|
+
# `rubocop --auto-gen-config`
|
|
3
|
+
# on 2025-11-08 05:21:43 UTC using RuboCop version 1.81.7.
|
|
4
|
+
# The point is for the user to remove these configuration records
|
|
5
|
+
# one by one as the offenses are removed from the code base.
|
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
8
|
+
|
|
9
|
+
# Offense count: 1
|
|
10
|
+
# Configuration parameters: Severity.
|
|
11
|
+
Gemspec/RequiredRubyVersion:
|
|
12
|
+
Exclude:
|
|
13
|
+
- 'excavate.gemspec'
|
|
14
|
+
|
|
15
|
+
# Offense count: 8
|
|
16
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
17
|
+
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
|
|
18
|
+
# URISchemes: http, https
|
|
19
|
+
Layout/LineLength:
|
|
20
|
+
Exclude:
|
|
21
|
+
- 'bin/rspec'
|
|
22
|
+
- 'excavate.gemspec'
|
|
23
|
+
- 'lib/excavate/extractors/rpm_extractor.rb'
|
|
24
|
+
- 'lib/excavate/extractors/xz_extractor.rb'
|
|
25
|
+
- 'lib/excavate/utils.rb'
|
|
26
|
+
- 'spec/excavate/extractors/xz_extractor_spec.rb'
|
|
27
|
+
|
|
28
|
+
# Offense count: 1
|
|
29
|
+
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
|
|
30
|
+
Lint/DuplicateBranch:
|
|
31
|
+
Exclude:
|
|
32
|
+
- 'lib/excavate/utils.rb'
|
|
33
|
+
|
|
34
|
+
# Offense count: 1
|
|
35
|
+
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
36
|
+
Metrics/MethodLength:
|
|
37
|
+
Max: 15
|
|
38
|
+
|
|
39
|
+
# Offense count: 1
|
|
40
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
41
|
+
Performance/RegexpMatch:
|
|
42
|
+
Exclude:
|
|
43
|
+
- 'lib/excavate/utils.rb'
|
|
44
|
+
|
|
45
|
+
# Offense count: 42
|
|
46
|
+
# Configuration parameters: Prefixes, AllowedPatterns.
|
|
47
|
+
# Prefixes: when, with, without
|
|
48
|
+
RSpec/ContextWording:
|
|
49
|
+
Exclude:
|
|
50
|
+
- 'spec/excavate/archive_spec.rb'
|
|
51
|
+
- 'spec/excavate/cli_spec.rb'
|
|
52
|
+
- 'spec/support/fresh_work_dir.rb'
|
|
53
|
+
|
|
54
|
+
# Offense count: 9
|
|
55
|
+
# Configuration parameters: CountAsOne.
|
|
56
|
+
RSpec/ExampleLength:
|
|
57
|
+
Max: 7
|
|
58
|
+
|
|
59
|
+
# Offense count: 1
|
|
60
|
+
# Configuration parameters: .
|
|
61
|
+
# SupportedStyles: have_received, receive
|
|
62
|
+
RSpec/MessageSpies:
|
|
63
|
+
EnforcedStyle: receive
|
|
64
|
+
|
|
65
|
+
# Offense count: 10
|
|
66
|
+
RSpec/MultipleExpectations:
|
|
67
|
+
Max: 2
|
|
68
|
+
|
|
69
|
+
# Offense count: 14
|
|
70
|
+
# Configuration parameters: AllowedGroups.
|
|
71
|
+
RSpec/NestedGroups:
|
|
72
|
+
Max: 4
|
|
73
|
+
|
|
74
|
+
# Offense count: 2
|
|
75
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
76
|
+
Style/IdenticalConditionalBranches:
|
|
77
|
+
Exclude:
|
|
78
|
+
- 'lib/excavate/utils.rb'
|
data/Gemfile
CHANGED
data/README.adoc
CHANGED
|
@@ -1,11 +1,101 @@
|
|
|
1
|
+
= Excavate: Extraction of nested archives
|
|
2
|
+
|
|
1
3
|
image:https://img.shields.io/gem/v/excavate.svg["Gem Version", link="https://rubygems.org/gems/excavate"]
|
|
2
4
|
image:https://codeclimate.com/github/fontist/excavate/badges/gpa.svg["Code Climate", link="https://codeclimate.com/github/fontist/excavate"]
|
|
3
|
-
image:https://github.com/fontist/excavate/workflows/
|
|
5
|
+
image:https://github.com/fontist/excavate/workflows/rake/badge.svg["Build Status", link="https://github.com/fontist/excavate/actions?workflow=rake"]
|
|
6
|
+
|
|
7
|
+
== Purpose
|
|
8
|
+
|
|
9
|
+
Excavate is a Ruby gem that provides a unified interface for extracting
|
|
10
|
+
nested archives across multiple compression and archive formats. The gem
|
|
11
|
+
enables recursive extraction of archives within archives, making it ideal for
|
|
12
|
+
processing complex software distributions, font packages, and other nested
|
|
13
|
+
archive scenarios.
|
|
4
14
|
|
|
5
|
-
|
|
15
|
+
== Features
|
|
6
16
|
|
|
7
|
-
|
|
17
|
+
* <<basic-extraction,Basic archive extraction>>
|
|
18
|
+
* <<recursive-extraction,Recursive extraction of nested archives>>
|
|
19
|
+
* <<selective-extraction,Selective file extraction>>
|
|
20
|
+
* <<filter-extraction,Filter-based extraction>>
|
|
21
|
+
* <<cli-interface,Command-line interface>>
|
|
22
|
+
* <<supported-formats,Supported archive formats>>
|
|
8
23
|
|
|
24
|
+
== Architecture
|
|
25
|
+
|
|
26
|
+
Excavate follows a clean object-oriented architecture with clear separation of
|
|
27
|
+
concerns:
|
|
28
|
+
|
|
29
|
+
.Excavate architecture overview
|
|
30
|
+
----
|
|
31
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
32
|
+
│ │
|
|
33
|
+
│ Excavate::Archive │
|
|
34
|
+
│ (Facade providing unified interface) │
|
|
35
|
+
│ │
|
|
36
|
+
└───────────────────┬─────────────────────────────────────────┘
|
|
37
|
+
│
|
|
38
|
+
│ delegates to
|
|
39
|
+
│
|
|
40
|
+
┌───────────────┴───────────────┐
|
|
41
|
+
│ │
|
|
42
|
+
│ Format Type Registry │
|
|
43
|
+
│ (TYPES hash mapping) │
|
|
44
|
+
│ │
|
|
45
|
+
└───────────────┬───────────────┘
|
|
46
|
+
│
|
|
47
|
+
│ instantiates
|
|
48
|
+
│
|
|
49
|
+
┌───────────────┴───────────────────────────────────┐
|
|
50
|
+
│ │
|
|
51
|
+
│ Extractors::Extractor │
|
|
52
|
+
│ (Abstract base class) │
|
|
53
|
+
│ │
|
|
54
|
+
└───────────────┬───────────────────────────────────┘
|
|
55
|
+
│
|
|
56
|
+
│ specialized by
|
|
57
|
+
│
|
|
58
|
+
┌───────────────┴───────────────────────────┐
|
|
59
|
+
│ │
|
|
60
|
+
├─ CabExtractor ├─ RpmExtractor │
|
|
61
|
+
├─ CpioExtractor ├─ SevenZipExtractor │
|
|
62
|
+
├─ GzipExtractor ├─ TarExtractor │
|
|
63
|
+
├─ OleExtractor ├─ XarExtractor │
|
|
64
|
+
├─ XzExtractor ├─ ZipExtractor │
|
|
65
|
+
│ │
|
|
66
|
+
└───────────────────────────────────────────┘
|
|
67
|
+
----
|
|
68
|
+
|
|
69
|
+
.Data flow for archive extraction
|
|
70
|
+
----
|
|
71
|
+
User Request
|
|
72
|
+
│
|
|
73
|
+
├── Archive.new(file_path)
|
|
74
|
+
│ │
|
|
75
|
+
│ ├── Detect format from extension
|
|
76
|
+
│ │
|
|
77
|
+
│ └── Select appropriate Extractor
|
|
78
|
+
│
|
|
79
|
+
├── Archive#extract(target_dir)
|
|
80
|
+
│ │
|
|
81
|
+
│ ├── Create target directory
|
|
82
|
+
│ │
|
|
83
|
+
│ ├── Extractor#extract(target)
|
|
84
|
+
│ │ │
|
|
85
|
+
│ │ └── Format-specific extraction
|
|
86
|
+
│ │
|
|
87
|
+
│ └── [Optional] Recursive extraction
|
|
88
|
+
│ │
|
|
89
|
+
│ └── Repeat for nested archives
|
|
90
|
+
│
|
|
91
|
+
└── Return: Extracted files
|
|
92
|
+
----
|
|
93
|
+
|
|
94
|
+
The architecture follows these principles:
|
|
95
|
+
|
|
96
|
+
* **Single Responsibility**: Each extractor handles one format
|
|
97
|
+
* **Open/Closed**: New formats can be added without modifying existing code
|
|
98
|
+
* **Dependency Inversion**: Archive class depends on Extractor abstraction
|
|
9
99
|
|
|
10
100
|
== Installation
|
|
11
101
|
|
|
@@ -20,144 +110,482 @@ And then execute:
|
|
|
20
110
|
|
|
21
111
|
[source,sh]
|
|
22
112
|
----
|
|
23
|
-
|
|
113
|
+
bundle install
|
|
24
114
|
----
|
|
25
115
|
|
|
26
116
|
Or install it yourself as:
|
|
27
117
|
|
|
28
118
|
[source,sh]
|
|
29
119
|
----
|
|
30
|
-
|
|
120
|
+
gem install excavate
|
|
31
121
|
----
|
|
32
122
|
|
|
123
|
+
[[supported-formats]]
|
|
124
|
+
== Supported formats
|
|
125
|
+
|
|
126
|
+
Excavate supports the following archive and compression formats:
|
|
127
|
+
|
|
128
|
+
* CAB (`.cab`, `.exe` with CAB)
|
|
129
|
+
* CPIO (`.cpio`)
|
|
130
|
+
* GZIP (`.gz`)
|
|
131
|
+
* MSI (`.msi`)
|
|
132
|
+
* RPM (`.rpm`)
|
|
133
|
+
* 7-Zip (`.7z`, `.exe` with 7z)
|
|
134
|
+
* TAR (`.tar`)
|
|
135
|
+
* XAR (`.pkg`)
|
|
136
|
+
* XZ (`.xz`, `.tar.xz`)
|
|
137
|
+
* ZIP (`.zip`)
|
|
138
|
+
|
|
139
|
+
All formats support recursive extraction for nested archives.
|
|
140
|
+
|
|
141
|
+
[[basic-extraction]]
|
|
142
|
+
== Basic extraction
|
|
143
|
+
|
|
144
|
+
=== General
|
|
33
145
|
|
|
34
|
-
|
|
146
|
+
This feature provides the fundamental capability to extract archives to a
|
|
147
|
+
target directory. It is the core functionality of Excavate and is used as the
|
|
148
|
+
foundation for all other extraction features.
|
|
35
149
|
|
|
36
|
-
|
|
150
|
+
=== Syntax
|
|
37
151
|
|
|
38
152
|
[source,ruby]
|
|
39
153
|
----
|
|
154
|
+
Excavate::Archive.new(archive_path).extract(target_directory) <1> <2>
|
|
155
|
+
----
|
|
156
|
+
<1> `archive_path` - Path to the archive file to extract
|
|
157
|
+
<2> `target_directory` - Directory where files will be extracted (optional)
|
|
158
|
+
|
|
159
|
+
Where,
|
|
160
|
+
|
|
161
|
+
`archive_path`:: (required) Path to the archive file to extract. Can be an
|
|
162
|
+
absolute or relative path.
|
|
163
|
+
`target_directory`:: (optional) Target directory for extraction. If omitted, a
|
|
164
|
+
directory with the archive's base name will be created in the current directory.
|
|
165
|
+
|
|
166
|
+
=== Usage example
|
|
167
|
+
|
|
168
|
+
.Extracting a ZIP archive to a specific directory
|
|
169
|
+
[example]
|
|
170
|
+
====
|
|
171
|
+
[source,ruby]
|
|
172
|
+
----
|
|
173
|
+
require "excavate"
|
|
174
|
+
require "tmpdir"
|
|
175
|
+
|
|
176
|
+
# Create a temporary directory for extraction
|
|
40
177
|
target = Dir.mktmpdir
|
|
41
|
-
|
|
178
|
+
|
|
179
|
+
# Extract the archive
|
|
180
|
+
Excavate::Archive.new("fonts.zip").extract(target)
|
|
181
|
+
|
|
182
|
+
# List extracted files
|
|
183
|
+
Dir.glob(File.join(target, "**", "*")).each do |file|
|
|
184
|
+
puts file if File.file?(file)
|
|
185
|
+
end
|
|
42
186
|
----
|
|
43
187
|
|
|
44
|
-
|
|
188
|
+
This example extracts a ZIP archive to a temporary directory and lists all
|
|
189
|
+
extracted files.
|
|
190
|
+
====
|
|
45
191
|
|
|
192
|
+
.Extracting with automatic target directory creation
|
|
193
|
+
[example]
|
|
194
|
+
====
|
|
46
195
|
[source,ruby]
|
|
47
196
|
----
|
|
197
|
+
require "excavate"
|
|
198
|
+
|
|
199
|
+
# Extract will create a directory named "fonts" in current directory
|
|
200
|
+
Excavate::Archive.new("fonts.zip").extract
|
|
201
|
+
|
|
202
|
+
# Files are now in ./fonts/
|
|
203
|
+
----
|
|
204
|
+
|
|
205
|
+
When no target is specified, Excavate creates a directory with the archive's
|
|
206
|
+
base name (without extension) in the current working directory.
|
|
207
|
+
====
|
|
208
|
+
|
|
209
|
+
[[recursive-extraction]]
|
|
210
|
+
== Recursive extraction
|
|
211
|
+
|
|
212
|
+
=== General
|
|
213
|
+
|
|
214
|
+
This feature enables automatic extraction of nested archives. When an archive
|
|
215
|
+
contains other archives, Excavate can recursively extract them all in a single
|
|
216
|
+
operation. This is particularly useful for complex software distributions that
|
|
217
|
+
package multiple archives together.
|
|
218
|
+
|
|
219
|
+
=== Syntax
|
|
220
|
+
|
|
221
|
+
[source,ruby]
|
|
222
|
+
----
|
|
223
|
+
Excavate::Archive.new(archive_path).extract(
|
|
224
|
+
target_directory,
|
|
225
|
+
recursive_packages: true <1>
|
|
226
|
+
)
|
|
227
|
+
----
|
|
228
|
+
<1> Enable recursive extraction of nested archives
|
|
229
|
+
|
|
230
|
+
Where,
|
|
231
|
+
|
|
232
|
+
`recursive_packages`:: (optional) Boolean flag to enable recursive extraction.
|
|
233
|
+
When `true`, archives found within the extracted files are automatically
|
|
234
|
+
extracted. Default is `false`.
|
|
235
|
+
|
|
236
|
+
=== Usage example
|
|
237
|
+
|
|
238
|
+
.Recursively extracting nested archives
|
|
239
|
+
[example]
|
|
240
|
+
====
|
|
241
|
+
[source,ruby]
|
|
242
|
+
----
|
|
243
|
+
require "excavate"
|
|
244
|
+
require "tmpdir"
|
|
245
|
+
|
|
48
246
|
target = Dir.mktmpdir
|
|
49
|
-
|
|
50
|
-
|
|
247
|
+
|
|
248
|
+
# Extract an MSI file that contains CAB archives
|
|
249
|
+
Excavate::Archive.new("fonts.msi").extract(
|
|
250
|
+
target,
|
|
251
|
+
recursive_packages: true
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# All nested CAB files are automatically extracted
|
|
255
|
+
# Final structure contains only the font files
|
|
256
|
+
----
|
|
257
|
+
|
|
258
|
+
This example shows extraction of an MSI installer that contains nested CAB
|
|
259
|
+
archives. With `recursive_packages: true`, both the MSI and all contained CAB
|
|
260
|
+
files are automatically extracted.
|
|
261
|
+
====
|
|
262
|
+
|
|
263
|
+
.Processing files during recursive extraction
|
|
264
|
+
[example]
|
|
265
|
+
====
|
|
266
|
+
[source,ruby]
|
|
267
|
+
----
|
|
268
|
+
require "excavate"
|
|
269
|
+
|
|
270
|
+
fonts = []
|
|
271
|
+
|
|
272
|
+
Excavate::Archive.new("fonts.tar.gz").files(
|
|
273
|
+
recursive_packages: true
|
|
274
|
+
) do |file|
|
|
275
|
+
fonts << file if file.end_with?(".ttf", ".otf")
|
|
51
276
|
end
|
|
277
|
+
|
|
278
|
+
puts "Found #{fonts.size} font files"
|
|
52
279
|
----
|
|
53
280
|
|
|
281
|
+
The `files` method with `recursive_packages: true` processes each extracted
|
|
282
|
+
file through a block, allowing selective collection of specific file types.
|
|
283
|
+
====
|
|
54
284
|
|
|
55
|
-
|
|
285
|
+
[[selective-extraction]]
|
|
286
|
+
== Selective extraction
|
|
56
287
|
|
|
57
|
-
|
|
288
|
+
=== General
|
|
58
289
|
|
|
59
|
-
|
|
290
|
+
This feature allows extraction of specific files from an archive without
|
|
291
|
+
extracting the entire contents. It is useful when working with large archives
|
|
292
|
+
where only certain files are needed.
|
|
293
|
+
|
|
294
|
+
=== Syntax
|
|
295
|
+
|
|
296
|
+
[source,ruby]
|
|
60
297
|
----
|
|
61
|
-
|
|
298
|
+
Excavate::Archive.new(archive_path).extract(
|
|
299
|
+
target_directory,
|
|
300
|
+
files: [file1, file2, ...] <1>
|
|
301
|
+
)
|
|
62
302
|
----
|
|
303
|
+
<1> Array of specific file paths to extract from the archive
|
|
63
304
|
|
|
64
|
-
|
|
305
|
+
Where,
|
|
65
306
|
|
|
66
|
-
|
|
307
|
+
`files`:: (optional) Array of file paths to extract. Paths should match the
|
|
308
|
+
structure within the archive. If a file is not found, a `TargetNotFoundError`
|
|
309
|
+
is raised.
|
|
310
|
+
|
|
311
|
+
=== Usage example
|
|
312
|
+
|
|
313
|
+
.Extracting specific files from an archive
|
|
314
|
+
[example]
|
|
315
|
+
====
|
|
316
|
+
[source,ruby]
|
|
67
317
|
----
|
|
68
|
-
|
|
318
|
+
require "excavate"
|
|
319
|
+
|
|
320
|
+
target = "/tmp/extracted"
|
|
321
|
+
|
|
322
|
+
# Extract only specific font files
|
|
323
|
+
files = Excavate::Archive.new("fonts.zip").extract(
|
|
324
|
+
target,
|
|
325
|
+
files: ["Fonts/Arial.ttf", "Fonts/Verdana.ttf"]
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
puts "Extracted #{files.size} files:"
|
|
329
|
+
files.each { |f| puts " - #{File.basename(f)}" }
|
|
330
|
+
----
|
|
331
|
+
|
|
332
|
+
This extracts only the specified files, even though the archive may contain
|
|
333
|
+
many more files.
|
|
334
|
+
====
|
|
335
|
+
|
|
336
|
+
.Extracting files from nested archives
|
|
337
|
+
[example]
|
|
338
|
+
====
|
|
339
|
+
[source,ruby]
|
|
340
|
+
----
|
|
341
|
+
require "excavate"
|
|
342
|
+
|
|
343
|
+
# Extract a specific file from a nested archive
|
|
344
|
+
# Path format: nested.zip/inner_file
|
|
345
|
+
files = Excavate::Archive.new("outer.zip").extract(
|
|
346
|
+
"/tmp/out",
|
|
347
|
+
files: ["nested.zip/important.txt"],
|
|
348
|
+
recursive_packages: true
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# The file from the nested archive is extracted
|
|
352
|
+
----
|
|
353
|
+
|
|
354
|
+
When combined with `recursive_packages: true`, you can specify paths through
|
|
355
|
+
nested archives using the format `archive.zip/path/to/file`.
|
|
356
|
+
====
|
|
357
|
+
|
|
358
|
+
[[filter-extraction]]
|
|
359
|
+
== Filter-based extraction
|
|
360
|
+
|
|
361
|
+
=== General
|
|
362
|
+
|
|
363
|
+
This feature provides pattern-based file selection for extraction. Instead of
|
|
364
|
+
specifying exact file paths, you can use glob patterns to match multiple files,
|
|
365
|
+
making it ideal for extracting files by type or naming convention.
|
|
366
|
+
|
|
367
|
+
=== Syntax
|
|
368
|
+
|
|
369
|
+
[source,ruby]
|
|
370
|
+
----
|
|
371
|
+
Excavate::Archive.new(archive_path).extract(
|
|
372
|
+
target_directory,
|
|
373
|
+
filter: "pattern" <1>
|
|
374
|
+
)
|
|
375
|
+
----
|
|
376
|
+
<1> Glob pattern to match files for extraction
|
|
377
|
+
|
|
378
|
+
Where,
|
|
379
|
+
|
|
380
|
+
`filter`:: (optional) Glob pattern string to match files. Supports standard
|
|
381
|
+
glob syntax including `*` (any characters), `**` (any directories), and
|
|
382
|
+
character classes. If no files match, a `TargetNotFoundError` is raised.
|
|
383
|
+
|
|
384
|
+
=== Usage example
|
|
385
|
+
|
|
386
|
+
.Extracting all files of a specific type
|
|
387
|
+
[example]
|
|
388
|
+
====
|
|
389
|
+
[source,ruby]
|
|
390
|
+
----
|
|
391
|
+
require "excavate"
|
|
392
|
+
|
|
393
|
+
# Extract only TrueType fonts from any directory
|
|
394
|
+
files = Excavate::Archive.new("fonts.zip").extract(
|
|
395
|
+
"/tmp/fonts",
|
|
396
|
+
filter: "**/*.ttf"
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
puts "Extracted #{files.size} TrueType font files"
|
|
400
|
+
----
|
|
401
|
+
|
|
402
|
+
The pattern `**/*.ttf` matches all `.ttf` files in any subdirectory within the
|
|
403
|
+
archive.
|
|
404
|
+
====
|
|
405
|
+
|
|
406
|
+
.Extracting files with complex patterns
|
|
407
|
+
[example]
|
|
408
|
+
====
|
|
409
|
+
[source,ruby]
|
|
69
410
|
----
|
|
411
|
+
require "excavate"
|
|
70
412
|
|
|
71
|
-
|
|
413
|
+
# Extract configuration files from specific directories
|
|
414
|
+
files = Excavate::Archive.new("config.tar.gz").extract(
|
|
415
|
+
"/tmp/conf",
|
|
416
|
+
filter: "etc/**/*.{conf,cfg}"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Extracts .conf and .cfg files only from the 'etc' directory tree
|
|
420
|
+
----
|
|
421
|
+
|
|
422
|
+
Complex patterns can use brace expansion to match multiple extensions or
|
|
423
|
+
patterns.
|
|
424
|
+
====
|
|
425
|
+
|
|
426
|
+
[[cli-interface]]
|
|
427
|
+
== Command-line interface
|
|
428
|
+
|
|
429
|
+
=== General
|
|
430
|
+
|
|
431
|
+
Excavate provides a command-line tool for extracting archives directly from the
|
|
432
|
+
shell. The CLI supports all the same features as the Ruby API, making it
|
|
433
|
+
suitable for shell scripts and interactive use.
|
|
434
|
+
|
|
435
|
+
=== Syntax
|
|
72
436
|
|
|
73
437
|
[source,sh]
|
|
74
438
|
----
|
|
75
|
-
|
|
439
|
+
excavate [OPTIONS] ARCHIVE [FILES...] <1> <2> <3>
|
|
76
440
|
----
|
|
441
|
+
<1> Command options for controlling extraction behavior
|
|
442
|
+
<2> Path to the archive file
|
|
443
|
+
<3> Optional list of specific files to extract
|
|
444
|
+
|
|
445
|
+
Where,
|
|
77
446
|
|
|
78
|
-
|
|
447
|
+
`--recursive`:: Enable recursive extraction of nested archives
|
|
448
|
+
`--filter PATTERN`:: Extract only files matching the glob pattern
|
|
449
|
+
`ARCHIVE`:: Path to the archive file to extract
|
|
450
|
+
`FILES...`:: Optional list of specific file paths to extract
|
|
79
451
|
|
|
452
|
+
=== Usage example
|
|
453
|
+
|
|
454
|
+
.Basic command-line extraction
|
|
455
|
+
[example]
|
|
456
|
+
====
|
|
80
457
|
[source,sh]
|
|
81
458
|
----
|
|
82
|
-
|
|
459
|
+
# Extract archive to a directory with the archive's base name
|
|
460
|
+
excavate fonts.zip
|
|
461
|
+
|
|
462
|
+
# Extract with recursive nested archive processing
|
|
463
|
+
excavate --recursive application.msi
|
|
464
|
+
|
|
465
|
+
# Extract from a directory of archives
|
|
466
|
+
excavate --recursive archive_directory/
|
|
83
467
|
----
|
|
84
468
|
|
|
85
|
-
|
|
469
|
+
Basic extraction creates a directory named after the archive (without extension)
|
|
470
|
+
in the current directory.
|
|
471
|
+
====
|
|
86
472
|
|
|
473
|
+
.Selective extraction via CLI
|
|
474
|
+
[example]
|
|
475
|
+
====
|
|
87
476
|
[source,sh]
|
|
88
477
|
----
|
|
89
|
-
|
|
478
|
+
# Extract specific files
|
|
479
|
+
excavate fonts.zip Fonts/Arial.ttf Fonts/Verdana.ttf
|
|
480
|
+
|
|
481
|
+
# Extract files matching a pattern
|
|
482
|
+
excavate --filter "**/*.ttf" fonts.zip
|
|
483
|
+
|
|
484
|
+
# Extract from nested archives
|
|
485
|
+
excavate --recursive outer.zip nested.zip/file.txt
|
|
90
486
|
----
|
|
91
487
|
|
|
92
|
-
|
|
488
|
+
The CLI supports the same selective extraction features as the Ruby API.
|
|
489
|
+
====
|
|
93
490
|
|
|
491
|
+
.Processing XZ compressed archives
|
|
492
|
+
[example]
|
|
493
|
+
====
|
|
94
494
|
[source,sh]
|
|
95
495
|
----
|
|
96
|
-
|
|
496
|
+
# Extract TAR.XZ archive
|
|
497
|
+
excavate wine-10.18.tar.xz
|
|
498
|
+
|
|
499
|
+
# Extract XZ with recursive processing
|
|
500
|
+
excavate --recursive package.tar.xz
|
|
501
|
+
|
|
502
|
+
# Extract specific files from XZ archive
|
|
503
|
+
excavate package.tar.xz --filter "*.conf"
|
|
97
504
|
----
|
|
98
505
|
|
|
506
|
+
XZ compressed archives (both `.xz` and `.tar.xz`) are fully supported through
|
|
507
|
+
the command-line interface.
|
|
508
|
+
====
|
|
509
|
+
|
|
99
510
|
== Dependencies
|
|
100
511
|
|
|
101
|
-
|
|
102
|
-
https://github.com/fontist/ffi-libarchive-binary[ffi-libarchive-binary]
|
|
103
|
-
has the following requirements:
|
|
512
|
+
Excavate depends on the following system libraries through the
|
|
513
|
+
https://github.com/fontist/ffi-libarchive-binary[ffi-libarchive-binary] gem:
|
|
104
514
|
|
|
105
515
|
* zlib
|
|
106
516
|
* Expat
|
|
107
517
|
* OpenSSL (for Linux only)
|
|
108
518
|
|
|
109
|
-
These dependencies are generally present on all systems
|
|
110
|
-
|
|
519
|
+
These dependencies are generally present on all systems and require no special
|
|
520
|
+
installation steps.
|
|
111
521
|
|
|
112
522
|
== Development
|
|
113
523
|
|
|
114
|
-
|
|
524
|
+
=== General
|
|
525
|
+
|
|
526
|
+
When contributing to Excavate, follow these development guidelines to maintain
|
|
527
|
+
code quality and consistency.
|
|
528
|
+
|
|
529
|
+
=== Coding standards
|
|
530
|
+
|
|
531
|
+
We follow Sandi Metz's Rules for this gem. You can read the
|
|
115
532
|
http://robots.thoughtbot.com/post/50655960596/sandi-metz-rules-for-developers[description of the rules here].
|
|
116
|
-
All new code should follow these
|
|
117
|
-
|
|
118
|
-
|
|
533
|
+
All new code should follow these rules. If you make changes in a pre-existing
|
|
534
|
+
file that violates these rules, you should fix the violations as part of your
|
|
535
|
+
contribution.
|
|
536
|
+
|
|
537
|
+
=== Testing
|
|
538
|
+
|
|
539
|
+
Run the test suite with:
|
|
540
|
+
|
|
541
|
+
[source,sh]
|
|
542
|
+
----
|
|
543
|
+
bundle exec rspec
|
|
544
|
+
----
|
|
119
545
|
|
|
546
|
+
Ensure all tests pass before submitting a pull request.
|
|
120
547
|
|
|
121
548
|
== Releasing
|
|
122
549
|
|
|
123
|
-
Releasing is done automatically with GitHub
|
|
550
|
+
Releasing is done automatically with GitHub Actions. Just bump and tag with
|
|
551
|
+
`gem-release`.
|
|
124
552
|
|
|
125
553
|
For a patch release (0.0.x) use:
|
|
126
554
|
|
|
127
|
-
[source,
|
|
555
|
+
[source,sh]
|
|
128
556
|
----
|
|
129
557
|
gem bump --version patch --tag --push
|
|
130
558
|
----
|
|
131
559
|
|
|
132
560
|
For a minor release (0.x.0) use:
|
|
133
561
|
|
|
134
|
-
[source,
|
|
562
|
+
[source,sh]
|
|
135
563
|
----
|
|
136
564
|
gem bump --version minor --tag --push
|
|
137
565
|
----
|
|
138
566
|
|
|
139
|
-
|
|
140
567
|
== Contributing
|
|
141
568
|
|
|
142
569
|
First, thank you for contributing! We love pull requests from everyone. By
|
|
143
|
-
participating in this project, you hereby grant
|
|
144
|
-
right to grant or transfer an unlimited
|
|
145
|
-
sub-licenses to third parties, under the
|
|
146
|
-
to use the contribution by all means.
|
|
570
|
+
participating in this project, you hereby grant
|
|
571
|
+
https://www.ribose.com[Ribose Inc.] the right to grant or transfer an unlimited
|
|
572
|
+
number of non exclusive licenses or sub-licenses to third parties, under the
|
|
573
|
+
copyright covering the contribution to use the contribution by all means.
|
|
147
574
|
|
|
148
575
|
Here are a few technical guidelines to follow:
|
|
149
576
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
577
|
+
. Open an https://github.com/fontist/excavate/issues[issue] to discuss a new
|
|
578
|
+
feature.
|
|
579
|
+
. Write tests to support your new feature.
|
|
580
|
+
. Make sure the entire test suite passes locally and on CI.
|
|
581
|
+
. Open a Pull Request.
|
|
582
|
+
. https://github.com/thoughtbot/guides/tree/master/protocol/git#write-a-feature[Squash your commits]
|
|
583
|
+
after receiving feedback.
|
|
584
|
+
. Party!
|
|
158
585
|
|
|
159
586
|
== License
|
|
160
587
|
|
|
161
588
|
This gem is distributed with a BSD 3-Clause license.
|
|
162
589
|
|
|
163
|
-
This gem is developed, maintained and funded by
|
|
590
|
+
This gem is developed, maintained and funded by
|
|
591
|
+
https://www.ribose.com/[Ribose Inc.]
|
data/excavate.gemspec
CHANGED
|
@@ -31,20 +31,16 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
32
32
|
spec.require_paths = ["lib"]
|
|
33
33
|
|
|
34
|
-
spec.
|
|
35
|
-
spec.
|
|
34
|
+
spec.add_dependency "arr-pm", "~> 0.0"
|
|
35
|
+
spec.add_dependency "bundler", "~> 2.3", ">= 2.3.24"
|
|
36
36
|
# Workaround for https://github.com/metanorma/ruby-libmspack/issues/2
|
|
37
|
-
spec.
|
|
38
|
-
spec.
|
|
39
|
-
spec.
|
|
40
|
-
spec.
|
|
41
|
-
spec.
|
|
42
|
-
spec.
|
|
43
|
-
spec.
|
|
44
|
-
|
|
45
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
|
46
|
-
spec.add_development_dependency "rubocop", "~> 1.7"
|
|
47
|
-
spec.add_development_dependency "rubocop-performance", "~> 1.15"
|
|
37
|
+
spec.add_dependency "ffi-compiler2", ">= 2.2.2"
|
|
38
|
+
spec.add_dependency "ffi-libarchive-binary", "~> 0.3"
|
|
39
|
+
spec.add_dependency "libmspack", "~> 0.1"
|
|
40
|
+
spec.add_dependency "ruby-ole", "~> 1.0"
|
|
41
|
+
spec.add_dependency "rubyzip", "~> 2.3"
|
|
42
|
+
spec.add_dependency "seven-zip", "~> 1.4"
|
|
43
|
+
spec.add_dependency "thor", "~> 1.0"
|
|
48
44
|
|
|
49
45
|
spec.metadata["rubygems_mfa_required"] = "false"
|
|
50
46
|
end
|
data/lib/excavate/archive.rb
CHANGED
|
@@ -8,9 +8,10 @@ module Excavate
|
|
|
8
8
|
"exe" => Extractors::SevenZipExtractor,
|
|
9
9
|
"gz" => Extractors::GzipExtractor,
|
|
10
10
|
"msi" => Extractors::OleExtractor,
|
|
11
|
+
"pkg" => Extractors::XarExtractor,
|
|
11
12
|
"rpm" => Extractors::RpmExtractor,
|
|
12
13
|
"tar" => Extractors::TarExtractor,
|
|
13
|
-
"
|
|
14
|
+
"xz" => Extractors::XzExtractor,
|
|
14
15
|
"zip" => Extractors::ZipExtractor }.freeze
|
|
15
16
|
|
|
16
17
|
def initialize(archive)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require "ffi-libarchive-binary"
|
|
2
|
+
|
|
3
|
+
module Excavate
|
|
4
|
+
module Extractors
|
|
5
|
+
# Extractor for XZ compressed archives (both .xz and .tar.xz formats)
|
|
6
|
+
#
|
|
7
|
+
# This extractor handles:
|
|
8
|
+
# - Pure XZ compressed files (.xz)
|
|
9
|
+
# - Compound TAR+XZ archives (.tar.xz)
|
|
10
|
+
#
|
|
11
|
+
# Uses libarchive through ffi-libarchive-binary for extraction,
|
|
12
|
+
# which provides native XZ decompression support.
|
|
13
|
+
#
|
|
14
|
+
# @example Extract a .tar.xz file
|
|
15
|
+
# extractor = XzExtractor.new("archive.tar.xz")
|
|
16
|
+
# extractor.extract("/target/directory")
|
|
17
|
+
#
|
|
18
|
+
# @example Extract a pure .xz file
|
|
19
|
+
# extractor = XzExtractor.new("file.xz")
|
|
20
|
+
# extractor.extract("/target/directory")
|
|
21
|
+
class XzExtractor < Extractor
|
|
22
|
+
# Extract the XZ archive to the specified target directory
|
|
23
|
+
#
|
|
24
|
+
# @param target [String] the directory path where files should be extracted
|
|
25
|
+
# @return [void]
|
|
26
|
+
#
|
|
27
|
+
# @raise [StandardError] if extraction fails
|
|
28
|
+
def extract(target)
|
|
29
|
+
extract_with_libarchive(target)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Perform extraction using libarchive
|
|
35
|
+
#
|
|
36
|
+
# This method uses libarchive's reader API to:
|
|
37
|
+
# 1. Open the XZ archive
|
|
38
|
+
# 2. Iterate through all entries
|
|
39
|
+
# 3. Extract each entry with appropriate permissions
|
|
40
|
+
# 4. Close the reader
|
|
41
|
+
#
|
|
42
|
+
# @param target [String] the target directory for extraction
|
|
43
|
+
# @return [void]
|
|
44
|
+
def extract_with_libarchive(target)
|
|
45
|
+
flags = ::Archive::EXTRACT_PERM
|
|
46
|
+
reader = ::Archive::Reader.open_filename(@archive)
|
|
47
|
+
|
|
48
|
+
Dir.chdir(target) do
|
|
49
|
+
reader.each_entry do |entry|
|
|
50
|
+
reader.extract(entry, flags.to_i)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
reader.close
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/excavate/extractors.rb
CHANGED
|
@@ -7,4 +7,5 @@ require_relative "extractors/rpm_extractor"
|
|
|
7
7
|
require_relative "extractors/seven_zip_extractor"
|
|
8
8
|
require_relative "extractors/tar_extractor"
|
|
9
9
|
require_relative "extractors/xar_extractor"
|
|
10
|
+
require_relative "extractors/xz_extractor"
|
|
10
11
|
require_relative "extractors/zip_extractor"
|
data/lib/excavate/file_magic.rb
CHANGED
|
@@ -14,9 +14,14 @@ module Excavate
|
|
|
14
14
|
when "MSCF\x00\x00\x00\x00".force_encoding("BINARY")
|
|
15
15
|
:cab
|
|
16
16
|
else
|
|
17
|
-
case beginning.byteslice(0,
|
|
18
|
-
when "\
|
|
19
|
-
:
|
|
17
|
+
case beginning.byteslice(0, 6)
|
|
18
|
+
when "\xFD7zXZ\x00".force_encoding("BINARY")
|
|
19
|
+
:xz
|
|
20
|
+
else
|
|
21
|
+
case beginning.byteslice(0, 2)
|
|
22
|
+
when "\x1F\x8B".force_encoding("BINARY")
|
|
23
|
+
:gzip
|
|
24
|
+
end
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
end
|
data/lib/excavate/utils.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Excavate
|
|
|
4
4
|
|
|
5
5
|
def silence_stream(stream)
|
|
6
6
|
old_stream = stream.dup
|
|
7
|
-
stream.reopen(RbConfig::CONFIG["host_os"]
|
|
7
|
+
stream.reopen(/mswin|mingw/.match?(RbConfig::CONFIG["host_os"]) ? File::NULL : File::NULL)
|
|
8
8
|
stream.sync = true
|
|
9
9
|
yield
|
|
10
10
|
ensure
|
data/lib/excavate/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: excavate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2025-11-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: arr-pm
|
|
@@ -142,48 +142,6 @@ dependencies:
|
|
|
142
142
|
- - "~>"
|
|
143
143
|
- !ruby/object:Gem::Version
|
|
144
144
|
version: '1.0'
|
|
145
|
-
- !ruby/object:Gem::Dependency
|
|
146
|
-
name: rspec
|
|
147
|
-
requirement: !ruby/object:Gem::Requirement
|
|
148
|
-
requirements:
|
|
149
|
-
- - "~>"
|
|
150
|
-
- !ruby/object:Gem::Version
|
|
151
|
-
version: '3.0'
|
|
152
|
-
type: :development
|
|
153
|
-
prerelease: false
|
|
154
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
155
|
-
requirements:
|
|
156
|
-
- - "~>"
|
|
157
|
-
- !ruby/object:Gem::Version
|
|
158
|
-
version: '3.0'
|
|
159
|
-
- !ruby/object:Gem::Dependency
|
|
160
|
-
name: rubocop
|
|
161
|
-
requirement: !ruby/object:Gem::Requirement
|
|
162
|
-
requirements:
|
|
163
|
-
- - "~>"
|
|
164
|
-
- !ruby/object:Gem::Version
|
|
165
|
-
version: '1.7'
|
|
166
|
-
type: :development
|
|
167
|
-
prerelease: false
|
|
168
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
169
|
-
requirements:
|
|
170
|
-
- - "~>"
|
|
171
|
-
- !ruby/object:Gem::Version
|
|
172
|
-
version: '1.7'
|
|
173
|
-
- !ruby/object:Gem::Dependency
|
|
174
|
-
name: rubocop-performance
|
|
175
|
-
requirement: !ruby/object:Gem::Requirement
|
|
176
|
-
requirements:
|
|
177
|
-
- - "~>"
|
|
178
|
-
- !ruby/object:Gem::Version
|
|
179
|
-
version: '1.15'
|
|
180
|
-
type: :development
|
|
181
|
-
prerelease: false
|
|
182
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
183
|
-
requirements:
|
|
184
|
-
- - "~>"
|
|
185
|
-
- !ruby/object:Gem::Version
|
|
186
|
-
version: '1.15'
|
|
187
145
|
description: Extract nested archives with a single command.
|
|
188
146
|
email:
|
|
189
147
|
- open.source@ribose.com
|
|
@@ -192,10 +150,14 @@ executables:
|
|
|
192
150
|
extensions: []
|
|
193
151
|
extra_rdoc_files: []
|
|
194
152
|
files:
|
|
195
|
-
- ".github/workflows/
|
|
153
|
+
- ".github/workflows/post-rake.yml"
|
|
154
|
+
- ".github/workflows/rake-metanorma.yaml"
|
|
155
|
+
- ".github/workflows/rake.yml"
|
|
156
|
+
- ".github/workflows/release.yml"
|
|
196
157
|
- ".gitignore"
|
|
197
158
|
- ".rspec"
|
|
198
159
|
- ".rubocop.yml"
|
|
160
|
+
- ".rubocop_todo.yml"
|
|
199
161
|
- Gemfile
|
|
200
162
|
- LICENSE.adoc
|
|
201
163
|
- README.adoc
|
|
@@ -217,6 +179,7 @@ files:
|
|
|
217
179
|
- lib/excavate/extractors/seven_zip_extractor.rb
|
|
218
180
|
- lib/excavate/extractors/tar_extractor.rb
|
|
219
181
|
- lib/excavate/extractors/xar_extractor.rb
|
|
182
|
+
- lib/excavate/extractors/xz_extractor.rb
|
|
220
183
|
- lib/excavate/extractors/zip_extractor.rb
|
|
221
184
|
- lib/excavate/file_magic.rb
|
|
222
185
|
- lib/excavate/utils.rb
|
|
@@ -244,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
244
207
|
- !ruby/object:Gem::Version
|
|
245
208
|
version: '0'
|
|
246
209
|
requirements: []
|
|
247
|
-
rubygems_version: 3.5.
|
|
210
|
+
rubygems_version: 3.5.22
|
|
248
211
|
signing_key:
|
|
249
212
|
specification_version: 4
|
|
250
213
|
summary: Extract nested archives with a single command.
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
name: test-and-release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [ main ]
|
|
6
|
-
tags: [ 'v*' ]
|
|
7
|
-
pull_request:
|
|
8
|
-
|
|
9
|
-
concurrency:
|
|
10
|
-
group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
|
|
11
|
-
cancel-in-progress: true
|
|
12
|
-
|
|
13
|
-
env:
|
|
14
|
-
BUNDLER_VER: 2.4.22
|
|
15
|
-
# Forcing bundler version to ensure that it is consistent everywhere and
|
|
16
|
-
# does not cause bundler gem reinstalls
|
|
17
|
-
|
|
18
|
-
jobs:
|
|
19
|
-
prepare:
|
|
20
|
-
uses: metanorma/ci/.github/workflows/prepare-rake.yml@main
|
|
21
|
-
|
|
22
|
-
test:
|
|
23
|
-
name: Test on Ruby ${{ matrix.ruby.version }} ${{ matrix.os }}
|
|
24
|
-
runs-on: ${{ matrix.os }}
|
|
25
|
-
|
|
26
|
-
needs: prepare
|
|
27
|
-
if: needs.prepare.outputs.push-for-tag != 'true'
|
|
28
|
-
|
|
29
|
-
continue-on-error: ${{ matrix.ruby.experimental }}
|
|
30
|
-
strategy:
|
|
31
|
-
fail-fast: false
|
|
32
|
-
max-parallel: 5
|
|
33
|
-
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
|
34
|
-
|
|
35
|
-
steps:
|
|
36
|
-
- uses: actions/checkout@v4
|
|
37
|
-
|
|
38
|
-
- uses: ruby/setup-ruby@v1
|
|
39
|
-
with:
|
|
40
|
-
ruby-version: ${{ matrix.ruby.version }}
|
|
41
|
-
rubygems: ${{ matrix.ruby.rubygems }}
|
|
42
|
-
bundler: ${{ env.BUNDLER_VER }}
|
|
43
|
-
bundler-cache: true
|
|
44
|
-
|
|
45
|
-
- run: bundle exec rspec
|
|
46
|
-
|
|
47
|
-
metanorma:
|
|
48
|
-
name: Test with Metanorma on Ruby ${{ matrix.ruby.version }} ${{ matrix.os }}
|
|
49
|
-
runs-on: ${{ matrix.os }}
|
|
50
|
-
|
|
51
|
-
needs: prepare
|
|
52
|
-
if: needs.prepare.outputs.push-for-tag != 'true'
|
|
53
|
-
|
|
54
|
-
continue-on-error: ${{ matrix.ruby.experimental }}
|
|
55
|
-
|
|
56
|
-
strategy:
|
|
57
|
-
fail-fast: false
|
|
58
|
-
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
|
59
|
-
|
|
60
|
-
steps:
|
|
61
|
-
- uses: actions/checkout@v4
|
|
62
|
-
with:
|
|
63
|
-
repository: metanorma/metanorma
|
|
64
|
-
|
|
65
|
-
- uses: metanorma/metanorma-build-scripts/inkscape-setup-action@main
|
|
66
|
-
|
|
67
|
-
- uses: actions/checkout@v4
|
|
68
|
-
with:
|
|
69
|
-
path: excavate
|
|
70
|
-
|
|
71
|
-
- uses: ruby/setup-ruby@v1
|
|
72
|
-
with:
|
|
73
|
-
ruby-version: ${{ matrix.ruby.version }}
|
|
74
|
-
rubygems: ${{ matrix.ruby.rubygems }}
|
|
75
|
-
bundler: ${{ env.BUNDLER_VER }}
|
|
76
|
-
bundler-cache: false
|
|
77
|
-
|
|
78
|
-
- name: Install excavate from source
|
|
79
|
-
run: |
|
|
80
|
-
cd excavate
|
|
81
|
-
bundle install
|
|
82
|
-
bundle exec rake install
|
|
83
|
-
|
|
84
|
-
- name: Install metanorma
|
|
85
|
-
run: bundle install
|
|
86
|
-
|
|
87
|
-
- run: bundle exec rake
|
|
88
|
-
|
|
89
|
-
release:
|
|
90
|
-
name: Release gem
|
|
91
|
-
needs: [ test, metanorma ]
|
|
92
|
-
runs-on: ubuntu-latest
|
|
93
|
-
if: contains(github.ref, 'refs/tags/v')
|
|
94
|
-
steps:
|
|
95
|
-
- uses: actions/checkout@v4
|
|
96
|
-
|
|
97
|
-
- uses: cadwallion/publish-rubygems-action@master
|
|
98
|
-
env:
|
|
99
|
-
RUBYGEMS_API_KEY: ${{secrets.FONTIST_CI_RUBYGEMS_API_KEY}}
|