mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
join: support custom empty filler
This commit is contained in:
parent
2db220e820
commit
128a38965e
4 changed files with 88 additions and 18 deletions
|
@ -44,6 +44,7 @@ struct Settings {
|
|||
separator: Sep,
|
||||
autoformat: bool,
|
||||
format: Vec<Spec>,
|
||||
empty: String,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
|
@ -56,6 +57,7 @@ impl Default for Settings {
|
|||
separator: Sep::Whitespaces,
|
||||
autoformat: false,
|
||||
format: vec![],
|
||||
empty: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,17 +66,29 @@ impl Default for Settings {
|
|||
struct Repr<'a> {
|
||||
separator: char,
|
||||
format: &'a [Spec],
|
||||
empty: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
fn new(separator: char, format: &'a [Spec]) -> Repr {
|
||||
Repr { separator, format }
|
||||
fn new(separator: char, format: &'a [Spec], empty: &'a str) -> Repr<'a> {
|
||||
Repr { separator, format, empty }
|
||||
}
|
||||
|
||||
fn uses_format(&self) -> bool {
|
||||
!self.format.is_empty()
|
||||
}
|
||||
|
||||
/// Print the field or empty filler if the field is not set.
|
||||
fn print_field(&self, field: Option<&str>)
|
||||
{
|
||||
let value = match field {
|
||||
Some(field) => field,
|
||||
None => self.empty,
|
||||
};
|
||||
|
||||
print!("{}", value);
|
||||
}
|
||||
|
||||
/// Print each field except the one at the index.
|
||||
fn print_fields(&self, line: &Line, index: usize, max_fields: usize) {
|
||||
for i in 0..min(max_fields, line.fields.len()) {
|
||||
|
@ -96,7 +110,7 @@ impl<'a> Repr<'a> {
|
|||
|
||||
let field = match f(&self.format[i]) {
|
||||
Some(value) => value,
|
||||
None => "",
|
||||
None => self.empty,
|
||||
};
|
||||
|
||||
print!("{}", field);
|
||||
|
@ -151,11 +165,11 @@ impl Line {
|
|||
}
|
||||
|
||||
/// Get field at index.
|
||||
fn get_field(&self, index: usize) -> &str {
|
||||
fn get_field(&self, index: usize) -> Option<&str> {
|
||||
if index < self.fields.len() {
|
||||
&self.fields[index]
|
||||
Some(&self.fields[index])
|
||||
} else {
|
||||
""
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -244,21 +258,21 @@ impl<'a> State<'a> {
|
|||
for line2 in &other.seq {
|
||||
if repr.uses_format() {
|
||||
repr.print_format(|spec| match spec {
|
||||
&Spec::Key => Some(key),
|
||||
&Spec::Key => key,
|
||||
&Spec::Field(file_num, field_num) => {
|
||||
if file_num == self.file_num {
|
||||
return Some(line1.get_field(field_num));
|
||||
return line1.get_field(field_num);
|
||||
}
|
||||
|
||||
if file_num == other.file_num {
|
||||
return Some(line2.get_field(field_num));
|
||||
return line2.get_field(field_num);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
});
|
||||
} else {
|
||||
print!("{}", key);
|
||||
repr.print_field(key);
|
||||
repr.print_fields(&line1, self.key, self.max_fields);
|
||||
repr.print_fields(&line2, other.key, self.max_fields);
|
||||
}
|
||||
|
@ -311,15 +325,15 @@ impl<'a> State<'a> {
|
|||
fn print_unpaired_line(&self, line: &Line, repr: &Repr) {
|
||||
if repr.uses_format() {
|
||||
repr.print_format(|spec| match spec {
|
||||
&Spec::Key => Some(line.get_field(self.key)),
|
||||
&Spec::Key => line.get_field(self.key),
|
||||
&Spec::Field(file_num, field_num) => if file_num == self.file_num {
|
||||
Some(line.get_field(field_num))
|
||||
line.get_field(field_num)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
});
|
||||
} else {
|
||||
print!("{}", line.get_field(self.key));
|
||||
repr.print_field(line.get_field(self.key));
|
||||
repr.print_fields(line, self.key, self.max_fields);
|
||||
}
|
||||
|
||||
|
@ -344,6 +358,11 @@ When FILE1 or FILE2 (not both) is -, read standard input.")
|
|||
.value_name("FILENUM")
|
||||
.help("also print unpairable lines from file FILENUM, where
|
||||
FILENUM is 1 or 2, corresponding to FILE1 or FILE2"))
|
||||
.arg(Arg::with_name("e")
|
||||
.short("e")
|
||||
.takes_value(true)
|
||||
.value_name("EMPTY")
|
||||
.help("replace missing input fields with EMPTY"))
|
||||
.arg(Arg::with_name("i")
|
||||
.short("i")
|
||||
.long("ignore-case")
|
||||
|
@ -421,6 +440,10 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2"))
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(empty) = matches.value_of("e") {
|
||||
settings.empty = empty.to_string();
|
||||
}
|
||||
|
||||
let file1 = matches.value_of("file1").unwrap();
|
||||
let file2 = matches.value_of("file2").unwrap();
|
||||
|
||||
|
@ -456,6 +479,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
|
|||
_ => ' ',
|
||||
},
|
||||
&settings.format,
|
||||
&settings.empty,
|
||||
);
|
||||
|
||||
state1.initialize(settings.separator, settings.autoformat);
|
||||
|
@ -522,10 +546,20 @@ fn parse_field_number_option(value: Option<&str>) -> Option<usize> {
|
|||
Some(parse_field_number(value?))
|
||||
}
|
||||
|
||||
fn compare(field1: &str, field2: &str, ignore_case: bool) -> Ordering {
|
||||
if ignore_case {
|
||||
fn compare(field1: Option<&str>, field2: Option<&str>, ignore_case: bool) -> Ordering {
|
||||
if let (Some(field1), Some(field2)) = (field1, field2) {
|
||||
return if ignore_case {
|
||||
field1.to_lowercase().cmp(&field2.to_lowercase())
|
||||
} else {
|
||||
field1.cmp(field2)
|
||||
};
|
||||
}
|
||||
|
||||
match field1 {
|
||||
Some(_) => Ordering::Greater,
|
||||
None => match field2 {
|
||||
Some(_) => Ordering::Less,
|
||||
None => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
5
tests/fixtures/join/empty_key.expected
vendored
Normal file
5
tests/fixtures/join/empty_key.expected
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
x 1
|
||||
x 2
|
||||
x 3
|
||||
x 5
|
||||
x 8
|
5
tests/fixtures/join/missing_format_fields.expected
vendored
Normal file
5
tests/fixtures/join/missing_format_fields.expected
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
1 a x
|
||||
2 b x
|
||||
3 c f
|
||||
4 d x
|
||||
5 e x
|
|
@ -173,3 +173,29 @@ fn empty_format() {
|
|||
.arg("")
|
||||
.fails().stderr_is("join: error: invalid file number in field spec: ''");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_key() {
|
||||
new_ucmd!()
|
||||
.arg("fields_1.txt")
|
||||
.arg("empty.txt")
|
||||
.arg("-j")
|
||||
.arg("2")
|
||||
.arg("-a")
|
||||
.arg("1")
|
||||
.arg("-e")
|
||||
.arg("x")
|
||||
.succeeds().stdout_only_fixture("empty_key.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_format_fields() {
|
||||
new_ucmd!()
|
||||
.arg("fields_2.txt")
|
||||
.arg("different_lengths.txt")
|
||||
.arg("-o")
|
||||
.arg("0 1.2 2.4")
|
||||
.arg("-e")
|
||||
.arg("x")
|
||||
.succeeds().stdout_only_fixture("missing_format_fields.expected");
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue