rust_release_artefact 0.1.3

Safely extract installable files from Rust release artefacts.
Documentation
// extract-artefact: Extract an artefact and print its metadata
//
// This example shows how to extract a Rust release artefact to a given
// directory, and how to access the resulting metadata.
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate rust_release_artefact as rra;

use std::env;
use std::error;
use std::fs;
use std::io;
use std::path;
use std::process;

// Ordinarily, the artefact source (such as the release channel
// manifest, or the Content-Type header from an HTTP response) will tell
// you what kind of artefact to expect, so this kind of guessing isn't
// required. It's only because we're reading directly from a file that
// this is necessary.
fn open_artefact(
    src_path: &path::Path,
    dst_path: &path::Path,
) -> Result<rra::ExtractedArtefact, Box<error::Error>> {
    // If an artefact has previously been extracted to this path, let's just
    // re-use it instead of re-extracting it.
    match rra::ExtractedArtefact::new(dst_path) {
        Ok(extracted_artefact) => return Ok(extracted_artefact),
        Err(rra::Error::NoArtefacts(_)) => {
            // Probably nothing has been extracted here yet,
            // so we should continue.
        }
        Err(e) => {
            // Probably something *has* been extracted here already, and it has
            // a problem we won't solve by extracting again.
            return Err(e.into());
        }
    };

    let artefact_handle = io::BufReader::new(fs::File::open(src_path)?);

    let extension = src_path
        .extension()
        .and_then(|ext| ext.to_str())
        .unwrap_or("");

    Ok(match extension {
        "gz" => rra::ExtractedArtefact::from_tar_gz(artefact_handle, dst_path)?,
        "xz" => rra::ExtractedArtefact::from_tar_xz(artefact_handle, dst_path)?,
        _ => Err(format!("Unrecognised extension: {:?}", extension))?,
    })
}

// Dump a description of the artefact's contents to stdout.
fn dump_artefact(artefact: rra::ExtractedArtefact) {
    println!("Version: {:?}", artefact.version);
    println!("Git commit hash: {:?}", artefact.git_commit_hash);

    for (name, component) in &artefact.components {
        println!("Component: {:?} in {:?}", name, component.root);
        for path in &component.files {
            println!("  - {:?}", path);
        }
    }
}

fn main() {
    env_logger::init();

    info!("Parsing command-line arguments...");
    let args = env::args_os().collect::<Vec<_>>();

    if args.len() != 3 {
        error!("Usage: extract-artefact ARTEFACT STAGEPATH");
        process::exit(1);
    }
    let src_path = path::Path::new(&args[1]);
    let dst_path = path::Path::new(&args[2]);

    info!("Creating destination directory if needed: {:?}", &args[2]);
    fs::create_dir_all(dst_path).unwrap_or_else(|err| {
        error!("Couldn't create destination path: {}", err);
        process::exit(1)
    });

    info!("Extracting artefact...");
    let extracted_artefact =
        open_artefact(src_path, dst_path).unwrap_or_else(|err| {
            error!("{}", err);
            process::exit(1)
        });

    info!("Dumping artefact metadata...");
    dump_artefact(extracted_artefact);
}