mirror of
https://github.com/sagiegurari/duckscript
synced 2024-10-02 22:24:40 +00:00
Implement zip command
This commit is contained in:
parent
a24d4729a0
commit
10a647dcae
50
Cargo.lock
generated
50
Cargo.lock
generated
|
@ -95,12 +95,39 @@ version = "3.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
|
@ -298,6 +325,7 @@ dependencies = [
|
|||
"walkdir",
|
||||
"which",
|
||||
"whoami",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1074,18 +1102,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.37"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.37"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1398,3 +1426,17 @@ name = "windows_x86_64_msvc"
|
|||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bzip2",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
|
|
@ -47,6 +47,7 @@ suppaftp = "^4.5"
|
|||
walkdir = "^2"
|
||||
which = { version = "^4", default-features = false }
|
||||
whoami = "^1"
|
||||
zip = "^0.5"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
uname = "^0.1"
|
||||
|
|
|
@ -28,6 +28,7 @@ mod test;
|
|||
mod thread;
|
||||
mod time;
|
||||
mod var;
|
||||
mod zip;
|
||||
|
||||
use duckscript::types::command::Commands;
|
||||
use duckscript::types::error::ScriptError;
|
||||
|
@ -66,6 +67,7 @@ pub(crate) fn load(commands: &mut Commands) -> Result<(), ScriptError> {
|
|||
thread::load(commands, PACKAGE)?;
|
||||
time::load(commands, PACKAGE)?;
|
||||
var::load(commands, PACKAGE)?;
|
||||
zip::load(commands, PACKAGE)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
15
duckscript_sdk/src/sdk/std/zip/mod.rs
Normal file
15
duckscript_sdk/src/sdk/std/zip/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
mod pack;
|
||||
|
||||
use duckscript::types::command::Commands;
|
||||
use duckscript::types::error::ScriptError;
|
||||
use crate::utils::pckg;
|
||||
|
||||
static PACKAGE: &str = "zip";
|
||||
|
||||
pub(crate) fn load(commands: &mut Commands, parent: &str) -> Result<(), ScriptError> {
|
||||
let package = pckg::concat(parent, PACKAGE);
|
||||
|
||||
commands.set(pack::create(&package))?;
|
||||
|
||||
Ok(())
|
||||
}
|
35
duckscript_sdk/src/sdk/std/zip/pack/help.md
Normal file
35
duckscript_sdk/src/sdk/std/zip/pack/help.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
```
|
||||
zip [--base basedir] [--compression comp] [--append] <zipfile> <files>
|
||||
```
|
||||
|
||||
Packs the provided files into a ZIP archive.
|
||||
|
||||
File paths in the archive will be relative to current working directory.
|
||||
|
||||
### Parameters
|
||||
|
||||
- zipfile - The path to the ZIP archive to be created.
|
||||
- files - One or more file paths to pack. No globbing is performed.
|
||||
- Optional base directory via `--base <basedir>` --- this directory will be used
|
||||
as a base for the file paths inside the archive.
|
||||
- Optional append flag via `--append` --- if set, the files will be added to the
|
||||
existing archive. If the archive does not exist, it will be created.
|
||||
- Optional compression mode via `--compression comp` where `comp` is one of
|
||||
`deflate`, `bzip2`, `none`. If not specified, `deflate` is used by default.
|
||||
|
||||
### Return value
|
||||
|
||||
true if successful
|
||||
|
||||
### Examples
|
||||
|
||||
```sh
|
||||
# create some files for the example
|
||||
mkdir ./test
|
||||
touch ./test/foo/bar.txt
|
||||
touch ./test/baz/qux.png
|
||||
touch ./test/folder/another/file.doc
|
||||
|
||||
# pack the files
|
||||
zipped = zip ./archive.zip ./test/foo/bar.txt ./test/folder/another/file.doc
|
||||
```
|
193
duckscript_sdk/src/sdk/std/zip/pack/mod.rs
Normal file
193
duckscript_sdk/src/sdk/std/zip/pack/mod.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use zip::write::FileOptions;
|
||||
use zip::{CompressionMethod, ZipWriter};
|
||||
use duckscript::types::command::{Command, CommandResult};
|
||||
use crate::utils::pckg;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CommandImpl {
|
||||
package: String,
|
||||
}
|
||||
|
||||
enum LookingFor {
|
||||
Options,
|
||||
Base,
|
||||
Compression,
|
||||
Files,
|
||||
}
|
||||
|
||||
impl Command for CommandImpl {
|
||||
fn name(&self) -> String {
|
||||
pckg::concat(&self.package, "Pack")
|
||||
}
|
||||
|
||||
fn aliases(&self) -> Vec<String> {
|
||||
vec!["zip".to_string()]
|
||||
}
|
||||
|
||||
fn help(&self) -> String {
|
||||
include_str!("help.md").to_string()
|
||||
}
|
||||
|
||||
fn clone_and_box(&self) -> Box<dyn Command> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
|
||||
fn run(&self, arguments: Vec<String>) -> CommandResult {
|
||||
if arguments.len() < 2 {
|
||||
return CommandResult::Error(
|
||||
"Paths to the ZIP file and/or files to pack are not provided.".to_string());
|
||||
}
|
||||
|
||||
let mut looking_for = LookingFor::Options;
|
||||
let mut append = false;
|
||||
let mut base = None;
|
||||
let mut compression = CompressionMethod::Deflated;
|
||||
let mut zipfile = None;
|
||||
let mut files = vec![];
|
||||
|
||||
for argument in &arguments {
|
||||
match looking_for {
|
||||
LookingFor::Options => match argument.as_str() {
|
||||
"--compression" => looking_for = LookingFor::Compression,
|
||||
"--base" => looking_for = LookingFor::Base,
|
||||
"--append" => append = true,
|
||||
|
||||
_ => {
|
||||
zipfile = Some(argument.as_str());
|
||||
looking_for = LookingFor::Files;
|
||||
}
|
||||
},
|
||||
|
||||
LookingFor::Compression => {
|
||||
match argument.as_str() {
|
||||
"deflate" => compression = CompressionMethod::Deflated,
|
||||
"bzip2" => compression = CompressionMethod::Bzip2,
|
||||
"none" => compression = CompressionMethod::Stored,
|
||||
|
||||
_ => return CommandResult::Error("Unknown compression method.".to_string()),
|
||||
}
|
||||
|
||||
looking_for = LookingFor::Options;
|
||||
},
|
||||
|
||||
LookingFor::Base => {
|
||||
let base_str = argument.as_str();
|
||||
base = Some(base_str.strip_prefix("./").unwrap_or(base_str));
|
||||
looking_for = LookingFor::Options;
|
||||
},
|
||||
|
||||
LookingFor::Files => files.push(argument.as_str()),
|
||||
}
|
||||
}
|
||||
|
||||
if files.is_empty() {
|
||||
return CommandResult::Error("Input files not provided.".to_string());
|
||||
}
|
||||
|
||||
let zipfile = match zipfile {
|
||||
Some(filename) => filename,
|
||||
None => return CommandResult::Error("ZIP file name not provided.".to_string()),
|
||||
};
|
||||
|
||||
let zipfile = Path::new(zipfile);
|
||||
let zipfile_dir = match zipfile.parent() {
|
||||
Some(path) => path,
|
||||
None => return CommandResult::Error("Couldn't parse ZIP file directory.".to_string()),
|
||||
};
|
||||
match std::fs::create_dir_all(zipfile_dir) {
|
||||
Ok(_) => (),
|
||||
Err(err) =>
|
||||
return CommandResult::Error(format!("Couldn't create ZIP file directory: {}", err)),
|
||||
};
|
||||
let zip_file_existed = zipfile.exists();
|
||||
let zip_file = match OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(!zip_file_existed)
|
||||
.truncate(!append)
|
||||
.open(zipfile)
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(err) =>
|
||||
return CommandResult::Error(format!("Couldn't create/open ZIP file: {}", err)),
|
||||
};
|
||||
let mut zip = if append && zip_file_existed {
|
||||
match ZipWriter::new_append(zip_file) {
|
||||
Ok(writer) => writer,
|
||||
Err(err) =>
|
||||
return CommandResult::Error(format!("Couldn't open ZIP file: {}", err)),
|
||||
}
|
||||
} else {
|
||||
ZipWriter::new(zip_file)
|
||||
};
|
||||
|
||||
let zip_options = FileOptions::default()
|
||||
.compression_method(compression)
|
||||
.unix_permissions(0o755);
|
||||
|
||||
for file_to_add_str in files {
|
||||
let file_to_add_path = Path::new(file_to_add_str);
|
||||
let mut file_to_add = match File::open(file_to_add_path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => return CommandResult::Error(
|
||||
format!("File does not exist or can't be opened: {} ({})",
|
||||
file_to_add_str,
|
||||
err)),
|
||||
};
|
||||
let mut file_to_add_contents = Vec::new();
|
||||
match file_to_add.read_to_end(&mut file_to_add_contents) {
|
||||
Ok(_) => (),
|
||||
Err(err) =>
|
||||
return CommandResult::Error(
|
||||
format!("Could not read file {}: {}", file_to_add_str, err)),
|
||||
};
|
||||
|
||||
let file_to_add_path_stripped = file_to_add_path
|
||||
.strip_prefix("./").unwrap_or(file_to_add_path);
|
||||
|
||||
let file_to_add_path_stripped = match base {
|
||||
Some(base) =>
|
||||
file_to_add_path_stripped.strip_prefix(base)
|
||||
.unwrap_or(file_to_add_path_stripped),
|
||||
None => file_to_add_path_stripped,
|
||||
};
|
||||
|
||||
let file_to_add_path_str = match file_to_add_path_stripped.to_str() {
|
||||
Some(str) => str,
|
||||
None => return CommandResult::Error("Invalid file path".to_string()),
|
||||
};
|
||||
|
||||
match zip.start_file(file_to_add_path_str, zip_options) {
|
||||
Ok(_) => (),
|
||||
Err(err) =>
|
||||
return CommandResult::Error(
|
||||
format!("Could not write file to archive: {} ({})", file_to_add_str, err)),
|
||||
};
|
||||
|
||||
match zip.write_all(&file_to_add_contents) {
|
||||
Ok(_) => (),
|
||||
Err(err) =>
|
||||
return CommandResult::Error(
|
||||
format!("Could not write file to archive: {} ({})", file_to_add_str, err)),
|
||||
};
|
||||
}
|
||||
|
||||
match zip.finish() {
|
||||
Ok(_) => (),
|
||||
Err(err) =>
|
||||
return CommandResult::Error(
|
||||
format!("Could not finish the archive: {}", err)),
|
||||
};
|
||||
|
||||
CommandResult::Continue(Some("true".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create(package: &str) -> Box<dyn Command> {
|
||||
Box::new(CommandImpl {
|
||||
package: package.to_string(),
|
||||
})
|
||||
}
|
23
duckscript_sdk/src/sdk/std/zip/unpack/help.md
Normal file
23
duckscript_sdk/src/sdk/std/zip/unpack/help.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
```
|
||||
unzip <zipfile> [target]
|
||||
```
|
||||
|
||||
Unpacks the ZIP file into the `target` directory, if provided, or
|
||||
into current working directory otherwise.
|
||||
|
||||
### Parameters
|
||||
|
||||
- zipfile - The path to the ZIP archive to be created.
|
||||
- target - The directory to unpack files into. If not provided,
|
||||
current working directory is used.
|
||||
|
||||
### Return value
|
||||
|
||||
true if successful (i.e. the zip file exists and there are no existing file conflicts)
|
||||
|
||||
### Examples
|
||||
|
||||
```sh
|
||||
# ./stuff directory will be created automatically
|
||||
unzipped = unzip ./archive.zip ./stuff
|
||||
```
|
58
test/std/zip/pack_test.ds
Normal file
58
test/std/zip/pack_test.ds
Normal file
|
@ -0,0 +1,58 @@
|
|||
fn test_create_simple
|
||||
zipped = zip ./target/_duckscript_test/zip/test_simple.zip ./Cargo.toml ./Cargo.lock
|
||||
assert ${zipped}
|
||||
|
||||
exists = is_file ./target/_duckscript_test/zip/test_simple.zip
|
||||
assert ${exists}
|
||||
end
|
||||
|
||||
fn test_create_base
|
||||
touch ./target/_duckscript_test/zip/foo.txt
|
||||
|
||||
zipped = zip --base ./target/_duckscript_test ./target/_duckscript_test/zip/test_base.zip ./target/_duckscript_test/zip/foo.txt
|
||||
assert ${zipped}
|
||||
|
||||
exists = is_file ./target/_duckscript_test/zip/test_base.zip
|
||||
assert ${exists}
|
||||
end
|
||||
|
||||
fn test_append
|
||||
touch ./target/_duckscript_test/zip/foo.txt
|
||||
touch ./target/_duckscript_test/zip/bar.txt
|
||||
touch ./target/_duckscript_test/zip/baz/qux.txt
|
||||
|
||||
zipped = zip --base ./target/_duckscript_test ./target/_duckscript_test/zip/test_append.zip ./target/_duckscript_test/zip/foo.txt
|
||||
assert ${zipped}
|
||||
|
||||
zipped = zip --append --base ./target/_duckscript_test ./target/_duckscript_test/zip/test_append.zip ./target/_duckscript_test/zip/bar.txt
|
||||
assert ${zipped}
|
||||
|
||||
zipped = zip --append --base ./target/_duckscript_test ./target/_duckscript_test/zip/test_append.zip ./target/_duckscript_test/zip/baz/qux.txt
|
||||
assert ${zipped}
|
||||
|
||||
exists = is_file ./target/_duckscript_test/zip/test_append.zip
|
||||
assert ${exists}
|
||||
end
|
||||
|
||||
fn test_compression_mode
|
||||
touch ./target/_duckscript_test/zip/foo.txt
|
||||
touch ./target/_duckscript_test/zip/bar.txt
|
||||
|
||||
zipped = zip --compression deflate ./target/_duckscript_test/zip/test_deflate.zip ./target/_duckscript_test/zip/foo.txt ./target/_duckscript_test/zip/bar.txt
|
||||
assert ${zipped}
|
||||
|
||||
exists = is_file ./target/_duckscript_test/zip/test_deflate.zip
|
||||
assert ${exists}
|
||||
|
||||
zipped = zip --compression bzip2 ./target/_duckscript_test/zip/test_bzip2.zip ./target/_duckscript_test/zip/foo.txt ./target/_duckscript_test/zip/bar.txt
|
||||
assert ${zipped}
|
||||
|
||||
exists = is_file ./target/_duckscript_test/zip/test_bzip2.zip
|
||||
assert ${exists}
|
||||
|
||||
zipped = zip --compression none ./target/_duckscript_test/zip/test_none.zip ./target/_duckscript_test/zip/foo.txt ./target/_duckscript_test/zip/bar.txt
|
||||
assert ${zipped}
|
||||
|
||||
exists = is_file ./target/_duckscript_test/zip/test_none.zip
|
||||
assert ${exists}
|
||||
end
|
Loading…
Reference in a new issue