|
@@ -10,14 +10,6 @@ use std::process::Command;
|
|
|
|
|
|
mod cache;
|
|
|
|
|
|
-// I'm not sure if reqwest::blocking's write_to is working right or not.
|
|
|
-// It seems like it might be storing the entire file in memory...
|
|
|
-// Which I don't want. I might have to go back to tokio and chunk.
|
|
|
-
|
|
|
-// see reqwest/web-o/src/cache.rs for example cache
|
|
|
-// It restores reqwest::header::HeaderMap
|
|
|
-// (which allows duplicates... and ignores case on keys)
|
|
|
-
|
|
|
#[derive(Parser)]
|
|
|
#[command(
|
|
|
about = "Go Updater",
|
|
@@ -33,10 +25,7 @@ struct Cli {
|
|
|
#[arg(short, long, default_value = "cache")]
|
|
|
cache: PathBuf,
|
|
|
|
|
|
- // If I make this optional, I can't seem to get the display_help code
|
|
|
- // to work now. Not sure why.
|
|
|
#[command(subcommand)]
|
|
|
- // command: Commands,
|
|
|
command: Option<Commands>,
|
|
|
}
|
|
|
|
|
@@ -49,18 +38,26 @@ enum Commands {
|
|
|
}
|
|
|
|
|
|
/// Query `go env`
|
|
|
+///
|
|
|
+/// This is a good example of parsing output line by line.
|
|
|
#[must_use]
|
|
|
fn get_go_env() -> Result<HashMap<String, String>> {
|
|
|
let output = Command::new("go").arg("env").output()?;
|
|
|
if output.status.success() {
|
|
|
let mut result = HashMap::<String, String>::new();
|
|
|
- for mut line in String::from_utf8_lossy(output.stdout.as_slice()).split("\n") {
|
|
|
+
|
|
|
+ let output_str = std::str::from_utf8(output.stdout.as_slice())?;
|
|
|
+ for mut line in output_str.split("\n") {
|
|
|
line = line.trim();
|
|
|
if let Some(parts) = line.split_once("=") {
|
|
|
let value = parts.1.trim_matches('\'');
|
|
|
result.insert(parts.0.to_string(), value.to_string());
|
|
|
}
|
|
|
}
|
|
|
+ /*
|
|
|
+ for mut line in String::from_utf8_lossy(output.stdout.as_slice()).split("\n") {
|
|
|
+ }
|
|
|
+ */
|
|
|
Ok(result)
|
|
|
} else {
|
|
|
bail!("Failed to query `go env`");
|
|
@@ -94,12 +91,12 @@ fn find_go() -> Result<String> {
|
|
|
}
|
|
|
|
|
|
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"),);
|
|
|
|
|
|
+// I've changed the parser, so scrapy isn't needed.
|
|
|
+// I'll revisit this when I get further with html tokeparser.
|
|
|
+
|
|
|
/*
|
|
|
CSS Selector elements of interest: (a href="" part).
|
|
|
href are relative...
|
|
@@ -122,35 +119,11 @@ and grab the section of td's. class=filename has a href, last has SHA256.
|
|
|
<a class="download" href="/dl/go1.24.0.linux-arm64.tar.gz">go1.24.0.linux-arm64.tar.gz</a>
|
|
|
<a class="download" href="/dl/go1.24.0.windows-amd64.zip">go1.24.0.windows-amd64.zip</a>
|
|
|
<a class="download" href="/dl/go1.24.0.windows-amd64.msi">go1.24.0.windows-amd64.msi</a>
|
|
|
-
|
|
|
*/
|
|
|
|
|
|
-/*
|
|
|
-fn download_and_save(url: &str, filename: &str) -> Result<()> {
|
|
|
- let client = reqwest::blocking::Client::builder()
|
|
|
- .user_agent(APP_USER_AGENT)
|
|
|
- .build()?;
|
|
|
- print!("Downloading: {url} ");
|
|
|
- let _ = stdout().flush();
|
|
|
-
|
|
|
- let mut resp = client.get(url).send().context("Failed get")?;
|
|
|
-
|
|
|
- if resp.status().is_success() {
|
|
|
- let mut file =
|
|
|
- File::create(filename).with_context(|| format!("Creating file {filename} failed."))?;
|
|
|
- resp.copy_to(&mut file)?;
|
|
|
- } else {
|
|
|
- bail!("Status Code: {:?}", resp.status());
|
|
|
- }
|
|
|
- println!("OK");
|
|
|
-
|
|
|
- Ok(())
|
|
|
-}
|
|
|
-*/
|
|
|
-
|
|
|
// URL: https://go.dev/dl/go1.24.1.linux-amd64.tar.gz
|
|
|
#[must_use]
|
|
|
-/// Get go version from download URL.
|
|
|
+/// Get go version from download link.
|
|
|
fn version_from_url(url: &str, arch: &str) -> Option<String> {
|
|
|
if let Some(parts) = url.split_once(arch) {
|
|
|
if let Some(part) = parts.0.rsplit_once("/go") {
|
|
@@ -161,17 +134,7 @@ fn version_from_url(url: &str, arch: &str) -> Option<String> {
|
|
|
None
|
|
|
}
|
|
|
|
|
|
-#[must_use]
|
|
|
-/// Get go version from `go version` output.
|
|
|
-fn version_from_go(text: &str) -> Option<String> {
|
|
|
- let parts: Vec<&str> = text.split(' ').collect();
|
|
|
- if parts.len() == 4 {
|
|
|
- return Some(parts[2].to_string().replace("go", ""));
|
|
|
- }
|
|
|
- None
|
|
|
-}
|
|
|
-
|
|
|
-/// Return just the href="<return this part>".
|
|
|
+/// Return just the href="<return just this part>".
|
|
|
#[must_use]
|
|
|
fn just_href(link: &str) -> Result<String> {
|
|
|
let parts = link.split_once("href=\"").unwrap_or(("", "")).1;
|
|
@@ -182,26 +145,28 @@ fn just_href(link: &str) -> Result<String> {
|
|
|
bail!("Unable to locate href");
|
|
|
}
|
|
|
|
|
|
-/// Find a href link for given arch (architecture)
|
|
|
+/// Find a href link for given os and arch (architecture)
|
|
|
///
|
|
|
/// Look for <a class="download" href="
|
|
|
#[must_use]
|
|
|
-fn find_arch_link(arch: &str, fp: &File) -> Result<String> {
|
|
|
+fn find_arch_link(os_arch: &str, fp: &File) -> Result<String> {
|
|
|
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) {
|
|
|
+ if line.contains(os_arch) {
|
|
|
// Return just the href part.
|
|
|
return just_href(&line);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- bail!("Unable to locate architecture download link");
|
|
|
+ bail!("Unable to locate OS architecture download link");
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
+// This is terrible, since it has all of the HTML in a string.
|
|
|
+
|
|
|
/// find_link for given arch (architecture)
|
|
|
///
|
|
|
/// Look for <a class="download" href=""
|
|
@@ -226,9 +191,9 @@ fn find_link(arch: &str) -> Result<String> {
|
|
|
*/
|
|
|
|
|
|
/// Get value from given HashMap<String,String>
|
|
|
-///
|
|
|
+///
|
|
|
/// This gives us a String, not a &String, and not an Option.
|
|
|
-fn hashget(hash: &HashMap<String,String>, key: &str) -> String {
|
|
|
+fn hashget(hash: &HashMap<String, String>, key: &str) -> String {
|
|
|
if let Some(value) = hash.get(key) {
|
|
|
return value.clone();
|
|
|
}
|
|
@@ -243,7 +208,7 @@ fn main() -> Result<()> {
|
|
|
This seems to be managed by go now. So running ```go env``` would
|
|
|
have/show GOROOT being set by that instance of go.
|
|
|
GOPATH is another story. It's where go stores things it downloads from
|
|
|
- the web, and most certainly shouldn't be $HOME/go (!= GOROOT).
|
|
|
+ the web, and most certainly shouldn't be $HOME/go != GOROOT.
|
|
|
|
|
|
I want to be a bit looser on requiring GOROOT later on.
|
|
|
*/
|
|
@@ -252,8 +217,7 @@ fn main() -> Result<()> {
|
|
|
let go_path = env::var("GOPATH").unwrap_or(String::new());
|
|
|
let go_root = env::var("GOROOT").unwrap_or(String::new());
|
|
|
|
|
|
- let go_env = get_go_env()
|
|
|
- .context("Calling `go env` failed.")?;
|
|
|
+ let go_env = get_go_env().context("Calling `go env` failed.")?;
|
|
|
let mut go_version = hashget(&go_env, "GOVERSION");
|
|
|
if go_version.starts_with("go") {
|
|
|
go_version.remove(0);
|
|
@@ -264,10 +228,9 @@ fn main() -> Result<()> {
|
|
|
let go_os = hashget(&go_env, "GOOS");
|
|
|
let go_path = hashget(&go_env, "GOPATH");
|
|
|
let go_root = hashget(&go_env, "GOROOT");
|
|
|
+ // Construct OS-ARCH value.
|
|
|
let go_os_arch = format!("{go_os}-{go_arch}");
|
|
|
|
|
|
- println!("{} - {}-{}", go_version, go_os, go_arch);
|
|
|
-
|
|
|
// Initialize the cache.
|
|
|
let cache = cache::Cache::new(cli.cache, None)?;
|
|
|
|
|
@@ -279,12 +242,53 @@ fn main() -> Result<()> {
|
|
|
// Since the go.dev site doesn't allow caching or knowing when it changes,
|
|
|
// we always end up in Status::Fetched arm.
|
|
|
|
|
|
+ if let Some(filename) = status.download_path() {
|
|
|
+ // Make this function the same whether or not we have a cache hit.
|
|
|
+
|
|
|
+ let fp = File::open(filename)?;
|
|
|
+ let link = find_arch_link(&go_os_arch, &fp);
|
|
|
+
|
|
|
+ if let Ok(relative) = link {
|
|
|
+ // Download link for arch located. Make absolute URL.
|
|
|
+ let latest_version = version_from_url(&relative, &go_os_arch);
|
|
|
+ if let Some(latest) = latest_version {
|
|
|
+ println!("Version: {} [have {}]", latest, go_version);
|
|
|
+ if go_version != latest {
|
|
|
+ println!("Downloading newer version...");
|
|
|
+ let abs = relative_to_absolute(GO_URL, &relative).unwrap();
|
|
|
+
|
|
|
+ let latest_status = cache.fetch(&abs);
|
|
|
+ if let Some(update) = latest_status.download_path() {
|
|
|
+ // Ok, we have the update! Now what?
|
|
|
+ println!("Ready to install update.");
|
|
|
+
|
|
|
+ } else {
|
|
|
+ println!("Download failed: {:?}", latest_status);
|
|
|
+ }
|
|
|
+ // println!("Latest: {:?}", latest_status);
|
|
|
+ } else {
|
|
|
+ println!("You're already good to GO.");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ println!("Finding version failed: [{}]", relative);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ bail!("Unable to locate download link");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ println!("Status = {:?}", status);
|
|
|
+ }
|
|
|
+ /*
|
|
|
match status {
|
|
|
- cache::Status::Fetched(filename) => {
|
|
|
+ cache::Status::Fetched(filename) |
|
|
|
+ cache::Status::Cached(filename) => {
|
|
|
+ // Make this function the same whether or not we have a cache hit.
|
|
|
+
|
|
|
let fp = File::open(filename)?;
|
|
|
let link = find_arch_link(&go_os_arch, &fp);
|
|
|
+
|
|
|
if let Ok(relative) = link {
|
|
|
- // Download link for arch located. Make useable.
|
|
|
+ // Download link for arch located. Make absolute URL.
|
|
|
let abs = relative_to_absolute(GO_URL, &relative).unwrap();
|
|
|
println!("URL: {}", abs);
|
|
|
|
|
@@ -295,27 +299,21 @@ fn main() -> Result<()> {
|
|
|
println!("Downloading newer version...");
|
|
|
let latest_status = cache.fetch(&abs);
|
|
|
println!("Latest: {:?}", latest_status);
|
|
|
+ } else {
|
|
|
+ println!("You're already good to GO.");
|
|
|
}
|
|
|
} else {
|
|
|
- println!("Finding version failed for string: [{}]", abs);
|
|
|
+ println!("Finding version failed: [{}]", abs);
|
|
|
}
|
|
|
} else {
|
|
|
bail!("Unable to locate download link");
|
|
|
}
|
|
|
}
|
|
|
- cache::Status::Cached(filename) => {
|
|
|
- println!("(from cache)"); // I wish I could see this.
|
|
|
- let fp = File::open(filename)?;
|
|
|
- let link = find_arch_link(&go_os_arch, &fp);
|
|
|
- if let Ok(relative) = link {
|
|
|
- let abs = relative_to_absolute(GO_URL, &relative).unwrap();
|
|
|
- println!("URL: {}", abs);
|
|
|
- }
|
|
|
- }
|
|
|
_ => {
|
|
|
println!("Status = {:?}", status);
|
|
|
}
|
|
|
}
|
|
|
+ */
|
|
|
}
|
|
|
Some(Commands::Info {}) => {
|
|
|
println!("GOPATH {}", go_path);
|