-
Notifications
You must be signed in to change notification settings - Fork 0
/
build.rs
139 lines (116 loc) · 3.85 KB
/
build.rs
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use color_eyre::Result;
use std::{env, fs, path::Path};
use hex::FromHex;
use project_euler_data::find_problem;
fn main() -> Result<()> {
color_eyre::install()?;
let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
// Look for available solutions
let sol_dir = Path::new(&manifest_dir).join("src").join("solution");
let mut problems = vec![];
for solution in sol_dir.read_dir()?.into_iter().filter_map(Result::ok) {
let Some(problem) = parse_number(solution.file_name()) else { continue };
let Ok(file_type) = solution.file_type() else {continue};
if !file_type.is_file() {
continue;
}
problems.push(problem);
}
let problems = problems;
// Create the solution module
fs::write(
Path::new(&manifest_dir).join("src").join("solution.rs"),
gen_sol_module(&problems)?,
)?;
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/solution");
Ok(())
}
/// Parse the problem number out of the solution file name
fn parse_number(file: impl AsRef<Path>) -> Option<u32> {
let file: &Path = file.as_ref();
let name = file.file_stem()?.to_str()?;
let extension = file.extension()?;
if extension != "rs" {
return None;
}
if !name.starts_with("sol_") {
return None;
}
Some(name.strip_prefix("sol_")?.parse().expect("solution number"))
}
/// Generates a solution glue module
fn gen_sol_module(problems: &[u32]) -> Result<String> {
// Import Solutions as Modules and add the expected hash
let modules: String = problems
.iter()
.map(|problem| {
let hash = find_problem(*problem)
.and_then(|problem| problem.hash)
.map(|hash| <[u8; 16]>::from_hex(hash).expect("valid md5 hash string"));
format!(
"/// Expected Hash of the Answer to Problem {problem}
static EXPECTED_ANSWER_HASH_{problem}: Option<[u8; 16]> = {hash:?};
mod sol_{problem};
"
)
})
.collect();
// Generate calls to the solution based on the problem id
let match_arms = problems
.iter()
.map(|problem| format!(" {problem} => check_solution(sol_{problem}::solution(), EXPECTED_ANSWER_HASH_{problem}),\n"))
.collect::<String>();
// Generate solution module
Ok(format!(
"// Auto Generated Module compiling all solutions
use thiserror::Error;
use std::fmt::Display;
/// Error while solving a Project Euler Problem
#[allow(dead_code)]
#[derive(Debug, Error)]
pub enum SolveError {{
#[error(\"Database is missing the hash, result: {{result}}, hash: {{hash:?}}\")]
NoHash {{ result: String, hash: [u8; 16] }},
#[error(\"{{0}} did not match the hashed result\")]
WrongSolution(String),
#[error(\"There is no available solution for Problem {{0}}\")]
NoSolution(u32),
}}
{modules}
#[allow(dead_code)]
fn check_solution(result: impl Display, expected_hash: Option<[u8; 16]>) -> color_eyre::Result<String> {{
use md5::{{Md5, Digest}};
use SolveError::{{NoHash, WrongSolution}};
let result = format!(\"{{}}\", result);
let hash: [u8; 16] = {{
let mut res = [0; 16];
res.copy_from_slice(&Md5::new_with_prefix(&result).finalize()[..]);
res
}};
if let Some(expected_hash) = expected_hash {{
if expected_hash == hash {{
Ok(result)
}} else {{
Err(WrongSolution(result).into())
}}
}} else {{
Err(NoHash{{ result, hash }}.into())
}}
}}
/// Available Solutions
///
/// Autogenerated
pub static AVAILABLE_SOLUTIONS: &[u32] = &{problems:?};
/// Runs the solution to a particular problem
///
/// Autogenerated
pub fn solve(problem: u32) -> color_eyre::Result<String> {{
match problem {{
{match_arms}
id => Err(SolveError::NoSolution(id).into()),
}}
}}
"
))
}