1use std::fs::{self, OpenOptions};
8use std::io::{self, Write};
9use std::path::Path;
10
11use crate::error::VanityError;
12use crate::flags::VanityFlags;
13use crate::vanity_addr_generator::chain::Chain;
14use crate::VanityMode;
15
16#[derive(Debug, Clone)]
19pub struct FileLineItem {
20 pub pattern: String,
22 pub flags: VanityFlags,
24}
25
26fn parse_line(line: &str) -> Option<FileLineItem> {
35 if line.starts_with('#') {
36 return None;
37 }
38
39 let mut tokens = line.split_whitespace();
40
41 let pattern = tokens.next()?.to_string();
43 let flags_vec: Vec<&str> = tokens.collect();
45
46 if flags_vec.is_empty() {
48 return Some(FileLineItem {
49 pattern,
50 flags: VanityFlags {
51 force_flags: false,
52 is_case_sensitive: false,
53 disable_fast_mode: false,
54 output_file_name: None,
55 vanity_mode: None,
56 chain: None,
57 threads: 16,
58 },
59 });
60 }
61
62 let is_case_sensitive = flags_vec.contains(&"-c") || flags_vec.contains(&"--case-sensitive");
63 let disable_fast_mode = flags_vec.contains(&"-d") || flags_vec.contains(&"--disable-fast");
64
65 let chain = if flags_vec.contains(&"--eth") {
67 Some(Chain::Ethereum)
68 } else if flags_vec.contains(&"--sol") {
69 Some(Chain::Solana)
70 } else if flags_vec.contains(&"--btc") {
71 Some(Chain::Bitcoin)
72 } else {
73 None
74 };
75
76 let vanity_mode = if flags_vec.contains(&"-r") || flags_vec.contains(&"--regex") {
78 Some(VanityMode::Regex)
79 } else if flags_vec.contains(&"-a") || flags_vec.contains(&"--anywhere") {
80 Some(VanityMode::Anywhere)
81 } else if flags_vec.contains(&"-s") || flags_vec.contains(&"--suffix") {
82 Some(VanityMode::Suffix)
83 } else if flags_vec.contains(&"-p") || flags_vec.contains(&"--prefix") {
84 Some(VanityMode::Prefix)
85 } else {
86 None
87 };
88
89 let mut output_file_name: Option<String> = None;
91 for (i, &flag) in flags_vec.iter().enumerate() {
92 if flag == "-o" || flag == "--output-file" {
93 if let Some(next_flag) = flags_vec.get(i + 1) {
94 output_file_name = Some(next_flag.to_string());
95 }
96 }
97 }
98
99 Some(FileLineItem {
100 pattern,
101 flags: VanityFlags {
102 force_flags: false,
103 is_case_sensitive,
104 disable_fast_mode,
105 output_file_name,
106 vanity_mode,
107 chain,
108 threads: 0,
109 },
110 })
111}
112
113pub fn parse_input_file(path: &str) -> Result<Vec<FileLineItem>, VanityError> {
125 let contents = fs::read_to_string(path)?;
126 let mut items = Vec::new();
127
128 for line in contents.lines() {
129 let line = line.trim();
130 if line.is_empty() || line.starts_with('#') {
131 continue;
132 }
133
134 if let Some(item) = parse_line(line) {
135 items.push(item);
136 }
137 }
138 Ok(items)
139}
140
141pub fn write_output_file(output_path: &Path, buffer: &str) -> Result<(), VanityError> {
155 let file_result = OpenOptions::new()
157 .append(true)
158 .create(true)
159 .open(output_path);
160 let mut file = match file_result {
161 Ok(file) => file,
162 Err(e) => {
163 return Err(VanityError::FileError(io::Error::other(format!(
164 "Failed to open or create file: {e}"
165 ))))
166 }
167 };
168
169 if let Err(e) = file.write_all(buffer.as_bytes()) {
171 return Err(VanityError::FileError(io::Error::new(
172 io::ErrorKind::WriteZero,
173 format!("Failed to write to file: {e}"),
174 )));
175 }
176
177 Ok(())
178}
179
180#[cfg(test)]
181mod tests {
182 use super::{parse_input_file, parse_line};
183 use crate::VanityMode;
184
185 #[test]
186 fn test_parse_line_with_valid_flags() {
187 let line = "test -p -c";
188 let item = parse_line(line).expect("Failed to parse valid line");
189
190 assert_eq!(item.pattern, "test");
191 assert_eq!(item.flags.vanity_mode, Some(VanityMode::Prefix));
192 assert!(item.flags.is_case_sensitive);
193 }
194
195 #[test]
196 fn test_parse_line_with_invalid_flags() {
197 let line = "test -z";
198 let item = parse_line(line).expect("Failed to parse line with invalid flags");
199
200 assert_eq!(item.pattern, "test");
201 assert!(item.flags.vanity_mode.is_none());
202 }
203
204 #[test]
205 fn test_parse_line_with_no_flags() {
206 let line = "test";
207 let item = parse_line(line).expect("Failed to parse line without flags");
208
209 assert_eq!(item.pattern, "test");
210 assert!(item.flags.vanity_mode.is_none());
211 assert!(!item.flags.is_case_sensitive);
212 assert!(!item.flags.disable_fast_mode);
213 }
214
215 #[test]
216 fn test_parse_line_with_output_file_flag() {
217 let line = "test -p -o output.txt";
218 let item = parse_line(line).expect("Failed to parse line with output file flag");
219
220 assert_eq!(item.pattern, "test");
221 assert_eq!(item.flags.vanity_mode, Some(VanityMode::Prefix));
222 assert_eq!(item.flags.output_file_name, Some("output.txt".to_string()));
223 }
224
225 #[test]
226 fn test_parse_empty_line() {
227 let line = "";
228 let item = parse_line(line);
229
230 assert!(item.is_none(), "Empty line should not be parsed");
231 }
232
233 #[test]
234 fn test_parse_comment_line() {
235 let line = "# This is a comment";
236 let item = parse_line(line);
237
238 assert!(item.is_none(), "Comment line should not be parsed");
239 }
240
241 #[test]
242 fn test_parse_input_file_with_valid_lines() {
243 let file_content = "test -p\nexample -s\nanywhere -a";
245 let file_path = "test_valid_input.txt";
246 std::fs::write(file_path, file_content).expect("Failed to create mock input file");
247
248 let result = parse_input_file(file_path);
250 assert!(result.is_ok(), "Failed to parse valid input file");
251
252 let items = result.unwrap();
253 assert_eq!(items.len(), 3);
254
255 assert_eq!(items[0].pattern, "test");
256 assert_eq!(items[0].flags.vanity_mode, Some(VanityMode::Prefix));
257
258 assert_eq!(items[1].pattern, "example");
259 assert_eq!(items[1].flags.vanity_mode, Some(VanityMode::Suffix));
260
261 assert_eq!(items[2].pattern, "anywhere");
262 assert_eq!(items[2].flags.vanity_mode, Some(VanityMode::Anywhere));
263
264 std::fs::remove_file(file_path).expect("Failed to delete mock input file");
266 }
267
268 #[test]
269 fn test_parse_input_file_with_invalid_lines() {
270 let file_content = "test -z\nexample --invalid";
272 let file_path = "test_invalid_input.txt";
273 std::fs::write(file_path, file_content).expect("Failed to create mock input file");
274
275 let result = parse_input_file(file_path);
277 assert!(result.is_ok(), "Failed to parse file with invalid lines");
278
279 let items = result.unwrap();
280 assert_eq!(items.len(), 2);
281
282 assert_eq!(items[0].pattern, "test");
283 assert!(items[0].flags.vanity_mode.is_none());
284
285 assert_eq!(items[1].pattern, "example");
286 assert!(items[1].flags.vanity_mode.is_none());
287
288 std::fs::remove_file(file_path).expect("Failed to delete mock input file");
290 }
291
292 #[test]
293 fn test_parse_input_file_with_empty_lines() {
294 let file_content = "\n\n";
296 let file_path = "test_empty_lines.txt";
297 std::fs::write(file_path, file_content).expect("Failed to create mock input file");
298
299 let result = parse_input_file(file_path);
301 assert!(result.is_ok(), "Failed to parse file with empty lines");
302
303 let items = result.unwrap();
304 assert!(
305 items.is_empty(),
306 "Parsed items should be empty for a file with only empty lines"
307 );
308
309 std::fs::remove_file(file_path).expect("Failed to delete mock input file");
311 }
312
313 #[test]
314 fn test_parse_input_file_with_invalid_path() {
315 let file_path = "non_existent_file.txt";
317
318 let result = parse_input_file(file_path);
320 assert!(
321 result.is_err(),
322 "Parsing a non-existent file should return an error"
323 );
324
325 if let Err(err) = result {
326 assert!(
327 err.to_string().contains("No such file"),
328 "Unexpected error message: {}",
329 err
330 );
331 }
332 }
333
334 #[test]
335 fn test_parse_input_file_with_missing_flags() {
336 let file_content = "test\nexample\nmissing_flags";
338 let file_path = "test_missing_flags.txt";
339 std::fs::write(file_path, file_content).expect("Failed to create mock input file");
340
341 let result = parse_input_file(file_path);
343 assert!(result.is_ok(), "Failed to parse file with missing flags");
344
345 let items = result.unwrap();
346 assert_eq!(items.len(), 3);
347
348 assert_eq!(items[0].pattern, "test");
349 assert!(items[0].flags.vanity_mode.is_none());
350
351 assert_eq!(items[1].pattern, "example");
352 assert!(items[1].flags.vanity_mode.is_none());
353
354 assert_eq!(items[2].pattern, "missing_flags");
355 assert!(items[2].flags.vanity_mode.is_none());
356
357 std::fs::remove_file(file_path).expect("Failed to delete mock input file");
359 }
360}