feat: rewrite tooling

This commit is contained in:
newt 2024-10-09 18:10:12 +01:00
parent 19742c35b0
commit 7945bdd0ce
16 changed files with 754 additions and 915 deletions

2
.gitignore vendored
View file

@ -1,3 +1 @@
/target
/binaries
/old

6
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,6 @@
{
"recommendations": [
"vadimcn.vscode-lldb",
"rust-lang.rust-analyzer"
]
}

26
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'euler'",
"cargo": {
"args": [
"build",
"--bin=euler",
"--package=euler"
],
"filter": {
"name": "euler",
"kind": "bin"
}
},
"args": [
"new",
"30"
],
"cwd": "${workspaceFolder}"
}
]
}

850
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,17 +5,19 @@ edition = "2021"
default-run = "euler"
[dependencies]
clap = { version = "4.0.4", features = ["derive"] }
scraper = "0.14.0"
requestty = "0.4.1"
reqwest = "0.11"
tokio = { version = "1.23", features = ["full"] }
regex = "1"
clap = { version = "4.4.10", features = ["derive"] }
scraper = "0.18.1"
requestty = "0.5.0"
reqwest = "0.11.22"
tokio = { version = "1.34.0", features = ["full"] }
regex = "1.10.2"
html-escape = "0.2.13"
phf = { version = "0.11.1", features = ["macros"] }
rayon = "1.7.0"
phf = { version = "0.11.2", features = ["macros"] }
rayon = "1.8.0"
owo-colors = "3.5.0"
num-bigint = "0.4.3"
num-bigint = "0.4.4"
once_cell = "1.18.0"
num-to-words = "0.1.1"
[profile.release]
opt-level = "z"

14
build
View file

