use anyhow::{bail, Result}; use clap::{Parser, Subcommand}; use std::env; use std::fs::File; use std::io::{BufRead, BufReader, Write}; use std::process::Command; /* Right now, this use tokio / reqwests async. I don't need async, change this to normal blocking. */ pub mod built_info { include!(concat!(env!("OUT_DIR"), "/built.rs")); } fn reqwests_version() -> Option { for (name, version) in built_info::DEPENDENCIES { if name == "reqwest" { return Some(version.to_string()); } } None } #[derive(Parser)] #[command(about = "Go updater")] struct Cli { #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Commands { /// Update go Update {}, Info {}, } /// Query `go version` fn find_go_version() -> Result { let output = Command::new("go").arg("version").output()?; if output.status.success() { // Ok! We have something! return Ok(String::from_utf8_lossy(output.stdout.as_slice()).to_string()); } bail!("Failed to query go version."); } // or possibly pathsearch or search_path fn find_go() -> Result { let output = Command::new("which").arg("go").output()?; if output.status.success() { return Ok(String::from_utf8_lossy(output.stdout.as_slice()).to_string()); } bail!("Failed to locate go binary."); } const GO_URL: &str = "https://go.dev/dl/"; const GO_FILE: &str = "go-dl.html"; // 2 MB download of html... static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); /* CSS Selector elements of interest: (a href="" part). href are relative... div[class="downloadWrapper"] > a[class="download downloadBox"] table[class="downloadtable"] > tr[class=" "] > td[class="filename"] > a[class="download"] Or possibly, table[class="downloadtable"] > tr[class=" "] or tr[class="highlight "] ? and grab the section of td's. class=filename has a href, last has SHA256. go1.24.0.src.tar.gz go1.24.0.linux-amd64.tar.gz go1.24.0.linux-arm64.tar.gz go1.24.0.windows-amd64.zip go1.24.0.windows-amd64.msi */ fn get_go_downloads() -> Result<()> { // Result<(), Box> { // save the file? let client = reqwest::blocking::Client::builder() .user_agent(APP_USER_AGENT) .build()?; print!("Downloading: {GO_URL} "); let mut resp = client.get(GO_URL).send()?; if resp.status().is_success() { // Yuck! // println!("{}", resp.text().await?); // https://webscraping.ai/faq/reqwest/what-is-the-best-way-to-handle-large-file-downloads-with-reqwest let mut file = File::create(GO_FILE)?; resp.copy_to(&mut file)?; /* while let Some(chunk) = resp.chunk()? { file.write_all(&chunk)?; } */ } else { panic!("Failed: {:?}", resp.status()); } println!("OK"); Ok(()) } // use std::fmt; use std::error::Error; /* #[derive(Debug)] struct ArchNotFoundError {} impl fmt::Display for ArchNotFoundError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Unable to locate architecture download link") } } */ /// find_link for given arch (architecture) fn find_link(arch: &str) -> Result> { let fp = File::open(GO_FILE)?; let reader = BufReader::new(fp); for line in reader.lines() { if let Ok(line) = line { if line.contains("a class=\"download\"") { if line.contains(arch) { // Ok, return just the link part of this. // NOTE: Relative path... return Ok(line); } } } } Err(Box::from("Unable to locate architecture download link")) } fn main() -> Result<()> { let cli = Cli::parse(); // Get go environment let go_path = env::var("GOPATH").unwrap_or(String::new()); let go_root = env::var("GOROOT").unwrap_or(String::new()); let go_version: String; if let Ok(version) = find_go_version() { go_version = version.as_str().trim().to_string(); } else { panic!("I wasn't able to locate go. I need `go version` to know what arch to dl."); } // let go_version = go_version().await; let go_where: String; if let Ok(location) = find_go() { go_where = location.as_str().trim().to_string(); } else { panic!("I wasn't able to locate the go binary."); } // Get arch let parts = go_version.split(" "); let mut arch = parts.last().unwrap().to_string(); arch = arch.replace("/", "-"); // println!("{:?}", built_info::DEPENDENCIES); println!("Reqwest version: {:?}", reqwests_version()); println!("path {} / root {}", go_path, go_root); println!("version: {}", go_version); println!("where: {}", go_where); println!("arch: {}", arch); // println!("Result: {:?}", get_go_downloads().await ); // Get go version and path match &cli.command { Some(Commands::Update {}) => { get_go_downloads()?; let link = find_link(&arch); println!("{:?}", link); } Some(Commands::Info {}) => {} None => { // Display help. let _show_help: Cli = Cli::parse_from(["--help"]); } } Ok(()) }