mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
join: support headers
This commit is contained in:
parent
fc7b1fcaba
commit
7dc8ff62cc
6 changed files with 104 additions and 11 deletions
|
@ -53,6 +53,7 @@ struct Settings {
|
||||||
format: Vec<Spec>,
|
format: Vec<Spec>,
|
||||||
empty: String,
|
empty: String,
|
||||||
check_order: CheckOrder,
|
check_order: CheckOrder,
|
||||||
|
headers: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
|
@ -67,6 +68,7 @@ impl Default for Settings {
|
||||||
format: vec![],
|
format: vec![],
|
||||||
empty: String::new(),
|
empty: String::new(),
|
||||||
check_order: CheckOrder::Default,
|
check_order: CheckOrder::Default,
|
||||||
|
headers: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,13 +258,10 @@ impl<'a> State<'a> {
|
||||||
/// Skip the current unpaired line.
|
/// Skip the current unpaired line.
|
||||||
fn skip_line(&mut self, input: &Input, repr: &Repr) {
|
fn skip_line(&mut self, input: &Input, repr: &Repr) {
|
||||||
if self.print_unpaired {
|
if self.print_unpaired {
|
||||||
self.print_unpaired_line(&self.seq[0], repr);
|
self.print_first_line(repr);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.next_line(input) {
|
self.reset_next_line(input);
|
||||||
Some(line) => self.seq[0] = line,
|
|
||||||
None => self.seq.clear(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keep reading line sequence until the key does not change, return
|
/// Keep reading line sequence until the key does not change, return
|
||||||
|
@ -285,6 +284,19 @@ impl<'a> State<'a> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print lines in the buffers as headers.
|
||||||
|
fn print_headers(&self, other: &State, repr: &Repr) {
|
||||||
|
if self.has_line() {
|
||||||
|
if other.has_line() {
|
||||||
|
self.combine(other, repr);
|
||||||
|
} else {
|
||||||
|
self.print_first_line(repr);
|
||||||
|
}
|
||||||
|
} else if other.has_line() {
|
||||||
|
other.print_first_line(repr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Combine two line sequences.
|
/// Combine two line sequences.
|
||||||
fn combine(&self, other: &State, repr: &Repr) {
|
fn combine(&self, other: &State, repr: &Repr) {
|
||||||
let key = self.seq[0].get_field(self.key);
|
let key = self.seq[0].get_field(self.key);
|
||||||
|
@ -326,6 +338,16 @@ impl<'a> State<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reset_read_line(&mut self, input: &Input) {
|
||||||
|
let line = self.read_line(input.separator);
|
||||||
|
self.reset(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_next_line(&mut self, input: &Input) {
|
||||||
|
let line = self.next_line(input);
|
||||||
|
self.reset(line);
|
||||||
|
}
|
||||||
|
|
||||||
fn has_line(&self) -> bool {
|
fn has_line(&self) -> bool {
|
||||||
!self.seq.is_empty()
|
!self.seq.is_empty()
|
||||||
}
|
}
|
||||||
|
@ -342,21 +364,22 @@ impl<'a> State<'a> {
|
||||||
|
|
||||||
fn finalize(&mut self, input: &Input, repr: &Repr) {
|
fn finalize(&mut self, input: &Input, repr: &Repr) {
|
||||||
if self.has_line() && self.print_unpaired {
|
if self.has_line() && self.print_unpaired {
|
||||||
self.print_unpaired_line(&self.seq[0], repr);
|
self.print_first_line(repr);
|
||||||
|
|
||||||
while let Some(line) = self.next_line(input) {
|
while let Some(line) = self.next_line(input) {
|
||||||
self.print_unpaired_line(&line, repr);
|
self.print_line(&line, repr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the next line without the order check.
|
||||||
fn read_line(&mut self, sep: Sep) -> Option<Line> {
|
fn read_line(&mut self, sep: Sep) -> Option<Line> {
|
||||||
let value = self.lines.next()?;
|
let value = self.lines.next()?;
|
||||||
self.line_num += 1;
|
self.line_num += 1;
|
||||||
Some(Line::new(crash_if_err!(1, value), sep))
|
Some(Line::new(crash_if_err!(1, value), sep))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare the next line.
|
/// Get the next line with the order check.
|
||||||
fn next_line(&mut self, input: &Input) -> Option<Line> {
|
fn next_line(&mut self, input: &Input) -> Option<Line> {
|
||||||
let line = self.read_line(input.separator)?;
|
let line = self.read_line(input.separator)?;
|
||||||
|
|
||||||
|
@ -384,7 +407,7 @@ impl<'a> State<'a> {
|
||||||
Some(line)
|
Some(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_unpaired_line(&self, line: &Line, repr: &Repr) {
|
fn print_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 => line.get_field(self.key),
|
&Spec::Key => line.get_field(self.key),
|
||||||
|
@ -401,6 +424,10 @@ impl<'a> State<'a> {
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_first_line(&self, repr: &Repr) {
|
||||||
|
self.print_line(&self.seq[0], repr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: Vec<String>) -> i32 {
|
pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
|
@ -482,6 +509,10 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2",
|
||||||
.long("nocheck-order")
|
.long("nocheck-order")
|
||||||
.help("do not check that the input is correctly sorted"),
|
.help("do not check that the input is correctly sorted"),
|
||||||
)
|
)
|
||||||
|
.arg(Arg::with_name("header").long("header").help(
|
||||||
|
"treat the first line in each file as field headers, \
|
||||||
|
print them without trying to pair them",
|
||||||
|
))
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("file1")
|
Arg::with_name("file1")
|
||||||
.required(true)
|
.required(true)
|
||||||
|
@ -544,6 +575,10 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2",
|
||||||
settings.check_order = CheckOrder::Enabled;
|
settings.check_order = CheckOrder::Enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches.is_present("header") {
|
||||||
|
settings.headers = true;
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
@ -591,6 +626,12 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
|
||||||
state1.initialize(settings.separator, settings.autoformat);
|
state1.initialize(settings.separator, settings.autoformat);
|
||||||
state2.initialize(settings.separator, settings.autoformat);
|
state2.initialize(settings.separator, settings.autoformat);
|
||||||
|
|
||||||
|
if settings.headers {
|
||||||
|
state1.print_headers(&state2, &repr);
|
||||||
|
state1.reset_read_line(&input);
|
||||||
|
state2.reset_read_line(&input);
|
||||||
|
}
|
||||||
|
|
||||||
while state1.has_line() && state2.has_line() {
|
while state1.has_line() && state2.has_line() {
|
||||||
let diff = state1.compare(&state2, settings.ignore_case);
|
let diff = state1.compare(&state2, settings.ignore_case);
|
||||||
|
|
||||||
|
@ -604,9 +645,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
let next_line1 = state1.extend(&input);
|
let next_line1 = state1.extend(&input);
|
||||||
let next_line2 = state2.extend(&input);
|
let next_line2 = state2.extend(&input);
|
||||||
|
|
||||||
state1.combine(&state2, &repr);
|
state1.combine(&state2, &repr);
|
||||||
|
|
||||||
state1.reset(next_line1);
|
state1.reset(next_line1);
|
||||||
state2.reset(next_line2);
|
state2.reset(next_line2);
|
||||||
}
|
}
|
||||||
|
|
4
tests/fixtures/join/header.expected
vendored
Normal file
4
tests/fixtures/join/header.expected
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
id field count
|
||||||
|
1 a abc 10
|
||||||
|
2 b abc 25
|
||||||
|
4 d 17 xyz
|
6
tests/fixtures/join/header_1.txt
vendored
Normal file
6
tests/fixtures/join/header_1.txt
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
id field
|
||||||
|
1 a abc
|
||||||
|
2 b abc
|
||||||
|
3 c
|
||||||
|
4 d
|
||||||
|
5 c
|
5
tests/fixtures/join/header_2.txt
vendored
Normal file
5
tests/fixtures/join/header_2.txt
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
id count
|
||||||
|
1 10
|
||||||
|
2 25
|
||||||
|
4 17 xyz
|
||||||
|
7 18 xyz
|
4
tests/fixtures/join/header_autoformat.expected
vendored
Normal file
4
tests/fixtures/join/header_autoformat.expected
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
id field count
|
||||||
|
1 a 10
|
||||||
|
2 b 25
|
||||||
|
4 d 17
|
|
@ -207,3 +207,38 @@ fn wrong_line_order() {
|
||||||
.arg("fields_4.txt")
|
.arg("fields_4.txt")
|
||||||
.fails().stderr_is("fields_4.txt:5: is not sorted");
|
.fails().stderr_is("fields_4.txt:5: is not sorted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headers() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("header_1.txt")
|
||||||
|
.arg("header_2.txt")
|
||||||
|
.arg("--header")
|
||||||
|
.succeeds().stdout_only_fixture("header.expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headers_autoformat() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("header_1.txt")
|
||||||
|
.arg("header_2.txt")
|
||||||
|
.arg("--header")
|
||||||
|
.arg("-o")
|
||||||
|
.arg("auto")
|
||||||
|
.succeeds().stdout_only_fixture("header_autoformat.expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_file_with_header() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("capitalized.txt")
|
||||||
|
.arg("empty.txt")
|
||||||
|
.arg("--header")
|
||||||
|
.succeeds().stdout_is("A 1");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("empty.txt")
|
||||||
|
.arg("capitalized.txt")
|
||||||
|
.arg("--header")
|
||||||
|
.succeeds().stdout_is("A 1");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue