Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
28a49a6
Adding TarParam and TarOperation framework
stillbeingnick Nov 23, 2025
c156fe1
changing arg match taroption processing
stillbeingnick Dec 15, 2025
5223477
fmt and derive default tarparams
stillbeingnick Dec 16, 2025
6a77bb8
Merge branch 'main' into feat/taroperation-tarparam
stillbeingnick Dec 16, 2025
6d0213b
merge for create verbose
stillbeingnick Dec 16, 2025
9f23bf2
cargo workspace dependency fixes
stillbeingnick Dec 29, 2025
818e1bd
Adding TarParam and TarOperation framework
stillbeingnick Nov 23, 2025
4e43d5e
changing arg match taroption processing
stillbeingnick Dec 15, 2025
e5a9b0d
fmt and derive default tarparams
stillbeingnick Dec 16, 2025
0e9970b
cargo workspace dependency fixes
stillbeingnick Dec 29, 2025
86e5ae9
Merge local to origin
stillbeingnick Dec 29, 2025
b1b486f
Adding TarParam and TarOperation framework
stillbeingnick Nov 23, 2025
4bef208
changing arg match taroption processing
stillbeingnick Dec 15, 2025
c959d90
Revert "changing arg match taroption processing"
stillbeingnick Dec 29, 2025
17534ac
Merge remote-tracking branch 'origin/feat/taroperation-tarparam' into…
stillbeingnick Dec 29, 2025
d771c52
cargo workspace dependency fixes
stillbeingnick Dec 29, 2025
df20a72
Adding TarParam and TarOperation framework
stillbeingnick Nov 23, 2025
79e4915
Merge remote-tracking branch 'origin/feat/taroperation-tarparam' into…
stillbeingnick Dec 30, 2025
bc044a2
remove dead and detail tarparam error message
stillbeingnick Feb 15, 2026
9ee96b2
Merge branch 'main' into feat/taroperation-tarparam
stillbeingnick Feb 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ tempfile = "3.10.1"
textwrap = { version = "0.16.1", features = ["terminal_size"] }
xattr = "1.3.1"
zip = "8.0"
jiff = "0.2.16"

[dependencies]
clap = { workspace = true }
Expand All @@ -63,8 +64,9 @@ uutests = { workspace = true }
uucore = { workspace = true }
uuhelp_parser = { workspace = true, optional = true }
zip = { workspace = true, optional = true }

tar = { optional = true, version = "0.0.1", package = "uu_tar", path = "src/uu/tar" }
jiff = { workspace = true }


[dev-dependencies]
chrono = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions src/uu/tar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ uucore = { workspace = true }
clap = { workspace = true }
regex = { workspace = true }
tar = { workspace = true }
jiff = { workspace = true }

[lib]
path = "src/tar.rs"
Expand Down
21 changes: 19 additions & 2 deletions src/uu/tar/src/operations/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,30 @@
// file that was distributed with this source code.

use crate::errors::TarError;
use crate::operations::TarOperation;
use crate::options::{TarOption, TarParams};
use std::collections::VecDeque;
use std::fs::{self, File};
use std::path::Component::{self, ParentDir, Prefix, RootDir};
use std::path::{self, Path, PathBuf};
use tar::Builder;
use uucore::error::UResult;

pub struct Create;

impl TarOperation for Create {
fn exec(&self, options: &TarParams) -> UResult<()> {
create_archive(
options.archive(),
options.files().as_slice(),
options
.options()
.iter()
.any(|x| matches!(x, TarOption::Verbose)),
)
}
}

