Skip to content

Commit

Permalink
add new zip-cli crate in workspace
Browse files Browse the repository at this point in the history
- implement absurd arg parsing
- add help text
- add long help text
- first iteration of compression
- add --stdout
- add large file support
- make the compress command mostly work
- make compress flags work better
- verbose output works!
- reduce size of zip-cli from 2.9M->1.3M
- make a new subcrate clite for different features/opt flags
- remove clap
- set --large-file automatically
- clarify info and extract are a TODO for now
- move OutputHandle to lib.rs
- factor out main method into lib.rs
- clarify the behavior of -f and -r around symlinks
- add --append option
- rename CompressError -> CommandError
- make much more subcommand logic generic through traits
- wrap compress help to 80 chars
- begin extract help text

squashed commits:
- implement matching logic except for --match
- FINALLY fix input zips iteration
- implement a couple basic transforms
- add terrible ContentTransformer
- add entry spec transformer
- give up and use an unsafecell
- impl transform_name
- initial extract impl
- add name matchers
- impl --match
- modularize extract
- begin impl transforms
- refactor args modules
- init entry data
- stub out utterly absurd cli spec
- parse our absurd cli spec
- impl merged input
- do absurd stuff without checking for compilation success
- ok it compiles now
- ok it might even run correctly now?
- make output files work
- default to regexp for replacement and disallow globs from replacement
- add --{min,max}-size match exprs
- impl pattern transformers
- support --transform!!
- anchoring, prefixes, the whole shebang. i think we're done here
- make glob and rx optional dependencies
- add stub for info command
- parameterize the match help text to reuse for info
- stub out info format specs
- write out some more help text
- remove unnecessary unsafe
- parse archive overview format strings
- make a trait for format parsing
- parse entry format
- finish help text for info
- add info command stub
- implement basic entry info
- write directly to the output stream, don't allocate a string
- add escaped tab component
- rename some traits and methods
- pass around a reference to EntryData instead
- mess with mode bits to make them look more like ls
- add some verbose logs
- refactor info modules
- refactor parseable directive
- make compiled format strings much more generic (!)
- refactor modules of compiled formatting
- archive format works!!!!! omg
- move entry and archive iteration into helper methods
- make archive formatting work fully with ArchiveData
- archive data works for stdin!
- add more logging to extraction
- remove allocations in perms todo sorting
- several preliminary notes added to extract command
- make process_entry() helper method
- remove UnsafeCell!!!
- don't reallocate the symlink target
- move symlink processing to a helper
- refactor a lot of extraction
- ok extract makes a lot more sense now
- support --archive-comment for compression
- add a TODO
- make symlink creation more readable
- refactor output parsing into modules
- make parsing outputs much MUCH more readable with a builder
- all info directives are now supported
  • Loading branch information
