rust/crates/vfs/src/loader.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

216 lines
6.5 KiB
Rust
Raw Normal View History

2020-06-15 06:29:07 -05:00
//! Object safe interface for file watching and reading.
use std::fmt;
2020-06-24 08:52:07 -05:00
use paths::{AbsPath, AbsPathBuf};
2020-06-15 06:29:07 -05:00
2021-01-12 10:22:57 -06:00
/// A set of files on the file system.
2020-07-18 09:40:10 -05:00
#[derive(Debug, Clone)]
2020-06-15 06:29:07 -05:00
pub enum Entry {
2021-01-12 10:22:57 -06:00
/// The `Entry` is represented by a raw set of files.
2020-06-15 06:29:07 -05:00
Files(Vec<AbsPathBuf>),
2021-01-12 10:22:57 -06:00
/// The `Entry` is represented by `Directories`.
2020-07-18 09:40:10 -05:00
Directories(Directories),
}
/// Specifies a set of files on the file system.
///
/// A file is included if:
/// * it has included extension
/// * it is under an `include` path
/// * it is not under `exclude` path
///
/// If many include/exclude paths match, the longest one wins.
2021-01-12 10:22:57 -06:00
///
/// If a path is in both `include` and `exclude`, the `exclude` one wins.
2020-07-21 05:52:51 -05:00
#[derive(Debug, Clone, Default)]
2020-07-18 09:40:10 -05:00
pub struct Directories {
pub extensions: Vec<String>,
pub include: Vec<AbsPathBuf>,
pub exclude: Vec<AbsPathBuf>,
2020-06-15 06:29:07 -05:00
}
2021-01-12 10:22:57 -06:00
/// [`Handle`]'s configuration.
2020-06-11 04:04:09 -05:00
#[derive(Debug)]
2020-06-15 06:29:07 -05:00
pub struct Config {
/// Version number to associate progress updates to the right config
/// version.
pub version: u32,
2021-01-12 10:22:57 -06:00
/// Set of initially loaded files.
2020-06-15 06:29:07 -05:00
pub load: Vec<Entry>,
2021-01-12 10:22:57 -06:00
/// Index of watched entries in `load`.
///
/// If a path in a watched entry is modified,the [`Handle`] should notify it.
2020-06-15 06:29:07 -05:00
pub watch: Vec<usize>,
}
2021-01-12 10:22:57 -06:00
/// Message about an action taken by a [`Handle`].
2020-06-15 06:29:07 -05:00
pub enum Message {
2021-01-12 10:22:57 -06:00
/// Indicate a gradual progress.
///
/// This is supposed to be the number of loaded files.
Progress { n_total: usize, n_done: usize, config_version: u32 },
2021-01-12 10:22:57 -06:00
/// The handle loaded the following files' content.
2020-06-15 06:29:07 -05:00
Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> },
}
2021-01-12 10:22:57 -06:00
/// Type that will receive [`Messages`](Message) from a [`Handle`].
2020-06-15 06:29:07 -05:00
pub type Sender = Box<dyn Fn(Message) + Send>;
2021-01-12 10:22:57 -06:00
/// Interface for reading and watching files.
2020-06-15 06:29:07 -05:00
pub trait Handle: fmt::Debug {
2021-01-12 10:22:57 -06:00
/// Spawn a new handle with the given `sender`.
2020-06-15 06:29:07 -05:00
fn spawn(sender: Sender) -> Self
where
Self: Sized;
2021-01-12 10:22:57 -06:00
/// Set this handle's configuration.
2020-06-15 06:29:07 -05:00
fn set_config(&mut self, config: Config);
2021-01-12 10:22:57 -06:00
/// The file's content at `path` has been modified, and should be reloaded.
2020-06-15 06:29:07 -05:00
fn invalidate(&mut self, path: AbsPathBuf);
2021-01-12 10:22:57 -06:00
/// Load the content of the given file, returning [`None`] if it does not
/// exists.
2020-06-24 08:52:07 -05:00
fn load_sync(&mut self, path: &AbsPath) -> Option<Vec<u8>>;
2020-06-15 06:29:07 -05:00
}
impl Entry {
2021-01-12 10:22:57 -06:00
/// Returns:
/// ```text
/// Entry::Directories(Directories {
/// extensions: ["rs"],
/// include: [base],
/// exclude: [base/.git],
/// })
/// ```
2020-06-15 06:29:07 -05:00
pub fn rs_files_recursively(base: AbsPathBuf) -> Entry {
2020-07-18 09:40:10 -05:00
Entry::Directories(dirs(base, &[".git"]))
2020-06-15 06:29:07 -05:00
}
2021-01-12 10:22:57 -06:00
/// Returns:
/// ```text
/// Entry::Directories(Directories {
/// extensions: ["rs"],
/// include: [base],
/// exclude: [base/.git, base/target],
/// })
/// ```
2020-06-15 06:29:07 -05:00
pub fn local_cargo_package(base: AbsPathBuf) -> Entry {
2020-07-18 09:40:10 -05:00
Entry::Directories(dirs(base, &[".git", "target"]))
2020-06-15 06:29:07 -05:00
}
2021-01-12 10:22:57 -06:00
/// Returns:
/// ```text
/// Entry::Directories(Directories {
/// extensions: ["rs"],
/// include: [base],
/// exclude: [base/.git, /tests, /examples, /benches],
/// })
/// ```
2020-06-15 06:29:07 -05:00
pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry {
2020-07-18 09:40:10 -05:00
Entry::Directories(dirs(base, &[".git", "/tests", "/examples", "/benches"]))
}
2021-01-12 10:22:57 -06:00
/// Returns `true` if `path` is included in `self`.
///
/// See [`Directories::contains_file`].
2020-07-18 09:40:10 -05:00
pub fn contains_file(&self, path: &AbsPath) -> bool {
match self {
Entry::Files(files) => files.iter().any(|it| it == path),
Entry::Directories(dirs) => dirs.contains_file(path),
}
}
2021-01-12 10:22:57 -06:00
/// Returns `true` if `path` is included in `self`.
///
/// - If `self` is `Entry::Files`, returns `false`
/// - Else, see [`Directories::contains_dir`].
2020-07-18 09:40:10 -05:00
pub fn contains_dir(&self, path: &AbsPath) -> bool {
match self {
Entry::Files(_) => false,
Entry::Directories(dirs) => dirs.contains_dir(path),
}
}
}
impl Directories {
2021-01-12 10:22:57 -06:00
/// Returns `true` if `path` is included in `self`.
2020-07-18 09:40:10 -05:00
pub fn contains_file(&self, path: &AbsPath) -> bool {
// First, check the file extension...
2020-07-18 09:40:10 -05:00
let ext = path.extension().unwrap_or_default();
if self.extensions.iter().all(|it| it.as_str() != ext) {
return false;
}
// Then, check for path inclusion...
self.includes_path(path)
2020-07-18 09:40:10 -05:00
}
2021-01-12 10:22:57 -06:00
/// Returns `true` if `path` is included in `self`.
///
/// Since `path` is supposed to be a directory, this will not take extension
/// into account.
2020-07-18 09:40:10 -05:00
pub fn contains_dir(&self, path: &AbsPath) -> bool {
self.includes_path(path)
}
2021-01-12 10:41:45 -06:00
/// Returns `true` if `path` is included in `self`.
///
/// It is included if
/// - An element in `self.include` is a prefix of `path`.
/// - This path is longer than any element in `self.exclude` that is a prefix
/// of `path`. In case of equality, exclusion wins.
2020-07-18 09:40:10 -05:00
fn includes_path(&self, path: &AbsPath) -> bool {
let mut include: Option<&AbsPathBuf> = None;
for incl in &self.include {
if path.starts_with(incl) {
include = Some(match include {
Some(prev) if prev.starts_with(incl) => prev,
_ => incl,
});
2020-07-18 09:40:10 -05:00
}
}
2020-07-18 09:40:10 -05:00
let include = match include {
Some(it) => it,
None => return false,
};
!self.exclude.iter().any(|excl| path.starts_with(excl) && excl.starts_with(include))
2020-06-15 06:29:07 -05:00
}
}
2021-01-12 10:41:45 -06:00
/// Returns :
/// ```text
/// Directories {
/// extensions: ["rs"],
/// include: [base],
/// exclude: [base/<exclude>],
/// }
/// ```
2020-07-18 09:40:10 -05:00
fn dirs(base: AbsPathBuf, exclude: &[&str]) -> Directories {
let exclude = exclude.iter().map(|it| base.join(it)).collect::<Vec<_>>();
Directories { extensions: vec!["rs".to_string()], include: vec![base], exclude }
2020-06-15 06:29:07 -05:00
}
impl fmt::Debug for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Message::Loaded { files } => {
f.debug_struct("Loaded").field("n_files", &files.len()).finish()
}
Message::Progress { n_total, n_done, config_version } => f
2020-06-11 04:04:09 -05:00
.debug_struct("Progress")
2020-06-24 09:58:49 -05:00
.field("n_total", n_total)
.field("n_done", n_done)
.field("config_version", config_version)
2020-06-11 04:04:09 -05:00
.finish(),
2020-06-15 06:29:07 -05:00
}
}
}
#[test]
fn handle_is_object_safe() {
fn _assert(_: &dyn Handle) {}
}