Text

Humanize

Durations, file sizes and relative times as people write them and back: 2h 30m, 1.5 GB, 2 GiB, 3 days ago, in 2 hours.

Version 1.0.0 · 5 functions · 2 crates

#text#time#files#forms

Functions

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

humanize_duration(seconds: float) → stror an error

seconds as a short duration: 9000 is "2h 30m", 90061 is "1day 1h 1m 1s"; fractions of a second as "ms". Negative is an error.

humanize_parse_duration(text: str) → floator an error

A duration typed by a person, in seconds: "2h 30m", "1day 3hours", "90s", "15 minutes", "500ms" (0.5). An error says what it could not read.

humanize_bytes(bytes: int, si: bool) → str

bytes as a size: "1.5 GB" in powers of 1000 when si is true (as disks and downloads count), "1.4 GiB" in powers of 1024 when false (as memory counts).

humanize_parse_bytes(text: str) → intor an error

A size typed by a person, in bytes: "1.5 GB" (1,500,000,000), "2 GiB" (2,147,483,648), "512 KB", "300" (bytes). Case does not matter.

humanize_ago(time_ms: int, now_ms: int) → str

How long ago (or how far ahead) time_ms is from now_ms, both in milliseconds since 1970 as now() gives: "just now", "5 minutes ago", "yesterday", "3 days ago", "in 2 hours", "last month", "2 years ago".

Get it

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

Or with curl:

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

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

main.qwe
use humanize.qweruse humanize_duration, humanize_parse_duration from humanize      // Quillo 0.6: only these

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

[dependencies]
humantime = "2.1"bytesize = "2"

Example

humanize.qwe
// A file list with sizes and ages as people read them, and settings typed as people write them.////   quillo run humanize.qwe      then open http://localhost:8080/filesuse "humanize.qwer"let t = now()let files = [    { name: "backup.tar.gz", bytes: 1500000000, at: t - 3 * 86400000 },    { name: "photo.jpg", bytes: 2400000, at: t - 5 * 60000 },    { name: "notes.txt", bytes: 812, at: t - 10000 },]route GET /files = files | map((f) => { name: f.name, size: humanize_bytes(f.bytes, true), memory: humanize_bytes(f.bytes, false), changed: humanize_ago(f.at, t) })// Settings a person typed: "session lasts 2h 30m", "uploads up to 25 MB".route GET /settings ? { session: str = "2h 30m", upload: str = "25 MB" } = {    session_seconds: humanize_parse_duration(session)?,    upload_bytes: humanize_parse_bytes(upload)?,    shown: humanize_duration(humanize_parse_duration(session)?)?,}route GET /ahead = { soon: humanize_ago(now() + 2 * 3600000, now()), later: humanize_ago(now() + 400 * 86400000, now()) }
$ quillo run humanize.qwe$ curl 'localhost:8080/files'$ curl 'localhost:8080/settings?session=1day%203hours&upload=2%20GiB'

Source

