Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
73 changes: 48 additions & 25 deletions src/uu/du/src/du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@ fn read_files_from(file_name: &OsStr) -> Result<Vec<PathBuf>, std::io::Error> {
Ok(paths)
}

fn get_block_size_arg_index_if_present(matches: &ArgMatches, flag: &str) -> Option<usize> {
fn get_size_format_flag_arg_index_if_present(matches: &ArgMatches, flag: &str) -> Option<usize> {
if matches.get_flag(flag) {
// Indices of returns index even if flag is not present, thats why we need to if guard it
matches
Expand All @@ -974,27 +974,64 @@ fn get_block_size_arg_index_if_present(matches: &ArgMatches, flag: &str) -> Opti
}
}

fn handle_block_size_arg_override(matches: &ArgMatches) -> Option<SizeFormat> {
fn get_size_format_string_arg_index_if_present(matches: &ArgMatches, flag: &str) -> Option<usize> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The test cases are great and testing around locally it seems great to me, I think we can make it a bit cleaner though. Do you think we could seperate into a helper function the parsing of the explicit block size argument?

fn parse_block_size_option(matches: &ArgMatches) -> UResult<Option<SizeFormat>> {
    matches
        .get_one::<String>(options::BLOCK_SIZE)
        .map(|s| {
            let bs = read_block_size(Some(s.as_ref()))?;
            if bs == 0 {
                return Err(std::io::Error::other(translate!(
                    "du-error-invalid-block-size-argument",
                    "option" => options::BLOCK_SIZE,
                    "value" => s.quote()
                ))
                .into());
            }
            Ok(SizeFormat::BlockSize(bs))
        })
        .transpose()
}

Then it can make that section below smaller

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I found yet another bug ... gnu always errors if the value for block-size is erroneous while upstream main (1 commit before #10664) does not because it only looks at block-size if no other option is present.

I will incorporate a more concise version and look at your suggestions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The test cases are great and testing around locally it seems great to me, I think we can make it a bit cleaner though. Do you think we could seperate into a helper function the parsing of the explicit block size argument?

fn parse_block_size_option(matches: &ArgMatches) -> UResult<Option<SizeFormat>> {
    matches
        .get_one::<String>(options::BLOCK_SIZE)
        .map(|s| {
            let bs = read_block_size(Some(s.as_ref()))?;
            if bs == 0 {
                return Err(std::io::Error::other(translate!(
                    "du-error-invalid-block-size-argument",
                    "option" => options::BLOCK_SIZE,
                    "value" => s.quote()
                ))
                .into());
            }
            Ok(SizeFormat::BlockSize(bs))
        })
        .transpose()
}

Then it can make that section below smaller

We cannot use map on the get_one because in read_block_size() a fallback value is define in case the parameter is None. If we use this in .map() we would never get that fallback value.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think you can also combine these two sections by using the value_source since it will work for both string and flag

        matches
            .value_source(arg)
            .is_some_and(|s| s == clap::parser::ValueSource::CommandLine)
            .then(|| matches.indices_of(arg).and_then(|mut i| i.next_back()))
            .flatten()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So I'm giving a bunch of suggestions, not to say that there's a right way to do that, feel free to not use any of them I just think its getting a bit verbose and hard to read so I'm just going to throw a bunch of ideas and feel free to try any that you think would make it easier to read?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wasn't experienced enough with the clap api to know about value_source(). This is ofc a massive improvement in code reusability and conciseness. Thanks!

if matches.get_one::<String>(flag).is_some() {
// Indices of returns index even if flag is not present, thats why we need to if guard it
matches
.indices_of(flag)
.and_then(|mut indices| indices.next_back())
} else {
None
}
}

fn handle_block_size_arg_override(matches: &ArgMatches) -> UResult<SizeFormat> {
let candidates = [
(
SizeFormat::BlockSize(1),
get_block_size_arg_index_if_present(matches, options::BYTES),
Some(SizeFormat::BlockSize(1)),
get_size_format_flag_arg_index_if_present(matches, options::BYTES),
),
(
Some(SizeFormat::BlockSize(1024)),
get_size_format_flag_arg_index_if_present(matches, options::BLOCK_SIZE_1K),
),
(
Some(SizeFormat::BlockSize(1024 * 1024)),
get_size_format_flag_arg_index_if_present(matches, options::BLOCK_SIZE_1M),
),
(
Some(SizeFormat::HumanBinary),
get_size_format_flag_arg_index_if_present(matches, options::HUMAN_READABLE),
),
(
SizeFormat::BlockSize(1024),
get_block_size_arg_index_if_present(matches, options::BLOCK_SIZE_1K),
Some(SizeFormat::HumanDecimal),
get_size_format_flag_arg_index_if_present(matches, options::SI),
),
// for block size we want to handle this not in closure but in else block later
(
SizeFormat::BlockSize(1024 * 1024),
get_block_size_arg_index_if_present(matches, options::BLOCK_SIZE_1M),
None,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How about you call the function to read the block size here and then you don't need to have the call into it later?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I called it even before to only use it once and then use the return value for unwrap_or().

Otherwise i didn't see a way to only call it once with the candidates as i feel like there was no sensible way to ensure it being the fallback in case no arg was present and we needed the fallback value from that call.

Obv we could rewrite the read_block_size func to not be the fallback either... maybe this would be a cleaner approach.

get_size_format_string_arg_index_if_present(matches, options::BLOCK_SIZE),
),
];

candidates
let ret = candidates
.into_iter()
.filter(|(_, idx)| idx.is_some())
.max_by_key(|&(_, idx)| idx.unwrap_or(0))
.map(|(size_format, _)| size_format)
.map(|(size_format, _)| size_format);

if let Some(Some(size_format)) = ret {
Ok(size_format)
} else {
// we handle block_size option here manually
let block_size_str = matches.get_one::<String>(options::BLOCK_SIZE);
let block_size = read_block_size(block_size_str.map(AsRef::as_ref))?;
if block_size == 0 {
return Err(std::io::Error::other(translate!("du-error-invalid-block-size-argument", "option" => options::BLOCK_SIZE, "value" => block_size_str.map_or("???BUG", |v| v).quote()))
.into());
}
Ok(SizeFormat::BlockSize(block_size))
}
}

#[uucore::main]
Expand Down Expand Up @@ -1048,21 +1085,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.map_or(MetadataTimeField::Modification, |s| s.as_str().into())
});

