flowscope_cli/
input.rs

1//! Input handling for file reading and stdin support.
2
3use anyhow::{Context, Result};
4use flowscope_core::FileSource;
5use std::io::{self, Read};
6use std::path::PathBuf;
7
8/// Read SQL input from files or stdin.
9///
10/// If no files are provided, reads from stdin.
11/// Returns a vector of FileSource for multi-file analysis.
12pub fn read_input(files: &[PathBuf]) -> Result<Vec<FileSource>> {
13    if files.is_empty() {
14        read_from_stdin()
15    } else {
16        read_from_files(files)
17    }
18}
19
20/// Read SQL from stdin
21fn read_from_stdin() -> Result<Vec<FileSource>> {
22    let mut content = String::new();
23    io::stdin()
24        .read_to_string(&mut content)
25        .context("Failed to read from stdin")?;
26
27    Ok(vec![FileSource {
28        // Use .sql extension so frontend filters include stdin content
29        name: "<stdin>.sql".to_string(),
30        content,
31    }])
32}
33
34/// Read SQL from multiple files
35fn read_from_files(files: &[PathBuf]) -> Result<Vec<FileSource>> {
36    files
37        .iter()
38        .map(|path| {
39            let content = std::fs::read_to_string(path)
40                .with_context(|| format!("Failed to read file: {}", path.display()))?;
41
42            Ok(FileSource {
43                name: path.display().to_string(),
44                content,
45            })
46        })
47        .collect()
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use std::io::Write;
54    use tempfile::NamedTempFile;
55
56    #[test]
57    fn test_read_single_file() {
58        let mut file = NamedTempFile::new().unwrap();
59        writeln!(file, "SELECT * FROM users").unwrap();
60
61        let sources = read_from_files(&[file.path().to_path_buf()]).unwrap();
62        assert_eq!(sources.len(), 1);
63        assert!(sources[0].content.contains("SELECT * FROM users"));
64    }
65
66    #[test]
67    fn test_read_multiple_files() {
68        let mut file1 = NamedTempFile::new().unwrap();
69        let mut file2 = NamedTempFile::new().unwrap();
70        writeln!(file1, "SELECT * FROM users").unwrap();
71        writeln!(file2, "SELECT * FROM orders").unwrap();
72
73        let sources =
74            read_from_files(&[file1.path().to_path_buf(), file2.path().to_path_buf()]).unwrap();
75        assert_eq!(sources.len(), 2);
76    }
77
78    #[test]
79    fn test_read_missing_file() {
80        let result = read_from_files(&[PathBuf::from("/nonexistent/file.sql")]);
81        assert!(result.is_err());
82    }
83}