mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
Merge branch 'main' into cp-lb
This commit is contained in:
Generated
-10
@@ -890,15 +890,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.4"
|
||||
@@ -2905,7 +2896,6 @@ version = "0.0.12"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 3.0.10",
|
||||
"getopts",
|
||||
"itertools",
|
||||
"quick-error",
|
||||
"regex",
|
||||
|
||||
+336
-2
@@ -5,7 +5,7 @@
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE file
|
||||
// that was distributed with this source code.
|
||||
// spell-checker:ignore itotal iused iavail ipcent pcent
|
||||
// spell-checker:ignore itotal iused iavail ipcent pcent tmpfs squashfs
|
||||
mod table;
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -106,6 +106,11 @@ impl From<&ArgMatches> for BlockSize {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters that control the behavior of `df`.
|
||||
///
|
||||
/// Most of these parameters control which rows and which columns are
|
||||
/// displayed. The `block_size` determines the units to use when
|
||||
/// displaying numbers of bytes or inodes.
|
||||
#[derive(Default)]
|
||||
struct Options {
|
||||
show_local_fs: bool,
|
||||
@@ -115,6 +120,9 @@ struct Options {
|
||||
show_inode_instead: bool,
|
||||
block_size: BlockSize,
|
||||
fs_selector: FsSelector,
|
||||
|
||||
/// Whether to show a final row comprising the totals for each column.
|
||||
show_total: bool,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
@@ -128,6 +136,7 @@ impl Options {
|
||||
show_inode_instead: matches.is_present(OPT_INODES),
|
||||
block_size: BlockSize::from(matches),
|
||||
fs_selector: FsSelector::from(matches),
|
||||
show_total: matches.is_present(OPT_TOTAL),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,9 +316,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
.filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
|
||||
println!("{}", Header::new(&opt));
|
||||
let mut total = Row::new("total");
|
||||
for row in data {
|
||||
println!("{}", DisplayRow::new(row, &opt));
|
||||
println!("{}", DisplayRow::new(&row, &opt));
|
||||
total += row;
|
||||
}
|
||||
if opt.show_total {
|
||||
println!("{}", DisplayRow::new(&total, &opt));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -431,3 +446,322 @@ pub fn uu_app<'a>() -> App<'a> {
|
||||
)
|
||||
.arg(Arg::new(OPT_PATHS).multiple_occurrences(true))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
mod mount_info_lt {
|
||||
|
||||
use crate::mount_info_lt;
|
||||
use uucore::fsext::MountInfo;
|
||||
|
||||
/// Instantiate a [`MountInfo`] with the given fields.
|
||||
fn mount_info(dev_name: &str, mount_root: &str, mount_dir: &str) -> MountInfo {
|
||||
MountInfo {
|
||||
dev_id: String::new(),
|
||||
dev_name: String::from(dev_name),
|
||||
fs_type: String::new(),
|
||||
mount_dir: String::from(mount_dir),
|
||||
mount_option: String::new(),
|
||||
mount_root: String::from(mount_root),
|
||||
remote: false,
|
||||
dummy: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_absolute() {
|
||||
// Prefer device name "/dev/foo" over "dev_foo".
|
||||
let m1 = mount_info("/dev/foo", "/", "/mnt/bar");
|
||||
let m2 = mount_info("dev_foo", "/", "/mnt/bar");
|
||||
assert!(!mount_info_lt(&m1, &m2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shorter() {
|
||||
// Prefer mount directory "/mnt/bar" over "/mnt/bar/baz"...
|
||||
let m1 = mount_info("/dev/foo", "/", "/mnt/bar");
|
||||
let m2 = mount_info("/dev/foo", "/", "/mnt/bar/baz");
|
||||
assert!(!mount_info_lt(&m1, &m2));
|
||||
|
||||
// ..but prefer mount root "/root" over "/".
|
||||
let m1 = mount_info("/dev/foo", "/root", "/mnt/bar");
|
||||
let m2 = mount_info("/dev/foo", "/", "/mnt/bar/baz");
|
||||
assert!(mount_info_lt(&m1, &m2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_over_mounted() {
|
||||
// Prefer the earlier entry if the devices are different but
|
||||
// the mount directory is the same.
|
||||
let m1 = mount_info("/dev/foo", "/", "/mnt/baz");
|
||||
let m2 = mount_info("/dev/bar", "/", "/mnt/baz");
|
||||
assert!(!mount_info_lt(&m1, &m2));
|
||||
}
|
||||
}
|
||||
|
||||
mod is_best {
|
||||
|
||||
use crate::is_best;
|
||||
use uucore::fsext::MountInfo;
|
||||
|
||||
/// Instantiate a [`MountInfo`] with the given fields.
|
||||
fn mount_info(dev_id: &str, mount_dir: &str) -> MountInfo {
|
||||
MountInfo {
|
||||
dev_id: String::from(dev_id),
|
||||
dev_name: String::new(),
|
||||
fs_type: String::new(),
|
||||
mount_dir: String::from(mount_dir),
|
||||
mount_option: String::new(),
|
||||
mount_root: String::new(),
|
||||
remote: false,
|
||||
dummy: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
let m = mount_info("0", "/mnt/bar");
|
||||
assert!(is_best(&[], &m));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_different_dev_id() {
|
||||
let m1 = mount_info("0", "/mnt/bar");
|
||||
let m2 = mount_info("1", "/mnt/bar");
|
||||
assert!(is_best(&[m1.clone()], &m2));
|
||||
assert!(is_best(&[m2], &m1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_same_dev_id() {
|
||||
// There are several conditions under which a `MountInfo` is
|
||||
// considered "better" than the others, we're just checking
|
||||
// one condition in this test.
|
||||
let m1 = mount_info("0", "/mnt/bar");
|
||||
let m2 = mount_info("0", "/mnt/bar/baz");
|
||||
assert!(!is_best(&[m1.clone()], &m2));
|
||||
assert!(is_best(&[m2], &m1));
|
||||
}
|
||||
}
|
||||
|
||||
mod is_included {
|
||||
|
||||
use crate::{is_included, FsSelector, Options};
|
||||
use std::collections::HashSet;
|
||||
use uucore::fsext::MountInfo;
|
||||
|
||||
/// Instantiate a [`MountInfo`] with the given fields.
|
||||
fn mount_info(fs_type: &str, mount_dir: &str, remote: bool, dummy: bool) -> MountInfo {
|
||||
MountInfo {
|
||||
dev_id: String::new(),
|
||||
dev_name: String::new(),
|
||||
fs_type: String::from(fs_type),
|
||||
mount_dir: String::from(mount_dir),
|
||||
mount_option: String::new(),
|
||||
mount_root: String::new(),
|
||||
remote,
|
||||
dummy,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote_included() {
|
||||
let opt = Default::default();
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", true, false);
|
||||
assert!(is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote_excluded() {
|
||||
let opt = Options {
|
||||
show_local_fs: true,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", true, false);
|
||||
assert!(!is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dummy_included() {
|
||||
let opt = Options {
|
||||
show_all_fs: true,
|
||||
show_listed_fs: true,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, true);
|
||||
assert!(is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dummy_excluded() {
|
||||
let opt = Default::default();
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, true);
|
||||
assert!(!is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exclude_match() {
|
||||
let exclude = HashSet::from([String::from("ext4")]);
|
||||
let fs_selector = FsSelector {
|
||||
exclude,
|
||||
..Default::default()
|
||||
};
|
||||
let opt = Options {
|
||||
fs_selector,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(!is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exclude_no_match() {
|
||||
let exclude = HashSet::from([String::from("tmpfs")]);
|
||||
let fs_selector = FsSelector {
|
||||
exclude,
|
||||
..Default::default()
|
||||
};
|
||||
let opt = Options {
|
||||
fs_selector,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include_match() {
|
||||
let include = HashSet::from([String::from("ext4")]);
|
||||
let fs_selector = FsSelector {
|
||||
include,
|
||||
..Default::default()
|
||||
};
|
||||
let opt = Options {
|
||||
fs_selector,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include_no_match() {
|
||||
let include = HashSet::from([String::from("tmpfs")]);
|
||||
let fs_selector = FsSelector {
|
||||
include,
|
||||
..Default::default()
|
||||
};
|
||||
let opt = Options {
|
||||
fs_selector,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(!is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include_and_exclude_match_neither() {
|
||||
let include = HashSet::from([String::from("tmpfs")]);
|
||||
let exclude = HashSet::from([String::from("squashfs")]);
|
||||
let fs_selector = FsSelector { include, exclude };
|
||||
let opt = Options {
|
||||
fs_selector,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(!is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include_and_exclude_match_exclude() {
|
||||
let include = HashSet::from([String::from("tmpfs")]);
|
||||
let exclude = HashSet::from([String::from("ext4")]);
|
||||
let fs_selector = FsSelector { include, exclude };
|
||||
let opt = Options {
|
||||
fs_selector,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(!is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include_and_exclude_match_include() {
|
||||
let include = HashSet::from([String::from("ext4")]);
|
||||
let exclude = HashSet::from([String::from("squashfs")]);
|
||||
let fs_selector = FsSelector { include, exclude };
|
||||
let opt = Options {
|
||||
fs_selector,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include_and_exclude_match_both() {
|
||||
// TODO The same filesystem type in both `include` and
|
||||
// `exclude` should cause an error, but currently does
|
||||
// not.
|
||||
let include = HashSet::from([String::from("ext4")]);
|
||||
let exclude = HashSet::from([String::from("ext4")]);
|
||||
let fs_selector = FsSelector { include, exclude };
|
||||
let opt = Options {
|
||||
fs_selector,
|
||||
..Default::default()
|
||||
};
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(!is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_paths_empty() {
|
||||
let opt = Default::default();
|
||||
let paths = [];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_in_paths() {
|
||||
let opt = Default::default();
|
||||
let paths = [String::from("/mnt/foo")];
|
||||
let m = mount_info("ext4", "/mnt/bar", false, false);
|
||||
assert!(!is_included(&m, &paths, &opt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_in_paths() {
|
||||
let opt = Default::default();
|
||||
let paths = [String::from("/mnt/foo")];
|
||||
let m = mount_info("ext4", "/mnt/foo", false, false);
|
||||
assert!(is_included(&m, &paths, &opt));
|
||||
}
|
||||
}
|
||||
|
||||
mod filter_mount_list {
|
||||
|
||||
use crate::filter_mount_list;
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
let opt = Default::default();
|
||||
let paths = [];
|
||||
let mount_infos = vec![];
|
||||
assert!(filter_mount_list(mount_infos, &paths, &opt).is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+65
-7
@@ -15,6 +15,7 @@ use crate::{BlockSize, Filesystem, Options};
|
||||
use uucore::fsext::{FsUsage, MountInfo};
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::AddAssign;
|
||||
|
||||
/// A row in the filesystem usage data table.
|
||||
///
|
||||
@@ -67,6 +68,63 @@ pub(crate) struct Row {
|
||||
inodes_usage: Option<f64>,
|
||||
}
|
||||
|
||||
impl Row {
|
||||
pub(crate) fn new(source: &str) -> Self {
|
||||
Self {
|
||||
fs_device: source.into(),
|
||||
fs_type: "-".into(),
|
||||
fs_mount: "-".into(),
|
||||
bytes: 0,
|
||||
bytes_used: 0,
|
||||
bytes_free: 0,
|
||||
bytes_usage: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
bytes_capacity: None,
|
||||
inodes: 0,
|
||||
inodes_used: 0,
|
||||
inodes_free: 0,
|
||||
inodes_usage: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Row {
|
||||
/// Sum the numeric values of two rows.
|
||||
///
|
||||
/// The `Row::fs_device` field is set to `"total"` and the
|
||||
/// remaining `String` fields are set to `"-"`.
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
let bytes = self.bytes + rhs.bytes;
|
||||
let bytes_used = self.bytes_used + rhs.bytes_used;
|
||||
let inodes = self.inodes + rhs.inodes;
|
||||
let inodes_used = self.inodes_used + rhs.inodes_used;
|
||||
*self = Self {
|
||||
fs_device: "total".into(),
|
||||
fs_type: "-".into(),
|
||||
fs_mount: "-".into(),
|
||||
bytes,
|
||||
bytes_used,
|
||||
bytes_free: self.bytes_free + rhs.bytes_free,
|
||||
bytes_usage: if bytes == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(bytes_used as f64 / bytes as f64)
|
||||
},
|
||||
// TODO Figure out how to compute this.
|
||||
#[cfg(target_os = "macos")]
|
||||
bytes_capacity: None,
|
||||
inodes,
|
||||
inodes_used,
|
||||
inodes_free: self.inodes_free + rhs.inodes_free,
|
||||
inodes_usage: if inodes == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(inodes_used as f64 / inodes as f64)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Filesystem> for Row {
|
||||
fn from(fs: Filesystem) -> Self {
|
||||
let MountInfo {
|
||||
@@ -120,7 +178,7 @@ impl From<Filesystem> for Row {
|
||||
/// The `options` control how the information in the row gets displayed.
|
||||
pub(crate) struct DisplayRow<'a> {
|
||||
/// The data in this row.
|
||||
row: Row,
|
||||
row: &'a Row,
|
||||
|
||||
/// Options that control how to display the data.
|
||||
options: &'a Options,
|
||||
@@ -135,7 +193,7 @@ pub(crate) struct DisplayRow<'a> {
|
||||
|
||||
impl<'a> DisplayRow<'a> {
|
||||
/// Instantiate this struct.
|
||||
pub(crate) fn new(row: Row, options: &'a Options) -> Self {
|
||||
pub(crate) fn new(row: &'a Row, options: &'a Options) -> Self {
|
||||
Self { row, options }
|
||||
}
|
||||
|
||||
@@ -354,7 +412,7 @@ mod tests {
|
||||
inodes_usage: Some(0.2),
|
||||
};
|
||||
assert_eq!(
|
||||
DisplayRow::new(row, &options).to_string(),
|
||||
DisplayRow::new(&row, &options).to_string(),
|
||||
"my_device 100 25 75 25% my_mount "
|
||||
);
|
||||
}
|
||||
@@ -385,7 +443,7 @@ mod tests {
|
||||
inodes_usage: Some(0.2),
|
||||
};
|
||||
assert_eq!(
|
||||
DisplayRow::new(row, &options).to_string(),
|
||||
DisplayRow::new(&row, &options).to_string(),
|
||||
"my_device my_type 100 25 75 25% my_mount "
|
||||
);
|
||||
}
|
||||
@@ -416,7 +474,7 @@ mod tests {
|
||||
inodes_usage: Some(0.2),
|
||||
};
|
||||
assert_eq!(
|
||||
DisplayRow::new(row, &options).to_string(),
|
||||
DisplayRow::new(&row, &options).to_string(),
|
||||
"my_device 10 2 8 20% my_mount "
|
||||
);
|
||||
}
|
||||
@@ -447,7 +505,7 @@ mod tests {
|
||||
inodes_usage: Some(0.2),
|
||||
};
|
||||
assert_eq!(
|
||||
DisplayRow::new(row, &options).to_string(),
|
||||
DisplayRow::new(&row, &options).to_string(),
|
||||
"my_device my_type 4.0k 1.0k 3.0k 25% my_mount "
|
||||
);
|
||||
}
|
||||
@@ -478,7 +536,7 @@ mod tests {
|
||||
inodes_usage: Some(0.2),
|
||||
};
|
||||
assert_eq!(
|
||||
DisplayRow::new(row, &options).to_string(),
|
||||
DisplayRow::new(&row, &options).to_string(),
|
||||
"my_device my_type 4.0Ki 1.0Ki 3.0Ki 25% my_mount "
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ path = "src/pr.rs"
|
||||
[dependencies]
|
||||
clap = { version = "3.0", features = ["wrap_help", "cargo"] }
|
||||
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
|
||||
getopts = "0.2.21"
|
||||
chrono = "0.4.19"
|
||||
quick-error = "2.0.1"
|
||||
itertools = "0.10.0"
|
||||
|
||||
+300
-334
File diff suppressed because it is too large
Load Diff
@@ -859,6 +859,11 @@ where
|
||||
///
|
||||
/// This function returns an error if there is a problem reading from
|
||||
/// `reader` or writing to one of the output files.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`kth_chunk_by_line`], which splits its input in the same way,
|
||||
/// but writes only one specified chunk to stdout.
|
||||
fn split_into_n_chunks_by_line<R>(
|
||||
settings: &Settings,
|
||||
reader: &mut R,
|
||||
@@ -915,6 +920,67 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Print the k-th chunk of a file, splitting by line.
|
||||
///
|
||||
/// This function is like [`split_into_n_chunks_by_line`], but instead
|
||||
/// of writing each chunk to its own file, it only writes to stdout
|
||||
/// the contents of the chunk identified by `chunk_number`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function returns an error if there is a problem reading from
|
||||
/// `reader` or writing to one of the output files.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`split_into_n_chunks_by_line`], which splits its input in the
|
||||
/// same way, but writes each chunk to its own file.
|
||||
fn kth_chunk_by_line<R>(
|
||||
settings: &Settings,
|
||||
reader: &mut R,
|
||||
chunk_number: u64,
|
||||
num_chunks: u64,
|
||||
) -> UResult<()>
|
||||
where
|
||||
R: BufRead,
|
||||
{
|
||||
// Get the size of the input file in bytes and compute the number
|
||||
// of bytes per chunk.
|
||||
let metadata = metadata(&settings.input).unwrap();
|
||||
let num_bytes = metadata.len();
|
||||
let chunk_size = (num_bytes / (num_chunks as u64)) as usize;
|
||||
|
||||
// Write to stdout instead of to a file.
|
||||
let stdout = std::io::stdout();
|
||||
let mut writer = stdout.lock();
|
||||
|
||||
let mut num_bytes_remaining_in_current_chunk = chunk_size;
|
||||
let mut i = 0;
|
||||
for line_result in reader.lines() {
|
||||
let line = line_result?;
|
||||
let bytes = line.as_bytes();
|
||||
if i == chunk_number {
|
||||
writer.write_all(bytes)?;
|
||||
writer.write_all(b"\n")?;
|
||||
}
|
||||
|
||||
// Add one byte for the newline character.
|
||||
let num_bytes = bytes.len() + 1;
|
||||
if num_bytes >= num_bytes_remaining_in_current_chunk {
|
||||
num_bytes_remaining_in_current_chunk = chunk_size;
|
||||
i += 1;
|
||||
} else {
|
||||
num_bytes_remaining_in_current_chunk -= num_bytes;
|
||||
}
|
||||
|
||||
if i > chunk_number {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn split(settings: &Settings) -> UResult<()> {
|
||||
let mut reader = BufReader::new(if settings.input == "-" {
|
||||
Box::new(stdin()) as Box<dyn Read>
|
||||
@@ -935,6 +1001,12 @@ fn split(settings: &Settings) -> UResult<()> {
|
||||
Strategy::Number(NumberType::Lines(num_chunks)) => {
|
||||
split_into_n_chunks_by_line(settings, &mut reader, num_chunks)
|
||||
}
|
||||
Strategy::Number(NumberType::KthLines(chunk_number, num_chunks)) => {
|
||||
// The chunk number is given as a 1-indexed number, but it
|
||||
// is a little easier to deal with a 0-indexed number.
|
||||
let chunk_number = chunk_number - 1;
|
||||
kth_chunk_by_line(settings, &mut reader, chunk_number, num_chunks)
|
||||
}
|
||||
Strategy::Number(_) => Err(USimpleError::new(1, "-n mode not yet fully implemented")),
|
||||
Strategy::Lines(chunk_size) => {
|
||||
let mut writer = LineChunkWriter::new(chunk_size, settings)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// spell-checker:ignore udev
|
||||
use crate::common::util::*;
|
||||
|
||||
#[test]
|
||||
@@ -77,4 +78,47 @@ fn test_type_option() {
|
||||
new_ucmd!().args(&["-t", "ext4", "-t", "ext3"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_total() {
|
||||
// Example output:
|
||||
//
|
||||
// Filesystem 1K-blocks Used Available Use% Mounted on
|
||||
// udev 3858016 0 3858016 0% /dev
|
||||
// ...
|
||||
// /dev/loop14 63488 63488 0 100% /snap/core20/1361
|
||||
// total 258775268 98099712 148220200 40% -
|
||||
let output = new_ucmd!().arg("--total").succeeds().stdout_move_str();
|
||||
|
||||
// Skip the header line.
|
||||
let lines: Vec<&str> = output.lines().skip(1).collect();
|
||||
|
||||
// Parse the values from the last row.
|
||||
let last_line = lines.last().unwrap();
|
||||
let mut iter = last_line.split_whitespace();
|
||||
assert_eq!(iter.next().unwrap(), "total");
|
||||
let reported_total_size = iter.next().unwrap().parse().unwrap();
|
||||
let reported_total_used = iter.next().unwrap().parse().unwrap();
|
||||
let reported_total_avail = iter.next().unwrap().parse().unwrap();
|
||||
|
||||
// Loop over each row except the last, computing the sum of each column.
|
||||
let mut computed_total_size = 0;
|
||||
let mut computed_total_used = 0;
|
||||
let mut computed_total_avail = 0;
|
||||
let n = lines.len();
|
||||
for line in &lines[..n - 1] {
|
||||
let mut iter = line.split_whitespace();
|
||||
iter.next().unwrap();
|
||||
computed_total_size += iter.next().unwrap().parse::<u64>().unwrap();
|
||||
computed_total_used += iter.next().unwrap().parse::<u64>().unwrap();
|
||||
computed_total_avail += iter.next().unwrap().parse::<u64>().unwrap();
|
||||
}
|
||||
|
||||
// Check that the sum of each column matches the reported value in
|
||||
// the last row.
|
||||
assert_eq!(computed_total_size, reported_total_size);
|
||||
assert_eq!(computed_total_used, reported_total_used);
|
||||
assert_eq!(computed_total_avail, reported_total_avail);
|
||||
// TODO We could also check here that the use percentage matches.
|
||||
}
|
||||
|
||||
// ToDO: more tests...
|
||||
|
||||
@@ -448,3 +448,16 @@ fn test_with_join_lines_option() {
|
||||
&valid_last_modified_template_vars(start),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_for_number_lines() {
|
||||
// *5 is of the form [SEP[NUMBER]] so is accepted and succeeds
|
||||
new_ucmd!().args(&["-n", "*5", "test.log"]).succeeds();
|
||||
|
||||
// a is of the form [SEP[NUMBER]] so is accepted and succeeds
|
||||
new_ucmd!().args(&["-n", "a", "test.log"]).succeeds();
|
||||
|
||||
// foo5.txt is of not the form [SEP[NUMBER]] so is not used as value.
|
||||
// Therefore, pr tries to access the file, which does not exist.
|
||||
new_ucmd!().args(&["-n", "foo5.txt", "test.log"]).fails();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes
|
||||
// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines
|
||||
extern crate rand;
|
||||
extern crate regex;
|
||||
|
||||
@@ -587,3 +587,11 @@ fn test_lines() {
|
||||
assert_eq!(file_read("xaa"), "1\n2\n3\n");
|
||||
assert_eq!(file_read("xab"), "4\n5\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lines_kth() {
|
||||
new_ucmd!()
|
||||
.args(&["-n", "l/3/10", "onehundredlines.txt"])
|
||||
.succeeds()
|
||||
.stdout_only("20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n");
|
||||
}
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
00
|
||||
01
|
||||
02
|
||||
03
|
||||
04
|
||||
05
|
||||
06
|
||||
07
|
||||
08
|
||||
09
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
27
|
||||
28
|
||||
29
|
||||
30
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42
|
||||
43
|
||||
44
|
||||
45
|
||||
46
|
||||
47
|
||||
48
|
||||
49
|
||||
50
|
||||
51
|
||||
52
|
||||
53
|
||||
54
|
||||
55
|
||||
56
|
||||
57
|
||||
58
|
||||
59
|
||||
60
|
||||
61
|
||||
62
|
||||
63
|
||||
64
|
||||
65
|
||||
66
|
||||
67
|
||||
68
|
||||
69
|
||||
70
|
||||
71
|
||||
72
|
||||
73
|
||||
74
|
||||
75
|
||||
76
|
||||
77
|
||||
78
|
||||
79
|
||||
80
|
||||
81
|
||||
82
|
||||
83
|
||||
84
|
||||
85
|
||||
86
|
||||
87
|
||||
88
|
||||
89
|
||||
90
|
||||
91
|
||||
92
|
||||
93
|
||||
94
|
||||
95
|
||||
96
|
||||
97
|
||||
98
|
||||
99
|
||||
Reference in New Issue
Block a user