From 10a647dcae6ccfb62aa60bf41691befe3f23e8b7 Mon Sep 17 00:00:00 2001 From: Nikita Medvedev Date: Sat, 12 Feb 2022 21:54:44 +0700 Subject: [PATCH 1/6] Implement zip command --- Cargo.lock | 50 ++++- duckscript_sdk/Cargo.toml | 1 + duckscript_sdk/src/sdk/std/mod.rs | 2 + duckscript_sdk/src/sdk/std/zip/mod.rs | 15 ++ duckscript_sdk/src/sdk/std/zip/pack/help.md | 35 ++++ duckscript_sdk/src/sdk/std/zip/pack/mod.rs | 193 ++++++++++++++++++ duckscript_sdk/src/sdk/std/zip/unpack/help.md | 23 +++ test/std/zip/pack_test.ds | 58 ++++++ 8 files changed, 373 insertions(+), 4 deletions(-) create mode 100644 duckscript_sdk/src/sdk/std/zip/mod.rs create mode 100644 duckscript_sdk/src/sdk/std/zip/pack/help.md create mode 100644 duckscript_sdk/src/sdk/std/zip/pack/mod.rs create mode 100644 duckscript_sdk/src/sdk/std/zip/unpack/help.md create mode 100644 test/std/zip/pack_test.ds diff --git a/Cargo.lock b/Cargo.lock index 1478165..f32db9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", +] diff --git a/duckscript_sdk/Cargo.toml b/duckscript_sdk/Cargo.toml index 3ce7494..835014f 100644 --- a/duckscript_sdk/Cargo.toml +++ b/duckscript_sdk/Cargo.toml @@ -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" diff --git a/duckscript_sdk/src/sdk/std/mod.rs b/duckscript_sdk/src/sdk/std/mod.rs index 4a25119..d1e6679 100755 --- a/duckscript_sdk/src/sdk/std/mod.rs +++ b/duckscript_sdk/src/sdk/std/mod.rs @@ -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(()) } diff --git a/duckscript_sdk/src/sdk/std/zip/mod.rs b/duckscript_sdk/src/sdk/std/zip/mod.rs new file mode 100644 index 0000000..03636fa --- /dev/null +++ b/duckscript_sdk/src/sdk/std/zip/mod.rs @@ -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(()) +} diff --git a/duckscript_sdk/src/sdk/std/zip/pack/help.md b/duckscript_sdk/src/sdk/std/zip/pack/help.md new file mode 100644 index 0000000..0b4ef66 --- /dev/null +++ b/duckscript_sdk/src/sdk/std/zip/pack/help.md @@ -0,0 +1,35 @@ +``` +zip [--base basedir] [--compression comp] [--append] +``` + +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 ` --- 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 +``` diff --git a/duckscript_sdk/src/sdk/std/zip/pack/mod.rs b/duckscript_sdk/src/sdk/std/zip/pack/mod.rs new file mode 100644 index 0000000..a5feb0d --- /dev/null +++ b/duckscript_sdk/src/sdk/std/zip/pack/mod.rs @@ -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 { + vec!["zip".to_string()] + } + + fn help(&self) -> String { + include_str!("help.md").to_string() + } + + fn clone_and_box(&self) -> Box { + Box::new((*self).clone()) + } + + fn run(&self, arguments: Vec) -> 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 { + Box::new(CommandImpl { + package: package.to_string(), + }) +} diff --git a/duckscript_sdk/src/sdk/std/zip/unpack/help.md b/duckscript_sdk/src/sdk/std/zip/unpack/help.md new file mode 100644 index 0000000..61bdddd --- /dev/null +++ b/duckscript_sdk/src/sdk/std/zip/unpack/help.md @@ -0,0 +1,23 @@ +``` +unzip [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 +``` diff --git a/test/std/zip/pack_test.ds b/test/std/zip/pack_test.ds new file mode 100644 index 0000000..94efa57 --- /dev/null +++ b/test/std/zip/pack_test.ds @@ -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 From fbac8cf292142b0dfbdde2ded4e0d210d10952f8 Mon Sep 17 00:00:00 2001 From: Nikita Medvedev Date: Wed, 4 Jan 2023 21:05:26 +0700 Subject: [PATCH 2/6] Implement unzip command --- duckscript_sdk/src/sdk/std/zip/mod.rs | 2 + duckscript_sdk/src/sdk/std/zip/unpack/mod.rs | 83 ++++++++++++++++++++ test/std/zip/unpack_test.ds | 27 +++++++ 3 files changed, 112 insertions(+) create mode 100644 duckscript_sdk/src/sdk/std/zip/unpack/mod.rs create mode 100644 test/std/zip/unpack_test.ds diff --git a/duckscript_sdk/src/sdk/std/zip/mod.rs b/duckscript_sdk/src/sdk/std/zip/mod.rs index 03636fa..04ebb23 100644 --- a/duckscript_sdk/src/sdk/std/zip/mod.rs +++ b/duckscript_sdk/src/sdk/std/zip/mod.rs @@ -1,4 +1,5 @@ mod pack; +mod unpack; use duckscript::types::command::Commands; use duckscript::types::error::ScriptError; @@ -10,6 +11,7 @@ pub(crate) fn load(commands: &mut Commands, parent: &str) -> Result<(), ScriptEr let package = pckg::concat(parent, PACKAGE); commands.set(pack::create(&package))?; + commands.set(unpack::create(&package))?; Ok(()) } diff --git a/duckscript_sdk/src/sdk/std/zip/unpack/mod.rs b/duckscript_sdk/src/sdk/std/zip/unpack/mod.rs new file mode 100644 index 0000000..c3f87ec --- /dev/null +++ b/duckscript_sdk/src/sdk/std/zip/unpack/mod.rs @@ -0,0 +1,83 @@ +use std::fs::OpenOptions; +use std::path::Path; + +use zip::ZipArchive; + +use duckscript::types::command::{Command, CommandResult}; + +use crate::utils::pckg; + +#[derive(Clone)] +pub(crate) struct CommandImpl { + package: String, +} + +impl Command for CommandImpl { + fn name(&self) -> String { + pckg::concat(&self.package, "Unpack") + } + + fn aliases(&self) -> Vec { + vec!["unzip".to_string()] + } + + fn help(&self) -> String { + include_str!("help.md").to_string() + } + + fn clone_and_box(&self) -> Box { + Box::new((*self).clone()) + } + + fn run(&self, arguments: Vec) -> CommandResult { + if arguments.len() < 2 { + return CommandResult::Error( + "Paths to the ZIP file and/or target directory are not provided.".to_string()); + } + + let zipfile = Path::new(&arguments[0]); + let target_dir = Path::new(&arguments[1]); + + match std::fs::create_dir_all(target_dir) { + Ok(_) => (), + Err(err) => + return CommandResult::Error(format!("Couldn't create target directory: {}", err)), + }; + + let zip_file = match OpenOptions::new() + .read(true) + .open(zipfile) + { + Ok(file) => file, + Err(err) => + return CommandResult::Error(format!("Couldn't open ZIP file: {}", err)), + }; + let mut zip = match ZipArchive::new(zip_file) { + Ok(archive) => archive, + Err(err) => + return CommandResult::Error(format!("Couldn't open ZIP file: {}", err)), + }; + + for file in zip.file_names() { + let file_path = target_dir.join(file); + if file_path.exists() && file_path.is_file() { + return CommandResult::Error( + format!("File already exists: {}", file_path.to_str().unwrap())); + } + } + + match zip.extract(target_dir) { + Ok(_) => (), + Err(err) => + return CommandResult::Error(format!("Couldn't unpack ZIP file: {}", err)), + }; + + CommandResult::Continue(Some("true".to_string())) + } +} + +pub(crate) fn create(package: &str) -> Box { + Box::new(CommandImpl { + package: package.to_string(), + }) +} diff --git a/test/std/zip/unpack_test.ds b/test/std/zip/unpack_test.ds new file mode 100644 index 0000000..02190a3 --- /dev/null +++ b/test/std/zip/unpack_test.ds @@ -0,0 +1,27 @@ +base = set ./target/_duckscript_test/zip/unpack + +fn test_unpack_simple + files = array foo.txt bar/baz.txt bar/baf.txt qux.txt + for file in ${files} + length = random_range 10 50 + text = random_text ${length} + writefile ${base}/src/${file} ${text} + end + + zipped = zip --base ${base}/src ${base}/test_unpack.zip ${base}/src/foo.txt ${base}/src/bar/baz.txt ${base}/src/bar/baf.txt ${base}/src/qux.txt + assert ${zipped} + + exists = is_file ${base}/test_unpack.zip + assert ${exists} + + unzipped = unzip ${base}/test_unpack.zip ${base}/unpacked + assert ${unzipped} + + for file in ${files} + src_hash = digest --algo sha512 --file ${base}/src/${file} + dst_hash = digest --algo sha512 --file ${base}/unpacked/${file} + assert_eq ${src_hash} ${dst_hash} + end + + release ${files} +end From 7c1589b8a845ada3856532258a4946d22091f7c5 Mon Sep 17 00:00:00 2001 From: Nikita Medvedev Date: Wed, 4 Jan 2023 21:41:01 +0700 Subject: [PATCH 3/6] Add list support to zip command, refactor the tests --- duckscript_sdk/src/sdk/std/zip/pack/mod.rs | 48 ++++++++++++--- test/std/zip/pack_test.ds | 71 +++++++++++++++------- test/std/zip/unpack_test.ds | 10 ++- 3 files changed, 98 insertions(+), 31 deletions(-) diff --git a/duckscript_sdk/src/sdk/std/zip/pack/mod.rs b/duckscript_sdk/src/sdk/std/zip/pack/mod.rs index a5feb0d..1e10100 100644 --- a/duckscript_sdk/src/sdk/std/zip/pack/mod.rs +++ b/duckscript_sdk/src/sdk/std/zip/pack/mod.rs @@ -1,10 +1,13 @@ +use std::collections::HashMap; 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; +use duckscript::types::command::{Command, CommandResult, Commands}; +use duckscript::types::instruction::Instruction; +use duckscript::types::runtime::StateValue; +use crate::utils::{pckg, state}; #[derive(Clone)] pub(crate) struct CommandImpl { @@ -35,7 +38,20 @@ impl Command for CommandImpl { Box::new((*self).clone()) } - fn run(&self, arguments: Vec) -> CommandResult { + fn requires_context(&self) -> bool { + true + } + + fn run_with_context( + &self, + arguments: Vec, + state: &mut HashMap, + _variables: &mut HashMap, + _output_variable: Option, + _instructions: &Vec, + _commands: &mut Commands, + _line: usize) -> CommandResult + { if arguments.len() < 2 { return CommandResult::Error( "Paths to the ZIP file and/or files to pack are not provided.".to_string()); @@ -46,7 +62,7 @@ impl Command for CommandImpl { let mut base = None; let mut compression = CompressionMethod::Deflated; let mut zipfile = None; - let mut files = vec![]; + let mut file_args = Vec::new(); for argument in &arguments { match looking_for { @@ -79,11 +95,11 @@ impl Command for CommandImpl { looking_for = LookingFor::Options; }, - LookingFor::Files => files.push(argument.as_str()), + LookingFor::Files => file_args.push(argument.as_str()), } } - if files.is_empty() { + if file_args.is_empty() { return CommandResult::Error("Input files not provided.".to_string()); } @@ -128,8 +144,26 @@ impl Command for CommandImpl { .compression_method(compression) .unix_permissions(0o755); + let mut files = Vec::new(); + for arg in file_args { + match state::get_handle(state, arg.to_string()) { + Some(value) => + match value { + StateValue::List(entries) => + for entry in entries { + match state::get_as_string(entry) { + Ok(file) => files.push(file), + Err(err) => return CommandResult::Error(err), + } + } + _ => files.push(arg.to_string()), + } + None => files.push(arg.to_string()), + }; + } + for file_to_add_str in files { - let file_to_add_path = Path::new(file_to_add_str); + 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( diff --git a/test/std/zip/pack_test.ds b/test/std/zip/pack_test.ds index 94efa57..9146ce1 100644 --- a/test/std/zip/pack_test.ds +++ b/test/std/zip/pack_test.ds @@ -1,58 +1,87 @@ +base = set ./target/_duckscript_test/zip/pack + +fn randfile + length = random_range 10 50 + text = random_text ${length} + writefile ${1} ${text} +end + fn test_create_simple - zipped = zip ./target/_duckscript_test/zip/test_simple.zip ./Cargo.toml ./Cargo.lock + randfile ${base}/foo.txt + randfile ${base}/bar/baz.txt + + zipped = zip ${base}/test_simple.zip ${base}/foo.txt ${base}/bar/baz.txt assert ${zipped} - exists = is_file ./target/_duckscript_test/zip/test_simple.zip + exists = is_file ${base}/test_simple.zip assert ${exists} end -fn test_create_base - touch ./target/_duckscript_test/zip/foo.txt +fn test_create_from_array + files = array foo.txt bar.txt baz/qux.txt + files_to_zip = array + for file in ${files} + randfile ${base}/${file} + array_push ${files_to_zip} ${base}/${file} + end - zipped = zip --base ./target/_duckscript_test ./target/_duckscript_test/zip/test_base.zip ./target/_duckscript_test/zip/foo.txt + zipped = zip ${base}/test_array.zip ${files_to_zip} assert ${zipped} - exists = is_file ./target/_duckscript_test/zip/test_base.zip + exists = is_file ${base}/test_array.zip + assert ${exists} + + release ${files} + release ${files_to_zip} +end + +fn test_create_base + randfile ${base}/foo.txt + + zipped = zip --base ${base} ${base}/test_base.zip ${base}/foo.txt + assert ${zipped} + + exists = is_file ${base}/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 + randfile ${base}/foo.txt + randfile ${base}/bar.txt + randfile ${base}/baz/qux.txt - zipped = zip --base ./target/_duckscript_test ./target/_duckscript_test/zip/test_append.zip ./target/_duckscript_test/zip/foo.txt + zipped = zip --base ${base} ${base}/test_append.zip ${base}/foo.txt assert ${zipped} - zipped = zip --append --base ./target/_duckscript_test ./target/_duckscript_test/zip/test_append.zip ./target/_duckscript_test/zip/bar.txt + zipped = zip --append --base ${base} ${base}/test_append.zip ${base}/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 + zipped = zip --append --base ${base} ${base}/test_append.zip ${base}/baz/qux.txt assert ${zipped} - exists = is_file ./target/_duckscript_test/zip/test_append.zip + exists = is_file ${base}/test_append.zip assert ${exists} end fn test_compression_mode - touch ./target/_duckscript_test/zip/foo.txt - touch ./target/_duckscript_test/zip/bar.txt + randfile ${base}/foo.txt + randfile ${base}/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 + zipped = zip --compression deflate ${base}/test_deflate.zip ${base}/foo.txt ${base}/bar.txt assert ${zipped} - exists = is_file ./target/_duckscript_test/zip/test_deflate.zip + exists = is_file ${base}/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 + zipped = zip --compression bzip2 ${base}/test_bzip2.zip ${base}/foo.txt ${base}/bar.txt assert ${zipped} - exists = is_file ./target/_duckscript_test/zip/test_bzip2.zip + exists = is_file ${base}/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 + zipped = zip --compression none ${base}/test_none.zip ${base}/foo.txt ${base}/bar.txt assert ${zipped} - exists = is_file ./target/_duckscript_test/zip/test_none.zip + exists = is_file ${base}/test_none.zip assert ${exists} end diff --git a/test/std/zip/unpack_test.ds b/test/std/zip/unpack_test.ds index 02190a3..18f41a8 100644 --- a/test/std/zip/unpack_test.ds +++ b/test/std/zip/unpack_test.ds @@ -1,11 +1,15 @@ base = set ./target/_duckscript_test/zip/unpack +fn randfile + length = random_range 10 50 + text = random_text ${length} + writefile ${1} ${text} +end + fn test_unpack_simple files = array foo.txt bar/baz.txt bar/baf.txt qux.txt for file in ${files} - length = random_range 10 50 - text = random_text ${length} - writefile ${base}/src/${file} ${text} + randfile ${base}/src/${file} end zipped = zip --base ${base}/src ${base}/test_unpack.zip ${base}/src/foo.txt ${base}/src/bar/baz.txt ${base}/src/bar/baf.txt ${base}/src/qux.txt From 0c78abf7c1a6ffa6ccc477f0d8c6f0375fd0a336 Mon Sep 17 00:00:00 2001 From: Nikita Medvedev Date: Wed, 4 Jan 2023 21:50:45 +0700 Subject: [PATCH 4/6] Update zip crate version to 0.6 --- Cargo.lock | 173 ++++++++++++++++++++++++++++++++++++-- duckscript_sdk/Cargo.toml | 2 +- 2 files changed, 169 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f32db9b..cdf4b5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "aho-corasick" version = "0.7.19" @@ -65,6 +77,12 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" +[[package]] +name = "base64ct" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" + [[package]] name = "bitflags" version = "1.3.2" @@ -133,6 +151,9 @@ name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -150,11 +171,20 @@ dependencies = [ "js-sys", "num-integer", "num-traits", - "time", + "time 0.1.44", "wasm-bindgen", "winapi", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -176,6 +206,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "core-foundation" version = "0.9.3" @@ -281,6 +317,7 @@ checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -533,6 +570,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.4" @@ -631,6 +677,15 @@ dependencies = [ "regex", ] +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -777,6 +832,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.42" @@ -822,6 +883,29 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1037,6 +1121,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.6" @@ -1054,6 +1149,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "suppaftp" version = "4.5.2" @@ -1140,6 +1241,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1429,14 +1557,49 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "zip" -version = "0.5.13" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" dependencies = [ + "aes", "byteorder", "bzip2", + "constant_time_eq", "crc32fast", + "crossbeam-utils", "flate2", - "thiserror", - "time", + "hmac", + "pbkdf2", + "sha1", + "time 0.3.17", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.4+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" +dependencies = [ + "cc", + "libc", ] diff --git a/duckscript_sdk/Cargo.toml b/duckscript_sdk/Cargo.toml index 835014f..f101d16 100644 --- a/duckscript_sdk/Cargo.toml +++ b/duckscript_sdk/Cargo.toml @@ -47,7 +47,7 @@ suppaftp = "^4.5" walkdir = "^2" which = { version = "^4", default-features = false } whoami = "^1" -zip = "^0.5" +zip = "^0.6" [target.'cfg(not(windows))'.dependencies] uname = "^0.1" From 3b6c16824246f526d2beabb61d09c6faf835b199 Mon Sep 17 00:00:00 2001 From: Nikita Medvedev Date: Wed, 4 Jan 2023 21:55:18 +0700 Subject: [PATCH 5/6] Small fix --- duckscript_sdk/src/sdk/std/zip/pack/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/duckscript_sdk/src/sdk/std/zip/pack/mod.rs b/duckscript_sdk/src/sdk/std/zip/pack/mod.rs index 1e10100..ca7b6fe 100644 --- a/duckscript_sdk/src/sdk/std/zip/pack/mod.rs +++ b/duckscript_sdk/src/sdk/std/zip/pack/mod.rs @@ -156,7 +156,12 @@ impl Command for CommandImpl { Err(err) => return CommandResult::Error(err), } } - _ => files.push(arg.to_string()), + + arg => + match state::get_as_string(arg) { + Ok(arg) => files.push(arg), + Err(err) => return CommandResult::Error(err), + } } None => files.push(arg.to_string()), }; From da297b0ff31a038de1b6582bf14a0fe83aa9799f Mon Sep 17 00:00:00 2001 From: Nikita Medvedev Date: Wed, 18 Jan 2023 23:30:14 +0700 Subject: [PATCH 6/6] Review fixes and small refactoring --- duckscript_sdk/src/sdk/std/fs/mod.rs | 3 + .../src/sdk/std/{ => fs}/zip/mod.rs | 2 +- .../src/sdk/std/{ => fs}/zip/pack/help.md | 3 +- duckscript_sdk/src/sdk/std/fs/zip/pack/mod.rs | 272 ++++++++++++++++++ .../src/sdk/std/{ => fs}/zip/unpack/help.md | 0 .../src/sdk/std/{ => fs}/zip/unpack/mod.rs | 28 +- duckscript_sdk/src/sdk/std/mod.rs | 2 - duckscript_sdk/src/sdk/std/zip/pack/mod.rs | 232 --------------- test/std/{ => fs}/zip/pack_test.ds | 2 +- test/std/{ => fs}/zip/unpack_test.ds | 2 +- 10 files changed, 293 insertions(+), 253 deletions(-) rename duckscript_sdk/src/sdk/std/{ => fs}/zip/mod.rs (100%) rename duckscript_sdk/src/sdk/std/{ => fs}/zip/pack/help.md (93%) create mode 100644 duckscript_sdk/src/sdk/std/fs/zip/pack/mod.rs rename duckscript_sdk/src/sdk/std/{ => fs}/zip/unpack/help.md (100%) rename duckscript_sdk/src/sdk/std/{ => fs}/zip/unpack/mod.rs (71%) delete mode 100644 duckscript_sdk/src/sdk/std/zip/pack/mod.rs rename test/std/{ => fs}/zip/pack_test.ds (97%) rename test/std/{ => fs}/zip/unpack_test.ds (94%) diff --git a/duckscript_sdk/src/sdk/std/fs/mod.rs b/duckscript_sdk/src/sdk/std/fs/mod.rs index 2c07deb..262face 100755 --- a/duckscript_sdk/src/sdk/std/fs/mod.rs +++ b/duckscript_sdk/src/sdk/std/fs/mod.rs @@ -29,6 +29,7 @@ mod temp_file; mod touch; mod write_bytes; mod write_text; +mod zip; use crate::utils::pckg; use duckscript::types::command::Commands; @@ -71,5 +72,7 @@ pub(crate) fn load(commands: &mut Commands, parent: &str) -> Result<(), ScriptEr commands.set(write_bytes::create(&package))?; commands.set(write_text::create(&package))?; + zip::load(commands, PACKAGE)?; + Ok(()) } diff --git a/duckscript_sdk/src/sdk/std/zip/mod.rs b/duckscript_sdk/src/sdk/std/fs/zip/mod.rs similarity index 100% rename from duckscript_sdk/src/sdk/std/zip/mod.rs rename to duckscript_sdk/src/sdk/std/fs/zip/mod.rs index 04ebb23..e328691 100644 --- a/duckscript_sdk/src/sdk/std/zip/mod.rs +++ b/duckscript_sdk/src/sdk/std/fs/zip/mod.rs @@ -1,9 +1,9 @@ mod pack; mod unpack; +use crate::utils::pckg; use duckscript::types::command::Commands; use duckscript::types::error::ScriptError; -use crate::utils::pckg; static PACKAGE: &str = "zip"; diff --git a/duckscript_sdk/src/sdk/std/zip/pack/help.md b/duckscript_sdk/src/sdk/std/fs/zip/pack/help.md similarity index 93% rename from duckscript_sdk/src/sdk/std/zip/pack/help.md rename to duckscript_sdk/src/sdk/std/fs/zip/pack/help.md index 0b4ef66..0ee64dd 100644 --- a/duckscript_sdk/src/sdk/std/zip/pack/help.md +++ b/duckscript_sdk/src/sdk/std/fs/zip/pack/help.md @@ -9,7 +9,8 @@ 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. + - files - One or more file paths to pack. No globbing is performed. However, array + handles containing the files to pack can be used. - Optional base directory via `--base ` --- 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 diff --git a/duckscript_sdk/src/sdk/std/fs/zip/pack/mod.rs b/duckscript_sdk/src/sdk/std/fs/zip/pack/mod.rs new file mode 100644 index 0000000..e7c5d02 --- /dev/null +++ b/duckscript_sdk/src/sdk/std/fs/zip/pack/mod.rs @@ -0,0 +1,272 @@ +use crate::utils::{pckg, state}; +use duckscript::types::command::{Command, CommandResult, Commands}; +use duckscript::types::instruction::Instruction; +use duckscript::types::runtime::StateValue; +use std::collections::HashMap; +use std::fs::{File, OpenOptions}; +use std::io; +use std::path::Path; +use zip::write::FileOptions; +use zip::{CompressionMethod, ZipWriter}; + +#[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 { + vec!["zip".to_string()] + } + + fn help(&self) -> String { + include_str!("help.md").to_string() + } + + fn clone_and_box(&self) -> Box { + Box::new((*self).clone()) + } + + fn requires_context(&self) -> bool { + true + } + + fn run_with_context( + &self, + arguments: Vec, + state: &mut HashMap, + _variables: &mut HashMap, + _output_variable: Option, + _instructions: &Vec, + _commands: &mut Commands, + _line: usize, + ) -> CommandResult { + if arguments.len() < 2 { + return CommandResult::Error( + "Paths to the ZIP file and/or files to pack are not provided.".to_string(), + ); + } + + let CommandOptions { + append, + base, + compression, + zipfile, + file_args, + } = match parse_args(&arguments) { + Ok(options) => options, + Err(err) => return CommandResult::Error(err), + }; + + let files = match collect_files_from_arrays(&file_args, state) { + Ok(files) => files, + Err(err) => return CommandResult::Error(err), + }; + + 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 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 io::copy(&mut file_to_add, &mut zip) { + 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())) + } +} + +struct CommandOptions<'a> { + append: bool, + base: Option<&'a str>, + compression: CompressionMethod, + zipfile: &'a str, + file_args: Vec<&'a str>, +} + +fn parse_args<'a>(arguments: &'a [String]) -> Result, 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 file_args = Vec::new(); + + 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 Err("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 => file_args.push(argument.as_str()), + } + } + + if file_args.is_empty() { + return Err("Input files not provided.".to_string()); + } + + let zipfile = match zipfile { + Some(filename) => filename, + None => return Err("ZIP file name not provided.".to_string()), + }; + + Ok(CommandOptions { + append, + base, + compression, + zipfile, + file_args, + }) +} + +fn collect_files_from_arrays( + files: &[&str], + state: &mut HashMap, +) -> Result, String> { + let mut collected = Vec::new(); + + for arg in files { + match state::get_handle(state, arg.to_string()) { + Some(value) => match value { + StateValue::List(entries) => { + for entry in entries { + collected.push(state::get_as_string(entry)?); + } + } + + arg => collected.push(state::get_as_string(arg)?), + }, + + None => collected.push(arg.to_string()), + }; + } + + Ok(collected) +} + +pub(crate) fn create(package: &str) -> Box { + Box::new(CommandImpl { + package: package.to_string(), + }) +} diff --git a/duckscript_sdk/src/sdk/std/zip/unpack/help.md b/duckscript_sdk/src/sdk/std/fs/zip/unpack/help.md similarity index 100% rename from duckscript_sdk/src/sdk/std/zip/unpack/help.md rename to duckscript_sdk/src/sdk/std/fs/zip/unpack/help.md diff --git a/duckscript_sdk/src/sdk/std/zip/unpack/mod.rs b/duckscript_sdk/src/sdk/std/fs/zip/unpack/mod.rs similarity index 71% rename from duckscript_sdk/src/sdk/std/zip/unpack/mod.rs rename to duckscript_sdk/src/sdk/std/fs/zip/unpack/mod.rs index c3f87ec..b2845e7 100644 --- a/duckscript_sdk/src/sdk/std/zip/unpack/mod.rs +++ b/duckscript_sdk/src/sdk/std/fs/zip/unpack/mod.rs @@ -32,7 +32,8 @@ impl Command for CommandImpl { fn run(&self, arguments: Vec) -> CommandResult { if arguments.len() < 2 { return CommandResult::Error( - "Paths to the ZIP file and/or target directory are not provided.".to_string()); + "Paths to the ZIP file and/or target directory are not provided.".to_string(), + ); } let zipfile = Path::new(&arguments[0]); @@ -40,36 +41,33 @@ impl Command for CommandImpl { match std::fs::create_dir_all(target_dir) { Ok(_) => (), - Err(err) => - return CommandResult::Error(format!("Couldn't create target directory: {}", err)), + Err(err) => { + return CommandResult::Error(format!("Couldn't create target directory: {}", err)) + } }; - let zip_file = match OpenOptions::new() - .read(true) - .open(zipfile) - { + let zip_file = match OpenOptions::new().read(true).open(zipfile) { Ok(file) => file, - Err(err) => - return CommandResult::Error(format!("Couldn't open ZIP file: {}", err)), + Err(err) => return CommandResult::Error(format!("Couldn't open ZIP file: {}", err)), }; let mut zip = match ZipArchive::new(zip_file) { Ok(archive) => archive, - Err(err) => - return CommandResult::Error(format!("Couldn't open ZIP file: {}", err)), + Err(err) => return CommandResult::Error(format!("Couldn't open ZIP file: {}", err)), }; for file in zip.file_names() { let file_path = target_dir.join(file); if file_path.exists() && file_path.is_file() { - return CommandResult::Error( - format!("File already exists: {}", file_path.to_str().unwrap())); + return CommandResult::Error(format!( + "File already exists: {}", + file_path.to_str().unwrap() + )); } } match zip.extract(target_dir) { Ok(_) => (), - Err(err) => - return CommandResult::Error(format!("Couldn't unpack ZIP file: {}", err)), + Err(err) => return CommandResult::Error(format!("Couldn't unpack ZIP file: {}", err)), }; CommandResult::Continue(Some("true".to_string())) diff --git a/duckscript_sdk/src/sdk/std/mod.rs b/duckscript_sdk/src/sdk/std/mod.rs index d1e6679..4a25119 100755 --- a/duckscript_sdk/src/sdk/std/mod.rs +++ b/duckscript_sdk/src/sdk/std/mod.rs @@ -28,7 +28,6 @@ mod test; mod thread; mod time; mod var; -mod zip; use duckscript::types::command::Commands; use duckscript::types::error::ScriptError; @@ -67,7 +66,6 @@ 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(()) } diff --git a/duckscript_sdk/src/sdk/std/zip/pack/mod.rs b/duckscript_sdk/src/sdk/std/zip/pack/mod.rs deleted file mode 100644 index ca7b6fe..0000000 --- a/duckscript_sdk/src/sdk/std/zip/pack/mod.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::collections::HashMap; -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, Commands}; -use duckscript::types::instruction::Instruction; -use duckscript::types::runtime::StateValue; -use crate::utils::{pckg, state}; - -#[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 { - vec!["zip".to_string()] - } - - fn help(&self) -> String { - include_str!("help.md").to_string() - } - - fn clone_and_box(&self) -> Box { - Box::new((*self).clone()) - } - - fn requires_context(&self) -> bool { - true - } - - fn run_with_context( - &self, - arguments: Vec, - state: &mut HashMap, - _variables: &mut HashMap, - _output_variable: Option, - _instructions: &Vec, - _commands: &mut Commands, - _line: usize) -> 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 file_args = Vec::new(); - - 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 => file_args.push(argument.as_str()), - } - } - - if file_args.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); - - let mut files = Vec::new(); - for arg in file_args { - match state::get_handle(state, arg.to_string()) { - Some(value) => - match value { - StateValue::List(entries) => - for entry in entries { - match state::get_as_string(entry) { - Ok(file) => files.push(file), - Err(err) => return CommandResult::Error(err), - } - } - - arg => - match state::get_as_string(arg) { - Ok(arg) => files.push(arg), - Err(err) => return CommandResult::Error(err), - } - } - None => files.push(arg.to_string()), - }; - } - - 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 { - Box::new(CommandImpl { - package: package.to_string(), - }) -} diff --git a/test/std/zip/pack_test.ds b/test/std/fs/zip/pack_test.ds similarity index 97% rename from test/std/zip/pack_test.ds rename to test/std/fs/zip/pack_test.ds index 9146ce1..7ec4b9b 100644 --- a/test/std/zip/pack_test.ds +++ b/test/std/fs/zip/pack_test.ds @@ -1,4 +1,4 @@ -base = set ./target/_duckscript_test/zip/pack +base = set ./target/_duckscript_test/fs/zip/pack fn randfile length = random_range 10 50 diff --git a/test/std/zip/unpack_test.ds b/test/std/fs/zip/unpack_test.ds similarity index 94% rename from test/std/zip/unpack_test.ds rename to test/std/fs/zip/unpack_test.ds index 18f41a8..485959b 100644 --- a/test/std/zip/unpack_test.ds +++ b/test/std/fs/zip/unpack_test.ds @@ -1,4 +1,4 @@ -base = set ./target/_duckscript_test/zip/unpack +base = set ./target/_duckscript_test/fs/zip/unpack fn randfile length = random_range 10 50