1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! A [BullshitGenerator](https://github.com/menzi11/BullshitGenerator) implementation in Rust.  
//! ## Example:  
//! ```
//! let s = BullshitGenerator::new().generate("sth", 1000);
//! ```  
//! ..or with first line indents:  
//! ```
//! let s = BullshitGenerator::with_indent(2).generate("sth", 1000);
//! ```  

use json::JsonValue;
use rand::{seq::IteratorRandom, Rng};

thread_local! {
    #[cfg(feature = "js")]
    static LOCAL_RNG: std::cell::RefCell<rand::prelude::StdRng> = std::cell::RefCell::new(
        <rand::prelude::StdRng as rand::SeedableRng>::seed_from_u64(js_sys::Date::now() as u64)
    );
    #[cfg(not(feature = "js"))]
    static LOCAL_RNG: std::cell::RefCell<rand::prelude::StdRng> = std::cell::RefCell::new(
            <rand::prelude::StdRng as rand::SeedableRng>::from_entropy()
    );
}

pub struct BullshitGenerator {
    data: JsonValue,
    indent: usize,
    text: String,
    total_length: usize,
    paragraph_length: usize,
}
impl BullshitGenerator {
    /// Create a new instance of `BullshitGenerator`.  
    pub fn new() -> Self {
        Self::with_indent(0)
    }
    /// Create a new instance of `BullshitGenerator`, with first line indent full-width spaces.  
    pub fn with_indent(indent: usize) -> Self {
        BullshitGenerator {
            data: json::parse(include_str!("../data.json")).unwrap(),
            indent,
            text: String::new(),
            total_length: 0,
            paragraph_length: 0,
        }
    }
    /// Generate an article by given topic and length.  
    /// **Note: `len` is an approximate value and it does not equal to the actual length of the result.**  
    pub fn generate(mut self, topic: &str, len: usize) -> String {
        self.text = String::with_capacity(len * 4);
        self.text += &*" ".repeat(self.indent);
        while self.total_length < len || self.paragraph_length < 200 {
            match LOCAL_RNG.with(|r| r.borrow_mut().gen_range(0..100)) {
                0..=5 if self.paragraph_length > 200 => self.new_line(),
                0..=20 => self.append_text(&*self.get_famous()),
                _ => self.append_text(&*self.get_bullshit(topic)),
            }
        }
        self.new_line();
        self.text.trim_end().into()
    }
    /// Generate a single `famous` sentence.  
    pub fn get_famous(&self) -> String {
        self.rand_str("famous")
            .to_string()
            .replace("a", self.rand_str("before"))
            .replace("b", self.rand_str("after"))
    }
    /// Generate a single `bullshit` sentence.  
    pub fn get_bullshit(&self, topic: &str) -> String {
        self.rand_str("bosh").replace("x", topic)
    }
    fn rand_str(&self, k: &str) -> &str {
        LOCAL_RNG.with(|r| {
            self.data[k]
                .members()
                .choose(&mut *r.borrow_mut())
                .unwrap()
                .as_str()
                .unwrap()
        })
    }
    fn append_text(&mut self, str: &str) {
        let len = str.chars().count();
        self.text += str;
        self.total_length += len;
        self.paragraph_length += len;
    }
    fn new_line(&mut self) {
        self.text.pop();
        self.text += "。\n";
        self.text += &*" ".repeat(self.indent);
        self.paragraph_length = 0;
    }
}

#[test]
#[cfg(test)]
fn test_generate() {
    println!(
        "{}",
        BullshitGenerator::with_indent(2).generate("黄油", u16::MAX as usize)
    );
}

#[test]
#[cfg(test)]
fn test_single_bullshit() {
    println!("{}", BullshitGenerator::with_indent(2).get_bullshit("黄油"));
}

#[test]
#[cfg(test)]
fn test_single_famous() {
    println!("{}", BullshitGenerator::with_indent(2).get_famous());
}