laburnum 1.17.0

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0
//
//! # laburnum::fs
//!
//! A virtual/physical filesystem abstraction layer optimized for building,
//! testing and running source code workspaces.
//! ## Quick Start
//!
//! ```rust
//! use laburnum::{
//!   Uri,
//!   fs::{
//!     FS,
//!     FileSystem,
//!     MemoryFileSystem,
//!   },
//! };
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//!
//! // Create an in-memory filesystem
//! let fs = MemoryFileSystem::new();
//! let base_uri = fs.root()?.path();
//!
//! // Write a file
//! fs.write(&base_uri.join("hello.txt").unwrap(), b"Hello World!")?;
//!
//! // Read a file
//! let contents = fs.read(&base_uri.join("hello.txt").unwrap())?;
//! assert_eq!(contents, b"Hello World!");
//!
//! // Find files matching a pattern
//! let files = fs.find(&base_uri, &["**/*.txt".to_string()])?;
//! for file in files {
//!   println!("Found file: {}", file.path());
//! }
//! # Ok(())
//! # }
//! ```
//!
//! ## Key Concepts
//!
//! ### Filesystem Trait
//!
//! The core `FileSystem` trait provides methods for:
//!
//! - File operations: `open()`, `read()`, `write()`, `append()`, `delete()`
//! - Directory operations: `dir()`, `root()`
//! - Finding files: `find()`
//! - Metadata access: `metadata()`
//!
//! ### URI-based Paths
//!
//! All filesystem operations use URIs for paths:
//!
//! - Physical filesystem: `file:///path/to/file.txt`
//! - Memory filesystem: `file:///path/to/file.txt`
//!
//! This provides a consistent interface across different storage backends.
//!
//! ### Directory Entry
//!
//! The `DirEntry` trait represents files and directories:
//!
//! ```rust
//! use laburnum::{
//!   Uri,
//!   fs::{
//!     FileType,
//!     Metadata,
//!   },
//! };
//!
//! pub trait DirEntry: std::fmt::Debug {
//!   fn path(&self) -> Uri;
//!   fn file_type(&self) -> FileType;
//!   fn is_dir(&self) -> bool;
//!   fn is_file(&self) -> bool;
//!   fn metadata(&self) -> Result<Metadata, Box<dyn std::error::Error>>;
//!   // ...
//! }
//! ```
//!
//! ## Example: Find All Source Files
//!
//! ```rust
//! use laburnum::{
//!   Uri,
//!   fs::{
//!     FS,
//!     FileSystem,
//!     PhysicalFileSystem,
//!   },
//! };
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//!
//! let base_uri = Uri::from_file_path("./src").unwrap();
//! let fs = PhysicalFileSystem::new(base_uri.clone())?;
//!
//! // Find all Rust source files, respecting .gitignore
//! let source_files = fs.find(&base_uri, &["**/*.rs".to_string()])?;
//!
//! for file in source_files {
//!   let contents = fs.read_to_string(&file.path())?;
//!   println!("Source file {}: {} bytes", file.path(), contents.len());
//! }
//! # Ok(())
//! # }
//! ```
//!
//! ## Safety & Scoping
//!
//! While laburnum::fs doesn't prevent all access outside the root directory by
//! default, it's designed to make accidental escapes unlikely. The Uri-based
//! paths and filesystem abstraction help maintain proper scoping.

pub mod filesystem;
use std::{
  ops::Deref,
  sync::Arc,
};

pub use filesystem::*;

pub mod errors;
pub use errors::Result;
pub mod macros;
pub mod memory;
pub mod physical;
mod uri_path;

pub use {
  memory::MemoryFileSystem,
  physical::PhysicalFileSystem,
  uri_path::*,
};

/// An abstraction over the physical or virtual file system, with an Arc
/// reference to the underlying implementation.
///
/// This is the main thing you want to use.
pub enum FS {
  Physical(Arc<PhysicalFileSystem>),
  Memory(Arc<MemoryFileSystem>),
}

impl From<PhysicalFileSystem> for FS {
  fn from(fs: PhysicalFileSystem) -> Self {
    FS::Physical(Arc::new(fs))
  }
}

impl From<MemoryFileSystem> for FS {
  fn from(fs: MemoryFileSystem) -> Self {
    FS::Memory(Arc::new(fs))
  }
}

impl std::hash::Hash for FS {
  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
    // Hash the discriminant to differentiate between variants
    std::mem::discriminant(self).hash(state);

    // Hash the pointer address of the Arc as a unique identifier
    match self {
      | FS::Physical(fs) => (Arc::as_ptr(fs) as usize).hash(state),
      | FS::Memory(fs) => (Arc::as_ptr(fs) as usize).hash(state),
    }
  }
}

impl Clone for FS {
  fn clone(&self) -> Self {
    match self {
      | FS::Physical(fs) => FS::Physical(Arc::clone(fs)),
      | FS::Memory(fs) => FS::Memory(Arc::clone(fs)),
    }
  }
}

impl std::fmt::Debug for FS {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      | FS::Physical(fs) => write!(f, "{fs:?}"),
      | FS::Memory(fs) => write!(f, "{fs:?}"),
    }
  }
}

impl Deref for FS {
  type Target = dyn FileSystem;

  fn deref(&self) -> &Self::Target {
    match self {
      | FS::Physical(fs) => fs.deref(),
      | FS::Memory(fs) => fs.deref(),
    }
  }
}

impl FileSystem for FS {
  fn uri(&self) -> crate::Uri {
    self.deref().uri()
  }

  fn open(
    &self,
    path: &crate::Uri,
    options: OpenOptions,
  ) -> Result<Box<dyn FileHandle>> {
    self.deref().open(path, options)
  }

  fn read(&self, path: &crate::Uri) -> Result<Vec<u8>> {
    self.deref().read(path)
  }

  fn read_to_new_rope(&self, path: &crate::Uri) -> Result<ropey::Rope> {
    self.deref().read_to_new_rope(path)
  }

  fn write(&self, path: &crate::Uri, data: &[u8]) -> Result<()> {
    self.deref().write(path, data)
  }

  fn write_str(&self, path: &crate::Uri, data: &str) -> Result<()> {
    self.deref().write_str(path, data)
  }

  fn append(&self, path: &crate::Uri, data: &[u8]) -> Result<()> {
    self.deref().append(path, data)
  }

  fn delete(&self, path: &crate::Uri) -> Result<()> {
    self.deref().delete(path)
  }

  fn metadata(&self, path: &crate::Uri) -> Result<Metadata> {
    self.deref().metadata(path)
  }

  fn root(&self) -> Result<Box<dyn DirEntry>> {
    self.deref().root()
  }

  fn dir(&self, path: &crate::Uri) -> Result<Box<dyn DirEntry>> {
    self.deref().dir(path)
  }

  fn find(
    &self,
    path: &crate::Uri,
    glob: &[String],
  ) -> Result<im::Vector<Arc<dyn DirEntry>>> {
    self.deref().find(path, glob)
  }

  fn iter_files(
    &self,
  ) -> Result<Box<dyn Iterator<Item = (crate::Uri, Vec<u8>)> + '_>> {
    self.deref().iter_files()
  }
}

#[cfg(test)]
mod tests;