Skip to main content

opy_rs/
project.rs

1use std::fmt;
2use std::fs;
3use std::io;
4use std::path::{Path, PathBuf};
5
6/// A filesystem-backed OPY project entry and the root used to resolve it.
7#[derive(Debug)]
8pub struct FilesystemProject {
9    source: String,
10    main_path: PathBuf,
11    root: PathBuf,
12}
13
14impl FilesystemProject {
15    /// Load an OPY project from its selected filesystem entry.
16    pub fn load(path: &Path) -> Result<Self, FilesystemProjectError> {
17        let canonical_path = path
18            .canonicalize()
19            .map_err(|error| FilesystemProjectError::entry_not_found(path.to_path_buf(), error))?;
20        let source = fs::read_to_string(&canonical_path).map_err(|error| {
21            FilesystemProjectError::entry_unreadable(canonical_path.clone(), error)
22        })?;
23        let root = canonical_path
24            .parent()
25            .map(Path::to_path_buf)
26            .unwrap_or_else(|| PathBuf::from("."));
27        Ok(Self {
28            source,
29            main_path: path.to_path_buf(),
30            root,
31        })
32    }
33
34    /// The source text read from the selected entry.
35    pub fn source(&self) -> &str {
36        &self.source
37    }
38
39    /// The selected entry path used as the source identity in diagnostics.
40    pub fn main_path(&self) -> &Path {
41        &self.main_path
42    }
43
44    /// The canonical parent directory used for project-relative resolution.
45    pub fn root(&self) -> &Path {
46        &self.root
47    }
48}
49
50/// Failure while loading a filesystem-backed OPY project entry.
51#[derive(Debug)]
52pub enum FilesystemProjectError {
53    EntryNotFound { path: PathBuf, source: io::Error },
54    EntryUnreadable { path: PathBuf, source: io::Error },
55}
56
57impl FilesystemProjectError {
58    fn entry_not_found(path: PathBuf, source: io::Error) -> Self {
59        Self::EntryNotFound { path, source }
60    }
61
62    fn entry_unreadable(path: PathBuf, source: io::Error) -> Self {
63        Self::EntryUnreadable { path, source }
64    }
65
66    /// Whether the entry could not be resolved to a canonical filesystem path.
67    pub fn is_entry_not_found(&self) -> bool {
68        matches!(self, Self::EntryNotFound { .. })
69    }
70}
71
72impl fmt::Display for FilesystemProjectError {
73    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
74        match self {
75            Self::EntryNotFound { source, .. } => write!(formatter, "cannot find entry: {source}"),
76            Self::EntryUnreadable { source, .. } => {
77                write!(formatter, "cannot read entry: {source}")
78            }
79        }
80    }
81}
82
83impl std::error::Error for FilesystemProjectError {
84    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
85        match self {
86            Self::EntryNotFound { source, .. } | Self::EntryUnreadable { source, .. } => {
87                Some(source)
88            }
89        }
90    }
91}