use clap::Command;
use color_eyre::Result;
use color_eyre::eyre::eyre;
use std::io::{BufRead, Read};
use crate::busybox::head::{open_file_as_bufread, open_file_as_read, print_file_header, parse_head_tail_options, head_tail_command};
pub struct TailOptions {
pub files: Vec<String>,
pub lines: Option<i64>,
pub bytes: Option<i64>,
}
pub fn parse_options(matches: &clap::ArgMatches) -> Result<TailOptions> {
let (files, lines, bytes) = parse_head_tail_options(matches, true, -10)?;
Ok(TailOptions { files, lines, bytes })
}
pub fn command() -> Command {
head_tail_command(
"tail",
"Output the last part of files",
"Number of lines (positive for start from line, negative for last N lines)",
"Number of bytes (positive for start from byte, negative for last N bytes)",
)
}
fn print_last_lines(reader: &mut dyn BufRead, num_lines: usize) -> Result<()> {
let mut lines = Vec::new();
for line_result in reader.lines() {
let line = line_result
.map_err(|e| eyre!("tail: error reading input: {}", e))?;
lines.push(line);
}
let start_idx = if lines.len() > num_lines {
lines.len() - num_lines
} else {
0
};
for line in &lines[start_idx..] {
println!("{}", line);
}
Ok(())
}
fn print_last_bytes(reader: &mut dyn Read, num_bytes: usize) -> Result<()> {
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer)
.map_err(|e| eyre!("tail: error reading input: {}", e))?;
let bytes_to_print = std::cmp::min(num_bytes, buffer.len());
let start_idx = buffer.len().saturating_sub(bytes_to_print);
let output = &buffer[start_idx..];
use std::io::Write;
std::io::stdout().write_all(output)
.map_err(|e| eyre!("tail: error writing output: {}", e))?;
Ok(())
}
fn print_from_byte(reader: &mut dyn Read, start_byte: usize) -> Result<()> {
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer)
.map_err(|e| eyre!("tail: error reading input: {}", e))?;
let start_idx = if start_byte == 0 { 0 } else { start_byte - 1 };
let output = if start_idx < buffer.len() {
&buffer[start_idx..]
} else {
&[]
};
use std::io::Write;
std::io::stdout().write_all(output)
.map_err(|e| eyre!("tail: error writing output: {}", e))?;
Ok(())
}
fn print_from_line(reader: &mut dyn BufRead, start_line: usize) -> Result<()> {
let mut lines = Vec::new();
for line_result in reader.lines() {
let line = line_result
.map_err(|e| eyre!("tail: error reading input: {}", e))?;
lines.push(line);
}
let start_idx = if start_line == 0 { 0 } else { start_line - 1 };
if start_idx < lines.len() {
for line in &lines[start_idx..] {
println!("{}", line);
}
}
Ok(())
}
pub fn run(options: TailOptions) -> Result<()> {
let mut first_file = true;
for file_path in &options.files {
if options.files.len() > 1 {
print_file_header(file_path, &mut first_file);
}
if let Some(lines_val) = options.lines {
let mut reader = open_file_as_bufread(file_path)
.map_err(|e| eyre!("tail: {}", e))?;
if lines_val > 0 {
print_from_line(&mut *reader, lines_val as usize)?;
} else {
print_last_lines(&mut *reader, (-lines_val) as usize)?;
}
} else if let Some(bytes_val) = options.bytes {
if bytes_val > 0 {
let mut reader = open_file_as_read(file_path)
.map_err(|e| eyre!("tail: {}", e))?;
print_from_byte(&mut *reader, bytes_val as usize)?;
} else {
let mut reader = open_file_as_read(file_path)
.map_err(|e| eyre!("tail: {}", e))?;
print_last_bytes(&mut *reader, (-bytes_val) as usize)?;
}
}
}
Ok(())
}