feat!: configurable title prefixes (#14)

This commit adds the `--prefix` CLI option. Providing this option allows
users to tell `gh-stack` their title prefixes begin and/or end with
something other than `[]`. Title prefixes can only be 2 characters
long. The starting prefix is the first character and the end prefix
is the second character.
This commit is contained in:
Luis H. Ball Jr 2021-11-24 10:17:32 -08:00 committed by GitHub
parent cabd354ec9
commit 037b33f66d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 19 deletions

12
Cargo.lock generated
View File

@ -319,7 +319,7 @@ dependencies = [
[[package]] [[package]]
name = "gh-stack" name = "gh-stack"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"clap", "clap",
"console", "console",
@ -698,9 +698,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.4.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]] [[package]]
name = "openssl" name = "openssl"
@ -1111,11 +1111,11 @@ dependencies = [
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.0.1" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [ dependencies = [
"lazy_static", "once_cell",
] ]
[[package]] [[package]]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "gh-stack" name = "gh-stack"
version = "0.2.0" version = "0.3.0"
authors = ["Timothy Andrew <mail@timothyandrew.net>, Luis Ball <luqven@gmail.com>"] authors = ["Timothy Andrew <mail@timothyandrew.net>, Luis Ball <luqven@gmail.com>"]
license = "MIT" license = "MIT"
repository = "https://github.com/luqven/gh-stack" repository = "https://github.com/luqven/gh-stack"

View File

@ -1,6 +1,7 @@
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use console::style; use console::style;
use git2::Repository; use git2::Repository;
use regex::Regex;
use std::env; use std::env;
use std::error::Error; use std::error::Error;
use std::rc::Rc; use std::rc::Rc;
@ -35,6 +36,11 @@ fn clap<'a, 'b>() -> App<'a, 'b> {
.takes_value(false) .takes_value(false)
.help("Skip waiting for confirmation"); .help("Skip waiting for confirmation");
let prefix = Arg::with_name("prefix")
.long("prefix")
.takes_value(true)
.help("PR title prefix identifier to remove from the title");
let annotate = SubCommand::with_name("annotate") let annotate = SubCommand::with_name("annotate")
.about("Annotate the descriptions of all PRs in a stack with metadata about all PRs in the stack") .about("Annotate the descriptions of all PRs in a stack with metadata about all PRs in the stack")
.setting(AppSettings::ArgRequiredElseHelp) .setting(AppSettings::ArgRequiredElseHelp)
@ -42,6 +48,7 @@ fn clap<'a, 'b>() -> App<'a, 'b> {
.arg(exclude.clone()) .arg(exclude.clone())
.arg(repository.clone()) .arg(repository.clone())
.arg(ci.clone()) .arg(ci.clone())
.arg(prefix.clone())
.arg(Arg::with_name("prelude") .arg(Arg::with_name("prelude")
.long("prelude") .long("prelude")
.short("p") .short("p")
@ -148,6 +155,12 @@ fn get_excluded(m: &ArgMatches) -> Vec<String> {
} }
} }
fn remove_title_prefixes(title: String, prefix: &str) -> String {
let regex = Regex::new(&format!("[{}]", prefix).to_string()).unwrap();
let result = regex.replace_all(&title, "").into_owned();
return result;
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> { async fn main() -> Result<(), Box<dyn Error>> {
dotenv::from_filename(".gh-stack.env").ok(); dotenv::from_filename(".gh-stack.env").ok();
@ -161,6 +174,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
match matches.subcommand() { match matches.subcommand() {
("annotate", Some(m)) => { ("annotate", Some(m)) => {
let identifier = m.value_of("identifier").unwrap(); let identifier = m.value_of("identifier").unwrap();
let prefix = m.value_of("prefix").unwrap_or("[]");
let prefix = regex::escape(prefix);
// if ci flag is set, set ci to true // if ci flag is set, set ci to true
let ci = m.is_present("ci"); let ci = m.is_present("ci");
// replace it with the -r argument value if set // replace it with the -r argument value if set
@ -173,18 +188,20 @@ async fn main() -> Result<(), Box<dyn Error>> {
panic!("{}", error); panic!("{}", error);
} }
let identifier = remove_title_prefixes(identifier.to_string(), &prefix);
println!( println!(
"Searching for {} identifier in {} repo", "Searching for {} identifier in {} repo",
style(identifier).bold(), style(&identifier).bold(),
style(repository).bold() style(repository).bold()
); );
let stack = let stack =
build_pr_stack_for_repo(identifier, repository, &credentials, get_excluded(m)) build_pr_stack_for_repo(&identifier, repository, &credentials, get_excluded(m))
.await?; .await?;
let table = let table =
markdown::build_table(&stack, identifier, m.value_of("prelude"), repository); markdown::build_table(&stack, &identifier, m.value_of("prelude"), repository);
for (pr, _) in stack.iter() { for (pr, _) in stack.iter() {
println!("{}: {}", pr.number(), pr.title()); println!("{}: {}", pr.number(), pr.title());
@ -195,7 +212,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
loop_until_confirm("Going to update these PRs ☝️ "); loop_until_confirm("Going to update these PRs ☝️ ");
} }
persist::persist(&stack, &table, &credentials).await?; persist::persist(&stack, &table, &credentials, &prefix).await?;
println!("Done!"); println!("Done!");
} }
@ -283,7 +300,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
&project, &project,
remote.name().unwrap(), remote.name().unwrap(),
m.value_of("boundary"), m.value_of("boundary"),
ci ci,
) )
.await?; .await?;
println!("All done!"); println!("All done!");

View File

@ -27,17 +27,24 @@ fn safe_replace(body: &str, table: &str) -> String {
} }
} }
fn remove_title_prefixes(row: String) -> String { fn remove_title_prefixes(row: String, prefix: &str) -> String {
// TODO: Make this configurable let prefix = String::from(prefix);
let regex = Regex::new(r"\[[^\]]+\]\s*").unwrap(); let prefix_1 = &prefix[0..2];
regex.replace_all(&row, "").into_owned() let prefix_2 = &prefix[2..4];
let regex_str = format!(r"{}[^\]]+{}\s*", prefix_1, prefix_2);
let regex = Regex::new(&regex_str).unwrap();
return regex.replace_all(&row, "").into_owned();
} }
pub async fn persist(
pub async fn persist(prs: &FlatDep, table: &str, c: &Credentials) -> Result<(), Box<dyn Error>> { prs: &FlatDep,
table: &str,
c: &Credentials,
prefix: &str,
) -> Result<(), Box<dyn Error>> {
let futures = prs.iter().map(|(pr, _)| { let futures = prs.iter().map(|(pr, _)| {
let body = table.replace(&pr.title()[..], &format!("👉 {}", pr.title())[..]); let body = table.replace(&pr.title()[..], &format!("👉 {}", pr.title())[..]);
let body = remove_title_prefixes(body); let body = remove_title_prefixes(body, prefix);
let description = safe_replace(pr.body(), body.as_ref()); let description = safe_replace(pr.body(), body.as_ref());
pull_request::update_description(description, pr.clone(), c) pull_request::update_description(description, pr.clone(), c)
}); });