/// Create a tar archive from the specified files
///
/// # Arguments
Expand All @@ -25,7 +42,7 @@
/// - The archive file cannot be created
/// - Any input file cannot be read
/// - Files cannot be added due to I/O or permission errors
pub fn create_archive(archive_path: &Path, files: &[&Path], verbose: bool) -> UResult<()> {
pub fn create_archive(archive_path: &Path, files: &[PathBuf], verbose: bool) -> UResult<()> {
// Create the output file
let file = File::create(archive_path).map_err(|e| {
TarError::TarOperationError(format!(
Expand All @@ -39,7 +56,7 @@
let mut builder = Builder::new(file);

// Add each file or directory to the archive
for &path in files {
for path in files {
// Check if path exists
if !path.exists() {
return Err(TarError::FileNotFound(path.display().to_string()).into());
Expand Down Expand Up @@ -75,7 +92,7 @@

normalized
} else {
path.to_path_buf()

Check failure on line 95 in src/uu/tar/src/operations/create.rs

View workflow job for this annotation

GitHub Actions / Style/lint (ubuntu-latest)

ERROR: `cargo clippy`: implicitly cloning a `PathBuf` by calling `to_path_buf` on its dereferenced type (file:'src/uu/tar/src/operations/create.rs', line:95)

Check failure on line 95 in src/uu/tar/src/operations/create.rs

View workflow job for this annotation

GitHub Actions / Style/lint (macos-latest)

ERROR: `cargo clippy`: implicitly cloning a `PathBuf` by calling `to_path_buf` on its dereferenced type (file:'src/uu/tar/src/operations/create.rs', line:95)
};

// If it's a directory, recursively add all contents
Expand Down
16 changes: 16 additions & 0 deletions src/uu/tar/src/operations/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@
// file that was distributed with this source code.

use crate::errors::TarError;
use crate::operations::operation::TarOperation;
use crate::options::options::{TarOption, TarParams};
use std::fs::File;
use std::path::Path;
use tar::Archive;
use uucore::error::UResult;

pub(crate) struct Extract;

impl TarOperation for Extract {
fn exec(&self, options: &TarParams) -> UResult<()> {
extract_archive(
options.archive(),
options
.options()
.iter()
.any(|x| matches!(x, TarOption::Verbose)),
)
}
}

/// Extract files from a tar archive
///
/// # Arguments
Expand Down
6 changes: 6 additions & 0 deletions src/uu/tar/src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@

pub mod create;
pub mod extract;
pub mod operation;

pub(crate) use self::create::Create;
pub(crate) use self::extract::Extract;
pub(crate) use self::operation::OperationKind;
pub use self::operation::TarOperation;
58 changes: 58 additions & 0 deletions src/uu/tar/src/operations/operation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::errors::TarError;
use crate::operations::Create;
use crate::operations::Extract;
use crate::options::TarParams;
use uucore::error::UResult;

/// The [`OperationKind`] Enum representation of Acdtrux arguments which is
/// later leveraged as selector for enum dispatch by the [`TarOperation`]
/// trait
pub enum OperationKind {
Concatenate,
Create,
Diff,
List,
Append,
Update,
Extract,
}

impl TryFrom<&str> for OperationKind {
type Error = TarError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"concate" => Ok(Self::Concatenate),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"concate" => Ok(Self::Concatenate),
"concatenate" => Ok(Self::Concatenate),

no ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"concate" => Ok(Self::Concatenate),
"catenate" | "concatenate" => Ok(Self::Concatenate),

Ya actually both gnu tar takes both -> All Tar options, BSD tar doesn't have concatenate. Also correct the spelling in this one, thank you for the catch!

"create" => Ok(Self::Create),
"diff" => Ok(Self::Diff),
"list" => Ok(Self::List),
"append" => Ok(Self::Append),
"update" => Ok(Self::Update),
"extract" => Ok(Self::Extract),
_ => Err(TarError::TarOperationError(format!(
"Invalid operation selected: {}",
value
))),
}
}
}

impl TarOperation for OperationKind {
fn exec(&self, options: &TarParams) -> UResult<()> {
match self {
Self::List => unimplemented!(),
Self::Create => Create.exec(options),
Self::Diff => unimplemented!(),
Self::Append => unimplemented!(),
Self::Update => unimplemented!(),
Self::Extract => Extract.exec(options),
Self::Concatenate => unimplemented!(),
}
}
}

/// [`TarOperation`] allows enum dispatch by enforcing the impl of the
/// trait to create the functionality to perform the operation requested via
/// the command line arg for this execution of tar
pub trait TarOperation {
fn exec(&self, options: &TarParams) -> UResult<()>;
}
6 changes: 6 additions & 0 deletions src/uu/tar/src/options/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// re-exported to remove the redundant level of inception in
// the module tree
#[allow(clippy::module_inception)]
pub mod options;
pub use crate::options::options::TarOption;
pub use crate::options::options::TarParams;
81 changes: 81 additions & 0 deletions src/uu/tar/src/options/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::errors::TarError;
use crate::operations::OperationKind;
use clap::ArgMatches;
use std::path::PathBuf;
use uucore::error::UResult;

/// [`TarParams`] Holds common information that is parsed from
/// command line arguments. That changes the current execution of
/// tar.
#[derive(Default)]
pub struct TarParams {
archive: PathBuf,
files: Vec<PathBuf>,
options: Vec<TarOption>,
}

impl From<&ArgMatches> for TarParams {
fn from(matches: &ArgMatches) -> TarParams {
let mut ops = Self::default();

// -v --verbose
if matches.get_flag("verbose") {
ops.options_mut().push(TarOption::Verbose);
}

// [FILES]...
if let Some(files) = matches.get_many::<PathBuf>("files") {
ops.files = files.map(|x| x.to_owned()).collect();
}

// -f --file
if let Some(a) = matches.get_one::<PathBuf>("file") {
ops.archive = a.to_owned();
}

ops
}
}

impl TarParams {
/// Convence method that parses the [`ArgMatches`]
/// processed by clap into [`TarParams`] and selects
/// the appropriate [`OperationKind`] for execution given back to the caller in a
/// tuple of ([`OperationKind`], [`TarParams`])
pub fn with_operation(matches: &ArgMatches) -> UResult<(OperationKind, Self)> {
if matches.get_flag("create") {
Ok((OperationKind::Create, Self::from(matches)))
} else if matches.get_flag("extract") {
Ok((OperationKind::Extract, Self::from(matches)))
} else {
Err(Box::new(TarError::TarOperationError(format!(
"Error processing: Unknown or Unimplmented Parameters: {:?}",
matches
.ids()
.map(|i| i.to_string())
.collect::<Vec<String>>()
))))
}
}
}

impl TarParams {
pub fn files(&self) -> &Vec<PathBuf> {
&self.files
}
pub fn archive(&self) -> &PathBuf {
&self.archive
}
pub fn options(&self) -> &Vec<TarOption> {
&self.options
}
pub fn options_mut(&mut self) -> &mut Vec<TarOption> {
&mut self.options
}
}

/// [`TarOption`] Enum of avaliable tar options for later use
/// by [`TarOperation`] impls, eg. List, Create, Delete
pub enum TarOption {
Verbose,
}
43 changes: 6 additions & 37 deletions src/uu/tar/src/tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

pub mod errors;
mod operations;
mod options;

use clap::{arg, crate_version, ArgAction, Command};
use std::path::{Path, PathBuf};
use operations::operation::TarOperation;
use options::TarParams;
use std::path::PathBuf;
use uucore::error::UResult;
use uucore::format_usage;

Expand All @@ -33,43 +36,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

let matches = uu_app().try_get_matches_from(args_to_parse)?;

let verbose = matches.get_flag("verbose");
let (op, options) = TarParams::with_operation(&matches)?;

// Handle extract operation
if matches.get_flag("extract") {
let archive_path = matches.get_one::<PathBuf>("file").ok_or_else(|| {
uucore::error::USimpleError::new(1, "option requires an argument -- 'f'")
})?;

return operations::extract::extract_archive(archive_path, verbose);
}

// Handle create operation
if matches.get_flag("create") {
let archive_path = matches.get_one::<PathBuf>("file").ok_or_else(|| {
uucore::error::USimpleError::new(1, "option requires an argument -- 'f'")
})?;

let files: Vec<&Path> = matches
.get_many::<PathBuf>("files")
.map(|v| v.map(|p| p.as_path()).collect())
.unwrap_or_default();

if files.is_empty() {
return Err(uucore::error::USimpleError::new(
1,
"Cowardly refusing to create an empty archive",
));
}

return operations::create::create_archive(archive_path, &files, verbose);
}

// If no operation specified, show error
Err(uucore::error::USimpleError::new(
1,
"You must specify one of the '-c' or '-x' options",
))
op.exec(&options)
}

#[allow(clippy::cognitive_complexity)]
Expand Down
Loading