1use std::{
2 borrow::Cow,
3 path::{Path, PathBuf},
4};
5
6use crate::diagnostics::{ArcSource, FileName, Source};
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::Path(self.0.clone().into_boxed_path())
52 }
53 }
54
55 pub fn into_arc_source(&self, strict: bool) -> std::io::Result<ArcSource> {
56 self.into_source(strict).map(ArcSource::new)
57 }
58
59 pub fn into_source(&self, strict: bool) -> std::io::Result<Source<'static>> {
60 let name = self.filename();
61 let code = self.read_to_string(strict).map(Cow::Owned)?;
62 Ok(Source { name, code })
63 }
64
65 pub fn get_file_types(&self, file_types: &[String]) -> Result<Vec<Input>, walkdir::Error> {
66 use walkdir::WalkDir;
67
68 let mut inputs = vec![];
69 let walker = WalkDir::new(&self.0).into_iter();
70 for entry in walker.filter_entry(|e| is_matching_file_type_or_dir(e, file_types)) {
71 let input = Self(entry?.into_path());
72 if input.is_directory() {
73 let mut children = input.get_file_types(file_types)?;
74 inputs.append(&mut children);
75 } else {
76 inputs.push(input);
77 }
78 }
79
80 Ok(inputs)
81 }
82
83 pub fn glob(&self, pattern: &str) -> Result<Vec<Input>, walkdir::Error> {
84 use glob::Pattern;
85 assert!(
86 self.is_directory(),
87 "cannot call `glob` on a non-directory path: {}",
88 self.0.display()
89 );
90
91 let path = self.0.as_os_str().to_string_lossy();
97 let mut pat = Pattern::escape(&path);
98 pat.push_str(std::path::MAIN_SEPARATOR_STR);
99 pat.push_str(pattern);
100 let pattern = Pattern::new(&pat).expect("invalid glob pattern");
101 self.glob_pattern(&pattern)
102 }
103
104 fn glob_pattern(&self, pattern: &glob::Pattern) -> Result<Vec<Input>, walkdir::Error> {
105 use walkdir::WalkDir;
106
107 let mut inputs = vec![];
108 let walker = WalkDir::new(&self.0).into_iter();
109 for entry in walker.filter_entry(|e| is_dir_or_pattern_match(e, pattern)) {
110 let input = Self(entry?.into_path());
111 if input.is_directory() {
112 let mut children = input.glob_pattern(pattern)?;
113 inputs.append(&mut children);
114 } else {
115 inputs.push(input);
116 }
117 }
118
119 Ok(inputs)
120 }
121
122 pub fn open(&self) -> std::io::Result<impl std::io::BufRead> {
123 use either::Either;
124 use std::fs::File;
125
126 Ok(if self.0.as_os_str() == "-" {
127 Either::Left(std::io::stdin().lock())
128 } else {
129 let file = if self.0.is_absolute() {
130 File::open(&self.0)?
131 } else {
132 let path = self.0.canonicalize()?;
133 File::open(path)?
134 };
135 Either::Right(std::io::BufReader::new(file))
136 })
137 }
138
139 pub fn read_to_string(&self, strict: bool) -> std::io::Result<String> {
140 use std::io::{BufRead, Read};
141
142 let mut buf = self.open()?;
143 let mut content = String::with_capacity(1024);
144 if strict {
145 buf.read_to_string(&mut content)?;
146 Ok(content)
147 } else {
148 for line in buf.lines() {
150 let mut line = line?;
151 unsafe {
157 let bytes = content.as_mut_vec();
158 bytes.push(b'\n');
159 bytes.append(line.as_mut_vec());
160 }
161 }
162
163 Ok(content)
164 }
165 }
166}
167
168fn is_matching_file_type_or_dir(entry: &walkdir::DirEntry, file_types: &[String]) -> bool {
169 let path = entry.path();
170 if path.is_dir() {
171 return true;
172 }
173 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
174 file_types.iter().any(|ft| ft == ext)
175 } else {
176 false
177 }
178}
179
180fn is_dir_or_pattern_match(entry: &walkdir::DirEntry, pattern: &glob::Pattern) -> bool {
181 let path = entry.path();
182 if path.is_dir() {
183 return true;
184 }
185 pattern.matches_path(path)
186}