Skip to main content

sift_queue/
collect.rs

1pub mod rg;
2
3use anyhow::Result;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Format {
7    RgJson,
8}
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct GroupedItem {
12    pub filepath: String,
13    pub text: String,
14    pub match_count: usize,
15}
16
17pub fn detect_format(input: &str) -> Option<Format> {
18    let first_line = input.lines().find(|line| !line.trim().is_empty())?;
19    let value = serde_json::from_str::<serde_json::Value>(first_line).ok()?;
20    match value.get("type").and_then(|value| value.as_str()) {
21        Some(_) => Some(Format::RgJson),
22        None => None,
23    }
24}
25
26pub fn filename_for(filepath: &str) -> &str {
27    std::path::Path::new(filepath)
28        .file_name()
29        .and_then(|name| name.to_str())
30        .unwrap_or(filepath)
31}
32
33pub fn render_title(
34    title: Option<&str>,
35    title_template: Option<&str>,
36    item: &GroupedItem,
37) -> Result<String> {
38    let template = title_template.unwrap_or("{{match_count}}:{{filepath}}");
39
40    if title_template.is_some() {
41        let rendered = template
42            .replace("{{filepath}}", &item.filepath)
43            .replace("{{filename}}", filename_for(&item.filepath))
44            .replace("{{match_count}}", &item.match_count.to_string());
45        return Ok(rendered);
46    }
47
48    if let Some(title) = title {
49        return Ok(title.to_string());
50    }
51
52    Ok(template
53        .replace("{{filepath}}", &item.filepath)
54        .replace("{{filename}}", filename_for(&item.filepath))
55        .replace("{{match_count}}", &item.match_count.to_string()))
56}
57
58#[cfg(test)]
59mod tests {
60    use super::{detect_format, render_title, Format, GroupedItem};
61
62    fn grouped_item() -> GroupedItem {
63        GroupedItem {
64            filepath: "lib/a.rb".to_string(),
65            text: "1: foo".to_string(),
66            match_count: 2,
67        }
68    }
69
70    #[test]
71    fn test_detect_format_rg_json() {
72        let input = r#"{"type":"begin","data":{"path":{"text":"lib/a.rb"}}}"#;
73        assert_eq!(detect_format(input), Some(Format::RgJson));
74    }
75
76    #[test]
77    fn test_detect_format_unknown() {
78        assert_eq!(detect_format("not json"), None);
79    }
80
81    #[test]
82    fn test_render_title_template_filepath() {
83        assert_eq!(
84            render_title(None, Some("{{filepath}}"), &grouped_item()).unwrap(),
85            "lib/a.rb"
86        );
87    }
88
89    #[test]
90    fn test_render_title_template_filename() {
91        assert_eq!(
92            render_title(None, Some("{{filename}}"), &grouped_item()).unwrap(),
93            "a.rb"
94        );
95    }
96
97    #[test]
98    fn test_render_title_template_match_count() {
99        assert_eq!(
100            render_title(None, Some("matches: {{match_count}}"), &grouped_item()).unwrap(),
101            "matches: 2"
102        );
103    }
104
105    #[test]
106    fn test_render_title_prefers_template_over_title() {
107        assert_eq!(
108            render_title(Some("fallback"), Some("{{filename}}"), &grouped_item()).unwrap(),
109            "a.rb"
110        );
111    }
112
113    #[test]
114    fn test_render_title_uses_literal_title() {
115        assert_eq!(
116            render_title(Some("explicit title"), None, &grouped_item()).unwrap(),
117            "explicit title"
118        );
119    }
120
121    #[test]
122    fn test_render_title_defaults_to_match_count_and_filepath() {
123        assert_eq!(
124            render_title(None, None, &grouped_item()).unwrap(),
125            "2:lib/a.rb"
126        );
127    }
128}