1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

join: support custom empty filler

This commit is contained in:
Konstantin Pospelov 2018-01-13 22:44:37 +03:00
parent 2db220e820
commit 128a38965e
4 changed files with 88 additions and 18 deletions

View file

@ -44,6 +44,7 @@ struct Settings {
separator: Sep, separator: Sep,
autoformat: bool, autoformat: bool,
format: Vec<Spec>, format: Vec<Spec>,
empty: String,
} }
impl Default for Settings { impl Default for Settings {
@ -56,6 +57,7 @@ impl Default for Settings {
separator: Sep::Whitespaces, separator: Sep::Whitespaces,
autoformat: false, autoformat: false,
format: vec![], format: vec![],
empty: String::new(),
} }
} }
} }
@ -64,17 +66,29 @@ impl Default for Settings {
struct Repr<'a> { struct Repr<'a> {
separator: char, separator: char,
format: &'a [Spec], format: &'a [Spec],
empty: &'a str,
} }
impl<'a> Repr<'a> { impl<'a> Repr<'a> {
fn new(separator: char, format: &'a [Spec]) -> Repr { fn new(separator: char, format: &'a [Spec], empty: &'a str) -> Repr<'a> {
Repr { separator, format } Repr { separator, format, empty }
} }
fn uses_format(&self) -> bool { fn uses_format(&self) -> bool {
!self.format.is_empty() !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. /// Print each field except the one at the index.
fn print_fields(&self, line: &Line, index: usize, max_fields: usize) { fn print_fields(&self, line: &Line, index: usize, max_fields: usize) {
for i in 0..min(max_fields, line.fields.len()) { 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]) { let field = match f(&self.format[i]) {
Some(value) => value, Some(value) => value,
None => "", None => self.empty,
}; };
print!("{}", field); print!("{}", field);
@ -151,11 +165,11 @@ impl Line {
} }
/// Get field at index. /// Get field at index.
fn get_field(&self, index: usize) -> &str { fn get_field(&self, index: usize) -> Option<&str> {
if index < self.fields.len() { if index < self.fields.len() {
&self.fields[index] Some(&self.fields[index])
} else { } else {
"" None
} }
} }
} }
@ -244,21 +258,21 @@ impl<'a> State<'a> {
for line2 in &other.seq { for line2 in &other.seq {
if repr.uses_format() { if repr.uses_format() {
repr.print_format(|spec| match spec { repr.print_format(|spec| match spec {
&Spec::Key => Some(key), &Spec::Key => key,
&Spec::Field(file_num, field_num) => { &Spec::Field(file_num, field_num) => {
if file_num == self.file_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 { if file_num == other.file_num {
return Some(line2.get_field(field_num)); return line2.get_field(field_num);
} }
None None
} }
}); });
} else { } else {
print!("{}", key); repr.print_field(key);
repr.print_fields(&line1, self.key, self.max_fields); repr.print_fields(&line1, self.key, self.max_fields);
repr.print_fields(&line2, other.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) { fn print_unpaired_line(&self, line: &Line, repr: &Repr) {
if repr.uses_format() { if repr.uses_format() {
repr.print_format(|spec| match spec { 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 { &Spec::Field(file_num, field_num) => if file_num == self.file_num {
Some(line.get_field(field_num)) line.get_field(field_num)
} else { } else {
None None
}, },
}); });
} else { } else {
print!("{}", line.get_field(self.key)); repr.print_field(line.get_field(self.key));
repr.print_fields(line, self.key, self.max_fields); 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") .value_name("FILENUM")
.help("also print unpairable lines from file FILENUM, where .help("also print unpairable lines from file FILENUM, where
FILENUM is 1 or 2, corresponding to FILE1 or FILE2")) 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") .arg(Arg::with_name("i")
.short("i") .short("i")
.long("ignore-case") .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 file1 = matches.value_of("file1").unwrap();
let file2 = matches.value_of("file2").unwrap(); let file2 = matches.value_of("file2").unwrap();
@ -456,6 +479,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
_ => ' ', _ => ' ',
}, },
&settings.format, &settings.format,
&settings.empty,
); );
state1.initialize(settings.separator, settings.autoformat); 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?)) Some(parse_field_number(value?))
} }
fn compare(field1: &str, field2: &str, ignore_case: bool) -> Ordering { fn compare(field1: Option<&str>, field2: Option<&str>, ignore_case: bool) -> Ordering {
if ignore_case { if let (Some(field1), Some(field2)) = (field1, field2) {
field1.to_lowercase().cmp(&field2.to_lowercase()) return if ignore_case {
} else { field1.to_lowercase().cmp(&field2.to_lowercase())
field1.cmp(field2) } else {
field1.cmp(field2)
};
}
match field1 {
Some(_) => Ordering::Greater,
None => match field2 {
Some(_) => Ordering::Less,
None => Ordering::Equal,
}
} }
} }

View file

@ -0,0 +1,5 @@
x 1
x 2
x 3
x 5
x 8

View file

@ -0,0 +1,5 @@
1 a x
2 b x
3 c f
4 d x
5 e x

View file

@ -173,3 +173,29 @@ fn empty_format() {
.arg("") .arg("")
.fails().stderr_is("join: error: invalid file number in field spec: ''"); .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");
}