let size_format = if matches.get_flag(options::HUMAN_READABLE) {
SizeFormat::HumanBinary
} else if matches.get_flag(options::SI) {
SizeFormat::HumanDecimal
} else if let Some(size_format) = handle_block_size_arg_override(&matches) {
size_format
} else {
let block_size_str = matches.get_one::<String>(options::BLOCK_SIZE);
let block_size = read_block_size(block_size_str.map(AsRef::as_ref))?;
if block_size == 0 {
return Err(std::io::Error::other(translate!("du-error-invalid-block-size-argument", "option" => options::BLOCK_SIZE, "value" => block_size_str.map_or("???BUG", |v| v).quote()))
.into());
}
SizeFormat::BlockSize(block_size)
};
let size_format = handle_block_size_arg_override(&matches)?;

let traversal_options = TraversalOptions {
all: matches.get_flag(options::ALL),
Expand Down
52 changes: 40 additions & 12 deletions tests/by-util/test_du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2064,24 +2064,45 @@ fn test_block_size_args_override() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
let dir = "override_args_dir";
let nested_dir = "override_args_dir/nested_dir";
let nested_dir_2 = "override_args_dir/nested_dir_2";

at.mkdir(dir);
let fpath = at.plus(format!("{dir}/file"));
at.mkdir_all(nested_dir);
at.mkdir_all(nested_dir_2);
let fpath = at.plus(format!("{nested_dir}/file"));
std::fs::File::create(fpath)
.expect("cannot create test file")
.set_len(100_000_000)
.expect("cannot set file size");

let fpath2 = at.plus(format!("{dir}/file_2"));
let fpath2 = at.plus(format!("{nested_dir}/file_2"));
std::fs::File::create(fpath2)
.expect("cannot create test file")
.set_len(100_000_000)
.expect("cannot set file size");

let fpath = at.plus(format!("{nested_dir_2}/file_3"));
std::fs::File::create(fpath)
.expect("cannot create test file")
.set_len(100_000)
.expect("cannot set file size");

let fpath2 = at.plus(format!("{nested_dir_2}/file_4"));
std::fs::File::create(fpath2)
.expect("cannot create test file")
.set_len(100)
.expect("cannot set file size");

let test_cases = [
(["-sk", "-m"], "-sm"),
(["-sk", "-b"], "-sb"),
(["-sm", "-k"], "-sk"),
(["-sk", "-m"], vec!["-sm"]),
(["-sk", "-b"], vec!["-sb"]),
(["-sm", "-k"], vec!["-sk"]),
(["-sk", "--si"], vec!["-s", "--si"]),
(["-sk", "-h"], vec!["-s", "-h"]),
(["-sm", "--block-size=128"], vec!["-s", "--block-size=128"]),
(["--block-size=128", "-b"], vec!["-b"]),
(["--si", "-b"], vec!["-b"]),
(["-h", "-b"], vec!["-b"]),
];

for (idx, (overwriting_args, expected)) in test_cases.into_iter().enumerate() {
Expand All @@ -2095,7 +2116,7 @@ fn test_block_size_args_override() {
let single_args = ts
.ucmd()
.arg(dir)
.arg(expected)
.args(&expected)
.succeeds()
.stdout_move_str();

Expand All @@ -2111,23 +2132,30 @@ fn test_block_override_b_still_has_apparent_size() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
let dir = "override_args_dir";
let nested_dir = "override_args_dir/nested_dir";

at.mkdir(dir);
let fpath = at.plus(format!("{dir}/file"));
at.mkdir_all(nested_dir);
let fpath = at.plus(format!("{nested_dir}/file"));
std::fs::File::create(fpath)
.expect("cannot create test file")
.set_len(100_000_000)
.expect("cannot set file size");

let fpath2 = at.plus(format!("{dir}/file_2"));
let fpath2 = at.plus(format!("{nested_dir}/file_2"));
std::fs::File::create(fpath2)
.expect("cannot create test file")
.set_len(100_000_000)
.expect("cannot set file size");

let test_cases = [
(["-sb", "-m"], ["-sm", "--apparent-size"]),
(["-sb", "-k"], ["-sk", "--apparent-size"]),
(["-b", "-m"], ["-m", "--apparent-size"]),
(["-b", "-k"], ["-k", "--apparent-size"]),
(["-b", "--si"], ["--si", "--apparent-size"]),
(["-b", "-h"], ["-h", "--apparent-size"]),
(
["-b", "--block-size=128"],
["--block-size=128", "--apparent-size"],
),
];

for (idx, (overwriting_args, expected)) in test_cases.into_iter().enumerate() {
Expand Down
Loading