Text

Text statistics

Words, sentences, syllables, reading and speaking time, Flesch reading ease and grade level of a text, for editors and writing tools.

Version 1.0.0 · 4 functions · 0 crates

#text#writing#seo#readability

Functions

What use text_stats.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use text_stats, text_word_count from text_stats (Quillo 0.6).

text_stats(text: str) → anyor an error

The counts of text: { characters, letters, words, sentences, paragraphs, syllables, reading_seconds, speaking_seconds, flesch_ease, grade_level, avg_words_per_sentence }. Reading is at 238 words a minute, speaking at 150. flesch_ease runs from about 0 (hard: law) to 100 (easy: comics), 60 to 70 being plain English; grade_level is the Flesch-Kincaid US school grade. Syllables are counted for English. Up to 5 MB.

text_word_count(text: str) → int

How many words text has, as a word processor counts them.

text_reading_time(text: str) → str

"4 min read" for text, at 238 words a minute; at least "1 min read".

text_syllables(word: str) → int

The syllables of one English word, estimated from its vowels: "readability" is 5.

Get it

$ quillo install text_stats               # text_stats.qwer into this folder$ quillo install text_stats --example     # and its example, text_stats.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/text_stats.qwer$ curl -O https://quillolang.org/plugins/files/text_stats.qwe

Then, in your program, all of it or only what you call:

main.qwe
use text_stats.qweruse text_stats, text_word_count from text_stats      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]

Example

text_stats.qwe
// An editor's sidebar: a draft's length, reading time and how easy it is to read.////   quillo run text_stats.qwe      then open http://localhost:8080/draftuse "text_stats.qwer"let easy = "The cat sat on the mat. It was a sunny day. The cat was happy."let hard = "Notwithstanding the aforementioned considerations, the committee's deliberations necessitated comprehensive reevaluation of institutional responsibilities."route GET /draft = { easy: text_stats(easy)?, hard: text_stats(hard)?, label: text_reading_time(easy) }route POST /stats = text_stats(request.body)?route GET /syllables ? { word: str } = { word, syllables: text_syllables(word) }
$ quillo run text_stats.qwe$ curl 'localhost:8080/draft'$ curl 'localhost:8080/syllables?word=readability'

Source

