use anyhow::{bail, ensure, Context, Result};
use git_url_parse::GitUrl;
use std::{env::current_dir, fs, path::PathBuf, str::FromStr};
use structopt::StructOpt;
use toml_edit::{Document, InlineTable, Value};
use walkdir::{DirEntry, WalkDir};
#[derive(Debug, Clone)]
enum Rewrite {
All,
Substrate(Option<String>),
Polkadot(Option<String>),
Cumulus(Option<String>),
Beefy(Option<String>),
}
#[derive(Debug, Clone)]
enum Version {
Tag(String),
Branch(String),
Rev(String),
}
#[derive(Debug, StructOpt)]
pub struct Update {
#[structopt(long)]
path: Option<PathBuf>,
#[structopt(long, short = "s")]
substrate: bool,
#[structopt(long, short = "p")]
polkadot: bool,
#[structopt(long, short = "c")]
cumulus: bool,
#[structopt(long, short = "b")]
beefy: bool,
#[structopt(long, short = "a")]
all: bool,
#[structopt(long, conflicts_with_all = &[ "rev", "tag" ])]
branch: Option<String>,
#[structopt(long, conflicts_with_all = &[ "branch", "tag" ])]
rev: Option<String>,
#[structopt(long, conflicts_with_all = &[ "rev", "branch" ])]
tag: Option<String>,
#[structopt(long)]
git: Option<String>,
}
impl Update {
fn into_parts(self) -> Result<(Rewrite, Version, Option<PathBuf>)> {
let version = if let Some(branch) = self.branch {
Version::Branch(branch)
} else if let Some(rev) = self.rev {
Version::Rev(rev)
} else if let Some(tag) = self.tag {
Version::Tag(tag)
} else {
bail!("You need to pass `--branch`, `--tag` or `--rev`");
};
let rewrite = if self.all {
if self.git.is_some() {
bail!("You need to pass `--substrate`, `--polkadot`, `--cumulus` or `--beefy` for `--git`.");
} else {
Rewrite::All
}
} else if self.substrate {
Rewrite::Substrate(self.git)
} else if self.beefy {
Rewrite::Beefy(self.git)
} else if self.polkadot {
Rewrite::Polkadot(self.git)
} else if self.cumulus {
Rewrite::Cumulus(self.git)
} else {
bail!("You must specify one of `--substrate`, `--polkadot`, `--cumulus`, `--beefy` or `--all`.");
};
Ok((rewrite, version, self.path))
}
pub fn run(self) -> Result<()> {
let (rewrite, version, path) = self.into_parts()?;
let path = path
.map(Ok)
.unwrap_or_else(|| current_dir().with_context(|| "Working directory is invalid."))?;
ensure!(
path.is_dir(),
"Path '{}' is not a directory.",
path.display()
);
let is_hidden = |entry: &DirEntry| {
entry
.file_name()
.to_str()
.map(|s| s.starts_with('.'))
.unwrap_or(false)
};
WalkDir::new(path)
.follow_links(true)
.into_iter()
.filter_entry(|e| !is_hidden(e))
.filter_map(|e| e.ok())
.filter(|e| {
e.file_type().is_file() && e.file_name().to_string_lossy().ends_with("Cargo.toml")
})
.try_for_each(|toml| handle_toml_file(toml.into_path(), &rewrite, &version))
}
}
fn handle_dependency(name: &str, dep: &mut InlineTable, rewrite: &Rewrite, version: &Version) {
let git = if let Some(git) = dep
.get("git")
.and_then(|v| v.as_str())
.and_then(|d| GitUrl::parse(d).ok())
{
git
} else {
return;
};
let new_git = match rewrite {
Rewrite::All => &None,
Rewrite::Substrate(new_git) if git.name == "substrate" => new_git,
Rewrite::Polkadot(new_git) if git.name == "polkadot" => new_git,
Rewrite::Cumulus(new_git) if git.name == "cumulus" => new_git,
Rewrite::Beefy(new_git) if git.name == "grandpa-bridge-gadget" => new_git,
_ => return,
};
if let Some(new_git) = new_git {
*dep.get_or_insert("git", "") = Value::from(new_git.as_str()).decorated(" ", "");
}
dep.remove("tag");
dep.remove("branch");
dep.remove("rev");
match version {
Version::Tag(tag) => {
*dep.get_or_insert("tag", "") = Value::from(tag.as_str()).decorated(" ", " ");
}
Version::Branch(branch) => {
*dep.get_or_insert("branch", "") = Value::from(branch.as_str()).decorated(" ", " ");
}
Version::Rev(rev) => {
*dep.get_or_insert("rev", "") = Value::from(rev.as_str()).decorated(" ", " ");
}
}
log::debug!(" updated: {:?} <= {}", version, name);
}
fn handle_toml_file(path: PathBuf, rewrite: &Rewrite, version: &Version) -> Result<()> {
log::info!("Processing: {}", path.display());
let mut toml_doc = Document::from_str(&fs::read_to_string(&path)?)?;
toml_doc
.clone()
.iter()
.filter(|(k, _)| k.contains("dependencies"))
.filter_map(|(k, v)| v.as_table().map(|t| (k, t)))
.for_each(|(k, t)| {
t.iter()
.filter_map(|v| v.1.as_inline_table().map(|_| v.0))
.for_each(|dn| {
let table = toml_doc[k][dn]
.as_inline_table_mut()
.expect("We filter by `is_inline_table`; qed");
handle_dependency(dn, table, rewrite, version);
})
});
fs::write(&path, toml_doc.to_string())?;
Ok(())
}