Skip to content
Open
Show file tree
Hide file tree
Changes from 18 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 = "7.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,12 +4,29 @@
// 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::{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 @@ -24,7 +41,7 @@ use uucore::error::UResult;
/// - 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 @@ -38,7 +55,7 @@ pub fn create_archive(archive_path: &Path, files: &[&Path], verbose: bool) -> UR
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
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
Copy Markdown
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
Copy Markdown
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;
92 changes: 92 additions & 0 deletions src/uu/tar/src/options/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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.
#[allow(dead_code)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we avoid this ?
it is usually a sign that the code needs to be refactored

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Turns out the lint wasn't needed. But there was dead enum variants and methods for TarParams and Options, those were removed and commited.

#[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 {
// TODO: update messaging
Err(Box::new(TarError::TarOperationError(
"Error processing: Operations".to_string(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yeah, we should probably provide more information in case of errors, no?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes I added the ids of the arguments passed in the error message so if the error is ever called it will provide some information. Clap will catch the malformed or non-existent args so this error message should never get executed but if it does we will have a thread to pull on.

In the future once we get more consistent language we can change the operation error messaging.

)))
}
}
}

#[allow(dead_code)]
impl TarParams {
pub fn files(&self) -> &Vec<PathBuf> {
&self.files
}
pub fn files_mut(&mut self) -> &mut Vec<PathBuf> {
&mut self.files
}
pub fn archive(&self) -> &PathBuf {
&self.archive
}
pub fn archive_mut(&mut self) -> &mut PathBuf {
&mut 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
#[allow(dead_code)]
pub enum TarOption {
AbsoluteNames,
ACLs,
AfterDate,
Anchored,
AtimePreserve { arg: String },
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