Skip to content

Commit

Permalink
make much more subcommand logic generic through traits
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmicexplorer committed Aug 21, 2024
1 parent 618a1b5 commit 52bdedb
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 79 deletions.
180 changes: 132 additions & 48 deletions cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{collections::VecDeque, ffi::OsString, path::PathBuf, sync::OnceLock};
use std::{collections::VecDeque, ffi::OsString, sync::OnceLock};

#[derive(Debug)]
pub enum ArgParseError {
Expand Down Expand Up @@ -29,11 +29,6 @@ impl ZipCli {
pub const ARGV_PARSE_FAILED_EXIT_CODE: i32 = 2;
pub const NON_FAILURE_EXIT_CODE: i32 = 0;

pub const INFO_DESCRIPTION: &'static str =
"(TODO) Print info about archive contents and individual entries.";
pub const EXTRACT_DESCRIPTION: &'static str =
"(TODO) Extract individual entries or an entire archive to stdout or the filesystem.";

pub fn binary_name() -> &'static str {
PARSED_EXE_NAME.get().expect("binary name was not set yet")
}
Expand All @@ -54,9 +49,9 @@ impl ZipCli {
{}
Commands:
{} {}
info {}
extract {}
{}{}{}
{} {}
{}{}{}
Options:
-v, --verbose Write information logs to stderr
Expand All @@ -66,9 +61,13 @@ Options:
Self::DESCRIPTION,
Self::generate_usage_line(),
compress::Compress::COMMAND_NAME,
compress::Compress::COMMAND_TABS,
compress::Compress::COMMAND_DESCRIPTION,
Self::INFO_DESCRIPTION,
Self::EXTRACT_DESCRIPTION,
info::Info::COMMAND_NAME,
info::Info::COMMAND_DESCRIPTION,
extract::Extract::COMMAND_NAME,
extract::Extract::COMMAND_TABS,
extract::Extract::COMMAND_DESCRIPTION,
)
}

Expand Down Expand Up @@ -137,7 +136,7 @@ For more information, try '--help'.
let (verbose, subcommand_name) = Self::parse_up_to_subcommand_name(&mut argv)?;
let command = match subcommand_name {
SubcommandName::Info => ZipCommand::Info,
SubcommandName::Extract => ZipCommand::Extract,
SubcommandName::Extract => ZipCommand::Extract(extract::Extract::parse_argv(argv)?),
SubcommandName::Compress => ZipCommand::Compress(compress::Compress::parse_argv(argv)?),
};
Ok(Self { verbose, command })
Expand All @@ -148,11 +147,63 @@ For more information, try '--help'.
pub enum ZipCommand {
Compress(compress::Compress),
Info,
Extract,
Extract(extract::Extract),
}

