1use std::{
2 fs,
3 io::{Read, Write},
4 path::{Path, PathBuf},
5 sync::{
6 atomic::{AtomicUsize, Ordering},
7 mpsc, Arc,
8 },
9};
10
11use anyhow::{anyhow, bail, Context, Result};
12use glob::glob;
13
14use crate::test::{parse_tests, TestEntry};
15
16pub enum CliInput {
17 Paths(Vec<PathBuf>),
18 Test {
19 name: String,
20 contents: Vec<u8>,
21 languages: Vec<Box<str>>,
22 },
23 Stdin(Vec<u8>),
24}
25
26pub fn get_input(
27 paths_file: Option<&Path>,
28 paths: Option<Vec<PathBuf>>,
29 test_number: Option<u32>,
30 cancellation_flag: &Arc<AtomicUsize>,
31) -> Result<CliInput> {
32 if let Some(paths_file) = paths_file {
33 return Ok(CliInput::Paths(
34 fs::read_to_string(paths_file)
35 .with_context(|| format!("Failed to read paths file {}", paths_file.display()))?
36 .trim()
37 .lines()
38 .map(PathBuf::from)
39 .collect::<Vec<_>>(),
40 ));
41 }
42
43 if let Some(test_number) = test_number {
44 let current_dir = std::env::current_dir().unwrap();
45 let test_dir = current_dir.join("test").join("corpus");
46
47 if !test_dir.exists() {
48 return Err(anyhow!(
49 "Test corpus directory not found in current directory, see https://tree-sitter.github.io/tree-sitter/creating-parsers/5-writing-tests"
50 ));
51 }
52
53 let test_entry = parse_tests(&test_dir)?;
54 let mut test_num = 0;
55 let Some((name, contents, languages)) =
56 get_test_info(&test_entry, test_number.max(1) - 1, &mut test_num)
57 else {
58 return Err(anyhow!("Failed to fetch contents of test #{test_number}"));
59 };
60
61 return Ok(CliInput::Test {
62 name,
63 contents,
64 languages,
65 });
66 }
67
68 if let Some(paths) = paths {
69 let mut result = Vec::new();
70
71 let mut incorporate_path = |path: PathBuf, positive| {
72 if positive {
73 result.push(path);
74 } else if let Some(index) = result.iter().position(|p| *p == path) {
75 result.remove(index);
76 }
77 };
78
79 for mut path in paths {
80 let mut positive = true;
81 if path.starts_with("!") {
82 positive = false;
83 path = path.strip_prefix("!").unwrap().to_path_buf();
84 }
85
86 if path.exists() {
87 incorporate_path(path, positive);
88 } else {
89 let Some(path_str) = path.to_str() else {
90 bail!("Invalid path: {}", path.display());
91 };
92 let paths =
93 glob(path_str).with_context(|| format!("Invalid glob pattern {path:?}"))?;
94 for path in paths {
95 incorporate_path(path?, positive);
96 }
97 }
98 }
99
100 if result.is_empty() {
101 return Err(anyhow!(
102 "No files were found at or matched by the provided pathname/glob"
103 ));
104 }
105
106 return Ok(CliInput::Paths(result));
107 }
108
109 let reader_flag = cancellation_flag.clone();
110 let (tx, rx) = mpsc::channel();
111
112 std::thread::spawn(move || {
114 let mut input = Vec::new();
115 let stdin = std::io::stdin();
116 let mut handle = stdin.lock();
117
118 loop {
120 if reader_flag.load(Ordering::Relaxed) == 1 {
121 break;
122 }
123 let mut buffer = [0; 1024];
124 match handle.read(&mut buffer) {
125 Ok(0) | Err(_) => break,
126 Ok(n) => input.extend_from_slice(&buffer[..n]),
127 }
128 }
129
130 tx.send(input).ok();
132 });
133
134 loop {
135 if cancellation_flag.load(Ordering::Relaxed) == 1 {
137 bail!("\n");
138 }
139
140 if let Ok(input) = rx.try_recv() {
142 return Ok(CliInput::Stdin(input));
143 }
144
145 std::thread::sleep(std::time::Duration::from_millis(50));
146 }
147}
148
149#[allow(clippy::type_complexity)]
150pub fn get_test_info(
151 test_entry: &TestEntry,
152 target_test: u32,
153 test_num: &mut u32,
154) -> Option<(String, Vec<u8>, Vec<Box<str>>)> {
155 match test_entry {
156 TestEntry::Example {
157 name,
158 input,
159 attributes,
160 ..
161 } => {
162 if *test_num == target_test {
163 return Some((name.clone(), input.clone(), attributes.languages.clone()));
164 }
165 *test_num += 1;
166 }
167 TestEntry::Group { children, .. } => {
168 for child in children {
169 if let Some((name, input, languages)) = get_test_info(child, target_test, test_num)
170 {
171 return Some((name, input, languages));
172 }
173 }
174 }
175 }
176
177 None
178}
179
180pub fn get_tmp_source_file(contents: &[u8]) -> Result<PathBuf> {
182 let parse_path = std::env::temp_dir().join(".tree-sitter-temp");
183 let mut parse_file = std::fs::File::create(&parse_path)?;
184 parse_file.write_all(contents)?;
185
186 Ok(parse_path)
187}