1use std::{
2 path::{Path, PathBuf},
3 sync::Arc,
4};
5
6use crate::diagnostics::{FileName, SourceFile, SourceManager};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Input(PathBuf);
10impl From<std::ffi::OsString> for Input {
11 fn from(s: std::ffi::OsString) -> Self {
12 Self(PathBuf::from(s))
13 }
14}
15impl From<&std::ffi::OsStr> for Input {
16 fn from(s: &std::ffi::OsStr) -> Self {
17 Self(PathBuf::from(s))
18 }
19}
20impl From<&Path> for Input {
21 fn from(path: &Path) -> Self {
22 Self(path.to_path_buf())
23 }
24}
25impl From<PathBuf> for Input {
26 fn from(path: PathBuf) -> Self {
27 Self(path)
28 }
29}
30impl Input {
31 pub fn exists(&self) -> bool {
32 self.0.exists()
33 }
34
35 pub fn is_file(&self) -> bool {
36 self.0.is_file()
37 }
38
39 pub fn is_directory(&self) -> bool {
40 self.0.is_dir()
41 }
42
43 pub fn path(&self) -> &Path {
44 self.0.as_ref()
45 }
46
47 pub fn filename(&self) -> FileName {
48 if self.0.as_os_str() == "-" {
49 FileName::Stdin
50 } else {
51 FileName::from(self.0.clone())
52 }
53 }
54
55 pub fn into_source(
56 &self,
57 strict: bool,
58 source_manager: &dyn SourceManager,
59 ) -> std::io::Result<Arc<SourceFile>> {
60 let name = self.filename();
61 let code = self.read_to_string(strict)?;
62 log::trace!(target: "input", "read '{name}': '{code}'");
63 Ok(source_manager.load(name.language(), name, code))
64 }
65
66 pub fn get_file_types(&self, file_types: &[String]) -> Result<Vec<Input>, walkdir::Error> {
67 use walkdir::WalkDir;
68
69 let mut inputs = vec![];
70 let walker = WalkDir::new(&self.0).into_iter();
71 for entry in walker.filter_entry(|e| is_matching_file_type_or_dir(e, file_types)) {
72 let input = Self(entry?.into_path());
73 if input.is_directory() {
74 let mut children = input.get_file_types(file_types)?;
75 inputs.append(&mut children);
76 } else {
77 inputs.push(input);
78 }
79 }
80
81 Ok(inputs)
82 }
83
84 pub fn glob(&self, pattern: &str) -> Result<Vec<Input>, walkdir::Error> {
85 use glob::Pattern;
86 assert!(
87 self.is_directory(),
88 "cannot call `glob` on a non-directory path: {}",
89 self.0.display()
90 );
91
92 let path = self.0.as_os_str().to_string_lossy();
98 let mut pat = Pattern::escape(&path);
99 pat.push_str(std::path::MAIN_SEPARATOR_STR);
100 pat.push_str(pattern);
101 let pattern = Pattern::new(&pat).expect("invalid glob pattern");
102 self.glob_pattern(&pattern)
103 }
104
105 fn glob_pattern(&self, pattern: &glob::Pattern) -> Result<Vec<Input>, walkdir::Error> {
106 use walkdir::WalkDir;
107
108 let mut inputs = vec![];
109 let walker = WalkDir::new(&self.0).into_iter();
110 for entry in walker.filter_entry(|e| is_dir_or_pattern_match(e, pattern)) {
111 let input = Self(entry?.into_path());
112 if input.is_directory() {
113 let mut children = input.glob_pattern(pattern)?;
114 inputs.append(&mut children);
115 } else {
116 inputs.push(input);
117 }
118 }
119
120 Ok(inputs)
121 }
122
123 pub fn open(&self) -> std::io::Result<impl std::io::BufRead> {
124 use either::Either;
125 use std::fs::File;
126
127 Ok(if self.0.as_os_str() == "-" {
128 Either::Left(std::io::stdin().lock())
129 } else {
130 let file = if self.0.is_absolute() {
131 File::open(&self.0)?
132 } else {
133 let path = self.0.canonicalize()?;
134 File::open(path)?
135 };
136 Either::Right(std::io::BufReader::new(file))
137 })
138 }
139
140 pub fn read_to_string(&self, strict: bool) -> std::io::Result<String> {
141 use std::io::{BufRead, Read};
142
143 let mut buf = self.open()?;
144 let mut content = String::with_capacity(1024);
145 if strict {
146 buf.read_to_string(&mut content)?;
147 Ok(content)
148 } else {
149 for (i, line) in buf.lines().enumerate() {
151 let mut line = line?;
152 unsafe {
158 let bytes = content.as_mut_vec();
159 if i > 0 {
160 bytes.push(b'\n');
161 }
162 bytes.append(line.as_mut_vec());
163 }
164 }
165
166 Ok(content)
167 }
168 }
169}
170
171fn is_matching_file_type_or_dir(entry: &walkdir::DirEntry, file_types: &[String]) -> bool {
172 let path = entry.path();
173 if path.is_dir() {
174 return true;
175 }
176 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
177 file_types.iter().any(|ft| ft == ext)
178 } else {
179 false
180 }
181}
182
183fn is_dir_or_pattern_match(entry: &walkdir::DirEntry, pattern: &glob::Pattern) -> bool {
184 let path = entry.path();
185 if path.is_dir() {
186 return true;
187 }
188 pattern.matches_path(path)
189}