portable-network-archive 0.31.0

Portable-Network-Archive cli
Documentation
#[cfg(not(feature = "memmap"))]
use crate::command::core::run_across_archive;
#[cfg(feature = "memmap")]
use crate::command::core::run_across_archive_mem as run_across_archive;
use crate::{
    command::{Command, core::collect_split_archives},
    utils::{self, PathWithCwd},
};
use anyhow::Context;
use clap::{ArgGroup, Parser, ValueHint};
use pna::Archive;
#[cfg(feature = "memmap")]
use std::io;
use std::path::PathBuf;

#[derive(Parser, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[command(
    group(ArgGroup::new("archive-args").args(["files", "archives"]).required(true)),
    group(ArgGroup::new("overwrite-flag").args(["overwrite", "no_overwrite"])),
)]
pub(crate) struct ConcatCommand {
    #[arg(long, help = "Overwrite file")]
    overwrite: bool,
    #[arg(
        long,
        help = "Do not overwrite files. This is the inverse option of --overwrite"
    )]
    no_overwrite: bool,
    #[arg(help = "Archive files to concatenate (deprecated, use --files)", value_hint = ValueHint::FilePath)]
    archives: Vec<PathBuf>,
    #[arg(short, long, help = "Archive files to concatenate", value_hint = ValueHint::FilePath)]
    files: Vec<PathBuf>,
}

impl Command for ConcatCommand {
    #[inline]
    fn execute(self, _ctx: &crate::cli::GlobalContext) -> anyhow::Result<()> {
        concat_entry(self)
    }
}

#[hooq::hooq(anyhow)]
fn concat_entry(args: ConcatCommand) -> anyhow::Result<()> {
    let mut archives = if args.files.is_empty() {
        if !args.archives.is_empty() {
            log::warn!("positional `archive` is deprecated, use `--file` instead");
        }
        args.archives
    } else {
        args.files
    };
    let archive = archives.remove(0);
    for item in &archives {
        if !utils::fs::is_pna(item)? {
            anyhow::bail!("{} is not a pna file", item.display());
        }
    }
    let file = utils::fs::file_create(&archive, args.overwrite)
        .with_context(|| format!("failed to create `{}`", PathWithCwd::new(&archive)))?;
    let mut archive = Archive::write_header(file)?;

    for item in &archives {
        let archives = collect_split_archives(item)?;
        #[cfg(feature = "memmap")]
        {
            let mmaps = archives
                .into_iter()
                .map(utils::mmap::Mmap::try_from)
                .collect::<io::Result<Vec<_>>>()?;
            let archives = mmaps.iter().map(|m| m.as_ref());
            run_across_archive(
                archives,
                #[hooq::skip_all]
                |reader| {
                    for entry in reader.raw_entries_slice() {
                        archive.add_entry(entry?)?;
                    }
                    Ok(())
                },
            )?;
        }
        #[cfg(not(feature = "memmap"))]
        {
            run_across_archive(
                archives,
                #[hooq::skip_all]
                |reader| {
                    for entry in reader.raw_entries() {
                        archive.add_entry(entry?)?;
                    }
                    Ok(())
                },
            )?;
        }
    }
    archive.finalize()?;
    Ok(())
}