Skip to main content

aurora_modules/
text.rs

1use aurora_core::{AuroraResult, Pipeline, Value};
2use std::fs;
3use std::io::{BufRead, BufReader};
4use regex::Regex;
5
6fn is_binary(data: &[u8]) -> bool {
7    data[..data.len().min(1024)].contains(&0)
8}
9
10pub fn text_grep(pattern: &str, path: &str) -> AuroraResult<Pipeline> {
11    let re = Regex::new(pattern)
12        .map_err(|e| aurora_core::AuroraError::InvalidInput(
13            format!("invalid regex: {e}")
14        ))?;
15
16    let path = std::path::Path::new(path);
17    let mut rows: Vec<Vec<Value>> = Vec::new();
18
19    if path.is_file() {
20        let data = fs::read(path)
21            .map_err(|e| aurora_core::AuroraError::Io(e))?;
22        if is_binary(&data) {
23            return Err(aurora_core::AuroraError::InvalidInput(
24                format!("binary file: {}", path.display())
25            ));
26        }
27        let file = fs::File::open(path)
28            .map_err(|e| aurora_core::AuroraError::Io(e))?;
29        let reader = BufReader::new(file);
30        for (i, line) in reader.lines().enumerate() {
31            let line = line.map_err(|e| aurora_core::AuroraError::Io(e))?;
32            if re.find(&line).is_some() {
33                rows.push(vec![
34                    Value::Int((i + 1) as i64),
35                    Value::String(line),
36                ]);
37            }
38        }
39    } else if path.is_dir() {
40        for entry in walkdir::WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
41            if !entry.file_type().is_file() {
42                continue;
43            }
44            let data = match fs::read(entry.path()) {
45                Ok(d) => d,
46                Err(_) => continue,
47            };
48            if is_binary(&data) {
49                continue;
50            }
51            let file_path = entry.path().to_string_lossy().to_string();
52            let content = match String::from_utf8(data) {
53                Ok(c) => c,
54                Err(_) => continue,
55            };
56            for (i, line) in content.lines().enumerate() {
57                if re.find(line).is_some() {
58                    rows.push(vec![
59                        Value::String(file_path.clone()),
60                        Value::Int((i + 1) as i64),
61                        Value::String(line.into()),
62                    ]);
63                }
64            }
65        }
66    }
67
68    let headers = if path.is_file() {
69        vec!["line".into(), "content".into()]
70    } else {
71        vec!["file".into(), "line".into(), "content".into()]
72    };
73
74    Ok(Pipeline::table(headers, rows))
75}
76
77pub fn text_replace(pattern: &str, replacement: &str, path_str: &str) -> AuroraResult<Pipeline> {
78    let re = Regex::new(pattern)
79        .map_err(|e| aurora_core::AuroraError::InvalidInput(
80            format!("invalid regex: {e}")
81        ))?;
82
83    let path = std::path::Path::new(path_str);
84    let data = fs::read(path)
85        .map_err(|e| aurora_core::AuroraError::Io(e))?;
86
87    if is_binary(&data) {
88        return Err(aurora_core::AuroraError::InvalidInput(
89            format!("binary file: {path_str}")
90        ));
91    }
92
93    let content = String::from_utf8(data)
94        .map_err(|_| aurora_core::AuroraError::InvalidInput(
95            format!("not valid UTF-8: {path_str}")
96        ))?;
97
98    let result = re.replace_all(&content, replacement).to_string();
99    let mut count = 0;
100    for _mat in re.find_iter(&content) {
101        count += 1;
102    }
103
104    fs::write(path, &result)
105        .map_err(|e| aurora_core::AuroraError::Io(e))?;
106
107    Ok(Pipeline::table(
108        vec!["action".into(), "file".into(), "replacements".into()],
109        vec![vec![
110            Value::String("replace".into()),
111            Value::String(path_str.into()),
112            Value::Int(count as i64),
113        ]],
114    ))
115}
116
117pub fn text_transform(path_str: &str, upper: bool, lower: bool) -> AuroraResult<Pipeline> {
118    let path = std::path::Path::new(path_str);
119    let data = fs::read(path)
120        .map_err(|e| aurora_core::AuroraError::Io(e))?;
121
122    if is_binary(&data) {
123        return Err(aurora_core::AuroraError::InvalidInput(
124            format!("binary file: {path_str}")
125        ));
126    }
127
128    let content = String::from_utf8(data)
129        .map_err(|_| aurora_core::AuroraError::InvalidInput(
130            format!("not valid UTF-8: {path_str}")
131        ))?;
132
133    let result = if upper {
134        content.to_uppercase()
135    } else if lower {
136        content.to_lowercase()
137    } else {
138        return Err(aurora_core::AuroraError::InvalidInput(
139            "specify --upper or --lower".into()
140        ));
141    };
142
143    fs::write(path, &result)
144        .map_err(|e| aurora_core::AuroraError::Io(e))?;
145
146    let action = if upper { "upper" } else { "lower" };
147    Ok(Pipeline::table(
148        vec!["action".into(), "file".into()],
149        vec![vec![
150            Value::String(action.into()),
151            Value::String(path_str.into()),
152        ]],
153    ))
154}