heroforge-core 0.2.2

Pure Rust core library for reading and writing Fossil SCM repositories
Documentation
//! File operations builder.

use crate::error::{FossilError, Result};
use crate::repo::{FileInfo, Repository};

/// Target for file queries.
#[derive(Clone)]
enum QueryTarget {
    Trunk,
    Branch(String),
    Tag(String),
    Commit(String),
}

/// Builder for file operations.
pub struct FilesBuilder<'a> {
    repo: &'a Repository,
}

impl<'a> FilesBuilder<'a> {
    pub(crate) fn new(repo: &'a Repository) -> Self {
        Self { repo }
    }

    /// Query files on trunk.
    pub fn on_trunk(self) -> FileQuery<'a> {
        FileQuery {
            repo: self.repo,
            target: QueryTarget::Trunk,
            directory: None,
        }
    }

    /// Query files on a specific branch.
    pub fn on_branch(self, name: &str) -> FileQuery<'a> {
        FileQuery {
            repo: self.repo,
            target: QueryTarget::Branch(name.to_string()),
            directory: None,
        }
    }

    /// Query files at a specific tag.
    pub fn at_tag(self, name: &str) -> FileQuery<'a> {
        FileQuery {
            repo: self.repo,
            target: QueryTarget::Tag(name.to_string()),
            directory: None,
        }
    }

    /// Query files at a specific commit.
    pub fn at_commit(self, hash: &str) -> FileQuery<'a> {
        FileQuery {
            repo: self.repo,
            target: QueryTarget::Commit(hash.to_string()),
            directory: None,
        }
    }
}

/// Query for files at a specific point in the repository.
pub struct FileQuery<'a> {
    repo: &'a Repository,
    target: QueryTarget,
    directory: Option<String>,
}

impl<'a> FileQuery<'a> {
    /// Resolve the target to a commit hash.
    fn resolve_hash(&self) -> Result<String> {
        match &self.target {
            QueryTarget::Trunk => {
                let tip = self.repo.database().get_trunk_tip()?;
                self.repo.database().get_hash_by_rid(tip)
            }
            QueryTarget::Branch(name) => {
                let tip = self.repo.database().get_branch_tip(name)?;
                self.repo.database().get_hash_by_rid(tip)
            }
            QueryTarget::Tag(name) => self.repo.get_tag_checkin_internal(name),
            QueryTarget::Commit(hash) => Ok(hash.clone()),
        }
    }

    /// Scope to a specific directory.
    pub fn in_dir(mut self, dir: &str) -> Self {
        self.directory = Some(dir.to_string());
        self
    }

    /// List all files.
    pub fn list(&self) -> Result<Vec<FileInfo>> {
        let hash = self.resolve_hash()?;
        if let Some(dir) = &self.directory {
            self.repo.list_directory_internal(&hash, dir)
        } else {
            self.repo.list_files_internal(&hash)
        }
    }

    /// Find files matching a glob pattern.
    pub fn find(&self, pattern: &str) -> Result<Vec<FileInfo>> {
        let hash = self.resolve_hash()?;
        self.repo.find_files_internal(&hash, pattern)
    }

    /// Read a file's content.
    pub fn read(&self, path: &str) -> Result<Vec<u8>> {
        let hash = self.resolve_hash()?;
        self.repo.read_file_internal(&hash, path)
    }

    /// Read a file as a UTF-8 string.
    pub fn read_string(&self, path: &str) -> Result<String> {
        let content = self.read(path)?;
        String::from_utf8(content).map_err(|e| FossilError::InvalidArtifact(e.to_string()))
    }

    /// List subdirectories in a directory.
    pub fn subdirs(&self, dir: &str) -> Result<Vec<String>> {
        let hash = self.resolve_hash()?;
        self.repo.list_subdirs_internal(&hash, dir)
    }
}