ruzip 0.2.1 → 0.3.0
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/.gitignore +2 -0
- data/.gitlab-ci.yml +7 -7
- data/CHANGELOG.md +8 -0
- data/README.md +7 -0
- data/Steepfile +37 -0
- data/ext/Cargo.lock +15 -15
- data/ext/Cargo.toml +1 -1
- data/ext/src/lib.rs +257 -37
- data/rbs_collection.yaml +20 -0
- data/ruzip.gemspec +2 -1
- data/sig/ruzip.rbs +18 -4
- data/sig/test/archive-zip.rbs +24 -0
- data/sig/test/gem.rbs +12 -0
- data/sig/test/io-like.rbs +18 -0
- data/sig/test/test-unit.rbs +28 -0
- data/sig/test/test.rbs +26 -0
- data/test/helper.rb +3 -0
- data/test/test_archive.rb +1 -0
- data/test/test_file.rb +39 -0
- data/test/test_writer.rb +185 -3
- metadata +23 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1c9e169da26aa71bb1aee7beb83cc3bd294280c915eb9fe326cce1de07cf4b4
|
|
4
|
+
data.tar.gz: 155882217b16ff068dcc669c3a1f0eb6b30a6d9ec6dcd70ce81f85b8c5de73d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e2f24eccfb60df789063186779cb9f785024e6dd42bbb05f38f85e33c9b0447a2afcac17fec92f842d93acc0fa7903663bd492fc0e78da6c0a12cbbf7e7b72f0
|
|
7
|
+
data.tar.gz: a1c5603e029df78f7b88683d6b3139c8755cb874a0ad5b8a6deff3642754946a15014800afdb8167c3ad9d66c166985845a9fb119bf3f17caa7562a360f38316
|
data/.gitignore
CHANGED
data/.gitlab-ci.yml
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
image: ruby
|
|
3
|
-
|
|
1
|
+
test:
|
|
2
|
+
image: ruby:$RUBY_VERSION
|
|
3
|
+
variables:
|
|
4
|
+
RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true'
|
|
4
5
|
before_script:
|
|
5
6
|
- apt-get update && apt-get install -y clang
|
|
6
7
|
- curl https://sh.rustup.rs -sSf | sh -s -- -y
|
|
7
8
|
- . "$HOME/.cargo/env"
|
|
8
9
|
- gem update --system
|
|
9
10
|
- bundle install
|
|
10
|
-
|
|
11
|
-
test:
|
|
12
|
-
variables:
|
|
13
|
-
RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true'
|
|
14
11
|
script:
|
|
15
12
|
- bundle exec rake test
|
|
13
|
+
parallel:
|
|
14
|
+
matrix:
|
|
15
|
+
- RUBY_VERSION: ["3.2", "3.3", "3.4", "4.0"]
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [0.3.0] - 2026-10-01
|
|
2
|
+
|
|
3
|
+
* `RuZip::File#name` returns UTF-8 or ASCII-8BIT string
|
|
4
|
+
* `Ruzip::Writer.new` accepts mode argument
|
|
5
|
+
* `RuZip::Writer#start_file` accepts keyword arguments: `compression_method`, `compression_level`, `last_modified_time`
|
|
6
|
+
* `RuZip::Writer#raw_copy_file` is added
|
|
7
|
+
* `RuZip::Writer#append_file` is added
|
|
8
|
+
|
|
1
9
|
## [0.2.1] - 2026-09-18
|
|
2
10
|
|
|
3
11
|
* Update crates
|
data/README.md
CHANGED
|
@@ -30,6 +30,13 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
30
30
|
|
|
31
31
|
archive.by_name("path/in/zip/archive") # => RuZip::File
|
|
32
32
|
|
|
33
|
+
writer = RuZip::Writer.new("path/to/output.zip")
|
|
34
|
+
["content1", "content2", "content3"].each_with_index do |content, i|
|
|
35
|
+
writer.start_file "inner/path/to/#{i}.txt"
|
|
36
|
+
writer.write content
|
|
37
|
+
end
|
|
38
|
+
writer.finish
|
|
39
|
+
|
|
33
40
|
## Development
|
|
34
41
|
|
|
35
42
|
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bundle exec rake console` for an interactive prompt that will allow you to experiment.
|
data/Steepfile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
D = Steep::Diagnostic
|
|
2
|
+
#
|
|
3
|
+
target :lib do
|
|
4
|
+
signature "sig"
|
|
5
|
+
ignore_signature "sig/test"
|
|
6
|
+
|
|
7
|
+
check "lib" # Directory name
|
|
8
|
+
# check "path/to/source.rb" # File name
|
|
9
|
+
# check "app/models/**/*.rb" # Glob
|
|
10
|
+
# ignore "lib/templates/*.rb"
|
|
11
|
+
|
|
12
|
+
# library "pathname" # Standard libraries
|
|
13
|
+
# library "strong_json" # Gems
|
|
14
|
+
|
|
15
|
+
# configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default)
|
|
16
|
+
# configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
|
|
17
|
+
# configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
|
|
18
|
+
# configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting
|
|
19
|
+
# configure_code_diagnostics do |hash| # You can setup everything yourself
|
|
20
|
+
# hash[D::Ruby::NoMethod] = :information
|
|
21
|
+
# end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
target :test do
|
|
25
|
+
unreferenced! # Skip type checking the `lib` code when types in `test` target is changed
|
|
26
|
+
signature "sig/test" # Put RBS files for tests under `sig/test`
|
|
27
|
+
check "test" # Type check Ruby scripts under `test`
|
|
28
|
+
|
|
29
|
+
# configure_code_diagnostics(D::Ruby.lenient) # Weak type checking for test code
|
|
30
|
+
configure_code_diagnostics do |hash|
|
|
31
|
+
hash[D::Ruby::MethodDefinitionMissing] = :warning
|
|
32
|
+
hash[D::Ruby::UnknownConstant] = :information
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
library "tmpdir", "tempfile", "fileutils" # Standard libraries
|
|
36
|
+
library "rubyzip"
|
|
37
|
+
end
|
data/ext/Cargo.lock
CHANGED
|
@@ -73,9 +73,9 @@ dependencies = [
|
|
|
73
73
|
|
|
74
74
|
[[package]]
|
|
75
75
|
name = "cc"
|
|
76
|
-
version = "1.
|
|
76
|
+
version = "1.5.1"
|
|
77
77
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
78
|
-
checksum = "
|
|
78
|
+
checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040"
|
|
79
79
|
dependencies = [
|
|
80
80
|
"find-msvc-tools",
|
|
81
81
|
"jobserver",
|
|
@@ -218,9 +218,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
|
218
218
|
|
|
219
219
|
[[package]]
|
|
220
220
|
name = "find-msvc-tools"
|
|
221
|
-
version = "0.1.
|
|
221
|
+
version = "0.1.14"
|
|
222
222
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
223
|
-
checksum = "
|
|
223
|
+
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
|
224
224
|
|
|
225
225
|
[[package]]
|
|
226
226
|
name = "flate2"
|
|
@@ -338,9 +338,9 @@ dependencies = [
|
|
|
338
338
|
|
|
339
339
|
[[package]]
|
|
340
340
|
name = "js-sys"
|
|
341
|
-
version = "0.3.
|
|
341
|
+
version = "0.3.106"
|
|
342
342
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
343
|
-
checksum = "
|
|
343
|
+
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
|
344
344
|
dependencies = [
|
|
345
345
|
"cfg-if",
|
|
346
346
|
"futures-util",
|
|
@@ -578,7 +578,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|
|
578
578
|
|
|
579
579
|
[[package]]
|
|
580
580
|
name = "ruzip"
|
|
581
|
-
version = "0.
|
|
581
|
+
version = "0.3.0"
|
|
582
582
|
dependencies = [
|
|
583
583
|
"magnus",
|
|
584
584
|
"rb-sys",
|
|
@@ -725,9 +725,9 @@ checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
|
|
725
725
|
|
|
726
726
|
[[package]]
|
|
727
727
|
name = "wasm-bindgen"
|
|
728
|
-
version = "0.2.
|
|
728
|
+
version = "0.2.129"
|
|
729
729
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
730
|
-
checksum = "
|
|
730
|
+
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
|
731
731
|
dependencies = [
|
|
732
732
|
"cfg-if",
|
|
733
733
|
"once_cell",
|
|
@@ -738,9 +738,9 @@ dependencies = [
|
|
|
738
738
|
|
|
739
739
|
[[package]]
|
|
740
740
|
name = "wasm-bindgen-macro"
|
|
741
|
-
version = "0.2.
|
|
741
|
+
version = "0.2.129"
|
|
742
742
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
743
|
-
checksum = "
|
|
743
|
+
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
|
744
744
|
dependencies = [
|
|
745
745
|
"quote",
|
|
746
746
|
"wasm-bindgen-macro-support",
|
|
@@ -748,9 +748,9 @@ dependencies = [
|
|
|
748
748
|
|
|
749
749
|
[[package]]
|
|
750
750
|
name = "wasm-bindgen-macro-support"
|
|
751
|
-
version = "0.2.
|
|
751
|
+
version = "0.2.129"
|
|
752
752
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
753
|
-
checksum = "
|
|
753
|
+
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
|
754
754
|
dependencies = [
|
|
755
755
|
"bumpalo",
|
|
756
756
|
"proc-macro2",
|
|
@@ -761,9 +761,9 @@ dependencies = [
|
|
|
761
761
|
|
|
762
762
|
[[package]]
|
|
763
763
|
name = "wasm-bindgen-shared"
|
|
764
|
-
version = "0.2.
|
|
764
|
+
version = "0.2.129"
|
|
765
765
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
766
|
-
checksum = "
|
|
766
|
+
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
|
767
767
|
dependencies = [
|
|
768
768
|
"unicode-ident",
|
|
769
769
|
]
|
data/ext/Cargo.toml
CHANGED
data/ext/src/lib.rs
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
use magnus::{
|
|
2
|
-
|
|
1
|
+
use magnus::{
|
|
2
|
+
Error, Integer, RHash, RString, Ruby, Symbol, Time, Value, function, method,
|
|
3
|
+
prelude::*,
|
|
4
|
+
scan_args::{KwArgs, get_kwargs, scan_args},
|
|
5
|
+
typed_data::Obj,
|
|
6
|
+
};
|
|
7
|
+
use std::cell::{RefCell, RefMut};
|
|
3
8
|
use std::fs;
|
|
4
9
|
use std::io::{Read, Write};
|
|
5
|
-
use std::
|
|
10
|
+
use std::str;
|
|
6
11
|
use std::sync::{Arc, Mutex};
|
|
7
12
|
use zip::{ZipArchive, ZipWriter};
|
|
8
13
|
|
|
@@ -15,12 +20,27 @@ fn map_err(err: impl std::error::Error, ruby: &Ruby) -> Error {
|
|
|
15
20
|
#[magnus::wrap(class = "RuZip::Archive", free_immediately, size)]
|
|
16
21
|
struct Archive(Arc<Mutex<ZipArchive<fs::File>>>);
|
|
17
22
|
|
|
23
|
+
#[cfg(windows)]
|
|
24
|
+
fn file_from_io(io: Value, ruby: &Ruby) -> Result<fs::File> {
|
|
25
|
+
todo!();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[cfg(not(windows))]
|
|
29
|
+
fn file_from_io(io: Value, ruby: &Ruby) -> Result<fs::File> {
|
|
30
|
+
use std::os::fd::BorrowedFd;
|
|
31
|
+
|
|
32
|
+
let fileno: Integer = io.funcall_public("fileno", ())?;
|
|
33
|
+
let raw_fd = fileno.to_i32()?;
|
|
34
|
+
let fd = unsafe { BorrowedFd::borrow_raw(raw_fd) }
|
|
35
|
+
.try_clone_to_owned()
|
|
36
|
+
.map_err(|e| map_err(e, ruby))?;
|
|
37
|
+
Ok(fs::File::from(fd))
|
|
38
|
+
}
|
|
39
|
+
|
|
18
40
|
impl Archive {
|
|
19
41
|
fn new(ruby: &Ruby, r_io: Value) -> Result<Self> {
|
|
20
42
|
let io = if r_io.is_kind_of(ruby.class_io()) {
|
|
21
|
-
|
|
22
|
-
let raw_fd = fileno.to_i32()?;
|
|
23
|
-
unsafe { fs::File::from_raw_fd(raw_fd) }
|
|
43
|
+
file_from_io(r_io, ruby)?
|
|
24
44
|
} else if r_io.respond_to("to_path", false)? {
|
|
25
45
|
fs::File::open(
|
|
26
46
|
r_io.funcall_public::<&str, (), RString>("to_path", ())?
|
|
@@ -58,8 +78,8 @@ impl Archive {
|
|
|
58
78
|
}
|
|
59
79
|
}
|
|
60
80
|
|
|
61
|
-
fn by_name(ruby: &Ruby, rb_self: &Self, name:
|
|
62
|
-
let name_bytes = name.
|
|
81
|
+
fn by_name(ruby: &Ruby, rb_self: &Self, name: RString) -> Result<Option<File>> {
|
|
82
|
+
let name_bytes = unsafe { name.as_slice() };
|
|
63
83
|
let mut archive = rb_self.0.lock().map_err(|e| map_err(e, ruby))?;
|
|
64
84
|
// TODO: Cache entries
|
|
65
85
|
for i in 0..archive.len() {
|
|
@@ -76,18 +96,14 @@ impl Archive {
|
|
|
76
96
|
struct File(Arc<Mutex<ZipArchive<fs::File>>>, usize);
|
|
77
97
|
|
|
78
98
|
impl File {
|
|
79
|
-
fn name(ruby: &Ruby, rb_self: &Self) -> Result<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.name_raw()
|
|
88
|
-
.into(),
|
|
89
|
-
)
|
|
90
|
-
.map_err(|e| map_err(e, ruby))
|
|
99
|
+
fn name(ruby: &Ruby, rb_self: &Self) -> Result<RString> {
|
|
100
|
+
let mut archive = rb_self.0.lock().map_err(|e| map_err(e, ruby))?;
|
|
101
|
+
let file = archive.by_index(rb_self.1).map_err(|e| map_err(e, ruby))?;
|
|
102
|
+
let bytes = file.name_raw();
|
|
103
|
+
Ok(match str::from_utf8(bytes) {
|
|
104
|
+
Ok(s) => ruby.str_new(s),
|
|
105
|
+
Err(_) => ruby.str_from_slice(bytes),
|
|
106
|
+
})
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
fn size(ruby: &Ruby, rb_self: &Self) -> Result<u64> {
|
|
@@ -135,10 +151,122 @@ impl File {
|
|
|
135
151
|
#[magnus::wrap(class = "RuZip::Writer", free_immediately, size)]
|
|
136
152
|
struct Writer(RefCell<Option<ZipWriter<fs::File>>>);
|
|
137
153
|
|
|
154
|
+
enum WriterMode {
|
|
155
|
+
Write,
|
|
156
|
+
Append,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
impl TryFrom<(&str, &Ruby)> for WriterMode {
|
|
160
|
+
type Error = Error;
|
|
161
|
+
|
|
162
|
+
fn try_from(value: (&str, &Ruby)) -> Result<Self> {
|
|
163
|
+
let (mode, ruby) = value;
|
|
164
|
+
Ok(match mode {
|
|
165
|
+
"w" => WriterMode::Write,
|
|
166
|
+
"a" => WriterMode::Append,
|
|
167
|
+
_ => {
|
|
168
|
+
return Err(Error::new(
|
|
169
|
+
ruby.exception_runtime_error(),
|
|
170
|
+
format!("Unsupported mode: {mode}"),
|
|
171
|
+
));
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fn compression_method_from_symbol(sym: Symbol, ruby: &Ruby) -> Result<zip::CompressionMethod> {
|
|
178
|
+
use zip::CompressionMethod::*;
|
|
179
|
+
Ok(match sym.name()?.as_ref() {
|
|
180
|
+
"stored" => Stored,
|
|
181
|
+
"deflated" => Deflated,
|
|
182
|
+
"deflated64" => Deflate64,
|
|
183
|
+
"bzip2" => Bzip2,
|
|
184
|
+
"aes" => Aes,
|
|
185
|
+
"zstd" => Zstd,
|
|
186
|
+
"lzma" => Lzma,
|
|
187
|
+
"xz" => Xz,
|
|
188
|
+
"ppmd" => Ppmd,
|
|
189
|
+
_ => {
|
|
190
|
+
return Err(Error::new(
|
|
191
|
+
ruby.exception_arg_error(),
|
|
192
|
+
format!("Unsupported compression method: {}", sym),
|
|
193
|
+
));
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fn date_time_from_time(time: Time, ruby: &Ruby) -> Result<zip::DateTime> {
|
|
199
|
+
zip::DateTime::from_date_and_time(
|
|
200
|
+
time.funcall("year", ())?,
|
|
201
|
+
time.funcall("month", ())?,
|
|
202
|
+
time.funcall("day", ())?,
|
|
203
|
+
time.funcall("hour", ())?,
|
|
204
|
+
time.funcall("min", ())?,
|
|
205
|
+
time.funcall("sec", ())?,
|
|
206
|
+
)
|
|
207
|
+
.map_err(|e| map_err(e, ruby))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// TODO: Move to Grey Knights
|
|
211
|
+
macro_rules! opop_t {
|
|
212
|
+
($($type:ty),+) => { ($(Option<Option<$type>>),+) }
|
|
213
|
+
}
|
|
214
|
+
macro_rules! opt_t {
|
|
215
|
+
($($type:ty),+) => { ($(Option<$type>),+) };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type StartFileKwArgsOptionalInput = opop_t!(Symbol, i64, Time);
|
|
219
|
+
type StartFileKwArgsOptionalOutput = opt_t!(Symbol, i64, Time);
|
|
220
|
+
type StartFileKwArgs = KwArgs<(), StartFileKwArgsOptionalOutput, ()>;
|
|
221
|
+
static START_FILE_KW_ARGS_NAME_OPTIONAL: &[&str] = &[
|
|
222
|
+
"compression_method",
|
|
223
|
+
"compression_level",
|
|
224
|
+
"last_modified_time",
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
fn flatten_optional_keywords(
|
|
228
|
+
optional: StartFileKwArgsOptionalInput,
|
|
229
|
+
) -> StartFileKwArgsOptionalOutput {
|
|
230
|
+
(
|
|
231
|
+
optional.0.flatten(),
|
|
232
|
+
optional.1.flatten(),
|
|
233
|
+
optional.2.flatten(),
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
fn get_start_file_kwargs(kw: RHash) -> Result<StartFileKwArgs> {
|
|
238
|
+
let kwargs = get_kwargs::<_, (), StartFileKwArgsOptionalInput, ()>(
|
|
239
|
+
kw,
|
|
240
|
+
&[],
|
|
241
|
+
START_FILE_KW_ARGS_NAME_OPTIONAL,
|
|
242
|
+
)?;
|
|
243
|
+
Ok(KwArgs {
|
|
244
|
+
required: kwargs.required,
|
|
245
|
+
optional: flatten_optional_keywords(kwargs.optional),
|
|
246
|
+
splat: kwargs.splat,
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
|
|
138
250
|
impl Writer {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
251
|
+
// TODO: Accept arg the same to Archive.new
|
|
252
|
+
// TODO: Accept block
|
|
253
|
+
fn new(ruby: &Ruby, args: &[Value]) -> Result<Self> {
|
|
254
|
+
let args = scan_args::<(String,), (Option<String>,), (), (), (), ()>(args)?;
|
|
255
|
+
let path = args.required.0;
|
|
256
|
+
let mode = args.optional.0;
|
|
257
|
+
let mut options = fs::OpenOptions::new();
|
|
258
|
+
let writer_mode = (mode.as_deref().unwrap_or("w"), ruby).try_into()?;
|
|
259
|
+
let file = match writer_mode {
|
|
260
|
+
WriterMode::Write => options.create(true),
|
|
261
|
+
WriterMode::Append => options.read(true),
|
|
262
|
+
}
|
|
263
|
+
.write(true)
|
|
264
|
+
.open(path)
|
|
265
|
+
.map_err(|e| map_err(e, ruby))?;
|
|
266
|
+
let writer = match writer_mode {
|
|
267
|
+
WriterMode::Write => ZipWriter::new(file),
|
|
268
|
+
WriterMode::Append => ZipWriter::new_append(file).map_err(|e| map_err(e, ruby))?,
|
|
269
|
+
};
|
|
142
270
|
Ok(Self(RefCell::new(Some(writer))))
|
|
143
271
|
}
|
|
144
272
|
|
|
@@ -157,25 +285,115 @@ impl Writer {
|
|
|
157
285
|
}
|
|
158
286
|
}
|
|
159
287
|
|
|
160
|
-
fn start_file(ruby: &Ruby, rb_self: &Self,
|
|
161
|
-
let
|
|
162
|
-
let
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
288
|
+
fn start_file(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<()> {
|
|
289
|
+
let args = scan_args::<(String,), (), (), (), _, ()>(args)?;
|
|
290
|
+
let name = args.required.0;
|
|
291
|
+
let (compression_method, compression_level, last_modified_time) =
|
|
292
|
+
get_start_file_kwargs(args.keywords)?.optional;
|
|
293
|
+
|
|
294
|
+
let mut option =
|
|
295
|
+
zip::write::SimpleFileOptions::default().compression_level(compression_level);
|
|
296
|
+
if let Some(method) = compression_method {
|
|
297
|
+
option = option.compression_method(compression_method_from_symbol(method, ruby)?);
|
|
298
|
+
}
|
|
299
|
+
if let Some(mod_time) = last_modified_time {
|
|
300
|
+
option = option.last_modified_time(date_time_from_time(mod_time, ruby)?);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
Self::writer(ruby, rb_self)?
|
|
304
|
+
.start_file(name, option)
|
|
167
305
|
.map_err(|e| map_err(e, ruby))?;
|
|
168
306
|
Ok(())
|
|
169
307
|
}
|
|
170
308
|
|
|
171
|
-
fn write(ruby: &Ruby, rb_self: &Self, buf:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "Already finished"))?;
|
|
175
|
-
writer.write_all(buf.as_bytes())
|
|
309
|
+
fn write(ruby: &Ruby, rb_self: &Self, buf: RString) -> Result<()> {
|
|
310
|
+
Self::writer(ruby, rb_self)?
|
|
311
|
+
.write_all(unsafe { buf.as_slice() })
|
|
176
312
|
.map_err(|e| map_err(e, ruby))?;
|
|
177
313
|
Ok(())
|
|
178
314
|
}
|
|
315
|
+
|
|
316
|
+
fn raw_copy_file(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<()> {
|
|
317
|
+
let args = scan_args::<(Obj<File>,), (), (), (), _, ()>(args)?;
|
|
318
|
+
let file = &*args.required.0;
|
|
319
|
+
let kwargs = get_kwargs::<
|
|
320
|
+
_,
|
|
321
|
+
(),
|
|
322
|
+
(
|
|
323
|
+
Option<Option<String>>,
|
|
324
|
+
Option<Option<Time>>,
|
|
325
|
+
Option<Option<u32>>,
|
|
326
|
+
),
|
|
327
|
+
(),
|
|
328
|
+
>(
|
|
329
|
+
args.keywords,
|
|
330
|
+
&[],
|
|
331
|
+
&["name", "last_modified_time", "unix_mode"],
|
|
332
|
+
)?;
|
|
333
|
+
let name = kwargs.optional.0.flatten();
|
|
334
|
+
let last_modified_time = kwargs.optional.1.flatten();
|
|
335
|
+
let unix_mode = kwargs.optional.2.flatten();
|
|
336
|
+
if last_modified_time.is_none() && unix_mode.is_some() {
|
|
337
|
+
return Err(Error::new(
|
|
338
|
+
ruby.exception_arg_error(),
|
|
339
|
+
"last_modified_time is nil but unix_mode is passed",
|
|
340
|
+
));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let mut writer = Self::writer(ruby, rb_self)?;
|
|
344
|
+
let mut archive = file.0.lock().map_err(|e| map_err(e, ruby))?;
|
|
345
|
+
let zip_file = archive.by_index(file.1).map_err(|e| map_err(e, ruby))?;
|
|
346
|
+
match (name, last_modified_time) {
|
|
347
|
+
(Some(_), Some(_)) => {
|
|
348
|
+
return Err(Error::new(
|
|
349
|
+
ruby.exception_arg_error(),
|
|
350
|
+
"name and last_modified_time cannot be passed at the same time",
|
|
351
|
+
));
|
|
352
|
+
}
|
|
353
|
+
(Some(n), None) => writer.raw_copy_file_rename(zip_file, n),
|
|
354
|
+
(None, Some(t)) => {
|
|
355
|
+
let mtime = date_time_from_time(t, ruby)?;
|
|
356
|
+
writer.raw_copy_file_touch(zip_file, mtime, unix_mode)
|
|
357
|
+
}
|
|
358
|
+
(None, None) => writer.raw_copy_file(zip_file),
|
|
359
|
+
}
|
|
360
|
+
.map_err(|e| map_err(e, ruby))?;
|
|
361
|
+
Ok(())
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
fn append_file(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<()> {
|
|
365
|
+
let args = scan_args::<(String, RString), (), (), (), _, ()>(args)?;
|
|
366
|
+
let (name, buf) = args.required;
|
|
367
|
+
let (compression_method, compression_level, last_modified_time) =
|
|
368
|
+
get_start_file_kwargs(args.keywords)?.optional;
|
|
369
|
+
|
|
370
|
+
let mut option =
|
|
371
|
+
zip::write::SimpleFileOptions::default().compression_level(compression_level);
|
|
372
|
+
if let Some(method) = compression_method {
|
|
373
|
+
option = option.compression_method(compression_method_from_symbol(method, ruby)?);
|
|
374
|
+
}
|
|
375
|
+
if let Some(mod_time) = last_modified_time {
|
|
376
|
+
option = option.last_modified_time(date_time_from_time(mod_time, ruby)?);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
let mut writer = Self::writer(ruby, rb_self)?;
|
|
380
|
+
writer
|
|
381
|
+
.start_file(name, option)
|
|
382
|
+
.map_err(|e| map_err(e, ruby))?;
|
|
383
|
+
writer
|
|
384
|
+
.write_all(unsafe { buf.as_slice() })
|
|
385
|
+
.map_err(|e| map_err(e, ruby))?;
|
|
386
|
+
Ok(())
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
fn writer<'a>(ruby: &Ruby, rb_self: &'a Self) -> Result<RefMut<'a, ZipWriter<fs::File>>> {
|
|
390
|
+
let writer = rb_self
|
|
391
|
+
.0
|
|
392
|
+
.try_borrow_mut()
|
|
393
|
+
.map_err(|_| Error::new(ruby.exception_runtime_error(), "Already finished"))?;
|
|
394
|
+
RefMut::filter_map(writer, Option::as_mut)
|
|
395
|
+
.map_err(|_| Error::new(ruby.exception_runtime_error(), "Already finished"))
|
|
396
|
+
}
|
|
179
397
|
}
|
|
180
398
|
|
|
181
399
|
#[magnus::init]
|
|
@@ -195,10 +413,12 @@ fn init(ruby: &Ruby) -> Result<()> {
|
|
|
195
413
|
file_class.define_method("read", method!(File::read, 0))?;
|
|
196
414
|
|
|
197
415
|
let writer_class = module.define_class("Writer", ruby.class_object())?;
|
|
198
|
-
writer_class.define_singleton_method("new", function!(Writer::new, 1))?;
|
|
416
|
+
writer_class.define_singleton_method("new", function!(Writer::new, -1))?;
|
|
199
417
|
writer_class.define_method("finish", method!(Writer::finish, 0))?;
|
|
200
|
-
writer_class.define_method("start_file", method!(Writer::start_file, 1))?;
|
|
418
|
+
writer_class.define_method("start_file", method!(Writer::start_file, -1))?;
|
|
201
419
|
writer_class.define_method("write", method!(Writer::write, 1))?;
|
|
420
|
+
writer_class.define_method("raw_copy_file", method!(Writer::raw_copy_file, -1))?;
|
|
421
|
+
writer_class.define_method("append_file", method!(Writer::append_file, -1))?;
|
|
202
422
|
|
|
203
423
|
Ok(())
|
|
204
424
|
}
|
data/rbs_collection.yaml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Download sources
|
|
2
|
+
sources:
|
|
3
|
+
- type: git
|
|
4
|
+
name: ruby/gem_rbs_collection
|
|
5
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
6
|
+
revision: main
|
|
7
|
+
repo_dir: gems
|
|
8
|
+
|
|
9
|
+
# You can specify local directories as sources also.
|
|
10
|
+
# - type: local
|
|
11
|
+
# path: path/to/your/local/repository
|
|
12
|
+
|
|
13
|
+
# A directory to install the downloaded RBSs
|
|
14
|
+
path: .gem_rbs_collection
|
|
15
|
+
|
|
16
|
+
gems:
|
|
17
|
+
# If you want to avoid installing rbs files for gems, you can specify them here.
|
|
18
|
+
# - name: GEM_NAME
|
|
19
|
+
# ignore: true
|
|
20
|
+
- name: rubyzip
|
data/ruzip.gemspec
CHANGED
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = "Library to support the reading and writing of zip files. A wrapper of Rust's zip crate."
|
|
13
13
|
spec.homepage = "https://gitlab.com/KitaitiMakoto/ruzip"
|
|
14
14
|
spec.license = "MIT"
|
|
15
|
-
spec.required_ruby_version = ">= 3.
|
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
16
16
|
spec.required_rubygems_version = ">= 3.3.11"
|
|
17
17
|
|
|
18
18
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
|
|
|
37
37
|
spec.add_development_dependency "rdoc"
|
|
38
38
|
spec.add_development_dependency "rbs"
|
|
39
39
|
spec.add_development_dependency "archive-zip"
|
|
40
|
+
spec.add_development_dependency "rubyzip"
|
|
40
41
|
spec.add_development_dependency "ruby-lsp"
|
|
41
42
|
spec.add_development_dependency "steep"
|
|
42
43
|
|
data/sig/ruzip.rbs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
module RuZip
|
|
2
2
|
class Archive
|
|
3
3
|
def self.new: (IO | path) -> instance
|
|
4
|
-
def
|
|
4
|
+
def length: -> Integer
|
|
5
5
|
def by_index: (Integer) -> File
|
|
6
6
|
def by_name: (String) -> File
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
class File
|
|
10
|
+
# UTF-8 when valid, ASCII-8BIT otherwise
|
|
10
11
|
def name: -> String
|
|
11
12
|
|
|
12
13
|
# The size of decompressed file
|
|
@@ -18,9 +19,22 @@ module RuZip
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
class Writer
|
|
21
|
-
def self.new: (String) -> instance
|
|
22
|
+
def self.new: (String path, ?String mode) -> instance
|
|
22
23
|
def finish: -> Archive
|
|
23
|
-
def start_file: (
|
|
24
|
-
|
|
24
|
+
def start_file: (
|
|
25
|
+
String name,
|
|
26
|
+
?compression_method: Symbol?,
|
|
27
|
+
?compression_level: Integer?,
|
|
28
|
+
?last_modified_date: Time?
|
|
29
|
+
) -> void
|
|
30
|
+
def write: (String buf) -> void
|
|
31
|
+
def raw_copy_file: (File file, ?name: String?, ?last_modified_time: Time?, ?unix_mode: Integer?) -> void
|
|
32
|
+
def append_file: (
|
|
33
|
+
String name,
|
|
34
|
+
String buf,
|
|
35
|
+
?compression_method: Symbol?,
|
|
36
|
+
?compression_level: Integer?,
|
|
37
|
+
?last_modified_date: Time?
|
|
38
|
+
) -> void
|
|
25
39
|
end
|
|
26
40
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Archive
|
|
2
|
+
class Zip
|
|
3
|
+
include Enumerable[Entry]
|
|
4
|
+
|
|
5
|
+
def self.open: (path, ?(String | Symbol) mode) { (instance) -> untyped } -> untyped
|
|
6
|
+
def each: { (Entry) -> untyped } -> untyped
|
|
7
|
+
| -> Enumerator[Entry]
|
|
8
|
+
def add_entry: (Entry) -> self
|
|
9
|
+
alias << add_entry
|
|
10
|
+
|
|
11
|
+
module Entry
|
|
12
|
+
def zip_path: -> String
|
|
13
|
+
def mtime: -> Time
|
|
14
|
+
|
|
15
|
+
class File
|
|
16
|
+
include Entry
|
|
17
|
+
|
|
18
|
+
def initialize: (String) -> void
|
|
19
|
+
def file_data: -> IO::Like
|
|
20
|
+
def file_data=: (IO::LikeHelpers::AbstractIO) -> void
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/sig/test/gem.rbs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class IO
|
|
2
|
+
class Like
|
|
3
|
+
def read: (?Integer? length, ?string buffer) -> String?
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
module LikeHelpers
|
|
7
|
+
class AbstractIO
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class DelegatedIO < AbstractIO
|
|
11
|
+
def initialize: (_Reader) -> void
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class IOWrapper < DelegatedIO
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Test
|
|
2
|
+
module Unit
|
|
3
|
+
class TestCase
|
|
4
|
+
include Assertions
|
|
5
|
+
include TestCasePendingSupport
|
|
6
|
+
|
|
7
|
+
def self.test: (String) { (instance) -> void } -> void
|
|
8
|
+
def setup: { () -> void } -> void
|
|
9
|
+
def self.setup: { () -> void } -> void
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module Assertions
|
|
13
|
+
def assert: (boolish) -> void
|
|
14
|
+
| { () -> void } -> void
|
|
15
|
+
def assert_equal: (untyped expected, untyped actual) -> void
|
|
16
|
+
def assert_nil: (untyped) -> void
|
|
17
|
+
def assert_kind_of: (Module, untyped) -> void
|
|
18
|
+
def assert_instance_of: (Class, untyped) -> void
|
|
19
|
+
def assert_path_exist: (path) -> void
|
|
20
|
+
def assert_raise: (?Class) { () -> void } -> void
|
|
21
|
+
def assert_raise_kind_of: (Class) { () -> void } -> void
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module TestCasePendingSupport
|
|
25
|
+
def pend: -> void
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/sig/test/test.rbs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Test::Unit
|
|
2
|
+
class TestCase
|
|
3
|
+
extend Assertions
|
|
4
|
+
|
|
5
|
+
def self.fixture_path: (path) -> String
|
|
6
|
+
def self.pend: -> void
|
|
7
|
+
def fixture_path: (path) -> String
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class TestArchive < Test::Unit::TestCase
|
|
12
|
+
self.@fixture: String
|
|
13
|
+
@fixture: String
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class TestFile < Test::Unit::TestCase
|
|
17
|
+
self.@archive: RuZip::Archive
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class TestWriter < Test::Unit::TestCase
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class TestPackage < Test::Unit::TestCase
|
|
24
|
+
def test_build: -> void
|
|
25
|
+
def test_install: -> void
|
|
26
|
+
end
|
data/test/helper.rb
CHANGED
data/test/test_archive.rb
CHANGED
data/test/test_file.rb
CHANGED
|
@@ -21,4 +21,43 @@ XML
|
|
|
21
21
|
test "retrieve non-existent" do
|
|
22
22
|
assert_nil @archive.by_name("non-existent")
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
test "retrieve multi byte name" do
|
|
26
|
+
Dir.mktmpdir do |dir|
|
|
27
|
+
path = "test.zip"
|
|
28
|
+
inner_path = "日本語.txt"
|
|
29
|
+
content = "今日は"
|
|
30
|
+
Archive::Zip.open path, :w do |archive|
|
|
31
|
+
entry = Archive::Zip::Entry::File.new(inner_path)
|
|
32
|
+
entry.file_data = IO::LikeHelpers::IOWrapper.new(StringIO.new(content))
|
|
33
|
+
archive << entry
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
file = RuZip::Archive.new(path).by_name(inner_path)
|
|
37
|
+
assert_kind_of RuZip::File, file
|
|
38
|
+
assert_equal "日本語.txt", file.name
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
test "retrieve file with non-UTF-8 name" do
|
|
43
|
+
Dir.mktmpdir do |dir|
|
|
44
|
+
path = "test.zip"
|
|
45
|
+
inner_path = "日本語.txt"
|
|
46
|
+
inner_path_sjis = inner_path.encode("Shift_JIS")
|
|
47
|
+
content = "今日は"
|
|
48
|
+
Archive::Zip.open path, :w do |archive|
|
|
49
|
+
entry = Archive::Zip::Entry::File.new(inner_path_sjis)
|
|
50
|
+
entry.file_data = IO::LikeHelpers::IOWrapper.new(StringIO.new(content))
|
|
51
|
+
archive << entry
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
archive = RuZip::Archive.new(path)
|
|
55
|
+
|
|
56
|
+
assert_nil archive.by_name("日本語.txt")
|
|
57
|
+
|
|
58
|
+
file = archive.by_name(inner_path_sjis)
|
|
59
|
+
assert_kind_of RuZip::File, file
|
|
60
|
+
assert_equal inner_path_sjis, file.name.force_encoding("Shift_JIS")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
24
63
|
end
|
data/test/test_writer.rb
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
require_relative "helper"
|
|
2
|
-
require "tmpdir"
|
|
3
|
-
require "archive/zip"
|
|
4
2
|
|
|
5
3
|
class TestWriter < Test::Unit::TestCase
|
|
6
4
|
test "new with path" do
|
|
@@ -35,10 +33,194 @@ class TestWriter < Test::Unit::TestCase
|
|
|
35
33
|
writer.finish
|
|
36
34
|
|
|
37
35
|
Archive::Zip.open path do |archive|
|
|
38
|
-
file = archive.each.first
|
|
36
|
+
file = archive.each.first #: Archive::Zip::Entry::File
|
|
39
37
|
assert_equal inner_path, file.zip_path
|
|
40
38
|
assert_equal content, file.file_data.read(content.length)
|
|
41
39
|
end
|
|
42
40
|
end
|
|
43
41
|
end
|
|
42
|
+
|
|
43
|
+
test "add content multi times" do
|
|
44
|
+
Dir.mktmpdir do |dir|
|
|
45
|
+
path = File.join(dir, "output.zip")
|
|
46
|
+
writer = RuZip::Writer.new(path)
|
|
47
|
+
inner_path = "hello.txt"
|
|
48
|
+
writer.start_file(inner_path)
|
|
49
|
+
n = 3
|
|
50
|
+
n.times do |i|
|
|
51
|
+
writer.write i.to_s
|
|
52
|
+
end
|
|
53
|
+
writer.finish
|
|
54
|
+
|
|
55
|
+
Archive::Zip.open path do |archive|
|
|
56
|
+
file = archive.each.first #: Archive::Zip::Entry::File
|
|
57
|
+
assert_equal inner_path, file.zip_path
|
|
58
|
+
assert_equal "012", file.file_data.read(n)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
test "add content with compression option" do
|
|
64
|
+
Dir.mktmpdir do |dir|
|
|
65
|
+
path = File.join(dir, "output.zip")
|
|
66
|
+
writer = RuZip::Writer.new(path)
|
|
67
|
+
content = "Hello, World!" * 100
|
|
68
|
+
inner_path = "hello.txt"
|
|
69
|
+
inner_path_stored = "hello-xz.txt"
|
|
70
|
+
writer.start_file(inner_path)
|
|
71
|
+
writer.write content
|
|
72
|
+
writer.start_file(inner_path_stored, compression_method: :stored)
|
|
73
|
+
writer.write content
|
|
74
|
+
writer.finish
|
|
75
|
+
|
|
76
|
+
Zip::File.open path do |archive|
|
|
77
|
+
entries = archive.each.to_a
|
|
78
|
+
hello = entries[0]
|
|
79
|
+
hello_stored= entries[1]
|
|
80
|
+
assert do
|
|
81
|
+
hello.compressed_size < hello_stored.compressed_size
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
test "new with append mode add file" do
|
|
88
|
+
Dir.mktmpdir do |dir|
|
|
89
|
+
path = File.join(dir, "accessible_epub_3.epub")
|
|
90
|
+
FileUtils.copy fixture_path("accessible_epub_3.epub"), path
|
|
91
|
+
|
|
92
|
+
writer = RuZip::Writer.new(path, "a")
|
|
93
|
+
inner_path = "hello.txt"
|
|
94
|
+
writer.start_file(inner_path)
|
|
95
|
+
content = "Hello, World!"
|
|
96
|
+
writer.write content
|
|
97
|
+
writer.finish
|
|
98
|
+
|
|
99
|
+
Zip::File.open path do |archive|
|
|
100
|
+
entries = archive.each.to_a #: [Zip::Entry]
|
|
101
|
+
assert_equal 39, entries.length
|
|
102
|
+
|
|
103
|
+
file = entries.last
|
|
104
|
+
assert_equal inner_path, file.name
|
|
105
|
+
assert_equal content, file.get_input_stream.read
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
test "append_file" do
|
|
111
|
+
Dir.mktmpdir do |dir|
|
|
112
|
+
path = File.join(dir, "output.zip")
|
|
113
|
+
writer = RuZip::Writer.new(path)
|
|
114
|
+
inner_path = "hello.txt"
|
|
115
|
+
content = "Hello, World!"
|
|
116
|
+
writer.append_file(inner_path, content)
|
|
117
|
+
writer.finish
|
|
118
|
+
|
|
119
|
+
Archive::Zip.open path do |archive|
|
|
120
|
+
file = archive.each.first #: Archive::Zip::Entry::File
|
|
121
|
+
assert_equal inner_path, file.zip_path
|
|
122
|
+
assert_equal content, file.file_data.read(content.length)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
test "raw_copy_file" do
|
|
128
|
+
Dir.mktmpdir do |dir|
|
|
129
|
+
input = File.join(dir, "input.zip")
|
|
130
|
+
output = File.join(dir, "output.zip")
|
|
131
|
+
inner_path = "hello.txt"
|
|
132
|
+
content = "Hello, World!"
|
|
133
|
+
Archive::Zip.open input, :w do |archive|
|
|
134
|
+
entry = Archive::Zip::Entry::File.new(inner_path)
|
|
135
|
+
entry.file_data = IO::LikeHelpers::IOWrapper.new(StringIO.new(content))
|
|
136
|
+
archive << entry
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
file = RuZip::Archive.new(input).by_index(0)
|
|
140
|
+
writer = RuZip::Writer.new(output)
|
|
141
|
+
writer.raw_copy_file file
|
|
142
|
+
writer.finish
|
|
143
|
+
|
|
144
|
+
Archive::Zip.open output do |archive|
|
|
145
|
+
copy = archive.each.first #: Archive::Zip::Entry::File
|
|
146
|
+
assert_equal inner_path, copy.zip_path
|
|
147
|
+
assert_equal content, copy.file_data.read(content.length)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
test "raw_copy_file with name" do
|
|
153
|
+
Dir.mktmpdir do |dir|
|
|
154
|
+
input = File.join(dir, "input.zip")
|
|
155
|
+
output = File.join(dir, "output.zip")
|
|
156
|
+
inner_path = "hello.txt"
|
|
157
|
+
content = "Hello, World!"
|
|
158
|
+
Archive::Zip.open input, :w do |archive|
|
|
159
|
+
entry = Archive::Zip::Entry::File.new(inner_path)
|
|
160
|
+
entry.file_data = IO::LikeHelpers::IOWrapper.new(StringIO.new(content))
|
|
161
|
+
archive << entry
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
renamed_path = "renamed.txt"
|
|
165
|
+
file = RuZip::Archive.new(input).by_index(0)
|
|
166
|
+
writer = RuZip::Writer.new(output)
|
|
167
|
+
writer.raw_copy_file file, name: renamed_path
|
|
168
|
+
writer.finish
|
|
169
|
+
|
|
170
|
+
Archive::Zip.open output do |archive|
|
|
171
|
+
copy = archive.each.first #: Archive::Zip::Entry::File
|
|
172
|
+
assert_equal renamed_path, copy.zip_path
|
|
173
|
+
assert_equal content, copy.file_data.read(content.length)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
test "raw_copy_file with timestamp" do
|
|
179
|
+
Dir.mktmpdir do |dir|
|
|
180
|
+
input = File.join(dir, "input.zip")
|
|
181
|
+
output = File.join(dir, "output.zip")
|
|
182
|
+
inner_path = "hello.txt"
|
|
183
|
+
content = "Hello, World!"
|
|
184
|
+
Archive::Zip.open input, :w do |archive|
|
|
185
|
+
entry = Archive::Zip::Entry::File.new(inner_path)
|
|
186
|
+
entry.file_data = IO::LikeHelpers::IOWrapper.new(StringIO.new(content))
|
|
187
|
+
archive << entry
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
file = RuZip::Archive.new(input).by_index(0)
|
|
191
|
+
writer = RuZip::Writer.new(output)
|
|
192
|
+
writer.raw_copy_file file, last_modified_time: Time.new(2026, 1, 1)
|
|
193
|
+
writer.finish
|
|
194
|
+
|
|
195
|
+
Archive::Zip.open output do |archive|
|
|
196
|
+
copy = archive.each.first #: Archive::Zip::Entry::File
|
|
197
|
+
assert_equal Time.new(2026, 1, 1), copy.mtime
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
test "raw_copy_file with nil timestamp" do
|
|
203
|
+
Dir.mktmpdir do |dir|
|
|
204
|
+
input = File.join(dir, "input.zip")
|
|
205
|
+
output = File.join(dir, "output.zip")
|
|
206
|
+
inner_path = "hello.txt"
|
|
207
|
+
content = "Hello, World!"
|
|
208
|
+
Archive::Zip.open input, :w do |archive|
|
|
209
|
+
entry = Archive::Zip::Entry::File.new(inner_path)
|
|
210
|
+
entry.file_data = IO::LikeHelpers::IOWrapper.new(StringIO.new(content))
|
|
211
|
+
archive << entry
|
|
212
|
+
end
|
|
213
|
+
orig = Archive::Zip.open(input) {|archive| archive.each.first} #: Archive::Zip::Entry::File
|
|
214
|
+
|
|
215
|
+
file = RuZip::Archive.new(input).by_index(0)
|
|
216
|
+
writer = RuZip::Writer.new(output)
|
|
217
|
+
writer.raw_copy_file file, last_modified_time: nil
|
|
218
|
+
writer.finish
|
|
219
|
+
|
|
220
|
+
Archive::Zip.open output do |archive|
|
|
221
|
+
copy = archive.each.first #: Archive::Zip::Entry::File
|
|
222
|
+
assert_equal orig.mtime, copy.mtime
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
44
226
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruzip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kitaiti Makoto
|
|
@@ -135,6 +135,20 @@ dependencies:
|
|
|
135
135
|
- - ">="
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
137
|
version: '0'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: rubyzip
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - ">="
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '0'
|
|
138
152
|
- !ruby/object:Gem::Dependency
|
|
139
153
|
name: ruby-lsp
|
|
140
154
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -180,11 +194,18 @@ files:
|
|
|
180
194
|
- MIT-LICENSE
|
|
181
195
|
- README.md
|
|
182
196
|
- Rakefile
|
|
197
|
+
- Steepfile
|
|
183
198
|
- ext/Cargo.lock
|
|
184
199
|
- ext/Cargo.toml
|
|
185
200
|
- ext/src/lib.rs
|
|
201
|
+
- rbs_collection.yaml
|
|
186
202
|
- ruzip.gemspec
|
|
187
203
|
- sig/ruzip.rbs
|
|
204
|
+
- sig/test/archive-zip.rbs
|
|
205
|
+
- sig/test/gem.rbs
|
|
206
|
+
- sig/test/io-like.rbs
|
|
207
|
+
- sig/test/test-unit.rbs
|
|
208
|
+
- sig/test/test.rbs
|
|
188
209
|
- test/fixtures/.gitkeep
|
|
189
210
|
- test/helper.rb
|
|
190
211
|
- test/test_archive.rb
|
|
@@ -205,7 +226,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
205
226
|
requirements:
|
|
206
227
|
- - ">="
|
|
207
228
|
- !ruby/object:Gem::Version
|
|
208
|
-
version: 3.
|
|
229
|
+
version: 3.2.0
|
|
209
230
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
231
|
requirements:
|
|
211
232
|
- - ">="
|