alma 0.1.1

A Bevy-native modal text editor with Vim-style navigation.
Documentation
//! Filesystem configuration and workspace root discovery.

use super::error::FilesystemConfigError;
use bevy::prelude::Resource;
use std::path::{Path, PathBuf};

/// Maximum file size accepted by default when opening editor buffers.
pub const DEFAULT_MAX_FILE_BYTES: u64 = 32 * 1024 * 1024;

/// Filesystem policy used by editor file I/O.
#[derive(Clone, Debug, Eq, PartialEq, Resource)]
pub struct FilesystemConfig {
    /// Trusted root under which file-backed buffers may be opened or written.
    pub workspace_root: PathBuf,
    /// Maximum number of bytes accepted when reading a file into a buffer.
    pub max_file_bytes: u64,
}

impl FilesystemConfig {
    /// Builds a config from an explicit workspace root.
    ///
    /// # Errors
    ///
    /// Returns [`FilesystemConfigError`] when `workspace_root` cannot be canonicalized or is not a
    /// directory.
    pub fn from_workspace_root(
        workspace_root: impl AsRef<Path>,
    ) -> Result<Self, FilesystemConfigError> {
        let workspace_root = workspace_root
            .as_ref()
            .canonicalize()
            .map_err(|source| FilesystemConfigError::CanonicalizeRoot { source })?;

        if !workspace_root.is_dir() {
            return Err(FilesystemConfigError::RootIsNotDirectory { workspace_root });
        }

        Ok(Self {
            workspace_root,
            max_file_bytes: DEFAULT_MAX_FILE_BYTES,
        })
    }

    /// Builds a config for the current process directory.
    ///
    /// # Errors
    ///
    /// Returns [`FilesystemConfigError`] when the current directory cannot be read or the selected
    /// workspace root cannot be canonicalized.
    pub fn discover() -> Result<Self, FilesystemConfigError> {
        let current_dir = std::env::current_dir()
            .map_err(|source| FilesystemConfigError::CurrentDir { source })?;
        Self::from_workspace_root(discover_workspace_root(current_dir)?)
    }
}

/// Discovers a workspace root from `start`.
pub(super) fn discover_workspace_root(
    start: impl AsRef<Path>,
) -> Result<PathBuf, FilesystemConfigError> {
    let start = start
        .as_ref()
        .canonicalize()
        .map_err(|source| FilesystemConfigError::CanonicalizeRoot { source })?;
    for ancestor in start.ancestors() {
        if ancestor.join(".git").exists() {
            return Ok(ancestor.to_owned());
        }
    }
    Ok(start)
}