sit-rs 0.3.0

Rust-native extraction for StuffIt Expander archive files
Documentation
use std::{
    env, fs,
    path::{self, Path},
    process,
};

use sit::Fork;
use walkdir::{DirEntry, WalkDir};

fn main() {
    pretty_env_logger::init();
    log::debug!("Startup");
    let Some(path) = env::args().nth(1) else {
        eprintln!("Please provide a path to a StuffIt archive as the first argument");
        process::exit(-1);
    };

    match fs::metadata(&path) {
        Ok(meta) if meta.is_dir() => WalkDir::new(path)
            .into_iter()
            .filter_entry(|e| !is_hidden(e))
            .filter_map(|e| e.ok())
            .for_each(|f| match extract_file(f.path()) {
                Ok(_) => {}
                Err(e) => {
                    eprintln!(
                        "Could not fully extract file at {}: {}",
                        f.path().display(),
                        e
                    );
                }
            }),
        Ok(meta) if meta.is_file() => match extract_file(&path) {
            Ok(_) => {}
            Err(e) => {
                eprintln!("Could not extract file at {path}");
                eprintln!("{e:?}");
                process::exit(-3);
            }
        },
        Ok(_) => {
            eprintln!("Path is neither file nor directory, skipping analysis…");
        }
        Err(e) => {
            eprintln!("Could not determine if path is a file or directory!");
            eprintln!("{e:?}");
            process::exit(-2);
        }
    }

    log::debug!("Shutdown");
}

fn extract_file(path: impl AsRef<Path>) -> Result<(), sit::Error> {
    log::info!("Extracting {}", path.as_ref().display());
    let mut archive = match sit::Archive::open_path(path.as_ref()) {
        Ok(i) => i,
        Err(e) => {
            eprintln!("Could not open archive at {}", path.as_ref().display());
            eprintln!("{e:?}");
            return Err(e);
        }
    };

    let mut path = path::PathBuf::new();
    for entry in archive.iter() {
        match entry {
            sit::Entry::File(file) => {
                let cleaner_name = file.name().replace("/", ":");

                if file.has(sit::Fork::Resource) {
                    path.push(format!("{cleaner_name}.rsrc"));

                    let mut reader = archive.open_fork(&file, Fork::Resource)?;
                    let mut writer = fs::File::create_new(&path)?;
                    std::io::copy(&mut reader, &mut writer)?;

                    path.pop();
                }

                if file.has(sit::Fork::Data) {
                    path.push(cleaner_name);

                    let mut reader = archive.open_fork(&file, Fork::Data)?;
                    let mut writer = fs::File::create_new(&path)?;
                    std::io::copy(&mut reader, &mut writer)?;

                    path.pop();
                }
            }
            sit::Entry::Directory(directory) => {
                path.push(directory.name().replace("/", ":"));
                fs::create_dir(&path)?;
            }
            sit::Entry::DirectoryEnd(_) => {
                path.pop();
            }
        }
    }

    Ok(())
}

fn is_hidden(entry: &DirEntry) -> bool {
    entry
        .file_name()
        .to_str()
        .map(|s| s.starts_with(""))
        .unwrap_or(false)
}