use rand::thread_rng;
use rand::seq::SliceRandom;
use clap::clap_app;
use separator::Separatable;
use factorial::Factorial;
use num::bigint::BigUint;
macro_rules! xpg {
($words: expr) => { xpg($words) };
() => { xpg(4) }
}
#[cfg(test)]
mod tests {
use super::*;
use regex::Regex;
#[test]
fn default_xpg_macro_returns_four_words() {
let re = Regex::new(r"^([A-Z][a-z]+){4}$").unwrap();
let result = xpg!();
assert!(re.is_match(&result));
}
#[test]
fn xpg_macro_can_return_three_words() {
let re = Regex::new(r"^([A-Z][a-z]+){3}$").unwrap();
let result = xpg!(3);
assert!(re.is_match(&result));
}
#[test]
fn xpg_macro_can_return_four_words() {
let re = Regex::new(r"^([A-Z][a-z]+){4}$").unwrap();
let result = xpg!(4);
assert!(re.is_match(&result));
}
#[test]
fn xpg_macro_can_return_five_words() {
let re = Regex::new(r"^([A-Z][a-z]+){5}$").unwrap();
let result = xpg!(5);
assert!(re.is_match(&result));
}
#[test]
#[should_panic]
fn xpg_macro_cannot_return_zero_words() {
let _result = xpg!(0);
}
#[test]
fn xpg_macro_can_return_one_word() {
let re = Regex::new(r"^([A-Z][a-z]+)$").unwrap();
let result = xpg!(1);
assert!(re.is_match(&result));
}
#[test]
fn xpg_can_return_three_words() {
let re = Regex::new(r"^([A-Z][a-z]+){3}$").unwrap();
let result = xpg(3);
assert!(re.is_match(&result));
}
#[test]
fn xpg_can_return_four_words() {
let re = Regex::new(r"^([A-Z][a-z]+){4}$").unwrap();
let result = xpg(4);
assert!(re.is_match(&result));
}
#[test]
fn xpg_can_return_five_words() {
let re = Regex::new(r"^([A-Z][a-z]+){5}$").unwrap();
let result = xpg(5);
assert!(re.is_match(&result));
}
#[test]
#[should_panic]
fn xpg_cannot_return_zero_words() {
let _result = xpg(0);
}
#[test]
fn xpg_can_return_one_word() {
let re = Regex::new(r"^([A-Z][a-z]+)$").unwrap();
let result = xpg(1);
assert!(re.is_match(&result));
}
}
const WORDLIST: [&'static str; 1259] = [
"Africa",
"Alabama",
"Alaska",
"America",
"Amsterdam",
"April",
"Arizona",
"Asia",
"Athens",
"August",
"Australia",
"Austria",
"Barbados",
"Belfast",
"Belgium",
"Berlin",
"Botswana",
"Brazil",
"Britain",
"British",
"Bulgaria",
"California",
"Canada",
"Chile",
"China",
"Colombia",
"Congo",
"Copenhagen",
"Cuba",
"Damascus",
"December",
"Delaware",
"Denmark",
"Dublin",
"Earth",
"Egypt",
"England",
"English",
"Europe",
"February",
"Fiji",
"Finland",
"Florida",
"France",
"French",
"Friday",
"Germany",
"Gibraltar",
"Greece",
"Greek",
"Havana",
"Hawaii",
"Holland",
"Iceland",
"India",
"Indian",
"Ireland",
"Italy",
"Jamaica",
"Japan",
"Japanese",
"Jerusalem",
"Jordan",
"July",
"June",
"Jupiter",
"Kentucky",
"Kenya",
"Korea",
"Lisbon",
"London",
"Madrid",
"Malta",
"March",
"Mark",
"Mars",
"Maryland",
"Mercury",
"Mexico",
"Monday",
"Montana",
"Moon",
"Moscow",
"Nepal",
"Neptune",
"Netherlands",
"Nevada",
"Norway",
"November",
"October",
"Ohio",
"Oslo",
"Panama",
"Paris",
"Peru",
"Pluto",
"Poland",
"Portugal",
"Rome",
"Russia",
"Saturday",
"Saturn",
"Scotland",
"September",
"Singapore",
"Spain",
"Sunday",
"Sweden",
"Texas",
"Tokyo",
"Tuesday",
"Uranus",
"Venus",
"Vermont",
"Virginia",
"Wales",
"Warsaw",
"Washington",
"Wednesday",
"Able",
"About",
"Above",
"Across",
"Action",
"Actually",
"Addition",
"Adjective",
"Advance",
"Afraid",
"After",
"Again",
"Against",
"Agree",
"Agreed",
"Ahead",
"Airplane",
"Allow",
"Almost",
"Alone",
"Along",
"Already",
"Also",
"Although",
"Always",
"Among",
"Amount",
"Anger",
"Angle",
"Angry",
"Animal",
"Another",
"Answer",
"Anything",
"Appear",
"Apple",
"Area",
"Arms",
"Army",
"Around",
"Arrive",
"Arrived",
"Article",
"Attempt",
"Aunt",
"Away",
"Baby",
"Back",
"Ball",
"Bank",
"Banker",
"Base",
"Basket",
"Battle",
"Bean",
"Bear",
"Beat",
"Beautiful",
"Beauty",
"Became",
"Because",
"Become",
"Been",
"Before",
"Began",
"Begin",
"Behind",
"Being",
"Believe",
"Bell",
"Belong",
"Below",
"Beside",
"Best",
"Better",
"Between",
"Beyond",
"Bicycle",
"Bill",
"Bird",
"Birds",
"Black",
"Block",
"Blood",
"Blow",
"Blue",
"Board",
"Boat",
"Body",
"Bone",
"Bones",
"Book",
"Born",
"Borrow",
"Both",
"Bottle",
"Bottom",
"Branch",
"Branches",
"Bread",
"Break",
"Bridge",
"Bright",
"Bring",
"Broad",
"Broke",
"Broken",
"Brother",
"Brought",
"Brown",
"Build",
"Building",
"Built",
"Burn",
"Burning",
"Business",
"Busy",
"Butter",
"Cake",
"Call",
"Came",
"Cannot",
"Capital",
"Captain",
"Care",
"Carefully",
"Carry",
"Case",
"Catch",
"Cattle",
"Caught",
"Cause",
"Cells",
"Cent",
"Center",
"Cents",
"Century",
"Certain",
"Chair",
"Chance",
"Change",
"Character",
"Charge",
"Chart",
"Check",
"Chief",
"Child",
"Childhood",
"Children",
"Choose",
"Church",
"Cigarette",
"Circle",
"City",
"Class",
"Clean",
"Clear",
"Climbed",
"Clock",
"Close",
"Cloth",
"Clothes",
"Cloud",
"Coast",
"Coat",
"Cold",
"College",
"Color",
"Colour",
"Column",
"Come",
"Common",
"Company",
"Compare",
"Complete",
"Compound",
"Condition",
"Conditions",
"Consider",
"Considerable",
"Consonant",
"Contain",
"Continue",
"Continued",
"Control",
"Cook",
"Cool",
"Copy",
"Corn",
"Corner",
"Correct",
"Cost",
"Cotton",
"Could",
"Count",
"Country",
"Course",
"Cover",
"Covered",
"Cows",
"Create",
"Cried",
"Crops",
"Cross",
"Crowd",
"Current",
"Daily",
"Dance",
"Dare",
"Dark",
"Date",
"Daughter",
"Dead",
"Deal",
"Dear",
"Death",
"Decide",
"Decided",
"Decimal",
"Deep",
"Degree",
"Delight",
"Demand",
"Describe",
"Desert",
"Design",
"Desire",
"Destroy",
"Details",
"Determine",
"Developed",
"Device",
"Dictionary",
"Died",
"Difference",
"Different",
"Difficult",
"Dinner",
"Direct",
"Direction",
"Discover",
"Discovered",
"Dish",
"Distance",
"Distant",
"Divide",
"Divided",
"Division",
"Doctor",
"Does",
"Dollar",
"Dollars",
"Done",
"Door",
"Double",
"Doubt",
"Down",
"Draw",
"Drawing",
"Dream",
"Dress",
"Dried",
"Drink",
"Drive",
"Drop",
"Duck",
"During",
"Dusk",
"Duty",
"Each",
"Early",
"Ears",
"Earth",
"East",
"Easy",
"Edge",
"Effect",
"Effort",
"Eggs",
"Eight",
"Either",
"Electric",
"Electricity",
"Elements",
"Else",
"Enemy",
"Energy",
"Engine",
"Enjoy",
"Enough",
"Enter",
"Entered",
"Entire",
"Equal",
"Equation",
"Escape",
"Especially",
"Etching",
"Even",
"Evening",
"Ever",
"Every",
"Everyone",
"Everything",
"Exactly",
"Example",
"Except",
"Exciting",
"Exercise",
"Expect",
"Experience",
"Experiment",
"Explain",
"Express",
"Face",
"Fact",
"Factories",
"Factors",
"Fail",
"Fair",
"Fall",
"Family",
"Famous",
"Fancy",
"Farm",
"Farmers",
"Fast",
"Father",
"Favor",
"Fear",
"Feed",
"Feel",
"Feeling",
"Feet",
"Fell",
"Fellow",
"Felt",
"Fence",
"Field",
"Fifteen",
"Fifth",
"Fifty",
"Fight",
"Figure",
"Fill",
"Filled",
"Finally",
"Find",
"Fine",
"Finger",
"Fingers",
"Finish",
"Finished",
"Fire",
"Firm",
"First",
"Fish",
"Five",
"Flat",
"Flier",
"Floor",
"Flow",
"Flower",
"Flowers",
"Follow",
"Food",
"Fool",
"Foot",
"Force",
"Foreign",
"Forest",
"Forever",
"Forget",
"Form",
"Fortieth",
"Forty",
"Forward",
"Found",
"Four",
"Fraction",
"Free",
"Fresh",
"Friend",
"Friends",
"From",
"Front",
"Fruit",
"Full",
"Further",
"Future",
"Gain",
"Galaxy",
"Game",
"Garden",
"Gate",
"Gather",
"Gave",
"General",
"Gentle",
"Gentleman",
"Gift",
"Girl",
"Give",
"Gives",
"Glad",
"Glass",
"Glossary",
"Goes",
"Gold",
"Gone",
"Good",
"Goodbye",
"Govern",
"Government",
"Grain",
"Grass",
"Grave",
"Gray",
"Great",
"Green",
"Grew",
"Ground",
"Group",
"Grow",
"Grown",
"Guard",
"Guess",
"Guide",
"Hair",
"Half",
"Hall",
"Halt",
"Hand",
"Hang",
"Happen",
"Happened",
"Happy",
"Hard",
"Have",
"Head",
"Health",
"Hear",
"Heard",
"Heart",
"Heat",
"Heaven",
"Heavy",
"Height",
"Held",
"Hello",
"Help",
"Here",
"Hers",
"High",
"Hill",
"Himself",
"History",
"Hold",
"Hole",
"Home",
"Honor",
"Hope",
"Horse",
"Hour",
"Hours",
"House",
"However",
"Huge",
"Human",
"Hundred",
"Hunger",
"Hunt",
"Hunting",
"Hurry",
"Hurt",
"Husband",
"Idea",
"Important",
"Inch",
"Inches",
"Include",
"Increase",
"Indeed",
"Indicate",
"Industry",
"Information",
"Insects",
"Inside",
"Instead",
"Instruments",
"Interest",
"Into",
"Iron",
"Island",
"Itself",
"Join",
"Joined",
"Journey",
"Judge",
"Jump",
"Jumped",
"Just",
"Keep",
"Kept",
"Kill",
"Killed",
"Kind",
"King",
"Kiss",
"Kitchen",
"Knew",
"Know",
"Known",
"Labor",
"Ladder",
"Lady",
"Lake",
"Land",
"Language",
"Large",
"Last",
"Late",
"Later",
"Laugh",
"Laughed",
"Laughter",
"Lead",
"Leader",
"Learn",
"Least",
"Leave",
"Left",
"Legs",
"Lend",
"Length",
"Less",
"Letter",
"Level",
"Liar",
"Life",
"Lift",
"Lifted",
"Light",
"Like",
"Likely",
"Line",
"List",
"Listen",
"Little",
"Live",
"Located",
"Lone",
"Long",
"Look",
"Lord",
"Lose",
"Loss",
"Lost",
"Loud",
"Love",
"Lower",
"Machine",
"Made",
"Mail",
"Main",
"Major",
"Make",
"Manner",
"Many",
"March",
"Mark",
"Market",
"Marry",
"Master",
"Match",
"Material",
"Matter",
"Maybe",
"Mayor",
"Mean",
"Measure",
"Meat",
"Meet",
"Meeting",
"Melody",
"Member",
"Members",
"Metal",
"Method",
"Middle",
"Might",
"Mile",
"Milk",
"Million",
"Mind",
"Mine",
"Minute",
"Minutes",
"Miss",
"Mister",
"Modern",
"Molecules",
"Moment",
"Money",
"Month",
"Months",
"Moon",
"More",
"Morning",
"Most",
"Mother",
"Mountain",
"Mouth",
"Move",
"Movement",
"Much",
"Music",
"Must",
"Nail",
"Name",
"Nation",
"Natural",
"Nature",
"Near",
"Nearly",
"Necessary",
"Neck",
"Need",
"Needle",
"Neighbor",
"Neither",
"Nerve",
"Never",
"News",
"Next",
"Nice",
"Niece",
"Night",
"Nine",
"Noise",
"None",
"Noon",
"North",
"Northern",
"Nose",
"Note",
"Nothing",
"Notice",
"Noun",
"Number",
"Numeral",
"Object",
"Observe",
"Ocean",
"Offer",
"Office",
"Often",
"Once",
"Only",
"Open",
"Opinion",
"Opposite",
"Order",
"Orderly",
"Other",
"Ought",
"Outer",
"Outside",
"Over",
"Oxygen",
"Page",
"Paid",
"Pain",
"Paint",
"Pair",
"Paper",
"Paragraph",
"Park",
"Part",
"Partial",
"Particular",
"Party",
"Pass",
"Passed",
"Past",
"Pattern",
"Peace",
"People",
"Perfect",
"Perhaps",
"Period",
"Person",
"Phrase",
"Pick",
"Picked",
"Picture",
"Piece",
"Place",
"Plain",
"Plains",
"Plan",
"Plane",
"Planet",
"Plant",
"Plants",
"Play",
"Pleasant",
"Please",
"Pleasure",
"Plural",
"Poem",
"Point",
"Pole",
"Poor",
"Position",
"Possible",
"Pounds",
"Power",
"Practice",
"Prepare",
"Prepared",
"Present",
"President",
"Presidents",
"Press",
"Pretty",
"Price",
"Printed",
"Probable",
"Probably",
"Problem",
"Process",
"Produce",
"Products",
"Promise",
"Property",
"Proud",
"Prove",
"Provide",
"Public",
"Pull",
"Pulled",
"Pure",
"Push",
"Pushed",
"Quarter",
"Queen",
"Question",
"Questions",
"Quick",
"Quickly",
"Quiet",
"Quite",
"Race",
"Radio",
"Rain",
"Raise",
"Raised",
"Rather",
"Reach",
"Reached",
"Read",
"Ready",
"Real",
"Realize",
"Really",
"Reason",
"Receive",
"Received",
"Record",
"Region",
"Remain",
"Remember",
"Repeated",
"Reply",
"Report",
"Represent",
"Require",
"Resent",
"Rest",
"Result",
"Return",
"Rhythm",
"Rich",
"Ridden",
"Ride",
"Right",
"Ring",
"Rise",
"River",
"Road",
"Rock",
"Roll",
"Rolled",
"Room",
"Root",
"Rope",
"Rose",
"Round",
"Rule",
"Rush",
"Safe",
"Safety",
"Said",
"Sail",
"Salt",
"Same",
"Sand",
"Save",
"Says",
"Scale",
"Scene",
"School",
"Science",
"Scientists",
"Score",
"Season",
"Seat",
"Second",
"Section",
"Seed",
"Seeds",
"Seem",
"Seen",
"Self",
"Sell",
"Send",
"Sense",
"Sent",
"Sentence",
"Separate",
"Serve",
"Service",
"Settle",
"Settled",
"Seven",
"Several",
"Shade",
"Shake",
"Shall",
"Shape",
"Share",
"Sharp",
"Shine",
"Ship",
"Shirt",
"Shoe",
"Shoes",
"Shop",
"Shore",
"Short",
"Shot",
"Should",
"Shoulder",
"Shout",
"Shouted",
"Show",
"Shown",
"Sick",
"Side",
"Sight",
"Sign",
"Signal",
"Silent",
"Silver",
"Similar",
"Simple",
"Since",
"Sing",
"Single",
"Sister",
"Size",
"Skin",
"Sleep",
"Slept",
"Slow",
"Slowly",
"Small",
"Smell",
"Smiled",
"Smoke",
"Snow",
"Soft",
"Soil",
"Sold",
"Soldier",
"Soldiers",
"Solution",
"Some",
"Someone",
"Something",
"Sometimes",
"Song",
"Soon",
"Sorry",
"Sort",
"Sound",
"South",
"Southern",
"Space",
"Speak",
"Special",
"Speed",
"Spell",
"Spend",
"Spent",
"Spoke",
"Spot",
"Spread",
"Spring",
"Square",
"Stand",
"Star",
"Stars",
"Start",
"State",
"Statement",
"Station",
"Stay",
"Steel",
"Step",
"Stick",
"Still",
"Stock",
"Stone",
"Stood",
"Stop",
"Store",
"Storm",
"Story",
"Straight",
"Strange",
"Stranger",
"Stream",
"Street",
"Strength",
"Stretched",
"Strike",
"String",
"Strong",
"Student",
"Students",
"Study",
"Subject",
"Substances",
"Succeed",
"Success",
"Such",
"Sudden",
"Suddenly",
"Suffer",
"Suffix",
"Sugar",
"Suggested",
"Suit",
"Summer",
"Supply",
"Suppose",
"Sure",
"Surface",
"Surprise",
"Sweet",
"Swim",
"Syllables",
"Symbols",
"System",
"Table",
"Tail",
"Take",
"Taken",
"Talk",
"Tall",
"Taste",
"Teach",
"Teacher",
"Team",
"Tear",
"Tell",
"Temperature",
"Terms",
"Test",
"Than",
"Thank",
"That",
"Their",
"Them",
"Themselves",
"Then",
"There",
"Therefore",
"These",
"They",
"Thick",
"Thin",
"Thing",
"Think",
"Third",
"Thirteen",
"This",
"Those",
"Though",
"Thought",
"Thousand",
"Thousands",
"Three",
"Threw",
"Through",
"Throw",
"Thrown",
"Thus",
"Tied",
"Till",
"Time",
"Tiny",
"Today",
"Together",
"Told",
"Tomorrow",
"Tone",
"Took",
"Tools",
"Tore",
"Total",
"Touch",
"Toward",
"Town",
"Track",
"Trade",
"Train",
"Training",
"Travel",
"Tree",
"Triangle",
"Tried",
"Tries",
"Trip",
"Trouble",
"Truck",
"True",
"Trust",
"Tube",
"Turn",
"Twelve",
"Twenty",
"Type",
"Uncle",
"Under",
"Underline",
"Understand",
"Understood",
"Unit",
"Until",
"Upon",
"Usual",
"Usually",
"Valley",
"Value",
"Various",
"Verb",
"Very",
"View",
"Village",
"Visit",
"Voice",
"Vowel",
"Wagon",
"Wait",
"Walk",
"Wall",
"Want",
"Wants",
"Warm",
"Wash",
"Watch",
"Water",
"Wave",
"Waves",
"Weak",
"Wear",
"Weather",
"Wedge",
"Week",
"Weight",
"Welcome",
"Well",
"Went",
"Were",
"West",
"Western",
"What",
"Wheat",
"Wheel",
"Wheels",
"When",
"Where",
"Whether",
"Which",
"While",
"White",
"Whole",
"Whom",
"Whose",
"Wide",
"Wife",
"Wild",
"Will",
"Wind",
"Window",
"Wing",
"Wings",
"Winter",
"Wire",
"Wise",
"Wish",
"With",
"Within",
"Without",
"Woman",
"Women",
"Wonder",
"Wood",
"Word",
"Wore",
"Work",
"Workers",
"World",
"Worn",
"Worth",
"Would",
"Write",
"Written",
"Wrong",
"Wrote",
"Yard",
"Year",
"Yellow",
"Yesterday",
"Young",
"Your",
"Yourself",
];
pub fn xpg(words: usize) -> String {
if words < 1 {
panic!();
}
WORDLIST.choose_multiple(&mut thread_rng(), words)
.cloned().collect::<Vec<&str>>().join("")
}
fn permutations(n: u128, k: u128) -> u128 {
let b = BigUint::from(n).factorial();
let c = BigUint::from(n - k).factorial();
return format!("{}", b / c).parse::<u128>().unwrap();
}
pub fn cli() {
let a = clap_app!(xpg =>
(version: "0.1.2")
(about: "xkcd-style password generator")
(@arg analyze: --analyze "Analyze")
(@arg words: -w --words +takes_value
"Number of words in password; default: 4")
(@arg count: -c --count +takes_value
"Number of passwords; default: 1")
).get_matches();
let words = a.value_of("words").unwrap_or("4").parse::<usize>().unwrap();
let count = a.value_of("count").unwrap_or("1").parse::<usize>().unwrap();
let analyze = a.is_present("analyze");
if analyze {
let n = WORDLIST.len();
let p = permutations(n as u128, words as u128).separated_string();
let w = words.separated_string();
let d = (n - words).separated_string();
let n_ = n.separated_string();
println!("* Word list length: {}", n_);
println!("* Words in password: {}", w);
println!("* Total permutations (without repetition): {}\n", p);
println!(" ```");
println!(" {}! / ({} - {})!", n_, n_, w);
println!(" {}! / {}!", n_, d);
println!(" {}", p);
println!(" ```\n");
println!("Words | Permutations");
println!("---|---:");
for k in 1..=8 {
let p_ = permutations(n as u128, k as u128).separated_string();
println!("{} | {}", k, p_);
}
println!("... | ...\n");
} else {
for _ in 0..count {
let p = xpg!(words);
println!("{}", p);
}
}
}