diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 191 | ||||
| -rw-r--r-- | Cargo.toml | 12 | ||||
| -rw-r--r-- | LICENSE-MIT | 21 | ||||
| -rw-r--r-- | README.md | 56 | ||||
| -rw-r--r-- | build.rs | 46 | ||||
| -rw-r--r-- | src/lib.rs | 32 | ||||
| -rw-r--r-- | src/main.rs | 41 |
8 files changed, 400 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d3c194f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,191 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand-word-gen" +version = "1.0.0" +dependencies = [ + "anyhow", + "clap", + "rand", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..49ad710 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rand-word-gen" +version = "1.0.0" +authors = ["nsfisis"] +edition = "2018" +description = "Generates random pseudo-English words." +license = "MIT" + +[dependencies] +anyhow = "1.0.34" +clap = "2.33.3" +rand = "0.7.3" diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..bcfcb41 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 nsfisis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1cbb290 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Random word generator + +Generates random pseudo-English words. It is mainly for naming your hobby projects in a moment. + +It internally uses "Markov chain" to generate words. The chain model is built in a compiling phase of the tool, referring to `words` file (`/usr/share/dict/words`). + +## Usage + +``` +$ rand-word-gen +eendo +ddips +uphr +blinta +unto +ont +vedaro +dera +ekarrb +goice +sfodo +ainis +rier +fatem +myimd +grasic +honge +ustoge +ear +nal + +# Sets a number of generated words. +$ rand-word-gen -n 5 +piva +ors +glo +mapt +blclyp +``` + +See `rand-word-gen --help` for details. + +## Build + +``` +$ cargo build --release +``` + +## TODO + +* Pass a custom words file at runtime. +* Specify length of generated words. + +## License + +MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..dcf1cc3 --- /dev/null +++ b/build.rs @@ -0,0 +1,46 @@ +use std::fs::File; +use std::io::{BufRead, BufReader, BufWriter, Result, Write}; +use std::path::Path; + +const NUM_LETTERS: usize = 26; + +fn main() -> Result<()> { + let chars = { + let file = File::open("/usr/share/dict/words")?; + let reader = BufReader::new(file); + parse(reader)? + }; + + let out_path = Path::new(&std::env::var("OUT_DIR").unwrap()).join("prebuilt_model.rs"); + let file = File::create(out_path)?; + let mut writer = BufWriter::new(file); + writeln!(writer, "[")?; + for i in 0..(NUM_LETTERS + 1) { + write!(writer, "[")?; + for j in 0..(NUM_LETTERS + 1) { + write!(writer, "{},", chars[i][j])?; + } + writeln!(writer, "],")?; + } + writeln!(writer, "]")?; + + Ok(()) +} + +fn parse(r: BufReader<File>) -> Result<[[usize; NUM_LETTERS + 1]; NUM_LETTERS + 1]> { + let mut chars = [[0; NUM_LETTERS + 1]; NUM_LETTERS + 1]; + for line in r.lines() { + let word = line?; + let mut prefix = NUM_LETTERS; + for c in word.bytes() { + let c = c.to_ascii_lowercase(); + if !c.is_ascii_lowercase() { + continue; + } + let i = (c - b'a') as usize; + chars[prefix][i] += 1; + prefix = i; + } + } + Ok(chars) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..db8020e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,32 @@ +use rand::distributions::{Distribution, WeightedIndex}; +use rand::RngCore; + +const NUM_OF_LETTERS: usize = 26; + +pub struct Model { + chars: [[usize; NUM_OF_LETTERS + 1]; NUM_OF_LETTERS + 1], +} + +impl Model { + pub fn new() -> Model { + let chars = include!(concat!(env!("OUT_DIR"), "/prebuilt_model.rs")); + Model { chars } + } + + pub fn generate<Rng: RngCore>(&self, rng: &mut Rng, len: usize) -> String { + let mut result = String::with_capacity(len); + let mut prefix = NUM_OF_LETTERS; + for _ in 0..len { + let chars = &self.chars[prefix]; + let c = select_one_char(rng, &chars); + result.push(c as char); + prefix = (c - b'a') as usize; + } + result + } +} + +fn select_one_char<Rng: RngCore>(rng: &mut Rng, freq: &[usize; NUM_OF_LETTERS + 1]) -> u8 { + let dist = WeightedIndex::new(freq).expect("invalid frequency"); + b'a' + (dist.sample(rng) as u8) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4ebe0fa --- /dev/null +++ b/src/main.rs @@ -0,0 +1,41 @@ +use anyhow::{Context, Result}; +use rand::distributions::{Distribution, Uniform}; +use rand_word_gen::Model; +use std::io::{BufWriter, Write}; + +fn main() -> Result<()> { + let words = parse_args()?; + + let model = Model::new(); + let out = std::io::stdout(); + let mut out = BufWriter::new(out.lock()); + let mut rng = rand::thread_rng(); + let dist = Uniform::from(3..=6); + for _ in 0..words { + let len = dist.sample(&mut rng); + writeln!(out, "{}", model.generate(&mut rng, len))?; + } + + Ok(()) +} + +fn parse_args() -> Result<usize> { + use clap::{crate_description, crate_version, value_t, App, Arg}; + + let matches = App::new("rand-word-gen") + .version(crate_version!()) + .version_short("v") + .about(crate_description!()) + .arg( + Arg::with_name("words") + .short("n") + .long("words") + .value_name("NUMBER_OF_WORDS") + .default_value("20") + .help("Sets number of generated words"), + ) + .get_matches(); + + let words = value_t!(matches, "words", usize).context("'--words' must be a number")?; + Ok(words) +} |
