Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tail: Refactor tail #3952

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 53 additions & 22 deletions src/uu/tail/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@

// spell-checker:ignore (ToDO) kqueue Signum fundu

//! Parse the arguments passed to `tail` into a [`Settings`] struct.

use crate::paths::Input;
use crate::{parse, platform, Quotable};
use clap::crate_version;
use clap::{parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
use fundu::DurationParser;
use is_terminal::IsTerminal;
use same_file::Handle;
use std::collections::VecDeque;
use std::ffi::OsString;
use std::time::Duration;
use uucore::error::{UResult, USimpleError, UUsageError};
Expand Down Expand Up @@ -42,6 +43,7 @@ pub mod options {
pub static PRESUME_INPUT_PIPE: &str = "-presume-input-pipe"; // NOTE: three hyphens is correct
}

/// Represent a `u64` with sign. `0` is a special value and can be negative or positive.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Signum {
Negative(u64),
Expand All @@ -50,8 +52,12 @@ pub enum Signum {
MinusZero,
}

/// The tail operation mode. Can be either `Lines` or `Bytes`.
///
/// `Lines` for the `-n` option and `Bytes` for the `-c` option.
#[derive(Debug, PartialEq, Eq)]
pub enum FilterMode {
/// Mode for bytes.
Bytes(Signum),

/// Mode for lines delimited by delimiter as u8
Expand Down Expand Up @@ -117,31 +123,59 @@ impl Default for FilterMode {
}
}

/// The `tail` follow mode given by the `--follow` flag.
///
/// Can bei either `Descriptor` (`--follow=descriptor`) which is the default or `Name`
/// (`--follow=name`)
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum FollowMode {
Descriptor,
Name,
}

/// The result returned from [`Settings::verify`].
#[derive(Debug)]
pub enum VerificationResult {
/// Returned if [`Settings::verify`] has not detected any misconfiguration of the command line
/// arguments
Ok,

// Returned if one of the file arguments passed to `tail` is the stdin file descriptor (`-`) and
// `--follow=name` was given.
CannotFollowStdinByName,

/// Returned if tail will not output anything because of the configuration passed to `tail`.
NoOutput,
}

/// Store the configuration of `tail` parsed from the command line arguments (and defaults).
///
/// This struct is designed to store the initial values given from user provided command line
/// arguments if present or sane defaults if not. The fields of `Settings` and `Settings` itself
/// should be seen as constants. Please do not use `Settings` to store fields that need to be
/// mutable after the initialization of `Settings`.
#[derive(Debug)]
pub struct Settings {
/// `--follow`, `-f` and as part of `-F`
pub follow: Option<FollowMode>,
/// `--max-unchanged-stats`
pub max_unchanged_stats: u32,
/// `--lines`, `-n` or `--bytes`, `-c`
pub mode: FilterMode,
/// `--pid`
pub pid: platform::Pid,
/// `--retry` and as part of `-F`
pub retry: bool,
/// `--sleep-interval`, `-s`
pub sleep_sec: Duration,
/// `--use-polling` (non standard: divergence to gnu's `tail` )
pub use_polling: bool,
/// `--verbose`, `-v` and `--quiet`, `-q`
pub verbose: bool,
/// `---presume-input-pipe`
pub presume_input_pipe: bool,
pub inputs: VecDeque<Input>,
/// `FILE(s)` positional arguments
pub inputs: Vec<Input>,
}

impl Default for Settings {
Expand Down Expand Up @@ -173,15 +207,15 @@ impl Settings {
}
settings.mode = FilterMode::from_obsolete_args(args);
let input = if let Some(name) = name {
Input::from(&name)
Input::from(name)
} else {
Input::default()
};
settings.inputs.push_back(input);
settings.inputs.push(input);
settings
}

pub fn from(matches: &clap::ArgMatches) -> UResult<Self> {
pub fn from(matches: &ArgMatches) -> UResult<Self> {
let mut settings: Self = Self {
follow: if matches.get_flag(options::FOLLOW_RETRY) {
Some(FollowMode::Name)
Expand All @@ -202,6 +236,9 @@ impl Settings {
..Default::default()
};

settings.retry =
matches.get_flag(options::FOLLOW_RETRY) || matches.get_flag(options::RETRY);

if let Some(source) = matches.get_one::<String>(options::SLEEP_INT) {
// Advantage of `fundu` over `Duration::(try_)from_secs_f64(source.parse().unwrap())`:
// * doesn't panic on errors like `Duration::from_secs_f64` would.
Expand Down Expand Up @@ -257,19 +294,13 @@ impl Settings {
}
}

let mut inputs: VecDeque<Input> = matches
.get_many::<String>(options::ARG_FILES)
.map(|v| v.map(|string| Input::from(&string)).collect())
.unwrap_or_default();

// apply default and add '-' to inputs if none is present
if inputs.is_empty() {
inputs.push_front(Input::default());
}

settings.verbose = inputs.len() > 1 && !matches.get_flag(options::verbosity::QUIET);
settings.inputs = matches
.get_raw(options::ARG_FILES)
.map(|v| v.map(Input::from).collect())
.unwrap_or_else(|| vec![Input::default()]);

settings.inputs = inputs;
settings.verbose =
settings.inputs.len() > 1 && !matches.get_flag(options::verbosity::QUIET);

Ok(settings)
}
Expand Down Expand Up @@ -326,10 +357,11 @@ impl Settings {
}
}

/// Verify [`Settings`] and try to find unsolvable misconfigurations of tail originating from
/// user provided command line arguments. In contrast to [`Settings::check_warnings`] these
/// misconfigurations usually lead to the immediate exit or abortion of the running `tail`
/// process.
/// Verify the [`Settings`] and try to find unsolvable misconfigurations of tail originating
/// from user provided command line arguments.
///
/// In contrast to [`Settings::check_warnings`] these misconfigurations usually lead to the
/// immediate exit or abortion of the running `tail` process.
pub fn verify(&self) -> VerificationResult {
// Mimic GNU's tail for `tail -F`
if self.inputs.iter().any(|i| i.is_stdin()) && self.follow == Some(FollowMode::Name) {
Expand Down Expand Up @@ -550,7 +582,6 @@ pub fn uu_app() -> Command {
Arg::new(options::FOLLOW_RETRY)
.short('F')
.help("Same as --follow=name --retry")
.overrides_with_all([options::RETRY, options::FOLLOW])
.action(ArgAction::SetTrue),
)
.arg(
Expand Down
Loading