Skip to content

Commit

Permalink
Merge pull request #42 from mgeisler/lipsum-title
Browse files Browse the repository at this point in the history
Add lipsum_title function
  • Loading branch information
mgeisler authored Apr 22, 2018
2 parents 8c52332 + ae3783c commit 39c61ba
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 11 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,25 @@ This generates the lorem ipsum text show above:
The text becomes random after 18 words, so you might not see exactly
the same text.

Use `lipsum_title` instead if you just need a short text suitable for
a document title or a section heading. The text generated by that
function looks like this:

> Refugiendi et Omnino Rerum
Small words are kept uncapitalized and punctuation is stripped from
all words.


## Release History

This is a changelog with the most important changes in each release.

### Unreleased

The new `lipsum_title` function can be used to generate a short string
suitable for a document title or a section heading.

Dependencies were updated and the oldest supported version of Rust is
now 1.17.

Expand Down
5 changes: 5 additions & 0 deletions examples/lipsum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ extern crate lipsum;
use std::env;

fn main() {
// Generate lorem ipsum text with Title Case.
let title = lipsum::lipsum_title();
// Print underlined title and lorem ipsum text.
println!("{}\n{}\n", title, str::repeat("=", title.len()));

// First command line argument or "" if not supplied.
let arg = env::args().nth(1).unwrap_or_default();
// Number of words to generate.
Expand Down
97 changes: 86 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,24 +329,27 @@ fn is_ascii_punctuation(c: char) -> bool {
}
}

/// Capitalize the first character in a string.
fn capitalize<'a>(word: &'a str) -> String {
let idx = match word.chars().next() {
Some(c) => c.len_utf8(),
None => 0,
};

let mut result = String::with_capacity(word.len());
result.push_str(&word[..idx].to_uppercase());
result.push_str(&word[idx..]);
result
}

/// Join words from an iterator. The first word is always capitalized
/// and the generated sentence will end with `'.'` if it doesn't
/// already end with some other ASCII punctuation character.
fn join_words<'a, I: Iterator<Item = &'a str>>(mut words: I) -> String {
match words.next() {
None => String::new(),
Some(word) => {
let mut sentence = String::from(word);

// Capitalize first word if necessary.
if !sentence.starts_with(|c: char| c.is_uppercase()) {
let mut chars = word.chars();
if let Some(first) = chars.next() {
sentence.clear();
sentence.extend(first.to_uppercase());
sentence.extend(chars);
}
}
let mut sentence = capitalize(word);

// Add remaining words.
for word in words {
Expand Down Expand Up @@ -420,6 +423,60 @@ pub fn lipsum(n: usize) -> String {
})
}

/// Minimum number of words to include in a title.
const TITLE_MIN_WORDS: usize = 3;
/// Maximum number of words to include in a title.
const TITLE_MAX_WORDS: usize = 8;
/// Words shorter than this size are not capitalized.
const TITLE_SMALL_WORD: usize = 3;

/// Generate a short lorem ipsum string where the words are
/// capitalized and stripped for punctuation characters.
///
/// # Examples
///
/// ```
/// use lipsum::lipsum_title;
///
/// println!("{}", lipsum_title());
/// ```
///
/// This will generate a string like
///
/// > Grate Meminit et Praesentibus
///
/// which should be suitable for use in a document title for section
/// heading.
pub fn lipsum_title() -> String {
LOREM_IPSUM_CHAIN.with(|cell| {
let n = rand::thread_rng().gen_range(TITLE_MIN_WORDS, TITLE_MAX_WORDS);
let mut chain = cell.borrow_mut();
// The average word length with our corpus is 7.6 bytes so
// this capacity will avoid most allocations.
let mut title = String::with_capacity(8 * n);

let words = chain
.iter()
.map(|word| word.trim_matches(is_ascii_punctuation))
.filter(|word| !word.is_empty())
.take(n);

for (i, word) in words.enumerate() {
if i > 0 {
title.push(' ');
}

// Capitalize the first word and all long words.
if i == 0 || word.len() > TITLE_SMALL_WORD {
title.push_str(&capitalize(word));
} else {
title.push_str(word);
}
}
title
})
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -444,6 +501,24 @@ mod tests {
assert_eq!(lipsum(2).split_whitespace().count(), 2);
}

#[test]
fn generate_title() {
for word in lipsum_title().split_whitespace() {
assert!(
!word.starts_with(is_ascii_punctuation) && !word.ends_with(is_ascii_punctuation),
"Unexpected punctuation: {:?}",
word
);
if word.len() > TITLE_SMALL_WORD {
assert!(
word.starts_with(char::is_uppercase),
"Expected small word to be capitalized: {:?}",
word
);
}
}
}

#[test]
fn empty_chain() {
let mut chain = MarkovChain::new();
Expand Down

0 comments on commit 39c61ba

Please sign in to comment.