pub trait CommandFormat {
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 {
use super::{ArgParseError, ZipCli};
use super::{ArgParseError, CommandFormat};

use std::{collections::VecDeque, ffi::OsString, num::ParseIntError, path::PathBuf};

Expand Down Expand Up @@ -218,25 +269,18 @@ pub mod compress {
const ZSTD_HELP_LINE: &'static str = " - zstd: with zstd\n";
#[cfg(not(feature = "zstd"))]
const ZSTD_HELP_LINE: &'static str = "";
}

pub const COMMAND_NAME: &'static str = "compress";
pub const COMMAND_DESCRIPTION: &'static str = "Generate a zip archive from files, directories, and symlinks provided as arguments or read from filesystem paths.";
impl CommandFormat for Compress {
const COMMAND_NAME: &'static str = "compress";
const COMMAND_TABS: &'static str = "\t";
const COMMAND_DESCRIPTION: &'static str = "Generate a zip archive from files, directories, and symlinks provided as arguments or read from filesystem paths.";

fn generate_usage_line() -> String {
format!(
"Usage: {} {} [-h|--help] [OUTPUT-FLAG] [ENTRIES]... [--] [PATH]...",
ZipCli::binary_name(),
Self::COMMAND_NAME,
)
}
const USAGE_LINE: &'static str = "[-h|--help] [OUTPUT-FLAG] [ENTRIES]... [--] [PATH]...";

fn generate_full_help_text() -> String {
fn generate_help() -> String {
format!(
"\
{}
{}
"
-h, --help Print help
Output flags:
Expand Down Expand Up @@ -342,31 +386,13 @@ Positional entries:
reproducing files and symlinks.
Socket paths will produce an error.
",
Self::COMMAND_DESCRIPTION,
Self::generate_usage_line(),
Self::DEFLATE64_HELP_LINE,
Self::BZIP2_HELP_LINE,
Self::ZSTD_HELP_LINE,
)
}

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

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

pub fn parse_argv(mut argv: VecDeque<OsString>) -> Result<Self, ArgParseError> {
fn parse_argv(mut argv: VecDeque<OsString>) -> Result<Self, ArgParseError> {
let mut allow_stdout: bool = false;
let mut append_to_output_path: bool = false;
let mut output_path: Option<PathBuf> = None;
Expand Down Expand Up @@ -603,4 +629,62 @@ error: {context}
})
}
}

impl crate::driver::ExecuteCommand for Compress {
fn execute(self, err: impl std::io::Write) -> Result<(), crate::CommandError> {
crate::compress::execute_compress(err, self)
}
}
}

pub mod info {
#[derive(Debug)]
pub struct Info {}

impl Info {
pub const COMMAND_NAME: &'static str = "info";
pub const COMMAND_DESCRIPTION: &'static str =
"(TODO) Print info about archive contents and individual entries.";

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
}
}

pub mod extract {
use super::{ArgParseError, CommandFormat};

use std::{collections::VecDeque, ffi::OsString, path::PathBuf};

#[derive(Debug)]
pub enum ExtractArg {
Glob,
}

#[derive(Debug)]
pub enum OutputCollation {
Concatenate { output_path: Option<PathBuf> },
Filesystem { output_dir: PathBuf },
}

#[derive(Debug)]
pub struct Extract {
pub collation: OutputCollation,
pub args: Vec<ExtractArg>,
pub positional_names: Vec<String>,
}

impl CommandFormat for Extract {
const COMMAND_NAME: &'static str = "extract";
const COMMAND_TABS: &'static str = "\t";
const COMMAND_DESCRIPTION: &'static str =
"Extract individual entries or an entire archive into a stream or the filesystem.";

const USAGE_LINE: &'static str = "???";

fn generate_help() -> String {
"\n???".to_string()
}

fn parse_argv(_argv: VecDeque<OsString>) -> Result<Self, ArgParseError> {
todo!()
}
}
}
11 changes: 4 additions & 7 deletions cli/src/compress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use zip::{
use crate::{args::compress::*, CommandError, OutputHandle, WrapCommandErr};

fn enter_recursive_dir_entries(
err: &mut crate::ErrHandle<impl Write>,
err: &mut impl Write,
base_rename: Option<String>,
root: &Path,
writer: &mut ZipWriter<impl Write + Seek>,
Expand Down Expand Up @@ -127,10 +127,7 @@ fn enter_recursive_dir_entries(
Ok(())
}

pub fn execute_compress(
err: &mut crate::ErrHandle<impl Write>,
args: Compress,
) -> Result<(), CommandError> {
pub fn execute_compress(mut err: impl Write, args: Compress) -> Result<(), CommandError> {
let Compress {
allow_stdout,
append_to_output_path,
Expand Down Expand Up @@ -402,7 +399,7 @@ pub fn execute_compress(
"writing recursive dir entries for path {r:?} with name {last_name:?}"
)
.unwrap();
enter_recursive_dir_entries(err, last_name.take(), &r, &mut writer, options)?;
enter_recursive_dir_entries(&mut err, last_name.take(), &r, &mut writer, options)?;
}
}
}
Expand Down Expand Up @@ -469,7 +466,7 @@ pub fn execute_compress(
"writing positional recursive dir entry for {pos_arg:?}"
)
.unwrap();
enter_recursive_dir_entries(err, None, &pos_arg, &mut writer, options)?;
enter_recursive_dir_entries(&mut err, None, &pos_arg, &mut writer, options)?;
}
}

Expand Down
58 changes: 34 additions & 24 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,39 @@ pub mod driver {
use std::io::{self, Write};
use std::process;

use super::args::{compress::Compress, ArgParseError, ZipCli, ZipCommand};
use super::compress::execute_compress;
use super::args::{ArgParseError, CommandFormat, ZipCli, ZipCommand};
use super::{CommandError, ErrHandle};

pub trait ExecuteCommand: CommandFormat {
fn execute(self, err: impl Write) -> Result<(), CommandError>;

fn do_main(self, err: impl Write) -> !
where
Self: Sized,
{
match self.execute(err) {
Ok(()) => process::exit(ZipCli::NON_FAILURE_EXIT_CODE),
Err(e) => match e {
CommandError::InvalidArg(msg) => {
let msg = Self::generate_brief_help_text(&msg);
let _ = io::stderr().write_all(msg.as_bytes());
process::exit(ZipCli::ARGV_PARSE_FAILED_EXIT_CODE);
}
CommandError::Io(context, e) => {
let msg = format!("i/o error: {context}: {e}\n");
let _ = io::stderr().write_all(msg.as_bytes());
process::exit(ZipCli::INTERNAL_ERROR_EXIT_CODE);
}
CommandError::Zip(context, e) => {
let msg = format!("zip error: {context}: {e}\n");
let _ = io::stderr().write_all(msg.as_bytes());
process::exit(ZipCli::INTERNAL_ERROR_EXIT_CODE);
}
},
}
}
}

pub fn main() {
let ZipCli { verbose, command } = match ZipCli::parse_argv(env::args_os()) {
Ok(cli) => cli,
Expand All @@ -122,35 +151,16 @@ pub mod driver {
}
},
};
let mut err = if verbose {
let err = if verbose {
ErrHandle::Output(io::stderr())
} else {
ErrHandle::NoOutput
};

match command {
ZipCommand::Info => todo!("info command not implemented"),
ZipCommand::Extract => todo!("extract command not implemented"),
ZipCommand::Compress(compress) => match execute_compress(&mut err, compress) {
Ok(()) => (),
Err(e) => match e {
CommandError::InvalidArg(msg) => {
let msg = Compress::generate_brief_help_text(&msg);
let _ = io::stderr().write_all(msg.as_bytes());
process::exit(ZipCli::ARGV_PARSE_FAILED_EXIT_CODE);
}
CommandError::Io(context, e) => {
let msg = format!("i/o error: {context}: {e}\n");
let _ = io::stderr().write_all(msg.as_bytes());
process::exit(ZipCli::INTERNAL_ERROR_EXIT_CODE);
}
CommandError::Zip(context, e) => {
let msg = format!("zip error: {context}: {e}\n");
let _ = io::stderr().write_all(msg.as_bytes());
process::exit(ZipCli::INTERNAL_ERROR_EXIT_CODE);
}
},
},
ZipCommand::Extract(_extract) => todo!("extract command not implemented"),
ZipCommand::Compress(compress) => compress.do_main(err),
}
}
}

0 comments on commit 52bdedb

Please sign in to comment.