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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e798e50f0ea2651772541fab0975af5632d72b63a0481340e1538e9b0cf6c94
4
- data.tar.gz: fb9c5824456bf432d6621e996b3c4ac3dfed0761bb1716f4448cea2843ed4913
3
+ metadata.gz: 1355dad6341565ebea3901156ac04793db80b02eb1057ccb1c94672dddd639af
4
+ data.tar.gz: 308b7b43fa583c7c98a812f7f165c9c455ede142d35250ffc213370c9a10eaf9
5
5
  SHA512:
6
- metadata.gz: 1d0cd4330728c279ddee2777134d9a9a80b047f91ab6c467a0c538e2d556231e6d6bab3f3c897358a687255f20406a4f1c22ad17cac75110ac6f72a5fbe107f1
7
- data.tar.gz: 8e761a93f1bfcbdaaa7fda3ceb1498d9d2c4b5c87d8d37a939b0f46048020e3a9508702af0fc0e91509224d97dbdf51b799db9814f2380bb79101490cebffb91
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
- - 'https://raw.githubusercontent.com/fontist/oss-guides/master/ci/rubocop.yml'
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
- - 'pkg/**/*'
10
-
11
- Gemspec/RequireMFA:
12
- Enabled: false
10
+ - 'lib/excavate/extractors/cpio/cpio_old_format.rb'
11
+ - 'vendor/**/*'
13
12
 
14
- Layout/LineLength:
15
- Max: 160
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
@@ -3,3 +3,10 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
+
7
+ gem "openssl"
8
+ gem "rspec"
9
+ gem "rubocop"
10
+ gem "rubocop-performance"
11
+ gem "rubocop-rake"
12
+ gem "rubocop-rspec"
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/test-and-release/badge.svg["Build Status", link="https://github.com/fontist/excavate/actions?workflow=test-and-release"]
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
- = Excavate: Ruby gem to extract nested archives
15
+ == Features
6
16
 
7
- Extract nested archives with a single command.
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
- $ bundle install
113
+ bundle install
24
114
  ----
25
115
 
26
116
  Or install it yourself as:
27
117
 
28
118
  [source,sh]
29
119
  ----
30
- $ gem install excavate
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
- == Usage
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
- To extract an archive containing other archives inside:
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
- Excavate::Archive.new("path/to/archive.cab").extract(target, recursive_packages: true)
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
- The same but allowing to choose only necessary files inside:
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
- Excavate::Archive.new("path/to/archive.cab").files(recursive_packages: true) do |path|
50
- FileUtils.mv(path, target) if path.end_with?(".txt")
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
- == CLI
285
+ [[selective-extraction]]
286
+ == Selective extraction
56
287
 
57
- `excavate` can be used via command line. To extract an archive including the nested ones:
288
+ === General
58
289
 
59
- [source,sh]
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
- $ excavate --recursive path/to/archive.cab
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
- It supports recursive extraction of a directory containing archives:
305
+ Where,
65
306
 
66
- [source,sh]
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
- $ excavate --recursive path/to/dir_with_archives
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
- If you'd like to skip extraction of nested archives, just use:
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
- $ excavate path/to/archive.cab
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
- To extract a particular file or files specify them as last arguments:
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
- $ excavate --recursive archive.cab file1 dir/file2
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
- Also `excavate` supports extraction from nested archives:
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
- $ excavate --recursive archive.cab dir/nested.zip/file
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
- And filtering:
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
- $ excavate archive.cab --filter "**/specialfile*.txt"
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
- Depends on
102
- https://github.com/fontist/ffi-libarchive-binary[ffi-libarchive-binary] which
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
- We are following Sandi Metz's Rules for this gem, you can read the
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
- rules. If you make changes in a pre-existing file that violates these rules you
118
- should fix the violations as part of your contribution.
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 Action. Just bump and tag with `gem-release`.
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,ruby]
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,ruby]
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 https://www.ribose.com[Ribose Inc.] the
144
- right to grant or transfer an unlimited number of non exclusive licenses or
145
- sub-licenses to third parties, under the copyright covering the contribution
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
- 1. Open an https://github.com/fontist/excavate/issues[issue] to discuss a new feature.
151
- 1. Write tests to support your new feature.
152
- 1. Make sure the entire test suite passes locally and on CI.
153
- 1. Open a Pull Request.
154
- 1. https://github.com/thoughtbot/guides/tree/master/protocol/git#write-a-feature[Squash your commits]
155
- after receiving feedback.
156
- 1. Party!
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 https://www.ribose.com/[Ribose Inc.]
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.add_runtime_dependency "arr-pm", "~> 0.0"
35
- spec.add_runtime_dependency "bundler", "~> 2.3", ">= 2.3.24"
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.add_runtime_dependency "ffi-compiler2", ">= 2.2.2"
38
- spec.add_runtime_dependency "ffi-libarchive-binary", "~> 0.3"
39
- spec.add_runtime_dependency "libmspack", "~> 0.1"
40
- spec.add_runtime_dependency "ruby-ole", "~> 1.0"
41
- spec.add_runtime_dependency "rubyzip", "~> 2.3"
42
- spec.add_runtime_dependency "seven-zip", "~> 1.4"
43
- spec.add_runtime_dependency "thor", "~> 1.0"
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
@@ -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
- "pkg" => Extractors::XarExtractor,
14
+ "xz" => Extractors::XzExtractor,
14
15
  "zip" => Extractors::ZipExtractor }.freeze
15
16
 
16
17
  def initialize(archive)
@@ -50,7 +50,7 @@ module Excavate
50
50
  name.gsub!(/^.*(\\|\/)/, "")
51
51
 
52
52
  # Strip out the non-ascii character
53
- name.gsub!(/[^0-9A-Za-z.\-]/, "_")
53
+ name.gsub!(/[^0-9A-Za-z.-]/, "_")
54
54
  end
55
55
  end
56
56
 
@@ -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
@@ -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"
@@ -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, 2)
18
- when "\x1F\x8B".force_encoding("BINARY")
19
- :gzip
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
@@ -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"] =~ /mswin|mingw/ ? "NUL:" : "/dev/null") # rubocop:disable Performance/RegexpMatch, Metrics/LineLength
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Excavate
4
- VERSION = "0.3.7"
4
+ VERSION = "0.3.8"
5
5
  end
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.7
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: 2024-02-13 00:00:00.000000000 Z
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/test-and-release.yml"
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.3
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}}