humanize.qwer
//! Numbers as people write them and back: durations ("2h 30m"), file sizes ("1.5 GB") and relative times ("3 days ago", "in 2 hours").//! version: 1.0.0//!//! [dependencies]//! humantime = "2.1"//! bytesize = "2"use bytesize::ByteSize;use std::time::Duration;/// `seconds` as a short duration: 9000 is "2h 30m", 90061 is "1day 1h 1m 1s"; fractions of a second as "ms". Negative is an error.pub fn humanize_duration(seconds: f64) -> Result<String, String> {    if !(0.0..1e15).contains(&seconds) {        return Err("seconds from 0 up".into());    }    let d = Duration::from_millis((seconds * 1000.0).round() as u64);    Ok(humantime::format_duration(d).to_string())}/// A duration typed by a person, in seconds: "2h 30m", "1day 3hours", "90s", "15 minutes", "500ms" (0.5). An error says what it could not read.pub fn humanize_parse_duration(text: &str) -> Result<f64, String> {    humantime::parse_duration(text.trim()).map(|d| d.as_secs_f64()).map_err(|e| format!("{:?}: {}", text, e))}/// `bytes` as a size: "1.5 GB" in powers of 1000 when `si` is true (as disks and downloads count), "1.4 GiB" in powers of 1024 when false (as memory counts).pub fn humanize_bytes(bytes: i64, si: bool) -> String {    let b = ByteSize::b(bytes.max(0) as u64);    let s = if si { b.display().si().to_string() } else { b.display().iec().to_string() };    if bytes < 0 { format!("-{}", s) } else { s }}/// A size typed by a person, in bytes: "1.5 GB" (1,500,000,000), "2 GiB" (2,147,483,648), "512 KB", "300" (bytes). Case does not matter.pub fn humanize_parse_bytes(text: &str) -> Result<i64, String> {    let t = text.trim();    let n: ByteSize = t.parse().map_err(|e: String| format!("{:?}: {}", text, e))?;    i64::try_from(n.as_u64()).map_err(|_| "too large".into())}/// How long ago (or how far ahead) `time_ms` is from `now_ms`, both in milliseconds since 1970 as `now()` gives: "just now", "5 minutes ago", "yesterday", "3 days ago", "in 2 hours", "last month", "2 years ago".pub fn humanize_ago(time_ms: i64, now_ms: i64) -> String {    let diff = (now_ms as i128 - time_ms as i128) / 1000;    let past = diff >= 0;    let s = diff.unsigned_abs() as u64;    if s < 45 {        return "just now".into();    }    let (n, unit) = match s {        s if s < 90 => (1, "minute"),        s if s < 3600 => ((s + 30) / 60, "minute"),        s if s < 5400 => (1, "hour"),        s if s < 86400 => ((s + 1800) / 3600, "hour"),        s if s < 2 * 86400 => return if past { "yesterday".into() } else { "tomorrow".into() },        s if s < 7 * 86400 => ((s + 43200) / 86400, "day"),        s if s < 30 * 86400 => ((s + 3 * 86400) / (7 * 86400), "week"),        s if s < 365 * 86400 => ((s + 15 * 86400) / (30 * 86400), "month"),        s => ((s + 182 * 86400) / (365 * 86400), "year"),    };    let n = n.max(1);    if n == 1 && (unit == "week" || unit == "month" || unit == "year") {        return format!("{} {}", if past { "last" } else { "next" }, unit);    }    let words = if n == 1 { format!("{} {}", if unit == "hour" { "an" } else { "a" }, unit) } else { format!("{} {}s", n, unit) };    if past { format!("{} ago", words) } else { format!("in {}", words) }}
Case 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 downloadsInflection inflect.qwer · 1.0.0English words in the right form for counts and labels: plurals and singulars, 3 items, ordinals such as 1st and 22nd, and a or an by how a word sounds.textlabelsenglishforms6 views · 7 downloadsJapanese kana kana.qwer · 1.0.0Japanese kana: romaji typed as hiragana or katakana, kana written in Hepburn romaji, hiragana and katakana each to the other, half-width katakana from bank and till systems made full-width, and which scripts a text uses.unicodelanguagesformstext6 views · 10 downloadsProfanity filter profanity.qwer · 1.0.0Offensive words found and masked in comments, usernames and chat, with l33t spellings and spaced-out letters caught, and a severity per kind for moderation queues.moderationtextformscomments8 views · 8 downloadsSpam filter spam_filter.qwer · 1.0.0A spam filter that learns from your own messages: naive Bayes trained on what users mark, kept in a file, scoring comments and forms from 0 to 1.spammoderationcommentsformstext7 views · 7 downloadsSpellcheck spellcheck.qwer · 1.0.0Spelling checked against a word list you load, in any language: unknown words found with their places, suggestions ranked by closeness and frequency (SymSpell's method), text corrected for "did you mean", and your own words added.typostextsearchforms7 views · 8 downloads
Thread
711Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.