261 lines
9.7 KiB
Rust
261 lines
9.7 KiB
Rust
mod structs;
|
|
|
|
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
|
|
use quick_xml::{Reader, Writer};
|
|
use reqwest::{ClientBuilder, Url};
|
|
use std::borrow::Cow;
|
|
use std::env;
|
|
use std::io::Cursor;
|
|
use std::iter::Skip;
|
|
use std::time::Duration;
|
|
extern crate serde_json;
|
|
|
|
pub async fn run(mut args: Skip<env::Args>) {
|
|
let repo = args.next().expect("Missing repo");
|
|
let issue_number = args.next().expect("Missing issue number");
|
|
let query = match args.next().expect("Missing type").as_str() {
|
|
"issue" => structs::ISSUE_QUERY,
|
|
"mr" => structs::MR_QUERY,
|
|
_ => panic!("Unknown type"),
|
|
};
|
|
let client = ClientBuilder::new()
|
|
.timeout(Duration::from_secs(60))
|
|
.build()
|
|
.unwrap();
|
|
let text = client
|
|
.post("https://gitlab.com/api/graphql")
|
|
.body(
|
|
serde_json::json!({"query": query, "variables": {"repo": &repo, "id": &issue_number}})
|
|
.to_string(),
|
|
)
|
|
.header("Content-Type", "application/json")
|
|
.send()
|
|
.await
|
|
.unwrap()
|
|
.text()
|
|
.await
|
|
.unwrap();
|
|
let issue: structs::Data = serde_json::from_str(&text).unwrap();
|
|
let issue = match issue.data.project {
|
|
structs::IssueOrMR::Issue(i) => i,
|
|
structs::IssueOrMR::MergeRequest(i) => i,
|
|
};
|
|
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
{
|
|
let mut elem = BytesStart::owned(b"rss".to_vec(), 3);
|
|
elem.push_attribute(("version", "2.0"));
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"channel".to_vec(), 7);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"title".to_vec(), 5);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&issue.title).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"title".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"link".to_vec(), 4);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&issue.web_url).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"link".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"description".to_vec(), 11);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&format!("Comments and events from {}", &issue.title))
|
|
.into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"description".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"item".to_vec(), 4);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"title".to_vec(), 5);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&issue.title).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"title".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"link".to_vec(), 4);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&issue.web_url).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"link".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let mut elem = BytesStart::owned(b"guid".to_vec(), 4);
|
|
elem.push_attribute(("isPermaLink", "false"));
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&issue.id).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"guid".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"author".to_vec(), 6);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&issue.author.username).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"author".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"pubDate".to_vec(), 7);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&issue.created_at.to_rfc2822()).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"pubDate".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
if !issue.description_html.is_empty() {
|
|
let elem = BytesStart::owned(b"description".to_vec(), 11);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&fix_description(
|
|
&issue.description_html,
|
|
&issue.web_url,
|
|
))
|
|
.into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"description".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
}
|
|
|
|
let elem = BytesEnd::owned(b"item".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
}
|
|
|
|
for note in issue.notes.nodes {
|
|
let mut title = note.body.splitn(2, '\n').next().unwrap().to_string();
|
|
if title.len() > 80 {
|
|
title = format!("{}...", title.split_at(80).0);
|
|
}
|
|
let pub_date = note.created_at.to_rfc2822();
|
|
|
|
let elem = BytesStart::owned(b"item".to_vec(), 4);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"title".to_vec(), 5);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&title).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"title".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"link".to_vec(), 4);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(¬e.url).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"link".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let mut elem = BytesStart::owned(b"guid".to_vec(), 4);
|
|
elem.push_attribute(("isPermaLink", "false"));
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(¬e.id).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"guid".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"author".to_vec(), 6);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(¬e.author.username).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"author".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"pubDate".to_vec(), 7);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem = BytesText::from_plain_str(&pub_date).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"pubDate".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesStart::owned(b"description".to_vec(), 11);
|
|
writer.write_event(Event::Start(elem)).unwrap();
|
|
|
|
let elem =
|
|
BytesText::from_plain_str(&fix_description(¬e.body_html, ¬e.url)).into_owned();
|
|
writer.write_event(Event::Text(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"description".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"item".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
}
|
|
|
|
let elem = BytesEnd::owned(b"channel".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
let elem = BytesEnd::owned(b"rss".to_vec());
|
|
writer.write_event(Event::End(elem)).unwrap();
|
|
|
|
println!(
|
|
"{}",
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
);
|
|
}
|
|
|
|
fn fix_description(text: &str, html_url: &str) -> String {
|
|
let mut reader = Reader::from_str(text);
|
|
reader.check_end_names(false);
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
let mut buf = Vec::new();
|
|
loop {
|
|
match reader.read_event(&mut buf) {
|
|
Ok(Event::Start(ref e)) if e.name() == b"a" => {
|
|
let mut elem = BytesStart::owned(b"a".to_vec(), 1);
|
|
for mut attribute in e.attributes().filter_map(|i| i.ok()) {
|
|
if attribute.key == b"href" {
|
|
if let Ok(unescaped) = attribute.unescape_and_decode_value(&reader) {
|
|
if let Ok(url) = Url::parse(html_url).unwrap().join(&unescaped) {
|
|
attribute.value = Cow::Owned(url.as_str().as_bytes().to_vec());
|
|
}
|
|
}
|
|
}
|
|
elem.push_attribute((attribute.key, &attribute.value[..]));
|
|
}
|
|
writer.write_event(Event::Start(elem))
|
|
}
|
|
Ok(Event::Eof) => break,
|
|
Ok(e) => writer.write_event(e),
|
|
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
|
|
}
|
|
.unwrap();
|
|
buf.clear();
|
|
}
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|