snipgrep/
processor.rs

1//! This module provides functionality for collecting and inject snippets from
2//! files within a folder.
3//!
4//! It utilizes the `rayon` crate for parallel processing and `serde` for
5//! serialization and deserialization.
6use std::{
7    collections::{BTreeMap, HashMap},
8    fs,
9    path::{Path, PathBuf},
10};
11
12use rayon::prelude::*;
13use serde::{Deserialize, Serialize};
14
15use crate::{
16    db,
17    parser::{InjectSummary, ParseFile, Snippet},
18    walk::Walk,
19};
20
21/// A struct for collecting snippets from files within a folder.
22#[derive(Debug, Serialize, Deserialize)]
23pub struct Collector {
24    pub root_folder: PathBuf,
25    pub snippets: BTreeMap<PathBuf, Vec<Snippet>>,
26}
27
28/// Represents the inject status result
29#[derive(Debug, Serialize, Deserialize)]
30pub struct Injector {
31    pub root_folder: PathBuf,
32    pub results: BTreeMap<PathBuf, InjectStatus>,
33}
34
35/// Represent the injector status result
36#[derive(Debug, Serialize, Deserialize)]
37pub enum InjectStatus {
38    /// When found placeholder snippet section.
39    Injected(InjectSummary),
40    /// When found a snippet collection but not found inject snippet with the
41    /// same it to injector
42    None,
43    /// When error is encountered
44    Error(String),
45}
46
47impl Collector {
48    /// Constructs a `Collector` instance by collecting snippets from files
49    /// within the provided `Walk`.
50    #[must_use]
51    pub fn on_files(walk: &Walk) -> Self {
52        let snippets = walk
53            .get_files()
54            .par_iter()
55            .flat_map(|path| Self::on_file(path.as_path()).map(|findings| (path.clone(), findings)))
56            .collect::<BTreeMap<_, _>>();
57
58        Self {
59            root_folder: walk.folder.clone(),
60            snippets,
61        }
62    }
63
64    // Processes a single file and extracts snippets.
65    ///
66    /// # Returns
67    ///
68    /// Returns `Some` containing the collected snippets if successful,
69    /// otherwise returns `None`.
70    fn on_file(path: &Path) -> Option<Vec<Snippet>> {
71        let span = tracing::info_span!("parse_file", path = %path.display());
72        let _guard = span.enter();
73        let input = match fs::read_to_string(path) {
74            Ok(file_content) => file_content,
75            Err(err) => {
76                tracing::debug!(err = %err,"could not read file content");
77                return None;
78            }
79        };
80
81        match ParseFile::new(&input).parse() {
82            Ok(findings) => {
83                tracing::debug!("parsed successfully");
84                Some(findings)
85            }
86            Err(err) => {
87                tracing::debug!(err = %err, "could not parse the file");
88                None
89            }
90        }
91    }
92}
93
94impl Injector {
95    /// Constructs a `Collector` instance by collecting snippets from files
96    /// within the provided `Walk`.
97    #[must_use]
98    pub fn on_files(walk: &Walk, db_data: &db::DBData) -> Self {
99        let mut snippets_from = HashMap::new();
100
101        for (snippet_id, snippet_data) in &db_data.snippets {
102            if !snippet_data.inject {
103                snippets_from.insert(snippet_id.clone(), snippet_data);
104            }
105        }
106
107        let results = walk
108            .get_files()
109            .par_iter()
110            .map(|path| {
111                let status = match fs::read_to_string(path) {
112                    Ok(file_content) => Self::inject(&file_content, &snippets_from),
113                    Err(err) => {
114                        tracing::debug!(err = %err,"could not read file content");
115                        InjectStatus::Error(err.to_string())
116                    }
117                };
118                (path.clone(), status)
119            })
120            .collect::<BTreeMap<PathBuf, InjectStatus>>();
121
122        Self {
123            root_folder: walk.folder.clone(),
124            results,
125        }
126    }
127
128    // Processes a single file and extracts snippets.
129    ///
130    /// # Returns
131    ///
132    /// Returns `Some` containing the collected snippets if successful,
133    /// otherwise returns `None`.
134    pub fn inject(input: &str, snippets: &HashMap<String, &db::DbDataSnippet>) -> InjectStatus {
135        match ParseFile::new(input).inject(snippets) {
136            Ok(summary) => {
137                if summary.actions.is_empty() {
138                    tracing::debug!("not found inject content");
139                    InjectStatus::None
140                } else {
141                    tracing::debug!("content injected");
142                    InjectStatus::Injected(summary)
143                }
144            }
145            Err(err) => {
146                tracing::debug!(err = %err, "could not parse the given content");
147                InjectStatus::Error(err.to_string())
148            }
149        }
150    }
151}