1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use darling::FromMeta;
use itertools::Itertools;
use proc_macro::TokenStream;
use proc_macro_error::{emit_error, emit_warning, proc_macro_error};
use syn::{parse_macro_input, spanned::Spanned, AttributeArgs};
const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
#[proc_macro_error]
#[proc_macro_attribute]
pub fn track(args: TokenStream, input: TokenStream) -> TokenStream {
let attr_args = parse_macro_input!(args as AttributeArgs);
let issue = match Issue::from_list(&attr_args) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(e.write_errors());
}
};
match issue.is_closed() {
Err(e) => {
emit_error!(
attr_args[0].span(),
"unable to access {}\n {}",
issue.url,
e
)
}
Ok(true) => {
if std::env::var("ISSUE_HARD_FAIL").is_ok() {
emit_error!(attr_args[0].span(), "issue {} has been closed", issue.url)
} else {
emit_warning!(attr_args[0].span(), "issue {} has been closed", issue.url)
}
}
_ => (),
}
input
}
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
struct ApiIssue {
pub closed_at: Option<String>,
}
#[derive(Default, FromMeta)]
#[darling(default)]
struct Issue {
pub url: String,
}
impl Issue {
fn canonicalize_url(&self) -> url::Url {
let url = url::Url::parse(&self.url).unwrap();
if url.host_str().unwrap().contains("github") {
let path: String = Itertools::intersperse(url.path_segments().unwrap(), "/").collect();
return url::Url::parse(&format!("https://api.github.com/repos/{}", path)).unwrap();
}
unreachable!("only github public repositories are currently supported")
}
pub fn is_closed(&self) -> Result<bool, anyhow::Error> {
let client = reqwest::blocking::ClientBuilder::new()
.user_agent(APP_USER_AGENT)
.build()
.unwrap();
let response = client.get(self.canonicalize_url()).send()?;
if !response.status().is_success() {
anyhow::bail!(
"failed to fetch issue: {}",
response
.text()
.unwrap_or_else(|e| format!("no response found: {}", e))
)
}
let issue: ApiIssue = response.json()?;
Ok(issue.closed_at.is_some())
}
}