dolly-cli 0.1.3

Like apt, but for GitHub repositories — clone, build, install and update tools from source.
Documentation
use anyhow::{Context, anyhow, bail};
use dolly_cli::{binary, build, git, paths, recipe::Recipe, ui};

pub fn handle(name: &str) -> anyhow::Result<()> {
    let (owner, repo) = name
        .split_once('/')
        .filter(|(o, n)| !o.is_empty() && !n.is_empty() && !n.contains('/'))
        .ok_or_else(|| anyhow!("repo must be in `owner/repo` form"))?;

    let recipe = Recipe::find(repo).with_context(|| format!("loading recipe for `{repo}`"))?;
    if recipe.package.slug() != name {
        bail!(
            "recipe declares `{}` but CLI input is `{name}`",
            recipe.package.slug()
        );
    }

    // Clone the repo (skip if already cloned)
    let repositories_dir = paths::repositories_dir()?;
    let repo_path = repositories_dir.join(repo);

    if !repo_path.exists() {
        ui::status("Cloning", name);
        std::fs::create_dir_all(&repositories_dir)
            .with_context(|| format!("creating {}", repositories_dir.display()))?;
        let url = format!("https://github.com/{owner}/{repo}.git");
        git::clone(&url, &repo_path).with_context(|| format!("cloning `{name}`"))?;
    } else {
        ui::status("Cached", name);
    }

    ui::status("Building", name);
    build::run(&recipe.build.steps, &repo_path).with_context(|| format!("building `{name}`"))?;

    let binary_path = repo_path.join(&recipe.build.output);
    let bin_name = binary_path
        .file_name()
        .ok_or_else(|| anyhow!("`build.output` has no filename"))?;
    let install_path = paths::default_install_dir()?.join(bin_name);

    binary::place(&binary_path, &install_path).with_context(|| format!("installing `{name}`"))?;
    binary::make_executable(&install_path)
        .with_context(|| format!("setting executable bit on {}", install_path.display()))?;

    ui::status(
        "Installed",
        &format!("{repo} to {}", install_path.display()),
    );
    Ok(())
}