use std::fs::read_to_string;
use std::path::Path;
use thiserror::Error;
use rand::{distributions::Uniform, prelude::*};
use crate::entropy::Entropy;
use crate::prelude::*;
pub const BUILTIN_EFF_LARGE: &str = include_str!("../res/eff/large.txt");
pub const BUILTIN_EFF_SHORT: &str = include_str!("../res/eff/short.txt");
pub const BUILTIN_EFF_GENERAL_SHORT: &str = include_str!("../res/eff/general_short.txt");
#[derive(Clone, Debug)]
pub struct WordList {
words: Vec<String>,
}
impl WordList {
pub fn new(words: Vec<String>) -> Self {
if words.is_empty() {
panic!("cannot construct wordlist, given list of words is empty");
}
WordList { words }
}
pub fn load<P>(path: P) -> Result<Self, WordListError>
where
P: AsRef<Path>,
{
let words: Vec<String> = read_to_string(path)?
.split_terminator(char::is_whitespace)
.filter(|w| !w.is_empty())
.map(|w| w.to_owned())
.collect();
if words.is_empty() {
return Err(WordListError::Empty);
}
Ok(Self::new(words))
}
pub fn load_diced<P>(path: P) -> Result<Self, WordListError>
where
P: AsRef<Path>,
{
let words: Vec<String> = read_to_string(path)?
.lines()
.filter(|w| !w.is_empty())
.filter_map(|w| w.rsplit_terminator(char::is_whitespace).next())
.map(|w| w.to_owned())
.collect();
if words.is_empty() {
return Err(WordListError::Empty);
}
Ok(Self::new(words))
}
pub fn builtin_eff_large() -> Self {
Self::new(
BUILTIN_EFF_LARGE
.lines()
.map(|w| w.to_owned())
.collect::<Vec<String>>(),
)
}
pub fn builtin_eff_short() -> Self {
Self::new(
BUILTIN_EFF_SHORT
.lines()
.map(|w| w.to_owned())
.collect::<Vec<String>>(),
)
}
pub fn builtin_eff_general_short() -> Self {
Self::new(
BUILTIN_EFF_GENERAL_SHORT
.lines()
.map(|w| w.to_owned())
.collect::<Vec<String>>(),
)
}
pub fn sampler(&self) -> WordSampler {
WordSampler::new(self.words.clone())
}
}
impl Default for WordList {
fn default() -> WordList {
WordList::builtin_eff_large()
}
}
#[derive(Error, Debug)]
pub enum WordListError {
#[error("failed to load wordlist from file")]
Load(#[from] std::io::Error),
#[error("loaded wordlist did not contain words")]
Empty,
}
#[derive(Clone, Debug)]
pub struct WordSampler {
words: Vec<String>,
distribution: Uniform<usize>,
}
impl WordSampler {
pub fn new(words: Vec<String>) -> WordSampler {
WordSampler {
distribution: Uniform::new(0, words.len()),
words,
}
}
fn word_ref(&self) -> &str {
&self.words[rand::thread_rng().sample(self.distribution)]
}
}
impl WordProvider for WordSampler {
fn word(&self) -> String {
self.word_ref().to_owned()
}
}
impl HasEntropy for WordSampler {
fn entropy(&self) -> Entropy {
Entropy::from_real(self.words.len() as f64)
}
}
impl IntoIterator for WordSampler {
type Item = String;
type IntoIter = WordSamplerIter;
fn into_iter(self) -> Self::IntoIter {
WordSamplerIter { sampler: self }
}
}
pub struct WordSamplerIter {
sampler: WordSampler,
}
impl Iterator for WordSamplerIter {
type Item = String;
fn next(&mut self) -> Option<String> {
Some(self.sampler.word())
}
}