univert 0.2.0

Universal file converter (library and CLI)
Documentation
use clap::Parser;
use std::path::PathBuf;
use std::process::ExitCode;

use crate::{Options, conversion_map, convert};

/// Universal file converter.
#[derive(Parser, Debug)]
#[command(
  name = "univert",
  version,
  about = "Universal file converter",
  override_usage = "univert <input-file> <output-file> [OPTIONS]"
)]
pub struct Args {
  /// Overwrite existing file
  #[arg(short, long)]
  pub force: bool,

  /// List all available conversions
  #[arg(short, long)]
  pub list: bool,

  /// Split source file into several target files (where applicable)
  #[arg(short, long)]
  pub split: bool,

  /// Input file
  pub input: Option<PathBuf>,

  /// Output file
  pub output: Option<PathBuf>,
}

pub fn run() -> ExitCode {
  // Restore default SIGPIPE behavior so piping into `head` etc. terminates
  // cleanly instead of panicking from a broken-pipe write.
  #[cfg(unix)]
  {
    use std::os::raw::c_int;
    unsafe extern "C" {
      fn signal(signum: c_int, handler: usize) -> usize;
    }
    const SIGPIPE: c_int = 13;
    const SIG_DFL: usize = 0;
    unsafe {
      signal(SIGPIPE, SIG_DFL);
    }
  }

  let args = Args::parse();

  if args.list {
    for (from, to) in conversion_map::list_all() {
      println!("{from} -> {to}");
    }
    return ExitCode::SUCCESS;
  }

  let (Some(input), Some(output)) = (args.input.as_ref(), args.output.as_ref())
  else {
    eprintln!("usage: univert <input-file> <output-file> [OPTIONS]");
    return ExitCode::from(2);
  };

  let options = Options {
    force: args.force,
    split: args.split,
  };

  match convert(input, output, &options) {
    Ok(()) => {
      println!("Converted {} to {}", input.display(), output.display());
      ExitCode::SUCCESS
    }
    Err(err) => {
      eprintln!("{err}");
      ExitCode::FAILURE
    }
  }
}