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}