cargo-parcel 0.0.4

Experimental extended cargo installer
Documentation
#![deny(unsafe_code)]
#![warn(rust_2018_idioms)]

use std::{fs, io, path::Path};

use anyhow::{bail, Context, Error};
use pico_args::Arguments;

use cargo_parcel::util::DisplayArgs;

fn write_cargo_alias() -> anyhow::Result<()> {
    match fs::metadata(".cargo/config") {
        Ok(_) => {
            bail!("`.cargo/config` exists, not modifying");
        }
        Err(e) if e.kind() == io::ErrorKind::NotFound => {
            fs::create_dir_all(".cargo")?;
            fs::write(".cargo/config", CARGO_CONFIG_CONTENTS)
                .context("Could not write `.cargo/config`")?;
        }
        Err(e) => {
            bail!("Could not check for existence of `.cargo/config`: {}", e);
        }
    }
    Ok(())
}

fn write_parcel_crate() -> anyhow::Result<()> {
    match fs::metadata("parcel") {
        Ok(_) => {
            // TODO: could check file type for better error here.
            bail!("`parcel` exists, not creating");
        }
        Err(e) if e.kind() == io::ErrorKind::NotFound => {
            for dir in &["parcel", "parcel/src"] {
                fs::create_dir(dir)?;
            }
            for (filename, contents) in &[
                ("Cargo.toml", PARCEL_CARGO_TOML_CONTENTS),
                (".gitignore", PARCEL_GITIGNORE_CONTENTS),
                ("src/main.rs", PARCEL_MAIN_CONTENTS),
            ] {
                let path = Path::new("parcel").join(filename);
                fs::write(path, contents)?;
            }
        }
        Err(e) => {
            bail!("Could not check for existence of `parcel`: {}", e);
        }
    }
    Ok(())
}

fn run() -> Result<(), Error> {
    // TODO: Detect use as `cargo-parcel`.
    let subcommand = match std::env::args_os().nth(2) {
        None => {
            print!("{}", GLOBAL_HELP);
            return Ok(());
        }
        Some(s) => s,
    };
    let subcommand = &*subcommand.to_string_lossy();
    match subcommand {
        "init" => {
            let matches = Arguments::from_vec(std::env::args_os().skip(3).collect());
            let remainder = matches.finish();
            if !remainder.is_empty() {
                bail!("unprocessed arguments: {}", DisplayArgs(remainder.iter()))
            }
            write_parcel_crate()?;
            write_cargo_alias()?;
        }
        _ => {
            bail!("{}", GLOBAL_HELP);
        }
    }
    Ok(())
}

pub fn main() -> ! {
    let rc = match run() {
        Ok(()) => 0,
        Err(e) => {
            eprintln!("{}", e);
            1
        }
    };
    std::process::exit(rc);
}

static CARGO_CONFIG_CONTENTS: &str = r#"[alias]
parcel = "run --manifest-path ./parcel/Cargo.toml --"
"#;

static PARCEL_GITIGNORE_CONTENTS: &str = r#"/target
"#;

static PARCEL_CARGO_TOML_CONTENTS: &str = concat!(
    r#"[package]
name = "parcel"
version = "0.0.1"
edition = "2018"
publish = false

[dependencies]
cargo-parcel = ""#,
    env!("CARGO_PKG_VERSION"),
    r#""

[profile.dev]
# Disable debug info for faster builds, and less used diskspace.
debug = false
"#
);

static PARCEL_MAIN_CONTENTS: &str = r#"fn main() {
    cargo_parcel::main();
}
"#;

static GLOBAL_HELP: &str = r#"Extended cargo installer -- bootstrapper

USAGE:

    cargo parcel init

This is the "parcel" cargo plugin, which is normally overriden by a cargo alias
in `.cargo/config`. This plugin only implements the `install` subcommand to
prepare a crate for use with `cargo-parcel`.

See <https://gitlab.com/rotty/cargo-parcel/blob/master/README.md> for more
information.
"#;