wdl_cli/analysis/
source.rs1use std::path::Path;
4use std::path::PathBuf;
5
6use anyhow::Result;
7use anyhow::anyhow;
8use anyhow::bail;
9use path_clean::PathClean;
10use url::Url;
11use wdl_analysis::Analyzer;
12use wdl_engine::path::parse_url;
13
14#[derive(Clone, Debug)]
16pub enum Source {
17 Remote(Url),
19
20 File(Url),
22
23 Directory(PathBuf),
25}
26
27impl Source {
28 pub fn as_url(&self) -> Option<&Url> {
30 match self {
31 Source::Remote(url) | Source::File(url) => Some(url),
32 Source::Directory(_) => None,
33 }
34 }
35
36 pub async fn register<T: Send + Clone + 'static>(
38 self,
39 analyzer: &mut Analyzer<T>,
40 ) -> Result<()> {
41 match self {
42 Source::Remote(url) | Source::File(url) => analyzer.add_document(url).await,
43 Source::Directory(path) => analyzer.add_directory(path).await,
44 }
45 }
46}
47
48impl std::fmt::Display for Source {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 Source::Remote(url) => write!(f, "{url}"),
52 Source::File(url) => write!(f, "{url}"),
53 Source::Directory(path) => write!(f, "{path}", path = path.display()),
54 }
55 }
56}
57
58impl std::str::FromStr for Source {
59 type Err = anyhow::Error;
60
61 fn from_str(s: &str) -> Result<Self, Self::Err> {
62 if let Some(url) = parse_url(s) {
63 return Ok(Self::Remote(url));
64 }
65
66 let path = Path::new(s);
67
68 let path = std::path::absolute(path)
69 .map_err(|_| anyhow!("failed to convert `{path}` to a URI", path = path.display()))
70 .map(|path| path.clean())?;
71
72 if !path.exists() {
73 bail!("source file `{s}` does not exist");
74 }
75
76 if path.is_dir() {
77 return Ok(Source::Directory(path));
78 } else if path.is_file()
79 && let Ok(url) = Url::from_file_path(&path)
80 {
81 return Ok(Source::File(url));
82 }
83
84 bail!("failed to convert `{s}` to a URI")
85 }
86}
87
88impl Default for Source {
89 fn default() -> Self {
90 Source::Directory(
92 std::env::current_dir()
93 .unwrap_or_else(|_| PathBuf::from(std::path::Component::CurDir.as_os_str())),
94 )
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn file() {
104 let file = tempfile::NamedTempFile::new().unwrap();
105 let path = std::path::absolute(file.path()).unwrap();
106
107 let source = path.to_str().unwrap().parse::<Source>().unwrap();
108 assert!(matches!(source, Source::File(_)));
109 let url = source.as_url().unwrap();
110 assert_eq!(url.scheme(), "file");
111 assert_eq!(url.to_file_path().unwrap(), path);
112 }
113
114 #[test]
115 fn directory() {
116 let dir = tempfile::TempDir::new().unwrap();
117 let name = dir.path().as_os_str().to_str().unwrap();
118
119 assert!(matches!(name.parse().unwrap(),
120 Source::Directory(path)
121 if path.as_os_str().to_str().unwrap() == name));
122 }
123
124 #[test]
125 fn url() {
126 const EXAMPLE: &str = "https://example.com/";
127 assert!(matches!(EXAMPLE.parse().unwrap(),
128 Source::Remote(url)
129 if url.as_str()
130 == EXAMPLE
131 ));
132 }
133
134 #[test]
135 fn missing_file() {
136 let err = "a-random-file-that-doesnt-exist.txt"
137 .parse::<Source>()
138 .unwrap_err();
139
140 assert_eq!(
141 err.to_string(),
142 "source file `a-random-file-that-doesnt-exist.txt` does not exist"
143 );
144 }
145
146 #[test]
147 fn invalid_source() {
148 let err = "".parse::<Source>().unwrap_err();
149
150 assert_eq!(err.to_string(), "failed to convert `` to a URI");
151 }
152}