Skip to content

Commit

Permalink
use complicated build.rs to better shove words into binary
Browse files Browse the repository at this point in the history
  • Loading branch information
Joshix-1 committed Nov 21, 2023
1 parent 386145e commit c712112
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 117 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description = "Solves hangman puzzles"
license-file = "LICENSE"
include = [
"src/*",
"words/*",
"words/*.txt",
"Cargo.toml",
"Cargo.lock",
"LICENSE",
Expand All @@ -26,7 +26,10 @@ lto = true
[dependencies]
counter = "0.5.7"
directories = "5.0.0"
itertools = "0.10.5"
itertools = { version = "0.10.5", features = [] }
memoise = "0.3.2"
pyo3 = { version = "0.18.3", features = ["extension-module"] }
terminal_size = "0.2.6"

[build-dependencies]
itertools = { version = "0.10.5", features = [] }
61 changes: 61 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::env;
use std::fs;
use std::fs::File;
use std::io;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;

use itertools::Itertools;

fn read_lines_of_file(
path: &Path,
) -> Result<impl Iterator<Item = String>, io::Error> {
Ok(BufReader::new(File::open(path)?)
.lines()
.filter_map(std::result::Result::ok))
}

fn main() {
let out_dir = &env::var("OUT_DIR").unwrap();

let paths = fs::read_dir("./words/").unwrap();

for dir_entry in paths {
let p = dir_entry.unwrap().path();
let path = p.as_path();
println!("cargo:rerun-if-changed={}", path.display());
let dest_path = Path::new(out_dir)
.join(format!(
"{}.rs",
path.file_name().unwrap().to_str().unwrap()
))
.to_owned();
let mut words: Vec<String> =
read_lines_of_file(path).unwrap().collect();
words.sort_unstable();
words.sort_by_key(|word: &String| word.len());

let words: Vec<(usize, Vec<String>)> = words
.into_iter()
.unique()
.into_group_map_by(|word: &String| word.len())
.into_iter()
.sorted_by_key(|(length, _)| 0 + length)
.collect();

let mut output = String::new();
output += "match length {";
for (length, words_group) in words {
output += &*format!("{length} => vec![");

for word in words_group {
output += &*format!("\"{word}\",");
}

output += "],";
}
output += "_ => vec![]}";
fs::write(dest_path.clone(), output).unwrap();
}
}
124 changes: 9 additions & 115 deletions src/solver/solver.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
// SPDX-License-Identifier: EUPL-1.2
pub mod solver;

use std::collections::hash_map::DefaultHasher;
use std::collections::HashSet;
use std::env;
use std::fmt::Formatter;
use std::fs::File;
use std::hash::Hasher;
use std::io::{self, BufRead, BufReader, Write as IoWrite};
use std::io::{self, BufRead, Write as IoWrite};
use std::iter::zip;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::str::Lines;
use std::path::PathBuf;
use std::{char, fs};

use counter::Counter;
use directories::ProjectDirs;
use memoise::memoise;
use terminal_size::{terminal_size, Width};

#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Language {
Expand All @@ -43,21 +37,11 @@ impl Language {
}
}

pub fn read_words(self) -> Lines<'static> {
pub fn read_words(self, length: usize) -> Vec<&'static str> {
match self {
Self::DE => include_str!(r"../words/de.txt"),
Self::EN => include_str!(r"../words/en.txt"),
Self::DE => include!(concat!(env!("OUT_DIR"), "/de.txt.rs")),
Self::EN => include!(concat!(env!("OUT_DIR"), "/en.txt.rs")),
}
.lines()
}

#[must_use]
fn get_words_data_hash(self) -> u64 {
let mut s = DefaultHasher::new();
for word in self.read_words() {
s.write(word.as_bytes());
}
s.finish()
}
}

Expand Down Expand Up @@ -147,47 +131,6 @@ impl std::fmt::Display for HangmanResult {
}
}

fn get_cache_dir() -> Option<PathBuf> {
ProjectDirs::from("org", "asozial", "hangman_solver")
.map(|proj_dirs| proj_dirs.cache_dir().to_path_buf())
}

#[memoise(language)]
fn get_words_cache_folder(language: Language) -> Option<PathBuf> {
let words_cache_dir: PathBuf = get_cache_dir()?.join("words");
let hash: String = format!("{:x}", language.get_words_data_hash());

let lang_words_dir: PathBuf = words_cache_dir.join(language.as_string());
let words_dir: PathBuf = lang_words_dir.join(&*hash);

if lang_words_dir.exists() {
// remove old cache data
for entry in fs::read_dir(&lang_words_dir)
.ok()?
.filter_map(std::result::Result::ok)
{
if entry.path() == words_dir && entry.path().is_dir() {
continue;
}
if fs::remove_dir_all(entry.path()).is_err() {
eprintln!(
"Warning: Deleting old data in {} failed.",
entry.path().to_str().unwrap_or("")
);
}
}
}
if fs::create_dir_all(&words_dir).is_err() {
eprintln!(
"Failed to create {}",
words_dir.to_str().unwrap_or("cache dir")
);
return None;
}

Some(words_dir)
}

#[derive(Debug, Copy, Clone)]
enum WordListError {
NoCacheFolder,
Expand All @@ -213,58 +156,8 @@ impl std::fmt::Display for WordListError {
}
}

#[memoise(language, length)]
fn get_wordlist_file(
language: Language,
length: usize,
) -> Result<PathBuf, WordListError> {
let words_dir =
get_words_cache_folder(language).ok_or(WordListError::NoCacheFolder)?;
let file_name: PathBuf = words_dir.join(format!("{length}.txt"));
if !file_name.exists() {
let mut file = File::create(Path::new(&file_name))?;
for word in language.read_words().filter(|word| word.len() == length) {
file.write_all(word.as_bytes())?;
file.write_all(b"\n")?;
}
}
Ok(file_name)
}

fn read_lines_of_file(
path: &Path,
) -> Result<impl Iterator<Item = String>, io::Error> {
Ok(BufReader::new(File::open(path)?)
.lines()
.filter_map(std::result::Result::ok))
}

fn read_words_without_cache(
language: Language,
length: usize,
) -> impl Iterator<Item = String> {
language
.read_words()
.filter(move |word| word.len() == length)
.map(std::string::ToString::to_string)
}

fn read_words(
language: Language,
length: usize,
) -> Box<dyn Iterator<Item = String>> {
let it = match get_wordlist_file(language, length) {
Ok(file_path) => read_lines_of_file(&file_path).map(Box::new).ok(),
Err(e) => {
eprintln!("Error: {e}");
None
}
};
if let Some(x) = it {
x
} else {
Box::new(read_words_without_cache(language, length))
}
fn read_words(language: Language, length: usize) -> Vec<&'static str> {
language.read_words(length)
}

pub struct Pattern {
Expand Down Expand Up @@ -338,7 +231,8 @@ pub fn solve_hangman_puzzle(
) -> HangmanResult {
let pattern = Pattern::new(pattern_string, invalid_letters);

let possible_words = if pattern.known_letters_count() == 0
let possible_words: Vec<&'static str> = if pattern.known_letters_count()
== 0
&& pattern.invalid_letters.is_empty()
{
read_words(language, pattern.pattern.len()).collect()
Expand Down

0 comments on commit c712112

Please sign in to comment.