excavate 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1abeba693c6993f1d1731afa0b576ccff3d28c8d1eda24230b8402f726b8201
4
- data.tar.gz: 0302c3d0d399ca6c74ed22db0e3fa4243cdefdf144cac1503252a803661cce83
3
+ metadata.gz: d9ea70e2de46706b2324d98253d9012d175a706fc010478a0ccb1b6af00fc02e
4
+ data.tar.gz: 51c3c8e44013e17201114f4315b4314d9f44dc645c41d3f8627f81353b0269b1
5
5
  SHA512:
6
- metadata.gz: 401d92de053763fb7a35e3b6baadc79b2b3367374dd89b1ade6feb4b80ebf5d2f2d04f6791cdb147bdc2d7b67bd9559a638c3346bc384c77df6e957a05401f76
7
- data.tar.gz: bcbf911c434f369257d3ab6d4bb2c92f4aee076f68e3aff7c43cc940cb6c16937b9c67879df9fd34e5b4998c2200cb5e0feb9ea84c934f546b942d6035965a31
6
+ metadata.gz: 4f1c1c62794e14c5272cb8c9881dd50a8be3992c446086c0a5f58cecf51100458f962ddaeec1d2d329a0d3e03cff328bb287a9183ab5315534ba203fb1758544
7
+ data.tar.gz: 79a3aac7b28480c4a1675b68877e21e192d311099f348aaebc84ae157d966e6bb56c2dffe53c25aeea331a08286aa7b34e8e92fd015cb8588514da8340092d85
data/README.adoc CHANGED
@@ -61,6 +61,13 @@ end
61
61
  $ excavate --recursive path/to/archive.cab
62
62
  ----
63
63
 
64
+ It supports recursive extraction of a directory containing archives:
65
+
66
+ [source,sh]
67
+ ----
68
+ $ excavate --recursive path/to/dir_with_archives
69
+ ----
70
+
64
71
  If you'd like to skip extraction of nested archives, just use:
65
72
 
66
73
  [source,sh]
@@ -68,6 +75,26 @@ If you'd like to skip extraction of nested archives, just use:
68
75
  $ excavate path/to/archive.cab
69
76
  ----
70
77
 
78
+ To extract a particular file or files specify them as last arguments:
79
+
80
+ [source,sh]
81
+ ----
82
+ $ excavate --recursive archive.cab file1 dir/file2
83
+ ----
84
+
85
+ Also `excavate` supports extraction from nested archives:
86
+
87
+ [source,sh]
88
+ ----
89
+ $ excavate --recursive archive.cab dir/nested.zip/file
90
+ ----
91
+
92
+ And filtering:
93
+
94
+ [source,sh]
95
+ ----
96
+ $ excavate archive.cab --filter "**/specialfile*.txt"
97
+ ----
71
98
 
72
99
  == Dependencies
73
100
 
@@ -1,5 +1,8 @@
1
1
  module Excavate
2
2
  class Archive
