1use std::{env, fs, io, path::Path, path::PathBuf};
2
3use cfg_if::cfg_if;
4use proc_macro2::{Delimiter, Spacing, TokenStream, TokenTree};
5
6macro_rules! err {
7 ($format:literal $(, $($tt:tt)*)?) => {{
8 |err| ::std::io::Error::new(::std::io::ErrorKind::Other,
9 format!(concat!($format, ": {}"), $($($tt)*, )? err))
10 }}
11}
12
13mod gen;
14mod idents;
15mod schema;
16
17pub fn main() -> Result<(), Box<dyn std::error::Error>> {
18 #[cfg(feature = "dev")]
19 {
20 pretty_env_logger::init();
21 }
22
23 #[derive(serde::Deserialize)]
24 struct ReleaseData {
25 tag_name: String,
26 assets: Vec<Asset>,
27 }
28 #[derive(serde::Deserialize)]
29 struct Asset {
30 name: String,
31 browser_download_url: String,
32 }
33
34 let out_dir = PathBuf::from(&env::var("OUT_DIR").expect("defined by cargo"));
35
36 let pkg_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("api.github.com.json");
37 let json_path = if pkg_path.exists() {
38 pkg_path
39 } else {
40 cfg_if! {
41 if #[cfg(feature = "online")] {
42 let client = reqwest::blocking::Client::new();
43 let data = client.get("https://api.github.com/repos/octokit/routes/releases/latest")
44 .send().map_err(err!("Error fetching latest routes"))?
45 .error_for_status().map_err(err!("Error fetching latest routes"))?
46 .json::<ReleaseData>().map_err(err!("Error parsing routes release data"))?;
47
48 let json_path = out_dir.join(&format!("{}.json", &data.tag_name));
49 if !json_path.exists() {
50 log::info!("Downloading new routes version {}", &data.tag_name);
51 let url = match data.assets.iter().filter(|asset| &asset.name == "api.github.com.json")
52 .map(|asset| &asset.browser_download_url)
53 .next() {
54 Some(url) => url,
55 None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Latest octokit/routes release does not contain api.github.com.json"))),
56 };
57 let mut file = fs::File::create(url).map_err(Box::new)?;
58 let _ = client.get(url) .send().map_err(Box::new)?
60 .error_for_status().map_err(Box::new)?
61 .copy_to(&mut file).map_err(Box::new)?;
62 }
63 json_path
64 } else {
65 return Err(Box::new(io::Error::new(io::ErrorKind::Other, "`online` feature not enabled, but no packaged api.github.com.json is available")));
66 }
67 }
68 };
69
70 let out = out_dir.join("out.rs");
71 let types = out_dir.join("types.rs");
72 let mut out = fs::File::create(out)?;
73 let mut types = fs::File::create(types)?;
74
75 let (ts_out, ts_types) = run(&json_path)?;
76
77 write_token_stream(ts_out, &mut out, 0, &mut true)?;
78 write_token_stream(ts_types, &mut types, 0, &mut true)?;
79 Ok(())
80}
81
82fn run(json_path: &Path) -> io::Result<(TokenStream, TokenStream)> {
83 let index = schema::parse(json_path)?;
84 let (apis, types) = gen::gen(index);
85
86 Ok((apis, types))
87}
88
89fn write_token_stream(
90 ts: TokenStream,
91 f: &mut fs::File,
92 indent: usize,
93 start_of_line: &mut bool,
94) -> io::Result<()> {
95 use std::io::Write;
96
97 for token in ts {
98 if *start_of_line {
99 write!(f, "{}", " ".repeat(indent))?;
100 *start_of_line = false;
101 }
102 match token {
103 TokenTree::Literal(literal) => write!(f, "{}", literal)?,
104 TokenTree::Ident(ident) => write!(f, "{} ", ident)?,
105 TokenTree::Punct(punct) => {
106 match punct.spacing() {
107 Spacing::Alone => write!(f, "{} ", punct.as_char())?,
108 Spacing::Joint => write!(f, "{}", punct.as_char())?,
109 }
110 if punct.as_char() == ';' {
111 writeln!(f)?;
112 *start_of_line = true;
113 }
114 }
115 TokenTree::Group(group) => match group.delimiter() {
116 Delimiter::Parenthesis => {
117 write!(f, "(")?;
118 write_token_stream(group.stream(), f, indent + 2, start_of_line)?;
119 write!(f, ")")?;
120 }
121 Delimiter::Bracket => {
122 write!(f, "[")?;
123 write_token_stream(group.stream(), f, indent + 2, start_of_line)?;
124 write!(f, "]")?;
125 }
126 Delimiter::Brace => {
127 writeln!(f, "{{")?;
128 *start_of_line = true;
129 write_token_stream(group.stream(), f, indent + 1, start_of_line)?;
130 writeln!(f)?;
131 write!(
132 f,
133 "{}}}",
134 if *start_of_line {
135 " ".repeat(indent)
136 } else {
137 String::new()
138 }
139 )?;
140 if *start_of_line {
141 writeln!(f)?;
142 }
143 }
144 Delimiter::None => write_token_stream(group.stream(), f, indent, start_of_line)?,
145 },
146 }
147 }
148 Ok(())
149}