pagers 0.2.1

Page cache diagnostics and control tool for Linux and macOS
use std::io::IsTerminal as _;
use std::process::ExitCode;

use pagers_core::Cancellation;
use pagers_core::ops;

use clap::Parser;

mod cli;
mod daemon;
mod output;
mod runop;
pub mod size_range;
mod tracing;
use cli::*;
use pagers_core::mincore::DefaultPageMap;
use runop::{run_cli_command, run_daemon_command, run_tui_command};
use size_range::{SizeRange, parse_size};

#[derive(thiserror::Error, Debug)]
pub(crate) enum Error {
    #[error("range limits out of order after page alignment")]
    RangeOrder,
    #[error("daemon shut down unexpectedly")]
    DaemonShutdown,
    #[error("daemon child exited with status {0}")]
    DaemonExit(u8),
    #[error("TUI: {0}")]
    Tui(#[source] std::io::Error),
    #[error("TUI worker thread panicked")]
    TuiPanic,
    #[error("{0}")]
    Nix(#[from] nix::errno::Errno),
    #[error("{0}")]
    Core(#[from] pagers_core::Error),
}

fn main() -> ExitCode {
    let cli = cli::Cli::parse();
    let output = cli.command.output();
    if output.format.is_some() || output.is_quiet() {
        tracing::init(&output.verbosity);
    }

    let cancellation = Cancellation::new();

    match run(cli, &cancellation) {
        Ok(()) => ExitCode::SUCCESS,
        Err(Error::DaemonExit(code)) => ExitCode::from(code),
        Err(e) => {
            eprintln!("{e}");
            ExitCode::FAILURE
        }
    }
}

fn run(cli: cli::Cli, cancellation: &Cancellation) -> Result<(), Error> {
    match cli.command {
        Command::Query(ref a) => run_simple(ops::Query, a, cancellation),
        Command::Touch(ref a) => run_simple(ops::Touch, a, cancellation),
        Command::Evict(ref a) => run_simple(ops::Evict, a, cancellation),
        Command::Lock(ref a) => run_lockable(ops::Lock, a, cancellation),
        Command::Lockall(ref a) => run_lockable(ops::Lockall, a, cancellation),
    }
}

fn run_simple<O: ops::Op + Send + 'static>(
    op: O,
    a: &WithCommon<()>,
    cancellation: &Cancellation,
) -> Result<(), Error>
where
    O::Output: 'static,
{
    let quiet = a.output.is_quiet();
    if a.output.format.is_some() || quiet || !std::io::stdout().is_terminal() {
        run_cli_command::<_, DefaultPageMap>(
            &op,
            &a.common,
            cancellation,
            a.output.format,
            quiet,
            None,
        )
    } else {
        run_tui_command::<_, DefaultPageMap>(&op, &a.common, cancellation, None)
    }
}

fn run_lockable<O: ops::Op + Send + 'static>(
    op: O,
    a: &WithCommon<LockInner>,
    cancellation: &Cancellation,
) -> Result<(), Error>
where
    O::Output: 'static,
{
    let quiet = a.output.is_quiet();
    let use_cli = a.output.format.is_some() || quiet || !std::io::stdout().is_terminal();
    match (a.inner.daemon, use_cli) {
        (true, _) => {
            run_daemon_command::<_, DefaultPageMap>(&op, &a.common, cancellation, &a.inner)
        }
        (_, true) => run_cli_command::<_, DefaultPageMap>(
            &op,
            &a.common,
            cancellation,
            a.output.format,
            quiet,
            Some(&a.inner),
        ),
        (false, false) => {
            run_tui_command::<_, DefaultPageMap>(&op, &a.common, cancellation, Some(&a.inner))
        }
    }
}