json-grep 0.1.0

A grep-like utility for searching JSON data with regular expressions
Documentation
use std::io::{Read, stdin};
use std::num::NonZeroUsize;
use std::process::ExitCode;

use anyhow::Result;
use clap::Parser;
use regex::{Error as RegexError, RegexSet, RegexSetBuilder};
use serde::de::DeserializeSeed;
use serde_json::de::Deserializer;

use json_grep::{Callback, Path, PathCallback, Walker};

/// Search for the given pattern(s) in a JSON input.
#[derive(Clone, Debug, Parser)]
#[command(name = "json-grep")]
struct Args {
    /// The pattern(s) to search for.
    #[arg()]
    patterns: Vec<String>,

    /// Print just the number of matched strings.
    #[arg(short, long)]
    count: bool,

    /// Perform case-folding.
    #[arg(short, long)]
    ignore_case: bool,

    /// Stop after N matches.
    #[arg(short, long, value_name = "N")]
    max_count: Option<NonZeroUsize>,

    /// Print strings matching none of the given patterns.
    #[arg(short = 'v', long)]
    invert_match: bool,
}

impl Args {
    fn new_state<'a>(&self) -> Box<dyn Callback<'a> + 'a> {
        if self.count {
            self.with_base::<CountState>()
        } else {
            self.with_base::<State<'a>>()
        }
    }

    fn with_base<'a, C: Callback<'a> + Default + 'a>(&self) -> Box<dyn Callback<'a> + 'a> {
        match self.max_count {
            Some(value) => Box::new((C::default(), MaxCountState::new(value))),
            None => Box::<C>::default(),
        }
    }
}

impl TryFrom<&Args> for RegexSet {
    type Error = RegexError;

    fn try_from(value: &Args) -> Result<Self, Self::Error> {
        RegexSetBuilder::new(&value.patterns)
            .case_insensitive(value.ignore_case)
            .build()
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct State<'a> {
    has_match: bool,
    path: Path<'a>,
}

impl<'a> PathCallback<'a> for State<'a> {
    fn path(&mut self) -> &mut Path<'a> {
        &mut self.path
    }

    fn on_match(&mut self, matched: &str) -> bool {
        self.has_match = true;
        println!("{path}: {matched}", path = &self.path);
        false
    }

    fn on_finish(&mut self) -> bool {
        !self.has_match
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct CountState {
    count: usize,
}

impl Callback<'_> for CountState {
    fn on_match(&mut self, _matched: &str) -> bool {
        self.count += 1;
        false
    }

    fn on_finish(&mut self) -> bool {
        println!("{}", self.count);
        self.count == 0
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct MaxCountState {
    left: usize,
}

impl MaxCountState {
    fn new(left: NonZeroUsize) -> Self {
        Self { left: left.get() }
    }
}

impl Callback<'_> for MaxCountState {
    fn on_match(&mut self, _matched: &str) -> bool {
        self.left -= 1;
        self.left == 0
    }
}

fn main() -> Result<ExitCode> {
    let args = Args::parse();
    let mut input = Vec::new();
    stdin().read_to_end(&mut input)?;
    let mut deserializer = Deserializer::from_slice(&input);
    let mut state = args.new_state();
    let mut walker = Walker::new((&args).try_into()?, args.invert_match, state.as_mut());
    walker.deserialize(&mut deserializer)?;
    if state.on_finish() {
        Ok(ExitCode::FAILURE)
    } else {
        Ok(ExitCode::SUCCESS)
    }
}