cosmicexplorer committed Nov 27, 2024
1 parent 810d18a commit d4041b6
Show file tree
Hide file tree
Showing 24 changed files with 7,576 additions and 50 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ authors = [
license = "MIT"
repository = "https://github.com/zip-rs/zip2.git"
keywords = ["zip", "archive", "compression"]
categories = ["compression", "filesystem", "parser-implementations"]
rust-version = "1.73.0"
description = """
Library to support the reading and writing of zip files.
Expand All @@ -23,7 +24,9 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[workspace.dependencies]
time = { version = "0.3.1", default-features = false }
arbitrary = { version = "1.3.2", features = ["derive"] }
time = { version = "0.3.36", default-features = false }
zip = { path = ".", default-features = false }

[dependencies]
aes = { version = "0.8", optional = true }
Expand Down Expand Up @@ -53,7 +56,7 @@ lzma-rs = { version = "0.3", default-features = false, optional = true }
crossbeam-utils = "0.8.20"

[target.'cfg(fuzzing)'.dependencies]
arbitrary = { version = "1.3.2", features = ["derive"] }
arbitrary.workspace = true

[dev-dependencies]
bencher = "0.1.5"
Expand Down
70 changes: 70 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
[package]
name = "zip-cli"
version = "0.0.1"
authors = [
"Danny McClanahan <dmcC2@hypnicjerk.ai>",
]
license = "MIT"
repository = "https://github.com/zip-rs/zip2.git"
keywords = ["zip", "archive", "compression", "cli"]
categories = ["command-line-utilities", "compression", "filesystem", "development-tools::build-utils"]
rust-version = "1.74.0"
description = """
Binary for creation and manipulation of zip files.
"""
edition = "2021"

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[lib]

[[bin]]
name = "zip-cli"

[dependencies]
glob = { version = "0.3", optional = true }
regex = { version = "1", optional = true }

[dependencies.zip]
path = ".."
default-features = false

[features]
aes-crypto = ["zip/aes-crypto"]
bzip2 = ["zip/bzip2"]
chrono = ["zip/chrono"]
deflate64 = ["zip/deflate64"]
deflate = ["zip/deflate"]
deflate-flate2 = ["zip/deflate-flate2"]
deflate-zlib = ["zip/deflate-zlib"]
deflate-zlib-ng = ["zip/deflate-zlib-ng"]
deflate-zopfli = ["zip/deflate-zopfli"]
lzma = ["zip/lzma"]
time = ["zip/time"]
xz = ["zip/xz"]
zstd = ["zip/zstd"]

glob = ["dep:glob"]
rx = ["dep:regex"]

default = [
"aes-crypto",
"bzip2",
"deflate64",
"deflate",
"lzma",
"time",
"xz",
"zstd",
"glob",
"rx",
]


[profile.release]
strip = true
lto = true
opt-level = 3
codegen-units = 1
35 changes: 35 additions & 0 deletions cli/clite/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "zip-clite"
version = "0.0.1"
authors = [
"Danny McClanahan <dmcC2@hypnicjerk.ai>",
]
license = "MIT"
repository = "https://github.com/zip-rs/zip2.git"
keywords = ["zip", "archive", "compression", "cli"]
categories = ["command-line-utilities", "compression", "filesystem", "development-tools::build-utils"]
rust-version = "1.74.0"
description = """
Binary for creation and manipulation of zip files.
"""
edition = "2021"

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[[bin]]
name = "zip-clite"

[dependencies]

[dependencies.zip-cli]
path = ".."
default-features = false
features = ["deflate-flate2", "deflate-zlib"]

[profile.release]
strip = true
lto = true
opt-level = "s"
codegen-units = 1
3 changes: 3 additions & 0 deletions cli/clite/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
zip_cli::driver::main();
}
208 changes: 208 additions & 0 deletions cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
use std::{collections::VecDeque, ffi::OsString, fmt, sync::OnceLock};

#[derive(Debug)]
pub enum ArgParseError {
StdoutMessage(String),
StderrMessage(String),
}

#[derive(Debug)]
pub struct ZipCli {
pub verbose: bool,
pub command: ZipCommand,
}

#[derive(Debug)]
enum SubcommandName {
Compress,
Info,
Extract,
}

static PARSED_EXE_NAME: OnceLock<String> = OnceLock::new();

impl ZipCli {
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DESCRIPTION: &'static str = env!("CARGO_PKG_DESCRIPTION");

pub const INTERNAL_ERROR_EXIT_CODE: i32 = 3;
pub const ARGV_PARSE_FAILED_EXIT_CODE: i32 = 2;
pub const NON_FAILURE_EXIT_CODE: i32 = 0;

pub fn binary_name() -> &'static str {
PARSED_EXE_NAME.get().expect("binary name was not set yet")
}

fn generate_version_text() -> String {
format!("{} {}\n", Self::binary_name(), Self::VERSION)
}

fn generate_usage_line() -> String {
format!("Usage: {} [OPTIONS] <COMMAND>", Self::binary_name())
}

fn generate_full_help_text() -> String {
format!(
"\
{}
{}
Commands:
{}{}{}
{}{}{}
{}{}{}
Options:
-v, --verbose Write information logs to stderr
-h, --help Print help
-V, --version Print version
",
Self::DESCRIPTION,
Self::generate_usage_line(),
compress::Compress::COMMAND_NAME,
compress::Compress::COMMAND_TABS,
compress::Compress::COMMAND_DESCRIPTION,
info::Info::COMMAND_NAME,
info::Info::COMMAND_TABS,
info::Info::COMMAND_DESCRIPTION,
extract::Extract::COMMAND_NAME,
extract::Extract::COMMAND_TABS,
extract::Extract::COMMAND_DESCRIPTION,
)
}

fn generate_brief_help_text(context: &str) -> String {
format!(
"\
error: {context}
{}
For more information, try '--help'.
",
Self::generate_usage_line()
)
}

fn parse_up_to_subcommand_name(
argv: &mut VecDeque<OsString>,
) -> Result<(bool, SubcommandName), ArgParseError> {
let mut verbose: bool = false;
let mut subcommand_name: Option<SubcommandName> = None;
while subcommand_name.is_none() {
match argv.pop_front() {
None => {
let help_text = Self::generate_full_help_text();
return Err(ArgParseError::StderrMessage(help_text));
}
Some(arg) => match arg.as_encoded_bytes() {
b"-v" | b"--verbose" => verbose = true,
b"-V" | b"--version" => {
let version_text = Self::generate_version_text();
return Err(ArgParseError::StdoutMessage(version_text));
}
b"-h" | b"--help" => {
let help_text = Self::generate_full_help_text();
return Err(ArgParseError::StdoutMessage(help_text));
}
b"compress" => subcommand_name = Some(SubcommandName::Compress),
b"info" => subcommand_name = Some(SubcommandName::Info),
b"extract" => subcommand_name = Some(SubcommandName::Extract),
arg_bytes => {
let context = if arg_bytes.starts_with(b"-") {
format!("unrecognized global flag {arg:?}")
} else {
format!("unrecognized subcommand name {arg:?}")
};
let help_text = Self::generate_brief_help_text(&context);
return Err(ArgParseError::StderrMessage(help_text));
}
},
}
}
Ok((verbose, subcommand_name.unwrap()))
}

pub fn parse_argv(argv: impl IntoIterator<Item = OsString>) -> Result<Self, ArgParseError> {
let mut argv: VecDeque<OsString> = argv.into_iter().collect();
let exe_name: String = argv
.pop_front()
.expect("exe name not on command line")
.into_string()
.expect("exe name not valid unicode");
PARSED_EXE_NAME
.set(exe_name)
.expect("exe name already written");
let (verbose, subcommand_name) = Self::parse_up_to_subcommand_name(&mut argv)?;
let command = match subcommand_name {
SubcommandName::Info => ZipCommand::Info(info::Info::parse_argv(argv)?),
SubcommandName::Extract => ZipCommand::Extract(extract::Extract::parse_argv(argv)?),
SubcommandName::Compress => ZipCommand::Compress(compress::Compress::parse_argv(argv)?),
};
Ok(Self { verbose, command })
}
}

#[derive(Debug)]
pub enum ZipCommand {
Compress(compress::Compress),
Info(info::Info),
Extract(extract::Extract),
}

pub trait CommandFormat: fmt::Debug {
const COMMAND_NAME: &'static str;
const COMMAND_TABS: &'static str;
const COMMAND_DESCRIPTION: &'static str;

const USAGE_LINE: &'static str;

fn generate_usage_line() -> String {
format!(
"Usage: {} {} {}",
ZipCli::binary_name(),
Self::COMMAND_NAME,
Self::USAGE_LINE,
)
}

fn generate_help() -> String;

fn generate_full_help_text() -> String {
format!(
"\
{}
{}
{}",
Self::COMMAND_DESCRIPTION,
Self::generate_usage_line(),
Self::generate_help(),
)
}

fn generate_brief_help_text(context: &str) -> String {
format!(
"\
error: {context}
{}
",
Self::generate_usage_line()
)
}

fn exit_arg_invalid(context: &str) -> ArgParseError {
let message = Self::generate_brief_help_text(context);
ArgParseError::StderrMessage(message)
}

fn parse_argv(argv: VecDeque<OsString>) -> Result<Self, ArgParseError>
where
Self: Sized;
}

pub mod compress;
pub mod extract;
pub mod info;
Loading

0 comments on commit d4041b6

Please sign in to comment.