tsdl 1.5.0

A downloader/builder of many tree-sitter parsers
Documentation
use std::{fs, path::PathBuf};

use anyhow::{bail, Result};
use clap::Parser;
use self_update::self_replace;
use semver::Version;
use tracing::{error, info};

use tsdl::{
    args, build, config,
    consts::TREE_SITTER_PLATFORM,
    display::{self, Handle, Progress, ProgressState},
    logging,
};

fn main() -> Result<()> {
    set_panic_hook();
    let args = args::Args::parse();
    let _guard = logging::init(&args)?;
    info!("Starting");
    run(&args)?;
    info!("Done");
    Ok(())
}

fn run(args: &args::Args) -> Result<()> {
    match &args.command {
        args::Command::Build(command) => build::run(
            &config::current(&args.config, Some(command))?,
            display::current(&args.progress, &args.verbose),
        ),
        args::Command::Config { command } => config::run(command, &args.config),
        args::Command::Selfupdate => self_update(display::current(&args.progress, &args.verbose)),
    }
}

fn self_update(mut progress: Progress) -> Result<()> {
    let tsdl = env!("CARGO_BIN_NAME");
    let current_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
    let mut handle = progress.register("selfupdate", 4);

    handle.start("fetching releases".to_string());
    let releases = self_update::backends::github::ReleaseList::configure()
        .repo_owner("stackmystack")
        .repo_name(tsdl)
        .build()?
        .fetch()?;

    let name = format!("{tsdl}-{TREE_SITTER_PLATFORM}.gz");
    let asset = releases[0].assets.iter().find(|&asset| asset.name == name);
    if asset.is_none() {
        bail!("Could not find a suitable release for your platform");
    }

    let latest_version = Version::parse(&releases[0].version)?;
    if latest_version <= current_version {
        handle.msg("already at the latest version".to_string());
        return Ok(());
    }

    handle.step(format!("downloading {latest_version}"));
    let asset = asset.unwrap();
    let tmp_dir = tempfile::tempdir()?;
    let tmp_gz_path = tmp_dir.path().join(&asset.name);
    let tmp_gz = fs::File::create_new(&tmp_gz_path)?;

    self_update::Download::from_url(&asset.download_url)
        .set_header(reqwest::header::ACCEPT, "application/octet-stream".parse()?)
        .download_to(&tmp_gz)?;

    handle.step(format!("extracting {latest_version}"));
    let tsdl_bin = PathBuf::from(tsdl);
    self_update::Extract::from_source(&tmp_gz_path)
        .archive(self_update::ArchiveKind::Plain(Some(
            self_update::Compression::Gz,
        )))
        .extract_file(tmp_dir.path(), &tsdl_bin)?;

    let new_exe = tmp_dir.path().join(tsdl_bin);
    self_replace::self_replace(new_exe)?;

    handle.fin(format!("{latest_version}"));
    Ok(())
}

pub fn set_panic_hook() {
    std::panic::set_hook(Box::new(move |info| {
        #[cfg(not(debug_assertions))]
        {
            use human_panic::{handle_dump, print_msg, Metadata};
            let meta = Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))
                .authors(env!("CARGO_PKG_AUTHORS").replace(':', ", "))
                .homepage(env!("CARGO_PKG_HOMEPAGE"));

            let file_path = handle_dump(&meta, info);
            print_msg(file_path, &meta)
                .expect("human-panic: printing error message to console failed");
        }
        #[cfg(debug_assertions)]
        {
            better_panic::Settings::auto()
                .most_recent_first(false)
                .lineno_suffix(true)
                .verbosity(better_panic::Verbosity::Full)
                .create_panic_handler()(info);
        }
        error!("{}", info);
        std::process::exit(1);
    }));
}