3
+ INVALID_MEMORY_MESSAGE =
4
+ "invalid memory read at address=0x0000000000000000".freeze
5
+
3
6
  TYPES = { "cab" => Extractors::CabExtractor,
4
7
  "cpio" => Extractors::CpioExtractor,
5
8
  "exe" => Extractors::SevenZipExtractor,
@@ -14,9 +17,10 @@ module Excavate
14
17
  @archive = archive
15
18
  end
16
19
 
17
- def files(recursive_packages: false)
20
+ def files(recursive_packages: false, files: [], filter: nil)
18
21
  target = Dir.mktmpdir
19
- extract(target, recursive_packages: recursive_packages)
22
+ extract(target, recursive_packages: recursive_packages,
23
+ files: files, filter: filter)
20
24
 
21
25
  all_files_in(target).map do |file|
22
26
  yield file
@@ -25,10 +29,105 @@ module Excavate
25
29
  FileUtils.rm_rf(target)
26
30
  end
27
31
 
28
- def extract(target = nil, recursive_packages: false)
32
+ def extract(target = nil,
33
+ recursive_packages: false,
34
+ files: [],
35
+ filter: nil)
36
+ if files.size.positive?
37
+ extract_particular_files(target, files,
38
+ recursive_packages: recursive_packages)
39
+ elsif filter
40
+ extract_by_filter(target, filter,
41
+ recursive_packages: recursive_packages)
42
+ else
43
+ extract_all(target, recursive_packages: recursive_packages)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def extract_particular_files(target, files, recursive_packages: false)
50
+ tmp = Dir.mktmpdir
51
+ extract_all(tmp, recursive_packages: recursive_packages)
52
+ found_files = find_files(tmp, files)
53
+ copy_files(found_files, target || Dir.pwd)
54
+ ensure
55
+ FileUtils.rm_rf(tmp)
56
+ end
57
+
58
+ def copy_files(files, target)
59
+ files.map do |file|
60
+ FileUtils.mkdir_p(target)
61
+ target_path = File.join(target, File.basename(file))
62
+ ensure_not_exist(target_path)
63
+
64
+ FileUtils.cp(file, target_path)
65
+
66
+ target_path
67
+ end
68
+ end
69
+
70
+ def ensure_not_exist(path)
71
+ if File.exist?(path)
72
+ type = File.directory?(path) ? "directory" : "file"
73
+ raise(TargetExistsError,
74
+ "Target #{type} `#{File.basename(path)}` already exists.")
75
+ end
76
+ end
77
+
78
+ def find_files(source, files)
79
+ all_files = all_files_in(source)
80
+
81
+ files.map do |target_file|
82
+ found_file = all_files.find do |source_file|
83
+ file_matches?(source_file, target_file, source)
84
+ end
85
+
86
+ unless found_file
87
+ raise(TargetNotFoundError, "File `#{target_file}` not found.")
88
+ end
89
+
90
+ found_file
91
+ end
92
+ end
93
+
94
+ def file_matches?(source_file, target_file, source_dir)
95
+ base_path(source_file, source_dir) == target_file
96
+ end
97
+
98
+ def base_path(path, prefix)
99
+ path.sub(prefix, "").sub(/^\//, "").sub(/^\\/, "")
100
+ end
101
+
102
+ def extract_by_filter(target, filter, recursive_packages: false)
103
+ tmp = Dir.mktmpdir
104
+ extract_all(tmp, recursive_packages: recursive_packages)
105
+ found_files = find_by_filter(tmp, filter)
106
+ copy_files(found_files, target || Dir.pwd)
107
+ end
108
+
109
+ def find_by_filter(source, filter)
110
+ all_files = all_files_in(source)
111
+
112
+ found_files = all_files.select do |source_file|
113
+ file_matches_filter?(source_file, filter, source)
114
+ end
115
+
116
+ if found_files.empty?
117
+ raise(TargetNotFoundError, "Filter `#{filter}` matched no file.")
118
+ end
119
+
120
+ found_files
121
+ end
122
+
123
+ def file_matches_filter?(source_file, filter, source_dir)
124
+ File.fnmatch?(filter, base_path(source_file, source_dir))
125
+ end
126
+
127
+ def extract_all(target, recursive_packages: false)
29
128
  source = File.expand_path(@archive)
30
129
  target ||= default_target(source)
31
- raise(TargetNotEmptyError, "Target directory `#{File.basename(target)}` is not empty.") unless Dir.empty?(target)
130
+ ensure_empty(target)
32
131
 
33
132
  if recursive_packages
34
133
  extract_recursively(source, target)
@@ -39,11 +138,16 @@ module Excavate
39
138
  target
40
139
  end
41
140
 
42
- private
141
+ def ensure_empty(path)
142
+ unless Dir.empty?(path)
143
+ raise(TargetNotEmptyError,
144
+ "Target directory `#{File.basename(path)}` is not empty.")
145
+ end
146
+ end
43
147
 
44
148
  def default_target(source)
45
149
  target = File.expand_path(File.basename(source, ".*"))
46
- raise(TargetExistsError, "Target directory `#{File.basename(target)}` already exists.") if File.exist?(target)
150
+ ensure_not_exist(target)
47
151
 
48
152
  FileUtils.mkdir(target)
49
153
 
@@ -51,7 +155,11 @@ module Excavate
51
155
  end
52
156
 
53
157
  def extract_recursively(archive, target)
54
- extract_once(archive, target)
158
+ if File.directory?(archive)
159
+ duplicate_dir(archive, target)
160
+ else
161
+ extract_once(archive, target)
162
+ end
55
163
 
56
164
  all_files_in(target).each do |file|
57
165
  next unless archive?(file)
@@ -60,14 +168,25 @@ module Excavate
60
168
  end
61
169
  end
62
170
 
171
+ def duplicate_dir(source, target)
172
+ Dir.chdir(source) do
173
+ (Dir.entries(".") - [".", ".."]).each do |entry|
174
+ FileUtils.cp_r(entry, target)
175
+ end
176
+ end
177
+ end
178
+
63
179
  def extract_once(archive, target)
64
180
  extension = normalized_extension(archive)
65
181
  extractor_class = TYPES[extension]
66
- raise(UnknownArchiveError, "Could not unarchive `#{archive}`.") unless extractor_class
182
+ unless extractor_class
183
+ raise(UnknownArchiveError, "Could not unarchive `#{archive}`.")
184
+ end
67
185
 
68
186
  extractor_class.new(archive).extract(target)
69
187
  rescue StandardError => e
70
- raise unless extension == "exe" && e.message.start_with?("Invalid file format")
188
+ raise unless extension == "exe" &&
189
+ e.message.start_with?("Invalid file format")
71
190
 
72
191
  Extractors::CabExtractor.new(archive).extract(target)
73
192
  end
@@ -81,7 +200,7 @@ module Excavate
81
200
  rescue FFI::NullPointerError => e
82
201
  FileUtils.rmdir(target)
83
202
  raise unless normalized_extension(archive) == "exe" &&
84
- e.message.start_with?("invalid memory read at address=0x0000000000000000")
203
+ e.message.start_with?(INVALID_MEMORY_MESSAGE)
85
204
  end
86
205
 
87
206
  def normalized_extension(file)
data/lib/excavate/cli.rb CHANGED
@@ -8,6 +8,13 @@ module Excavate
8
8
  STATUS_UNKNOWN_ERROR = 1
9
9
  STATUS_TARGET_EXISTS = 2
10
10
  STATUS_TARGET_NOT_EMPTY = 3
11
+ STATUS_TARGET_NOT_FOUND = 4
12
+
13
+ ERROR_TO_STATUS = {
14
+ TargetExistsError => STATUS_TARGET_EXISTS,
15
+ TargetNotEmptyError => STATUS_TARGET_NOT_EMPTY,
16
+ TargetNotFoundError => STATUS_TARGET_NOT_FOUND,
17
+ }.freeze
11
18
 
12
19
  def self.exit_on_failure?
13
20
  false
@@ -23,15 +30,22 @@ module Excavate
23
30
  super(args, config)
24
31
  end
25
32
 
26
- desc "extract ARCHIVE", "Extract ARCHIVE to a new directory"
27
- option :recursive, aliases: :r, type: :boolean, default: false, desc: "Also extract all nested archives."
28
- def extract(archive)
29
- target = Excavate::Archive.new(archive).extract(recursive_packages: options[:recursive])
30
- success("Successfully extracted to #{File.basename(target)}/")
31
- rescue TargetExistsError => e
32
- error(e.message, STATUS_TARGET_EXISTS)
33
- rescue TargetNotEmptyError => e
34
- error(e.message, STATUS_TARGET_NOT_EMPTY)
33
+ desc "extract ARCHIVE [FILE...]",
34
+ "Extract FILE or all files from ARCHIVE to a new directory"
35
+ option :recursive, aliases: :r, type: :boolean, default: false,
36
+ desc: "Also extract all nested archives."
37
+ option :filter, type: :string,
38
+ desc: "Filter by pattern (supports **, *, ?, etc)"
39
+ def extract(archive, *files)
40
+ target = Excavate::Archive.new(archive).extract(
41
+ recursive_packages: options[:recursive],
42
+ files: files,
43
+ filter: options[:filter],
44
+ )
45
+
46
+ success("Successfully extracted to #{format_paths(target)}")
47
+ rescue Error => e
48
+ handle_error(e)
35
49
  end
36
50
  default_task :extract
37
51
 
@@ -42,9 +56,24 @@ module Excavate
42
56
  STATUS_SUCCESS
43
57
  end
44
58
 
59
+ def handle_error(exception)
60
+ status = ERROR_TO_STATUS[exception.class]
61
+ raise exception unless status
62
+
63
+ error(exception.message, status)
64
+ end
65
+
45
66
  def error(message, status)
46
67
  say(message, :red)
47
68
  status
48
69
  end
70
+
71
+ def format_paths(path_or_paths)
72
+ paths = Array(path_or_paths).map do |x|
73
+ File.directory?(x) ? "#{File.basename(x)}/" : File.basename(x)
74
+ end
75
+
76
+ paths.join(", ")
77
+ end
49
78
  end
50
79
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Excavate
4
- VERSION = "0.2.5"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/excavate.rb CHANGED
@@ -8,7 +8,12 @@ require_relative "excavate/utils"
8
8
 
9
9
  module Excavate
10
10
  class Error < StandardError; end
11
- class UnknownArchiveError < Error; end
11
+
12
12
  class TargetExistsError < Error; end
13
+
13
14
  class TargetNotEmptyError < Error; end
15
+
16
+ class TargetNotFoundError < Error; end
17
+
18
+ class UnknownArchiveError < Error; end
14
19
  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.2.5
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-21 00:00:00.000000000 Z
11
+ date: 2021-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: arr-pm