greentic-bundle 0.5.10

Greentic bundle authoring CLI scaffold with embedded i18n and answer-document contracts.
Documentation
mod backhand_writer;
mod native_mksquashfs_writer;
mod native_unsquashfs_reader;

use std::path::Path;

use anyhow::{Result, bail};

pub use backhand_writer::{BackhandBundleFsReader, BackhandBundleFsWriter};
pub use native_mksquashfs_writer::MksquashfsBundleFsWriter;
pub use native_unsquashfs_reader::UnsquashfsBundleFsReader;

pub const WRITER_ENV: &str = "GREENTIC_BUNDLE_SQUASHFS_WRITER";
pub const READER_ENV: &str = "GREENTIC_BUNDLE_SQUASHFS_READER";

pub trait BundleFsWriter {
    fn write_bundle(&self, input_dir: &Path, output_file: &Path) -> Result<()>;
}

pub trait BundleFsReader {
    fn list_bundle(&self, bundle_file: &Path) -> Result<Vec<BundleEntry>>;
    fn extract_bundle(&self, bundle_file: &Path, output_dir: &Path) -> Result<()>;
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BundleEntry {
    pub path: String,
    pub kind: BundleEntryKind,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BundleEntryKind {
    File,
    Directory,
    Symlink,
    Other,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BundleFsWriterKind {
    Backhand,
    Mksquashfs,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BundleFsReaderKind {
    Backhand,
    Unsquashfs,
}

impl BundleFsWriterKind {
    pub fn from_env_value(value: Option<&str>) -> Result<Self> {
        match value.map(str::trim).filter(|value| !value.is_empty()) {
            None => Ok(Self::Backhand),
            Some("backhand") => Ok(Self::Backhand),
            Some("mksquashfs") => Ok(Self::Mksquashfs),
            Some(value) => bail!(
                "{WRITER_ENV}={value} is not supported. Accepted values: backhand, mksquashfs"
            ),
        }
    }
}

impl BundleFsReaderKind {
    pub fn from_env_value(value: Option<&str>) -> Result<Self> {
        match value.map(str::trim).filter(|value| !value.is_empty()) {
            None => Ok(Self::Backhand),
            Some("backhand") => Ok(Self::Backhand),
            Some("unsquashfs") => Ok(Self::Unsquashfs),
            Some(value) => bail!(
                "{READER_ENV}={value} is not supported. Accepted values: backhand, unsquashfs"
            ),
        }
    }
}

pub fn selected_writer_kind() -> Result<BundleFsWriterKind> {
    BundleFsWriterKind::from_env_value(std::env::var(WRITER_ENV).ok().as_deref())
}

pub fn selected_reader_kind() -> Result<BundleFsReaderKind> {
    BundleFsReaderKind::from_env_value(std::env::var(READER_ENV).ok().as_deref())
}

pub fn write_bundle(input_dir: &Path, output_file: &Path) -> Result<()> {
    match selected_writer_kind()? {
        BundleFsWriterKind::Backhand => BackhandBundleFsWriter.write_bundle(input_dir, output_file),
        BundleFsWriterKind::Mksquashfs => {
            MksquashfsBundleFsWriter.write_bundle(input_dir, output_file)
        }
    }
}

pub fn list_bundle(bundle_file: &Path) -> Result<Vec<BundleEntry>> {
    match selected_reader_kind()? {
        BundleFsReaderKind::Backhand => BackhandBundleFsReader.list_bundle(bundle_file),
        BundleFsReaderKind::Unsquashfs => UnsquashfsBundleFsReader.list_bundle(bundle_file),
    }
}

pub fn extract_bundle(bundle_file: &Path, output_dir: &Path) -> Result<()> {
    match selected_reader_kind()? {
        BundleFsReaderKind::Backhand => {
            BackhandBundleFsReader.extract_bundle(bundle_file, output_dir)
        }
        BundleFsReaderKind::Unsquashfs => {
            UnsquashfsBundleFsReader.extract_bundle(bundle_file, output_dir)
        }
    }
}

pub fn read_bundle_file(bundle_file: &Path, inner_path: &str) -> Result<Vec<u8>> {
    match selected_reader_kind()? {
        BundleFsReaderKind::Backhand => {
            backhand_writer::read_bundle_file_with_backhand(bundle_file, inner_path)
        }
        BundleFsReaderKind::Unsquashfs => {
            native_unsquashfs_reader::read_bundle_file_with_unsquashfs(bundle_file, inner_path)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{BundleFsReaderKind, BundleFsWriterKind, READER_ENV, WRITER_ENV};

    #[test]
    fn writer_selection_defaults_to_backhand() {
        assert_eq!(
            BundleFsWriterKind::from_env_value(None).expect("writer kind"),
            BundleFsWriterKind::Backhand
        );
    }

    #[test]
    fn writer_selection_accepts_backhand() {
        assert_eq!(
            BundleFsWriterKind::from_env_value(Some("backhand")).expect("writer kind"),
            BundleFsWriterKind::Backhand
        );
    }

    #[test]
    fn writer_selection_accepts_mksquashfs() {
        assert_eq!(
            BundleFsWriterKind::from_env_value(Some("mksquashfs")).expect("writer kind"),
            BundleFsWriterKind::Mksquashfs
        );
    }

    #[test]
    fn writer_selection_rejects_unknown_values() {
        let error = BundleFsWriterKind::from_env_value(Some("external")).expect_err("error");
        let message = error.to_string();
        assert!(message.contains(WRITER_ENV));
        assert!(message.contains("backhand, mksquashfs"));
    }

    #[test]
    fn reader_selection_defaults_to_backhand() {
        assert_eq!(
            BundleFsReaderKind::from_env_value(None).expect("reader kind"),
            BundleFsReaderKind::Backhand
        );
    }

    #[test]
    fn reader_selection_accepts_backhand() {
        assert_eq!(
            BundleFsReaderKind::from_env_value(Some("backhand")).expect("reader kind"),
            BundleFsReaderKind::Backhand
        );
    }

    #[test]
    fn reader_selection_accepts_unsquashfs() {
        assert_eq!(
            BundleFsReaderKind::from_env_value(Some("unsquashfs")).expect("reader kind"),
            BundleFsReaderKind::Unsquashfs
        );
    }

    #[test]
    fn reader_selection_rejects_unknown_values() {
        let error = BundleFsReaderKind::from_env_value(Some("external")).expect_err("error");
        let message = error.to_string();
        assert!(message.contains(READER_ENV));
        assert!(message.contains("backhand, unsquashfs"));
    }
}