pub use command::{Command, CommandExecutor};
pub use memory_fs::MemoryFileSystem;
#[cfg(all(feature = "testing", feature = "os"))]
pub use os::testing::UserConfigDirectoryOverrideGuard;
#[cfg(feature = "os")]
pub use os::OsSystem;
use filetime::FileTime;
use ruff_notebook::{Notebook, NotebookError};
use ruff_python_ast::PySourceType;
use std::error::Error;
use std::fmt;
use std::fmt::Debug;
use std::process::Output;
pub use test::{DbWithTestSystem, DbWithWritableSystem, InMemorySystem, TestSystem};
use walk_directory::WalkDirectoryBuilder;
pub use self::path::{
DeduplicatedNestedPathsIter, SystemPath, SystemPathBuf, SystemVirtualPath,
SystemVirtualPathBuf, deduplicate_nested_paths,
};
use crate::file_revision::FileRevision;
mod command;
mod memory_fs;
#[cfg(feature = "os")]
mod os;
mod path;
mod test;
pub mod walk_directory;
pub type Result<T> = std::io::Result<T>;
pub type WhichResult = std::result::Result<SystemPathBuf, WhichError>;
pub trait System: Debug + Sync + Send {
fn path_metadata(&self, path: &SystemPath) -> Result<Metadata>;
fn canonicalize_path(&self, path: &SystemPath) -> Result<SystemPathBuf>;
fn is_same_file(&self, first: &SystemPath, second: &SystemPath) -> Result<bool>;
fn source_type(&self, path: &SystemPath) -> Option<PySourceType> {
let _ = path;
None
}
fn virtual_path_source_type(&self, path: &SystemVirtualPath) -> Option<PySourceType> {
let _ = path;
None
}
fn which(&self, binary_name: &str) -> WhichResult;
fn run_command(&self, command: Command) -> Result<Output> {
let Some(executor) = self.command_executor() else {
return Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"running commands is not supported by this system",
));
};
executor.execute(command)
}
fn command_executor(&self) -> Option<&dyn CommandExecutor> {
None
}
fn read_to_string(&self, path: &SystemPath) -> Result<String>;
fn read_to_notebook(&self, path: &SystemPath) -> std::result::Result<Notebook, NotebookError>;
fn read_virtual_path_to_string(&self, path: &SystemVirtualPath) -> Result<String>;
fn read_virtual_path_to_notebook(
&self,
path: &SystemVirtualPath,
) -> std::result::Result<Notebook, NotebookError>;
fn path_exists(&self, path: &SystemPath) -> bool {
self.path_metadata(path).is_ok()
}
fn is_directory(&self, path: &SystemPath) -> bool {
self.path_metadata(path)
.is_ok_and(|metadata| metadata.file_type.is_directory())
}
fn is_file(&self, path: &SystemPath) -> bool {
self.path_metadata(path)
.is_ok_and(|metadata| metadata.file_type.is_file())
}
fn current_directory(&self) -> &SystemPath;
fn user_config_directory(&self) -> Option<SystemPathBuf>;
fn cache_dir(&self) -> Option<SystemPathBuf>;
fn read_directory<'a>(
&'a self,
path: &SystemPath,
) -> Result<Box<dyn Iterator<Item = Result<DirectoryEntry>> + 'a>>;
fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder;
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
let _ = name;
Err(std::env::VarError::NotPresent)
}
fn as_writable(&self) -> Option<&dyn WritableSystem>;
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn dyn_clone(&self) -> Box<dyn System>;
}
pub trait WritableSystem: System {
fn create_new_file(&self, path: &SystemPath) -> Result<()>;
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
self.write_file_bytes(path, content.as_bytes())
}
fn write_file_bytes(&self, path: &SystemPath, content: &[u8]) -> Result<()>;
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
fn get_or_cache(
&self,
path: &SystemPath,
read_contents: &dyn Fn() -> Result<String>,
) -> Result<Option<SystemPathBuf>> {
let Some(cache_dir) = self.cache_dir() else {
return Ok(None);
};
let cache_path = cache_dir.join(path);
if self.is_file(&cache_path) {
return Ok(Some(cache_path));
}
let contents = read_contents()?;
self.create_directory_all(cache_path.parent().unwrap())?;
self.create_new_file(&cache_path)?;
self.write_file(&cache_path, &contents)?;
Ok(Some(cache_path))
}
fn dyn_clone(&self) -> Box<dyn WritableSystem>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Metadata {
revision: FileRevision,
permissions: Option<u32>,
file_type: FileType,
}
impl Metadata {
pub fn new(revision: FileRevision, permissions: Option<u32>, file_type: FileType) -> Self {
Self {
revision,
permissions,
file_type,
}
}
pub fn revision(&self) -> FileRevision {
self.revision
}
pub fn permissions(&self) -> Option<u32> {
self.permissions
}
pub fn file_type(&self) -> FileType {
self.file_type
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, get_size2::GetSize)]
pub enum FileType {
File,
Directory,
Symlink,
}
impl FileType {
pub const fn is_file(self) -> bool {
matches!(self, FileType::File)
}
pub const fn is_directory(self) -> bool {
matches!(self, FileType::Directory)
}
pub const fn is_symlink(self) -> bool {
matches!(self, FileType::Symlink)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct DirectoryEntry {
path: SystemPathBuf,
file_type: FileType,
}
impl DirectoryEntry {
pub fn new(path: SystemPathBuf, file_type: FileType) -> Self {
Self { path, file_type }
}
pub fn into_path(self) -> SystemPathBuf {
self.path
}
pub fn path(&self) -> &SystemPath {
&self.path
}
pub fn file_type(&self) -> FileType {
self.file_type
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn file_time_now() -> FileTime {
FileTime::now()
}
#[cfg(target_arch = "wasm32")]
pub fn file_time_now() -> FileTime {
let time = web_time::SystemTime::now();
time.duration_since(web_time::UNIX_EPOCH)
.map(|d| FileTime::from_unix_time(d.as_secs() as i64, d.subsec_nanos()))
.unwrap_or_else(|e| {
let until_epoch = e.duration();
let (sec_offset, nanos) = if until_epoch.subsec_nanos() == 0 {
(0, 0)
} else {
(-1, 1_000_000_000 - until_epoch.subsec_nanos())
};
FileTime::from_unix_time(-(until_epoch.as_secs() as i64) + sec_offset, nanos)
})
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum WhichError {
CannotFindBinaryPath,
CannotGetCurrentDirAndPathListEmpty,
CannotCanonicalize,
NonUtf8Path,
}
impl Error for WhichError {}
impl fmt::Display for WhichError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WhichError::CannotFindBinaryPath => write!(f, "cannot find binary path"),
WhichError::CannotGetCurrentDirAndPathListEmpty => write!(
f,
"no path to search and provided name is not an absolute path"
),
WhichError::CannotCanonicalize => write!(f, "cannot canonicalize path"),
WhichError::NonUtf8Path => write!(f, "non UTF-8 path"),
}
}
}