text_stats.qwer
//! Counts of a text for writers and editors: words, sentences, syllables, reading time, and how easy it is to read (Flesch reading ease and grade level).//! version: 1.0.0//!//! [dependencies]use serde_json::{json, Value};/// Syllables in an English word, by its vowel groups: close for most words, never 0.fn syllables(word: &str) -> usize {    let w: Vec<char> = word.to_lowercase().chars().filter(|c| c.is_alphabetic()).collect();    if w.is_empty() {        return 0;    }    if w.len() <= 3 {        return 1;    }    let vowel = |c: char| "aeiouy".contains(c);    let mut n = 0;    let mut prev = false;    for &c in &w {        let v = vowel(c);        if v && !prev {            n += 1;        }        prev = v;    }    let s: String = w.iter().collect();    // A silent e at the end ("make"), but not "-le" after a consonant ("table").    if s.ends_with('e') && !s.ends_with("le") && !s.ends_with("ee") && n > 1 {        n -= 1;    }    if (s.ends_with("ed") && !s.ends_with("ted") && !s.ends_with("ded")) && n > 1 {        n -= 1;    }    n.max(1)}fn words(text: &str) -> Vec<&str> {    text.split(|c: char| c.is_whitespace() || "—–/".contains(c))        .map(|w| w.trim_matches(|c: char| !c.is_alphanumeric()))        .filter(|w| !w.is_empty())        .collect()}fn sentences(text: &str) -> usize {    let mut n = 0;    let mut in_sentence = false;    for c in text.chars() {        if ".!?…".contains(c) || c == '\n' {            if in_sentence {                n += 1;            }            in_sentence = false;        } else if c.is_alphanumeric() {            in_sentence = true;        }    }    n + in_sentence as usize}fn round(x: f64, places: i32) -> f64 {    let p = 10f64.powi(places);    (x * p).round() / p}/// The counts of `text`: `{ characters, letters, words, sentences, paragraphs, syllables, reading_seconds, speaking_seconds, flesch_ease, grade_level, avg_words_per_sentence }`. Reading is at 238 words a minute, speaking at 150. `flesch_ease` runs from about 0 (hard: law) to 100 (easy: comics), 60 to 70 being plain English; `grade_level` is the Flesch-Kincaid US school grade. Syllables are counted for English. Up to 5 MB.pub fn text_stats(text: &str) -> Result<Value, String> {    if text.len() > 5_000_000 {        return Err("text up to 5 MB".into());    }    let ws = words(text);    let w = ws.len();    let s = sentences(text).max(if w > 0 { 1 } else { 0 });    let syl: usize = ws.iter().map(|x| syllables(x)).sum();    let paragraphs = text.split("\n\n").filter(|p| !p.trim().is_empty()).count();    let (ease, grade) = if w == 0 {        (0.0, 0.0)    } else {        let wps = w as f64 / s as f64;        let spw = syl as f64 / w as f64;        (206.835 - 1.015 * wps - 84.6 * spw, 0.39 * wps + 11.8 * spw - 15.59)    };    Ok(json!({        "characters": text.chars().count(),        "letters": text.chars().filter(|c| c.is_alphanumeric()).count(),        "words": w,        "sentences": s,        "paragraphs": paragraphs,        "syllables": syl,        "reading_seconds": ((w as f64) * 60.0 / 238.0).ceil() as i64,        "speaking_seconds": ((w as f64) * 60.0 / 150.0).ceil() as i64,        "flesch_ease": round(ease, 1),        "grade_level": round(grade.max(0.0), 1),        "avg_words_per_sentence": if s == 0 { 0.0 } else { round(w as f64 / s as f64, 1) },    }))}/// How many words `text` has, as a word processor counts them.pub fn text_word_count(text: &str) -> i64 {    words(text).len() as i64}/// "4 min read" for `text`, at 238 words a minute; at least "1 min read".pub fn text_reading_time(text: &str) -> String {    let m = ((words(text).len() as f64) / 238.0).ceil().max(1.0) as i64;    format!("{} min read", m)}/// The syllables of one English word, estimated from its vowels: "readability" is 5.pub fn text_syllables(word: &str) -> i64 {    syllables(word) as i64}
Sentences sentences.qwer · 1.0.0Text split into sentences as a reader would: "Dr.", "p.m.", initials, decimals, "e.g." and quoted questions handled, with abbreviations for seven languages; positions for highlighting, and previews of whole sentences that fit a length.textwritingseolanguages7 views · 9 downloadsHyphenation hyphenation.qwer · 1.0.0Words hyphenated by the rules of 80 languages and spellings, patterns built in: a word's syllables, soft hyphens put in text and HTML for narrow columns and e-mails, and text wrapped at a width with words broken where they may be.textlanguagesemailwriting6 views · 7 downloadsKeywords keywords.qwer · 1.0.0The keywords and key phrases of a text by RAKE, TextRank or TF-IDF, with stop words of 58 languages left out: tags, SEO and related posts.seotagstextsearch7 views · 6 downloadsSlugs slug.qwer · 1.0.0Slugs for addresses from titles in any script, unique ones for a blog, and text transliterated to ASCII.urlsseounicodetext7 views · 8 downloadsTypography typography.qwer · 1.0.0Typed text set as a typographer would, by language: curly quotes (“ ”, « », „ “) and apostrophes, en and em dashes, ellipses, ×, ©, French spaces before ; : ! ?, and no widows; in plain text or HTML, code left alone.writingtexthtmllocale7 views · 10 downloadsCase case.qwer · 1.0.0Text in snake_case, camelCase, PascalCase, kebab-case, CONSTANT_CASE, Title Case and Sentence case: column names as API keys and labels.textapinamingforms7 views · 8 downloads
Thread
811Useful? Like it, or add to its thread below.

Thread 0 posts

On Text statistics: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.