src/cli/command/tail.rs

//! `ripgrab tail` — follow mode entry point.

use std::io;

use anyhow::Result;

use crate::cli::args::TailArgs;
use crate::filter::dsl;
use crate::filter::FilterSet;
use crate::render::mode::stream::StreamRenderer;
use crate::source::file::FileSource;
use crate::source::stdin::StdinSource;
use crate::source::Source;

pub async fn run(args: TailArgs) -> Result<()> {
    let filter = FilterSet::compile(&args.includes, &args.excludes, None)?;
    let dsl_filter = args
        .where_clause
        .as_deref()
        .map(dsl::parse_and_compile)
        .transpose()?;

    let mut sources: Vec<Box<dyn Source>> = Vec::with_capacity(args.paths.len());
    for path in &args.paths {
        if path.as_os_str() == "-" {
            sources.push(Box::new(StdinSource::new()));
        } else if args.from_end > 0 {
            sources.push(Box::new(FileSource::follow(path).await?));
        } else {
            sources.push(Box::new(FileSource::open(path).await?));
        }
    }

    let stdout = io::stdout();
    let mut renderer = StreamRenderer::new(stdout.lock(), !args.no_origin, true);

    for src in &mut sources {
        while let Some(item) = src.next_line().await? {
            if let Some(compiled) = &dsl_filter {
                let mut ctx = crate::filter::dsl::EvalContext::new();
                ctx.set("origin", &item.origin);
                ctx.set("body", &item.line);
                if !crate::filter::dsl::evaluate(compiled, &ctx) {
                    continue;
                }
            }
            let outcome = filter.apply(&item.line);
            match outcome {
                crate::filter::MatchOutcome::Plain => {
                    renderer.render(&item.origin, &item.line)?;
                }
                crate::filter::MatchOutcome::Fields(fields) => {
                    let flat: String = fields
                        .iter()
                        .map(|(k, v)| format!("{}={}", k, v))
                        .collect::<Vec<_>>()
                        .join(" ");
                    renderer.render(&item.origin, &flat)?;
                }
            }
        }
    }
    renderer.flush()?;
    Ok(())
}