@ -1,14 +0,0 @@
#!/bin/bash
cargo build --release --bin "[0-9]*" -q
cargo build --release --bin "[0-9]*" -q --target x86_64-pc-windows-gnu
find ./target/release -maxdepth 1 -type f -perm 755 | while read file; do
arr=(${file//\// })
mv $file binaries/linux/${arr[3]} > /dev/null
done
find ./target/x86_64-pc-windows-gnu/release -maxdepth 1 -type f -perm 755 | while read file; do
arr=(${file//\// })
mv $file binaries/win64/${arr[4]} > /dev/null
done

View file

@ -16,7 +16,7 @@ All of the solutions here are written in rust. [main.rs](src/main.rs) is the hom
## Challenge Completion
### <!-- completed -->22<!-- completed --> out of 100 public challenges completed.
### 22 out of 100 public challenges completed.
- [x] 1 - [Multiples of 3 or 5](src/bin/1.rs)
- [x] 2 - [Even Fibonacci numbers](src/bin/2.rs)
@ -119,4 +119,4 @@ All of the solutions here are written in rust. [main.rs](src/main.rs) is the hom
- [ ] 99 - Largest exponential
- [ ] 100 - Arranged probability
<sub>Check out Project Euler <a href="https://projecteuler.net">here</a>.</sub>
<sub>Check out Project Euler <a href="https://projecteuler.net">here</a>.</sub>

View file

@ -4,6 +4,7 @@ Problem 16 - Power digit sum
2 15 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26.
What is the sum of the digits of the number 2 1000 ?
*/
use num_bigint::BigUint;
fn get_digits(number: usize) -> Vec<u32> {

View file

@ -5,11 +5,13 @@ Using names.txt (right click and 'Save Link/Target As...'), a 46K text file co
For example, when the list is sorted into alphabetical order, COLIN, which is worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the list. So, COLIN would obtain a score of 938 × 53 = 49714.
What is the total of all the name scores in the file?
*/
use euler::RESOURCES;
use regex::Regex;
use std::fs;
fn read_names() -> Vec<String> {
let mut names = fs::read_to_string(euler::resources_path().join("p022_names.txt"))
let mut names = fs::read_to_string(RESOURCES.join("p022_names.txt"))
.unwrap()
.split(",")
.map(|name| Regex::new("\"").unwrap().replace_all(name, "").to_string())

View file

@ -7,11 +7,12 @@ By converting each letter in a word to a number corresponding to its alphabetica
Using words.txt (right click and 'Save Link/Target As...'), a 16K text file containing nearly two-thousand common English words, how many are triangle words?
*/
use euler::RESOURCES;
use regex::Regex;
use std::fs;
fn read_words() -> Vec<String> {
return fs::read_to_string(euler::resources_path().join("p042_words.txt"))
return fs::read_to_string(RESOURCES.join("p042_words.txt"))
.unwrap()
.split(",")
.map(|name| {

View file

@ -9,10 +9,12 @@ That is, 3 + 7 + 4 + 9 = 23.
Find the maximum total from top to bottom in triangle.txt (right click and 'Save Link/Target As...'), a 15K text file containing a triangle with one-hundred rows.
NOTE: This is a much more difficult version of Problem 18 . It is not possible to try every route to solve this problem, as there are 2 99 altogether! If you could check one trillion (10 12 ) routes every second it would take over twenty billion years to check them all. There is an efficient algorithm to solve it. ;o)
*/
use euler::RESOURCES;
use std::fs;
fn read_triangle() -> Vec<Vec<usize>> {
return fs::read_to_string(euler::resources_path().join("p067_triangle.txt"))
return fs::read_to_string(RESOURCES.join("p067_triangle.txt"))
.unwrap()
.split("\n")
.map(|name| {

View file

@ -1,176 +1,107 @@
use euler::{Problem, Result, BASE_DIR, README, SOLUTIONS};
use num_to_words::integer_to_en_us as num_to_words;
use owo_colors::OwoColorize;
use regex::Regex;
use scraper::{Html, Selector};
use std::fs;
use std::io::BufReader;
use std::{
fs::{File, OpenOptions},
io::{BufRead, Write},
};
#[tokio::main]
pub async fn execute(problem: Option<u8>) -> Result<(), Box<dyn std::error::Error>> {
let code_path = euler::code_path();
let problem_number = euler::problem_number(
problem,
&code_path,
"Which problem would you like to solve?",
);
// Fetch the problem information
let body = reqwest::get(format!("https://projecteuler.net/problem={problem_number}"))
.await?
.text()
.await?;
let document = Html::parse_document(body.as_str());
let title_selector = Selector::parse("h2")?;
let content_selector = Selector::parse(".problem_content")?;
let html_tag_regex = Regex::new(r"<[^>]*>")?;
let mut title = document
.select(&title_selector)
.next()
.unwrap()
.text()
.collect::<Vec<&str>>()
.join("");
let mut problem = html_tag_regex
.replace_all(
document
.select(&content_selector)
.next()
.unwrap()
.inner_html()
.as_str(),
" ",
)
.to_string()
.replace("$$", " ");
let file_body = format!(
"/*
Problem {} - {}
{}
*/
pub fn main() {{
println!(\"Hello World!\");
}}",
problem_number,
html_escape::decode_html_entities(&mut title).to_string(),
html_escape::decode_html_entities(&mut problem)
.split("\n")
.map(|s| s.trim())
.filter(|s| s != &"")
.collect::<Vec<&str>>()
.join("\n")
);
// Create the file
let mut file = File::create(code_path.join(format!("{problem_number}.rs")))?;
file.write(file_body.as_bytes())?;
pub async fn execute(number: Option<u8>) -> Result<()> {
let problem = Problem::new(number).await?;
// create a file for the problem
let mut file = File::create(SOLUTIONS.join(format!("{}.rs", problem.number)))?;
file.write(problem.file_body().as_bytes())?;
drop(file);
// Read the contents of the readme for editing
let readme_path = euler::readme_path();
let mut readme_file = OpenOptions::new().read(true).open(&readme_path)?;
// open readme
let mut readme_file = OpenOptions::new().read(true).open(README.clone())?;
let mut readme_content = BufReader::new(&readme_file)
.lines()
.map(|s| s.unwrap())
.collect::<Vec<String>>();
let readme_content = {
// read
let mut content = BufReader::new(&readme_file)
.lines()
.map(|s| s.unwrap())
.collect::<Vec<String>>()
.join("\n");
drop(readme_file);
// mark problem as done on readme
content = content.replace(
&format!("\n- [ ] {}", problem.number),
&format!("\n- [x] {}", problem.number),
);
// update completed count
let completion_regex = Regex::new(r"(\d+) out of")?;
let problem_count = fs::read_dir(SOLUTIONS.clone())?.count();
content = completion_regex
.replace(&content, format!("{} out of", problem_count))
.to_string();
content
};
// write the new content to the readme
readme_file = OpenOptions::new().write(true).open(README.clone())?;
readme_file.write(readme_content.as_bytes())?;
drop(readme_file);
// Mark the problem as done on the readme
let readme_regex = Regex::new(format!(" {problem_number} - (.*)").as_str())?;
// add the problem to the run command
let mut run_file = OpenOptions::new()
.read(true)
.open(BASE_DIR.join("src").join("commands").join("run.rs"))?;
for i in 0..readme_content.len() {
let line = readme_content[i].as_str();
let run_content = {
let mut content: String = BufReader::new(&run_file)
.lines()
.map(|s| s.unwrap())
.collect::<Vec<String>>()
.join("\n");
drop(run_file);
if readme_regex.is_match(line) {
let matched = readme_regex.captures(line).unwrap();
readme_content[i] = format!(
"- [x] {problem_number} - [{}](src/bin/{problem_number}.rs)",
&matched[1].trim()
);
}
}
// add new mod
let mod_regex = Regex::new(r#"#\[path = ".+"\]\nmod [A-Za-z_]+;"#)?;
let final_mod = mod_regex.find_iter(&content).last().unwrap();
let word_number = num_to_words(problem.number as i64)?.replace(" ", "_");
// Update the summary statistics on the readme
let mut readme_string = readme_content.join("\n");
content.insert_str(
final_mod.end(),
&format!(
r#"
#[path = "../bin/{}.rs"]
mod {};"#,
problem.number, word_number
),
);
let completed_regex = Regex::new("<!-- completed -->([0-9]+)<!-- completed -->")?;
// add to match
let match_regex = Regex::new(r"\d+ => .+\(\),")?;
let final_branch = match_regex.find_iter(&content).last().unwrap();
let new_completed =
completed_regex.captures(readme_string.as_str()).unwrap()[1].parse::<u8>()? + 1;
content.insert_str(
final_branch.end(),
&format!(
r#"
{} => {}::main(),"#,
problem.number, word_number
),
);
readme_string = completed_regex
.replace(
readme_string.as_str(),
format!("<!-- completed -->{new_completed}<!-- completed -->"),
)
.to_string();
// Write the new content to the readme
readme_file = OpenOptions::new().write(true).open(&readme_path)?;
readme_file.write(readme_string.as_bytes())?;
drop(readme_file);
// Modify the run command
let run_path = euler::run_path();
let mut run_file = OpenOptions::new().read(true).open(&run_path)?;
let mut run_content = BufReader::new(&run_file)
.lines()
.map(|s| s.unwrap())
.collect::<Vec<String>>();
drop(run_file);
// Import the problem's file
let import_regex = Regex::new(r#"// #\[path = "\.\./bin/{x}\.rs"]"#.replace("{x}", problem_number.to_string().as_str()).as_str())?;
for i in 0..run_content.len() {
let line = run_content[i].as_str();
if import_regex.is_match(line) {
run_content[i] = line.replace("// ", "");
run_content[i + 1] = run_content[i + 1].replace("// ", "");
break;
}
}
// Add the problem to the match statement
let match_regex = Regex::new(
r"// {x} =>"
.replace("{x}", problem_number.to_string().as_str())
.as_str(),
)?;
for i in 0..run_content.len() {
let line = run_content[i].as_str();
if match_regex.is_match(line) {
run_content[i] = line.replace("// ", "");
break;
}
}
content
};
// Write the new content to the run file
run_file = OpenOptions::new()
.write(true)
.truncate(true)
.open(&run_path)?;
run_file.write(run_content.join("\n").as_bytes())?;
.open(BASE_DIR.join("src").join("commands").join("run.rs"))?;
run_file.write(run_content.as_bytes())?;
drop(run_file);
// Announce completion!

View file

@ -1,4 +1,6 @@
use euler::Problem;
use owo_colors::OwoColorize;
use std::time::Instant;
#[path = "../bin/8.rs"]
mod eight;
@ -10,6 +12,8 @@ mod eleven;
mod fifteen;
#[path = "../bin/5.rs"]
mod five;
#[path = "../bin/42.rs"]
mod forty_two;
#[path = "../bin/4.rs"]
mod four;
#[path = "../bin/14.rs"]
@ -26,6 +30,8 @@ mod seventeen;
mod six;
#[path = "../bin/16.rs"]
mod sixteen;
#[path = "../bin/67.rs"]
mod sixty_seven;
#[path = "../bin/10.rs"]
mod ten;
#[path = "../bin/13.rs"]
@ -34,186 +40,29 @@ mod thirteen;
mod three;
#[path = "../bin/12.rs"]
mod twelve;
#[path = "../bin/2.rs"]
mod two;
// #[path = "../bin/19.rs"]
// mod nineteen;
// #[path = "../bin/20.rs"]
// mod twenty;
// #[path = "../bin/21.rs"]
// mod twenty_one;
#[path = "../bin/22.rs"]
mod twenty_two;
// #[path = "../bin/23.rs"]
// mod twenty_three;
// #[path = "../bin/24.rs"]
// mod twenty_four;
// #[path = "../bin/25.rs"]
// mod twenty_five;
// #[path = "../bin/26.rs"]
// mod twenty_six;
#[path = "../bin/27.rs"]
mod twenty_seven;
// #[path = "../bin/28.rs"]
// mod twenty_eight;
// #[path = "../bin/29.rs"]
// mod twenty_nine;
// #[path = "../bin/30.rs"]
// mod thirty;
// #[path = "../bin/31.rs"]
// mod thirty_one;
// #[path = "../bin/32.rs"]
// mod thirty_two;
// #[path = "../bin/33.rs"]
// mod thirty_three;
// #[path = "../bin/34.rs"]
// mod thirty_four;
// #[path = "../bin/35.rs"]
// mod thirty_five;
// #[path = "../bin/36.rs"]
// mod thirty_six;
// #[path = "../bin/37.rs"]
// mod thirty_seven;
// #[path = "../bin/38.rs"]
// mod thirty_eight;
// #[path = "../bin/39.rs"]
// mod thirty_nine;
// #[path = "../bin/40.rs"]
// mod forty;
// #[path = "../bin/41.rs"]
// mod forty_one;
#[path = "../bin/42.rs"]
mod forty_two;
// #[path = "../bin/43.rs"]
// mod forty_three;
// #[path = "../bin/44.rs"]
// mod forty_four;
// #[path = "../bin/45.rs"]
// mod forty_five;
// #[path = "../bin/46.rs"]
// mod forty_six;
// #[path = "../bin/47.rs"]
// mod forty_seven;
// #[path = "../bin/48.rs"]
// mod forty_eight;
// #[path = "../bin/49.rs"]
// mod forty_nine;
// #[path = "../bin/50.rs"]
// mod fifty;
// #[path = "../bin/51.rs"]
// mod fifty_one;
// #[path = "../bin/52.rs"]
// mod fifty_two;
// #[path = "../bin/53.rs"]
// mod fifty_three;
// #[path = "../bin/54.rs"]
// mod fifty_four;
// #[path = "../bin/55.rs"]
// mod fifty_five;
// #[path = "../bin/56.rs"]
// mod fifty_six;
// #[path = "../bin/57.rs"]
// mod fifty_seven;
// #[path = "../bin/58.rs"]
// mod fifty_eight;
// #[path = "../bin/59.rs"]
// mod fifty_nine;
// #[path = "../bin/60.rs"]
// mod sixty;
// #[path = "../bin/61.rs"]
// mod sixty_one;
// #[path = "../bin/62.rs"]
// mod sixty_two;
// #[path = "../bin/63.rs"]
// mod sixty_three;
// #[path = "../bin/64.rs"]
// mod sixty_four;
// #[path = "../bin/65.rs"]
// mod sixty_five;
// #[path = "../bin/66.rs"]
// mod sixty_six;
#[path = "../bin/67.rs"]
mod sixty_seven;
// #[path = "../bin/68.rs"]
// mod sixty_eight;
// #[path = "../bin/69.rs"]
// mod sixty_nine;
// #[path = "../bin/70.rs"]
// mod seventy;
// #[path = "../bin/71.rs"]
// mod seventy_one;
// #[path = "../bin/72.rs"]
// mod seventy_two;
// #[path = "../bin/73.rs"]
// mod seventy_three;
// #[path = "../bin/74.rs"]
// mod seventy_four;
// #[path = "../bin/75.rs"]
// mod seventy_five;
// #[path = "../bin/76.rs"]
// mod seventy_six;
// #[path = "../bin/77.rs"]
// mod seventy_seven;
// #[path = "../bin/78.rs"]
// mod seventy_eight;
// #[path = "../bin/79.rs"]
// mod seventy_nine;
// #[path = "../bin/80.rs"]
// mod eighty;
// #[path = "../bin/81.rs"]
// mod eighty_one;
// #[path = "../bin/82.rs"]
// mod eighty_two;
// #[path = "../bin/83.rs"]
// mod eighty_three;
// #[path = "../bin/84.rs"]
// mod eighty_four;
// #[path = "../bin/85.rs"]
// mod eighty_five;
// #[path = "../bin/86.rs"]
// mod eighty_six;
// #[path = "../bin/87.rs"]
// mod eighty_seven;
// #[path = "../bin/88.rs"]
// mod eighty_eight;
// #[path = "../bin/89.rs"]
// mod eighty_nine;
// #[path = "../bin/90.rs"]
// mod ninety;
// #[path = "../bin/91.rs"]
// mod ninety_one;
// #[path = "../bin/92.rs"]
// mod ninety_two;
// #[path = "../bin/93.rs"]
// mod ninety_three;
// #[path = "../bin/94.rs"]
// mod ninety_four;
// #[path = "../bin/95.rs"]
// mod ninety_five;
// #[path = "../bin/96.rs"]
// mod ninety_six;
// #[path = "../bin/97.rs"]
// mod ninety_seven;
// #[path = "../bin/98.rs"]
// mod ninety_eight;
// #[path = "../bin/99.rs"]
// mod ninety_nine;
// #[path = "../bin/100.rs"]
// mod one_hundred;
#[path = "../bin/22.rs"]
mod twenty_two;
#[path = "../bin/2.rs"]
mod two;
#[tokio::main]
pub async fn execute(
problem: Option<u8>,
benchmark: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let code_path = euler::code_path();
let problem_number =
euler::problem_number(problem, &code_path, "Which problem would you like to run?");
let problem = problem
.unwrap_or_else(|| Problem::prompt_number("Please select a problem:", true).unwrap());
let mut exists = true; // assume it exists
let start = std::time::Instant::now();
let mut exists = true;
let start = if benchmark {
Some(Instant::now())
} else {
None
};
match problem_number {
// execute the solution
match problem {
1 => one::main(),
2 => two::main(),
3 => three::main(),
@ -232,105 +81,28 @@ pub async fn execute(
16 => sixteen::main(),
17 => seventeen::main(),
18 => eighteen::main(),
// 19 => nineteen::main(),
// 20 => twenty::main(),
// 21 => twenty_one::main(),
22 => twenty_two::main(),
// 23 => twenty_three::main(),
// 24 => twenty_four::main(),
// 25 => twenty_five::main(),
// 26 => twenty_six::main(),
27 => twenty_seven::main(),
// 28 => twenty_eight::main(),
// 29 => twenty_nine::main(),
// 30 => thirty::main(),
// 31 => thirty_one::main(),
// 32 => thirty_two::main(),
// 33 => thirty_three::main(),
// 34 => thirty_four::main(),
// 35 => thirty_five::main(),
// 36 => thirty_six::main(),
// 37 => thirty_seven::main(),
// 38 => thirty_eight::main(),
// 39 => thirty_nine::main(),
// 40 => forty::main(),
// 41 => forty_one::main(),
// 42 => forty_two::main(),
// 43 => forty_three::main(),
// 44 => forty_four::main(),
// 45 => forty_five::main(),
// 46 => forty_six::main(),
// 47 => forty_seven::main(),
// 48 => forty_eight::main(),
// 49 => forty_nine::main(),
// 50 => fifty::main(),
// 51 => fifty_one::main(),
// 52 => fifty_two::main(),
// 53 => fifty_three::main(),
// 54 => fifty_four::main(),
// 55 => fifty_five::main(),
// 56 => fifty_six::main(),
// 57 => fifty_seven::main(),
// 58 => fifty_eight::main(),
// 59 => fifty_nine::main(),
// 60 => sixty::main(),
// 61 => sixty_one::main(),
// 62 => sixty_two::main(),
// 63 => sixty_three::main(),
// 64 => sixty_four::main(),
// 65 => sixty_five::main(),
// 66 => sixty_six::main(),
67 => sixty_seven::main(),
// 68 => sixty_eight::main(),
// 69 => sixty_nine::main(),
// 70 => seventy::main(),
// 71 => seventy_one::main(),
// 72 => seventy_two::main(),
// 73 => seventy_three::main(),
// 74 => seventy_four::main(),
// 75 => seventy_five::main(),
// 76 => seventy_six::main(),
// 77 => seventy_seven::main(),
// 78 => seventy_eight::main(),
// 79 => seventy_nine::main(),
// 80 => eighty::main(),
// 81 => eighty_one::main(),
// 82 => eighty_two::main(),
// 83 => eighty_three::main(),
// 84 => eighty_four::main(),
// 85 => eighty_five::main(),
// 86 => eighty_six::main(),
// 87 => eighty_seven::main(),
// 88 => eighty_eight::main(),
// 89 => eighty_nine::main(),
// 90 => ninety::main(),
// 91 => ninety_one::main(),
// 92 => ninety_two::main(),
// 93 => ninety_three::main(),
// 94 => ninety_four::main(),
// 95 => ninety_five::main(),
// 96 => ninety_six::main(),
// 97 => ninety_seven::main(),
// 98 => ninety_eight::main(),
// 99 => ninety_nine::main(),
// 100 => one_hundred::main(),
_ => {
exists = false;
println!(
"{} {} {}",
"Problem".red().bold(),
problem_number.red().bold(),
"is not in this repository!".red().bold()
"{}",
format!("Problem #{problem} is yet to be solved!")
.red()
.bold()
)
}
}
let duration = start.elapsed();
// if the script exists, print the time elapsed
if benchmark && exists {
let duration = start.unwrap().elapsed();
println!(
"Time elapsed when executing problem {} is: {:?}",
problem_number, duration
"
Time elapsed when executing problem {} is: {:?}",
problem, duration
);
}

View file

@ -1,65 +1,16 @@
use std::path::{Path, PathBuf};
mod problem;
fn fetch_dir() -> &'static Path {
return Path::new(env!("CARGO_MANIFEST_DIR"));
}
use once_cell::sync::Lazy;
pub use problem::Problem;
use std::path::PathBuf;
pub fn code_path() -> PathBuf {
return fetch_dir().join("src").join("bin");
}
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub fn resources_path() -> PathBuf {
return fetch_dir().join("resources");
}
// paths
pub const BASE_DIR: Lazy<PathBuf> = Lazy::new(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")));
pub const SOLUTIONS: Lazy<PathBuf> = Lazy::new(|| BASE_DIR.join("src").join("bin"));
pub const RESOURCES: Lazy<PathBuf> = Lazy::new(|| BASE_DIR.join("resources"));
pub const README: Lazy<PathBuf> = Lazy::new(|| BASE_DIR.join("readme.md"));
pub fn readme_path() -> PathBuf {
return fetch_dir().join("readme.md");
}
pub fn run_path() -> PathBuf {
return fetch_dir().join("src").join("commands").join("run.rs");
}
pub fn problem_number(problem: Option<u8>, code_path: &PathBuf, prompt: &str) -> u8 {
if let Some(n) = problem {
return n;
} else {
return requestty::prompt_one(
requestty::Question::int("problemNumber")
.message(prompt)
.validate(|n, _| {
// All numbers must be positive
let mut pass = false;
// Ensure that the problem has not already got a file associated with it
let files = std::fs::read_dir(code_path).unwrap();
for file in files {
let file_number = file
.unwrap()
.file_name()
.to_str()
.unwrap()
.split('.')
.collect::<Vec<&str>>()[0]
.parse::<i64>()
.unwrap();
if n == file_number {
pass = true;
}
}
if pass {
Ok(())
} else {
Err("Please ensure that your input is valid!".to_owned())
}
})
.build(),
)
.unwrap()
.as_int()
.unwrap() as u8;
}
}
// panic channel
pub static mut SILENT_PANIC: bool = false;

View file

@ -1,8 +1,12 @@
#![allow(dead_code)]
use clap::{Args, Parser, Subcommand};
mod commands;
use std::panic;
use clap::{Args, Parser, Subcommand};
use euler::{Result, SILENT_PANIC};
#[derive(Parser)]
#[clap(about, author, version)]
struct Value {
@ -32,11 +36,22 @@ enum Commands {
Run(RunArgs),
}
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
#[tokio::main]
pub async fn main() -> Result<()> {
panic::set_hook(Box::new(move |panic_info| {
if unsafe { SILENT_PANIC } {
std::process::exit(0);
} else {
println!("{}", panic_info.to_string());
}
}));
let value = Value::parse();
match value.command {
Commands::New(NewArgs { problem }) => commands::new::execute(problem),
Commands::Run(RunArgs { problem, benchmark }) => commands::run::execute(problem, benchmark),
Commands::New(NewArgs { problem }) => commands::new::execute(problem).await,
Commands::Run(RunArgs { problem, benchmark }) => {
commands::run::execute(problem, benchmark).await
}
}
}

126
src/problem.rs Normal file
View file

@ -0,0 +1,126 @@
use crate::{Result, SILENT_PANIC, SOLUTIONS};
use regex::Regex;
use scraper::{Html, Selector};
pub struct Problem {
pub number: u8,
title: String,
content: String,
}
impl Problem {
pub fn prompt_number(prompt: &str, want_solved: bool) -> Result<u8> {
unsafe {
SILENT_PANIC = true;
}
let number = requestty::prompt_one(
requestty::Question::int("problemNumber")
.message(prompt)
.validate(|n, _| {
// ensure the number is in bound
if n <= 0 {
Err("Please enter a number greater than 0".to_string())
} else if n > 100 {
Err("Please enter a number less than or equal to 100".to_string())
}
// ensure that the problem has not already got a file associated with it
else {
let has_file = std::fs::read_dir(SOLUTIONS.clone()).unwrap().any(|x| {
x.unwrap()
.file_name()
.to_str()
.unwrap()
.split(".")
.next()
.unwrap()
.parse::<i64>()
.unwrap()
== n
});
if !want_solved && has_file {
Err("This problem has already been solved".to_string())
} else if want_solved && !has_file {
Err("This problem has not been solved yet".to_string())
} else {
Ok(())
}
}
})
.build(),
)?
.as_int()
.unwrap() as u8;
unsafe {
SILENT_PANIC = false;
}
Ok(number)
}
pub async fn new(number: Option<u8>) -> Result<Self> {
let number = number
.unwrap_or_else(|| Self::prompt_number("Please select a problem:", false).unwrap());
let body = reqwest::get(format!("https://projecteuler.net/problem={number}"))
.await?
.text()
.await?;
let document = Html::parse_document(body.as_str());
let title_selector = Selector::parse("h2")?;
let content_selector = Selector::parse(".problem_content")?;
let html_tag_regex = Regex::new(r"<[^>]*>")?;
let title = document
.select(&title_selector)
.next()
.unwrap()
.text()
.collect::<Vec<&str>>()
.join("");
let problem = html_tag_regex
.replace_all(
document
.select(&content_selector)
.next()
.unwrap()
.inner_html()
.as_str(),
" ",
)
.to_string()
.replace("$$", " ");
Ok(Self {
number,
title: title.trim().to_string(),
content: problem.trim().to_string(),
})
}
pub fn file_body(&self) -> String {
format!(
"/*
Problem {} - {}
{}
*/
pub fn main() {{
println!(\"Hello World!\");
}}",
self.number,
html_escape::decode_html_entities(&self.title).to_string(),
html_escape::decode_html_entities(&self.content)
.split("\n")
.map(|s| s.trim())
.filter(|s| s != &"")
.collect::<Vec<&str>>()
.join("\n")
)
}
}