lychee_lib/types/input/
source.rs1use crate::BaseInfo;
18use crate::ErrorKind;
19use crate::utils;
20
21use glob::Pattern;
22use reqwest::Url;
23use serde::{Deserialize, Deserializer, Serialize};
24use std::borrow::Cow;
25use std::fmt::Display;
26use std::path::PathBuf;
27use std::result::Result;
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
31#[non_exhaustive]
32pub enum InputSource {
33 RemoteUrl(Box<Url>),
35 FsGlob {
37 #[serde(deserialize_with = "InputSource::deserialize_pattern")]
39 pattern: Pattern,
40 ignore_case: bool,
42 },
43 FsPath(PathBuf),
45 Stdin,
47 String(Cow<'static, str>),
49}
50
51impl InputSource {
52 const STDIN: &str = "-";
53
54 pub fn new(input: &str, glob_ignore_case: bool) -> Result<Self, ErrorKind> {
63 if input == Self::STDIN {
64 return Ok(InputSource::Stdin);
65 }
66
67 if let Ok(url) = utils::url::parse_url_or_path(input) {
68 return Ok(InputSource::RemoteUrl(Box::new(url)));
69 }
70
71 let is_glob = glob::Pattern::escape(input) != input;
73
74 if is_glob {
75 return Ok(InputSource::FsGlob {
76 pattern: Pattern::new(input)?,
77 ignore_case: glob_ignore_case,
78 });
79 }
80
81 let path = PathBuf::from(input);
83
84 if path.exists() {
85 Ok(InputSource::FsPath(path))
86 } else {
87 Err(ErrorKind::InvalidInput(input.to_owned()))
88 }
89 }
90
91 fn deserialize_pattern<'de, D>(deserializer: D) -> Result<Pattern, D::Error>
92 where
93 D: Deserializer<'de>,
94 {
95 use serde::de::Error;
96 let s = String::deserialize(deserializer)?;
97 Pattern::new(&s).map_err(D::Error::custom)
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Hash)]
110pub enum ResolvedInputSource {
111 RemoteUrl(Box<Url>),
113 FsPath(PathBuf),
115 Stdin,
117 String(Cow<'static, str>),
119}
120
121impl ResolvedInputSource {
122 pub fn to_base_info(&self) -> Result<BaseInfo, ErrorKind> {
133 let url = match self {
134 Self::RemoteUrl(url) => Cow::Borrowed(&**url),
135 Self::FsPath(path) => std::path::absolute(path)
136 .ok()
137 .and_then(|x| Url::from_file_path(x).ok())
138 .map(Cow::Owned)
139 .ok_or_else(|| ErrorKind::InvalidUrlFromPath(path.to_owned()))?,
140 _ => return Ok(BaseInfo::none()),
141 };
142
143 Ok(BaseInfo::from_source_url(&url))
144 }
145}
146
147impl From<ResolvedInputSource> for InputSource {
148 fn from(resolved: ResolvedInputSource) -> Self {
149 match resolved {
150 ResolvedInputSource::RemoteUrl(url) => InputSource::RemoteUrl(url),
151 ResolvedInputSource::FsPath(path) => InputSource::FsPath(path),
152 ResolvedInputSource::Stdin => InputSource::Stdin,
153 ResolvedInputSource::String(s) => InputSource::String(s),
154 }
155 }
156}
157
158impl Display for ResolvedInputSource {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.write_str(match self {
161 Self::RemoteUrl(url) => url.as_str(),
162 Self::FsPath(path) => path.to_str().unwrap_or_default(),
163 Self::Stdin => "stdin",
164 Self::String(s) => s.as_ref(),
165 })
166 }
167}
168
169impl Serialize for InputSource {
179 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
180 where
181 S: serde::Serializer,
182 {
183 serializer.collect_str(self)
184 }
185}
186
187impl Display for InputSource {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 f.write_str(match self {
190 Self::RemoteUrl(url) => url.as_str(),
191 Self::FsGlob { pattern, .. } => pattern.as_str(),
192 Self::FsPath(path) => path.to_str().unwrap_or_default(),
193 Self::Stdin => "stdin",
194 Self::String(s) => s.as_ref(),
195 })
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
206 fn test_pattern_serialization_is_original_pattern() {
207 let pat = "asd[f]*";
208 assert_eq!(
209 serde_json::to_string(&InputSource::FsGlob {
210 pattern: Pattern::new(pat).unwrap(),
211 ignore_case: false,
212 })
213 .unwrap(),
214 serde_json::to_string(pat).unwrap(),
215 );
216 }
217}