119 lines
3.4 KiB
Rust
119 lines
3.4 KiB
Rust
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
use std::{fs, path::PathBuf};
|
|
|
|
use clap::*;
|
|
use log::*;
|
|
use semver::Version;
|
|
use semver::VersionReq;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[clap(author, version, about, long_about = None)]
|
|
struct CLI {
|
|
path: PathBuf,
|
|
}
|
|
|
|
fn init_logger() {
|
|
// if std::env::var("RUST_LOG").is_err() {
|
|
// std::env::set_var("RUST_LOG", "info");
|
|
// }
|
|
pretty_env_logger::init();
|
|
}
|
|
|
|
fn skip_entry(name: &str, v: &toml::Value) -> bool {
|
|
if v.is_table() {
|
|
if v.get("git").is_some() {
|
|
info!("[{name}] Skipping, Found Git src: {:?}", v);
|
|
return true;
|
|
} else if v.get("path").is_some() {
|
|
info!("[{name}] Skipping, Found local dep: {:?}", v);
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
async fn find_latest_version(c: &reqwest::Client, name: &str) -> Version {
|
|
let url = if name.len() == 1 {
|
|
format!("https://index.crates.io/1/{}", name)
|
|
} else if name.len() == 2 {
|
|
format!("https://index.crates.io/2/{}", name)
|
|
} else if name.len() == 3 {
|
|
format!("https://index.crates.io/3/{}/{}", &name[0..1], name)
|
|
} else {
|
|
format!(
|
|
"https://index.crates.io/{}/{}/{}",
|
|
&name[0..2].to_lowercase(),
|
|
&name[2..4].to_lowercase(),
|
|
name
|
|
)
|
|
};
|
|
|
|
let resp = c.get(&url).send().await.unwrap();
|
|
let text = resp.text().await.unwrap();
|
|
|
|
let mut latest = Version::parse("0.0.0").unwrap();
|
|
for line in text.lines() {
|
|
if line.trim().is_empty() {
|
|
continue;
|
|
}
|
|
let this: serde_json::Value =
|
|
serde_json::from_str(&line).unwrap_or_else(|_| panic!("{}", line));
|
|
let vers = this.get("vers").unwrap();
|
|
let vers = Version::parse(vers.as_str().unwrap()).unwrap();
|
|
if vers > latest {
|
|
latest = vers;
|
|
}
|
|
}
|
|
latest
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
init_logger();
|
|
|
|
let opts = CLI::parse();
|
|
let contents = fs::read_to_string(opts.path).unwrap();
|
|
let manifest: toml::Value = toml::from_str(&contents).unwrap();
|
|
let c =
|
|
reqwest::Client::builder();
|
|
let c = c.build().unwrap();
|
|
|
|
let mut updates = Vec::default();
|
|
|
|
if let Some(toml::Value::Table(deps)) = manifest.get("dependencies") {
|
|
for d in deps.iter() {
|
|
let name = d.0.as_str();
|
|
|
|
if skip_entry(name, d.1) {
|
|
continue;
|
|
}
|
|
|
|
let using = if d.1.is_str() {
|
|
d.1.as_str().unwrap()
|
|
} else {
|
|
d.1.get("version")
|
|
.unwrap_or_else(|| panic!("version not found: {:?}", d))
|
|
.as_str()
|
|
.unwrap()
|
|
};
|
|
let using = VersionReq::parse(using).unwrap();
|
|
debug!("name: {name} version: {using}");
|
|
|
|
let latest = find_latest_version(&c, name).await;
|
|
|
|
info!("{name} using: {using} latest {latest}");
|
|
// since remove version can only increase, failing to match can be assumed to be the
|
|
// same as "new version available"
|
|
if !using.matches(&latest) {
|
|
updates.push((name, (using, latest)));
|
|
}
|
|
}
|
|
}
|
|
for (name, (using, latest)) in updates.iter() {
|
|
println!("[{name}] {using} < {latest}")
|
|
}
|
|
}
|