diff --git a/Cargo.lock b/Cargo.lock index ac975d8..08d76fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +dependencies = [ + "memchr", +] + [[package]] name = "arc-swap" version = "0.4.6" @@ -234,6 +243,7 @@ version = "0.1.0" dependencies = [ "futures", "petgraph", + "regex", "reqwest", "serde", "tokio", @@ -713,6 +723,24 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + [[package]] name = "remove_dir_all" version = "0.5.2" @@ -898,6 +926,15 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "time" version = "0.1.43" diff --git a/Cargo.toml b/Cargo.toml index 9aa086e..3df8246 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ tokio = { version = "0.2", features = ["full"] } serde = { version = "1.0", features = ["derive"] } futures = "0.3.5" petgraph = "0.5" +regex = "1" diff --git a/src/api/search.rs b/src/api/search.rs index c57c7da..ea0eab3 100644 --- a/src/api/search.rs +++ b/src/api/search.rs @@ -2,7 +2,7 @@ use futures::future::join_all; use serde::Deserialize; use std::error::Error; -use crate::{api, markdown, Credentials}; +use crate::{api, Credentials}; #[derive(Deserialize, Debug, Clone)] pub struct SearchItem { @@ -23,7 +23,6 @@ pub struct PullRequest { number: usize, head: PullRequestRef, base: PullRequestRef, - merges_into: Option>, title: String, } @@ -36,21 +35,10 @@ impl PullRequest { &self.base.label } - pub fn set_merges_into(&mut self, into: PullRequest) { - // `clone` here to avoid an explosion of lifetime specifiers - self.merges_into = Some(Box::new(into)) - } + pub fn number(&self) -> usize { self.number } + pub fn title(&self) -> &str { &self.title } } -// impl markdown::AsMarkdown for PullRequest { -// fn as_markdown_table_row(&self) -> String { -// match self.merges_into { -// Some(into) => format!("|#{}|{}|#{}|", self.number, self.title, into.number), -// None => format!("|#{}|{}|`develop`/feature branch|", self.number, self.title), -// } -// } -// } - #[derive(Deserialize, Debug)] struct SearchResponse { items: Vec, @@ -81,6 +69,7 @@ pub async fn fetch_pull_requests_matching( .await .into_iter() .map(|item| item.unwrap()); + let responses: Vec<_> = join_all(items.map(|item| item.json::())) .await .into_iter() diff --git a/src/graph.rs b/src/graph.rs index bcb6777..3aff837 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,7 +1,5 @@ -use std::collections::{HashMap, HashSet}; -use petgraph::visit::IntoNodeReferences; +use std::collections::HashMap; use std::rc::Rc; -use std::cell::RefCell; use petgraph::Graph; use crate::api::search::PullRequest; @@ -15,7 +13,7 @@ pub fn build(prs: Vec>) -> Graph, usize> { for (i, pr) in prs.iter().enumerate() { let head_handle = handles[i]; if let Some(&base_handle) = handles_by_head.get(pr.base()) { - tree.add_edge(head_handle, *base_handle, 1); + tree.add_edge(*base_handle, head_handle, 1); } } diff --git a/src/main.rs b/src/main.rs index 9b17b5f..1d867fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,17 +29,16 @@ async fn main() -> Result<(), Box> { let prs = api::search::fetch_pull_requests_matching(&pattern, &credentials).await?; let prs = prs.into_iter().map(|pr| Rc::new(pr)).collect(); let tree = graph::build(prs); - println!("{:?}", tree); - - // markdown::build_table(&graph[..]); + let table = markdown::build_table(tree); + println!("{}", table); Ok(()) /* # TODO - [x] Authentication (personal access token) - [x] Fetch all PRs matching Jira - - [ ] Construct graph - - [ ] Create markdown table + - [x] Construct graph + - [x] Create markdown table - [ ] Persist table back to Github */ } diff --git a/src/markdown.rs b/src/markdown.rs index d6b16c1..1b3ac34 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -1,8 +1,39 @@ -use crate::api::search::PullRequest; -use std::fmt::Display; +use petgraph::{Direction,Graph}; +use std::rc::Rc; +use petgraph::visit::Bfs; +use petgraph::visit::EdgeRef; +use regex::Regex; -pub trait AsMarkdown { - fn as_markdown_table_row(&self) -> String; +use crate::api::search::PullRequest; + +fn process(row: String) -> String { + // TODO: Make this configurable + let regex = Regex::new(r"\[HEAP-\d+\]").unwrap(); + regex.replace_all(&row, "").into_owned() } -pub fn build_table(graph: &[T]) {} +pub fn build_table(graph: Graph, usize>) -> String { + let mut out = String::new(); + out.push_str("## Stacked PR Chain"); + out.push_str("| PR | Title | Merges Into |\n"); + out.push_str("|:--:|:------|:-------------:|\n"); + + let roots: Vec<_> = graph.externals(Direction::Incoming).collect(); + + for root in roots { + let mut bfs = Bfs::new(&graph, root); + while let Some(node) = bfs.next(&graph) { + let parent = graph.edges_directed(node, Direction::Incoming).next(); + let node: Rc = graph[node].clone(); + + let row = match parent { + Some(parent) => format!("|#{}|{}|#{}|\n", node.number(), node.title(), graph[parent.source().clone()].number()), + None => format!("|#{}|{}|**N/A**|\n", node.number(), node.title()), + }; + + out.push_str(&process(row)); + } + } + + out +}