asar-rust 0.1.0

Rust port of @electron/asar — create and extract Electron ASAR archives
Documentation
use asar_rust::asar::*;
use asar_rust::filesystem::ListOptions;
use clap::{Parser, Subcommand};
use std::fs;
use std::path::{Path, PathBuf};

#[derive(Parser)]
#[command(name = "asar")]
#[command(about = "Manipulate asar archive files")]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// create asar archive
    #[command(name = "pack", aliases = ["p"])]
    Pack {
        dir: PathBuf,
        output: PathBuf,
        /// path to a text file for ordering contents
        #[arg(long)]
        ordering: Option<PathBuf>,
        /// do not pack files matching glob <expression>
        #[arg(long)]
        unpack: Option<String>,
        /// do not pack dirs matching glob <expression>
        #[arg(long = "unpack-dir")]
        unpack_dir: Option<String>,
        /// exclude hidden files
        #[arg(long = "exclude-hidden")]
        exclude_hidden: bool,
    },
    /// list files of asar archive
    #[command(name = "list", aliases = ["l"])]
    List {
        archive: PathBuf,
        /// each file in the asar is pack or unpack
        #[arg(short = 'i', long = "is-pack")]
        is_pack: bool,
    },
    /// extract one file from archive
    #[command(name = "extract-file", aliases = ["ef"])]
    ExtractFile {
        archive: PathBuf,
        filename: String,
    },
    /// extract archive
    #[command(name = "extract", aliases = ["e"])]
    Extract {
        archive: PathBuf,
        dest: PathBuf,
    },
}

fn main() {
    let cli = Cli::parse();

    let result: Result<(), String> = match cli.command {
        Commands::Pack {
            dir,
            output,
            ordering,
            unpack,
            unpack_dir,
            exclude_hidden,
        } => {
            let options = CreateOptions {
                dot: !exclude_hidden,
                ordering,
                unpack,
                unpack_dir,
            };
            create_package_with_options(&dir, &output, options).map_err(|e| e.to_string())
        }
        Commands::List { archive, is_pack } => {
            let options = if is_pack {
                Some(ListOptions { is_pack: true })
            } else {
                None
            };
            list_package(&archive, options)
                .map(|files| {
                    for file in files {
                        println!("{}", file);
                    }
                })
                .map_err(|e| e.to_string())
        }
        Commands::ExtractFile { archive, filename } => {
            extract_file(&archive, &filename, true)
                .map_err(|e| e.to_string())
                .and_then(|content| {
                    let output_name = Path::new(&filename)
                        .file_name()
                        .unwrap_or_default()
                        .to_str()
                        .unwrap_or("output");
                    fs::write(output_name, content).map_err(|e| e.to_string())
                })
        }
        Commands::Extract { archive, dest } => {
            extract_all(&archive, &dest).map_err(|e| e.to_string())
        }
    };

    if let Err(e) = result {
        eprintln!("{}", e);
        std::process::exit(1);
    }
}