1use quote::quote;
4use std::{
5 fs::File,
6 io::prelude::*,
7 path::{Path, PathBuf},
8};
9
10pub mod template;
11
12pub fn aoc_auto() {
23 let years: Vec<String> = Path::new("src/")
25 .read_dir()
26 .unwrap()
27 .map(|e| e.unwrap())
28 .filter(|e| {
29 e.path().is_dir() &&
30 e.file_name().to_str().unwrap().starts_with("y") &&
31 e.file_name().to_str().unwrap().chars().skip(1).all(|c| c.is_digit(10))
33 })
34 .map(|e| e.file_name().to_str().unwrap().to_owned())
35 .collect();
36 for year in &years {
38 let days: Vec<String> = Path::new("src/")
39 .join(year.clone())
40 .read_dir()
41 .unwrap()
42 .map(|e| e.unwrap())
43 .filter(|e| {
44 let filename = e.file_name().into_string().unwrap();
45 e.path().is_file() && filename.starts_with("d") && filename.ends_with(".rs") &&
46 filename.replace(".rs", "").chars().skip(1).all(|c| c.is_digit(10))
48 })
49 .map(|e| e.file_name().to_str().unwrap().to_owned())
50 .collect();
51
52 let days_expr: Vec<syn::Expr> = days
53 .iter()
54 .map(|e| {
55 let d = e.replace(".rs", "");
56 syn::parse_str::<syn::Expr>(&d).unwrap()
57 })
58 .collect();
59 let days_num_expr: Vec<syn::Expr> = days
60 .iter()
61 .map(|e| e.replace("d", "").replace(".rs", ""))
62 .map(|e| syn::parse_str::<syn::Expr>(&e).unwrap())
63 .collect();
64 let mod_code = quote! {
65 #(pub mod #days_expr;)*
67
68 pub fn select_function(day: u32, part: u32) -> Result<fn(String) -> String, String> {
70 match day {
71 #(#days_num_expr =>
72 match part {
73 1 => Ok(#days_expr::part1),
74 2 => Ok(#days_expr::part2),
75 _ => Err("Invalid part!".into()),
76 }
77 ),*
78 _ => Err("Invalid day!".into()),
79 }
80 }
81 };
82
83 let mut mod_file_path = Path::new("src/").join(year).join("mod.rs");
84 write_and_format(mod_code.to_string(), &mut mod_file_path);
85 }
86
87 let years_expr: Vec<syn::Expr> = years
88 .iter()
89 .map(|e| syn::parse_str::<syn::Expr>(&e).unwrap())
90 .collect();
91 let auto_import_file = Path::new("src/auto_import.rs").to_owned();
92 let years_mod: Vec<String> = years.iter().map(|e| format!("{}/mod.rs", e)).collect();
93 let years_num_expr: Vec<syn::Expr> = years
94 .iter()
95 .map(|e| e.replace("y", ""))
96 .map(|e| syn::parse_str::<syn::Expr>(&e).unwrap())
97 .collect();
98
99 let auto_import_code = quote! {
100 #(
102 #[path = #years_mod]
103 pub mod #years_expr;
104 )*
105 pub fn select_function(year: u32, day: u32, part: u32) -> Result<fn(String) -> String, String> {
107 match year {
108 #(#years_num_expr => Ok(#years_expr::select_function(day, part)?),)*
109 _ => Err("Invalid year!".into()),
110 }
111 }
112 };
113
114 write_and_format(auto_import_code.to_string(), &auto_import_file)
115}
116
117fn write_and_format(file: String, path: &PathBuf) {
118 let syntax_tree = syn::parse_file(&file).unwrap();
119 let text = prettyplease::unparse(&syntax_tree);
120 let mut file: File = File::create(&path).unwrap();
121 file.write_all(text.as_bytes()).unwrap();
122}
123
124pub fn auto_template() {
128 let years: Vec<String> = Path::new("src/")
130 .read_dir()
131 .unwrap()
132 .map(|e| e.unwrap())
133 .filter(|e| {
134 e.path().is_dir() &&
135 e.file_name().to_str().unwrap().starts_with("y") &&
136 e.file_name().to_str().unwrap().chars().skip(1).all(|c| c.is_digit(10))
138 })
139 .map(|e| e.file_name().to_str().unwrap().to_owned())
140 .collect();
141
142 for year in &years {
144 let days: Vec<PathBuf> = Path::new("src/")
145 .join(year.clone())
146 .read_dir()
147 .unwrap()
148 .map(|e| e.unwrap())
149 .filter(|e| {
150 let filename = e.file_name().into_string().unwrap();
151 e.path().is_file() && filename.starts_with("d") && filename.ends_with(".rs") &&
152 filename.replace(".rs", "").chars().skip(1).all(|c| c.is_digit(10))
154 })
155 .map(|e| e.path())
156 .collect();
157
158 for day_path in days {
160 let metadata = std::fs::metadata(&day_path).unwrap();
162 if metadata.len() == 0 {
163 let day_name = day_path.file_name().unwrap().to_str().unwrap();
165 let day_number = day_name.replace("d", "").replace(".rs", "");
166
167 let year_number = year.replace("y", "");
169 let year_num: u32 = year_number.parse().unwrap_or(0);
170 let day_num: u32 = day_number.parse().unwrap_or(0);
171
172 let title = get_title(year_num, day_num)
174 .unwrap_or_else(|| format!("--- Day {} ---", day_number));
175
176 let template_data = template::TemplateData {
178 title,
179 ..Default::default()
180 };
181
182 let template_content = template::create_template(template_data);
184
185 let mut file = File::create(&day_path).unwrap();
187 file.write_all(template_content.as_bytes()).unwrap();
188
189 println!("Filled empty file: {:?}", day_path);
190 }
191 }
192 }
193}
194
195fn get_title(year: u32, day: u32) -> Option<String> {
196 let url = format!("https://adventofcode.com/{}/day/{}", year, day);
198
199 let body = match reqwest::blocking::get(&url) {
201 Ok(response) => {
202 if !response.status().is_success() {
203 return None;
204 }
205 match response.text() {
206 Ok(text) => text,
207 Err(_) => return None,
208 }
209 }
210 Err(_) => return None,
211 };
212
213 let document = scraper::Html::parse_document(&body);
215
216 let article_selector = match scraper::Selector::parse("article.day-desc") {
218 Ok(selector) => selector,
219 Err(_) => return None,
220 };
221
222 let h2_selector = match scraper::Selector::parse("h2") {
224 Ok(selector) => selector,
225 Err(_) => return None,
226 };
227
228 document.select(&article_selector)
230 .next()
231 .and_then(|article| article.select(&h2_selector).next())
232 .map(|h2| {
233 let title = h2.text().collect::<Vec<_>>().join("");
235 format!(" {}", title)
237 })
238}