archivelib 0.2.0

An implementaton of the Greenleaf ArchiveLib compression/decompression algorithm
Documentation
pub use archivelib::CompressionLevel;
use std::io::{Read, Write};
use std::{env, fs, io};

pub fn help() {
  let exe = env::current_exe()
    .ok()
    .and_then(|p| p.to_str().map(|s| s.to_string()))
    .unwrap_or("archivelib".to_string());
  eprintln!(
    "Summary:
  {} [-x] [-0|-1|-2|-3|-4] [INPUT [OUTPUT]]

Arguments:
  -x, --abort-on-panic:  Abort the process instead of unwinding the stack trace nicely. This is useful when fuzzing.
  -0, -1, -2, -3, -4:    Compress using the given level. Default: 0
  -?, -h, --help:        Show this help message.
",
    exe
  );
}

pub struct Args {
  pub level: CompressionLevel,
  pub abort_on_panic: bool,
  input_filename: Option<String>,
  output_filename: Option<String>,
}

impl Args {
  pub fn from_arg_array(args: Vec<String>) -> Result<Self, String> {
    if args.is_empty() {
      return Err("No arguments specified".into());
    }
    let mut level = None;
    let mut abort_on_panic = false;
    let mut file_args = Vec::with_capacity(2);
    for arg in args {
      if arg == "-x" && arg == "--abort-on-panic" {
        abort_on_panic = true;
      } else if arg == "-0" {
        level = Some(CompressionLevel::Level0);
      } else if arg == "-1" {
        level = Some(CompressionLevel::Level1);
      } else if arg == "-2" {
        level = Some(CompressionLevel::Level2);
      } else if arg == "-3" {
        level = Some(CompressionLevel::Level3);
      } else if arg == "-4" {
        level = Some(CompressionLevel::Level4);
      } else if arg == "-?" || arg == "-h" || arg == "--help" {
        return Err("".into());
      } else {
        file_args.push(arg)
      }
    }
    let (input_filename, output_filename) = {
      let mut f = file_args.into_iter();
      let input_filename = f.next();
      let output_filename = f.next();
      if let Some(arg) = f.next() {
        return Err(
          format!(
            "Too many file arguments. You can specify up to 2 files: {:?}, {:?}, {:?}",
            input_filename, output_filename, arg
          )
          .into(),
        );
      }
      (input_filename, output_filename)
    };
    Ok(Args {
      level: level.unwrap_or(CompressionLevel::Level0),
      abort_on_panic,
      input_filename,
      output_filename,
    })
  }

  pub fn input_data(&self) -> io::Result<Vec<u8>> {
    let mut buf = Vec::with_capacity(4096);
    match &self.input_filename {
      None => io::stdin().read_to_end(&mut buf)?,
      Some(f) => fs::File::open(f)?.read_to_end(&mut buf)?,
    };
    Ok(buf)
  }

  pub fn output_data(&self, data: &[u8]) -> io::Result<()> {
    match &self.output_filename {
      None => io::stdout().write_all(data)?,
      Some(f) => fs::File::open(f)?.write_all(data)?,
    };
    Ok(())
  }
}

#[macro_export]
macro_rules! run {
  (($input: ident, $level: ident) => $body:block) => {
    use std::env::args;
    use std::error::Error;
    use util::CompressionLevel;

    fn run($input: &[u8], $level: CompressionLevel) -> Result<Box<[u8]>, String> {
      $body
    }
    fn main() -> Result<(), Box<dyn Error>> {
      let args = match $crate::util::Args::from_arg_array(args().skip(1).collect()) {
        Ok(a) => a,
        Err(msg) => {
          $crate::util::help();
          return Err(msg.into());
        }
      };
      let input = args.input_data()?;
      let output = if args.abort_on_panic {
        match std::panic::catch_unwind(|| run(&input, args.level)) {
          Ok(v) => v?,
          Err(_) => std::process::abort(),
        }
      } else {
        run(&input, args.level)?
      };
      args.output_data(&output)?;
      Ok(())
    }
  };
}