wdl_cli/analysis/
source.rs

1//! Sources for a WDL documents used in analysis.
2
3use 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/// A source for an analysis.
15#[derive(Clone, Debug)]
16pub enum Source {
17    /// A remote URL.
18    Remote(Url),
19
20    /// A local file.
21    File(Url),
22
23    /// A local directory.
24    Directory(PathBuf),
25}
26
27impl Source {
28    /// Attempts to reference the source as a URL.
29    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    /// Registers the source within an [`Analyzer`].
37    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        // Default to the current directory.
91        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}