sema-strings

0.1.0

Case conversion, slugs, padding, wrapping, and string distance utilities

$ sema pkg install sema-strings
Readme Versions 1 Dependencies 0

sema-strings

Case conversion, slugs, padding, wrapping, and string distance utilities.

Modeled on camel-snake-kebab, s.el, and cuerdas — but covering only what the Sema stdlib lacks. The stdlib already ships string/camel-case, string/kebab-case, string/snake-case, string/pascal-case, string/capitalize, string/title-case, string/headline, string/pad-left, string/pad-right, string/lines, string/words, string/word-wrap, string/empty?, and string/number?; none of those are duplicated here.

Install

sema pkg add sema-strings

Quick start

(import "sema-strings")

(str/constant "fooBar")
; => "FOO_BAR"

(str/slug "Blåbær og rømme!")
; => "blabaer-og-romme"

(str/truncate "hello world" 5)
; => "hell…"

(str/pluralize 3 "file")
; => "3 files"

Unicode

Lengths and slices count Unicode scalar values (codepoints), same as the stdlib's string-length/substring: (string-length "æ") is 1, so str/pad-center, str/truncate, str/wrap, and str/levenshtein treat "æ" as one character. Grapheme clusters that span multiple codepoints (ZWJ emoji, combining marks) count per codepoint, and display width may differ from character count — for terminal column layout use the stdlib's string/width.

API

Function Description
(str/constant s) "foo-bar""FOO_BAR" (CONSTANT_CASE)
(str/human s) "foo-bar""Foo bar" (human sentence)
(str/slug s) URL slug: lowercase, transliterated, hyphenated
(str/pad-center s width char?) Center in a field of width characters
(str/truncate s max-len opts?) Truncate with an omission marker, never exceeding max-len
(str/wrap s width) Word-wrap to \n-joined lines; long words stay unbroken
(str/levenshtein a b) Levenshtein edit distance in characters
(str/blank? s) #t for nil, "", or whitespace-only
(str/numeric? s) #t for one or more ASCII digits, nothing else
(str/unlines lines) Join a list of strings with \n
(str/ordinal n) 1"1st", 12"12th"
(str/pluralize n word plural?) "3 files" — naive count + noun formatting

str/constant

(str/constant "foo-bar")     ; => "FOO_BAR"
(str/constant "fooBar")      ; => "FOO_BAR"
(str/constant "HTTPServer")  ; => "HTTP_SERVER"

Tokenizes with the stdlib's string/words (splits on -, _, spaces, ., and camelCase boundaries, including acronym runs), uppercases every word, and joins with _.

str/human

(str/human "foo-bar")     ; => "Foo bar"
(str/human "fooBarBaz")   ; => "Foo bar baz"
(str/human "HTTPServer")  ; => "Http server"

Capitalizes only the first word; acronyms are lowercased like any other word. For Every Word Capitalized, use the stdlib's string/headline.

str/slug

(str/slug "Hello, World!")      ; => "hello-world"
(str/slug "Blåbær og rømme")    ; => "blabaer-og-romme"
(str/slug "Über Straße Café")   ; => "uber-strasse-cafe"
(str/slug "Top 10 Lists")       ; => "top-10-lists"

Lowercases, transliterates common accented Latin characters (æae, øo, åa, ée, üu, ßss, œoe, ñn, ...), replaces every other non-alphanumeric run with a single hyphen, and trims leading/trailing hyphens. Characters outside the transliteration table (CJK, emoji) are dropped.

str/pad-center

(str/pad-center "hi" 6)      ; => "  hi  "
(str/pad-center "hi" 5)      ; => " hi  "   (extra padding goes right)
(str/pad-center "x" 5 "*")   ; => "**x**"
(str/pad-center "hello" 3)   ; => "hello"   (already wide enough)

char defaults to " " and must be a single-character string. Complements the stdlib's string/pad-left and string/pad-right.

str/truncate

(str/truncate "hello world" 5)                     ; => "hell…"
(str/truncate "hello world" 5 {:omission "..."})   ; => "he..."
(str/truncate "hi" 10)                             ; => "hi"

The omission (default "…") counts toward max-len, so the result never exceeds it. If the omission alone is max-len or longer, a prefix of the omission is returned ((str/truncate "hello" 2 {:omission "..."})"..").

str/wrap

(str/wrap "the quick brown fox" 10)
; => "the quick\nbrown fox"

(str/wrap "a supercalifragilistic word" 5)
; => "a\nsupercalifragilistic\nword"

Splits on whitespace runs and greedily fills lines of at most width characters, joined with \n. A word longer than width is kept unbroken on its own line (that line then exceeds width). Distinct from the stdlib's string/word-wrap, which returns a list of lines and hard-breaks long words.

str/levenshtein

(str/levenshtein "kitten" "sitting")   ; => 3
(str/levenshtein "cat" "cats")         ; => 1
(str/levenshtein "blæ" "bla")          ; => 1   (per character, not per byte)

Iterative two-row dynamic programming — linear memory, no recursion depth limits.

str/blank?

(str/blank? nil)       ; => #t
(str/blank? "")        ; => #t
(str/blank? " \t\n")   ; => #t
(str/blank? " x ")     ; => #f

Anything other than nil or a string raises an error.

str/numeric?

(str/numeric? "42")     ; => #t
(str/numeric? "3.14")   ; => #f
(str/numeric? "-2")     ; => #f
(str/numeric? "")       ; => #f

One or more ASCII digits 0-9, nothing else. Stricter than the stdlib's string/number?, which accepts anything parseable as a number.

str/unlines

(str/unlines (list "a" "b" "c"))   ; => "a\nb\nc"
(str/unlines (string/lines "a\nb\n"))  ; => "a\nb"

Joins with \n — the inverse of the stdlib's string/lines (up to a trailing newline, which string/lines drops).

str/ordinal

(str/ordinal 1)    ; => "1st"
(str/ordinal 2)    ; => "2nd"
(str/ordinal 12)   ; => "12th"
(str/ordinal 21)   ; => "21st"

English ordinal suffixes, with the 11/12/13 (and 111/112/113, ...) exceptions handled.

str/pluralize

(str/pluralize 1 "file")              ; => "1 file"
(str/pluralize 3 "file")              ; => "3 files"
(str/pluralize 2 "person" "people")   ; => "2 people"

Deliberately naive: the default plural just appends "s" — pass an explicit plural for irregular nouns. n = 1 selects the singular; everything else (including 0) selects the plural.

Testing

sema pkg add sema-test   # once
sema tests.sema

License

MIT

VersionSizePublished
0.1.0 8 KB 2026-07-07 21:28:15
No dependencies