We just released cl-str v0.21. It’s been a while since the last release, and many enhancements make it more useful than ever. Let’s review the changes, the newest first.

But first, I want to you thank everyone who contributed, by sending pull requests or feedback. Special thanks to @kilianmh who suddenly appeared one day, helped with new features as well as grunt work, and who is now a co-maintainer.

split by regex

The latest addition sent by ccQpein is that str:split now accepts a :regex key argument to split by regular expressions. The functions rsplit and split-omit-nulls have it too.

(str:split "[,|;]" "foo,bar;baz" :regex t)
;; => ("foo" "bar" "baz")

That’s handy for advent of code ;)

You can also use ppcre:split, this is the function that str:split relies on anyways, except that by default, str:split ensures that the split argument is not a regex. We need this:

(ppcre:split `(:sequence ,(string separator)) s …)

(and eventually, we remove null elements if :omit-nulls t was set)

replace by regex

str:replace-all, str:replace-first and str:replace-using got a :regex keyword too:

(str:replace-all "(?i)fo+" "frob" "FOO bar FOO" :regex t)
;; => "frob bar frob"

The ensure functions

These were added in March.

The “ensure-” functions return a string that has the specified prefix or suffix, appended if necessary.

ensure encapsulates the other two.

ensure-prefix, ensure-suffix (start/end s)

Ensure that s starts with start/end (or ends with start/end, respectively).

Return a new string with its prefix (or suffix) added, if necessary.


(str:ensure-prefix "/" "abc/") => "/abc/" (a prefix was added)
;; and
(str:ensure-prefix "/" "/abc/") => "/abc/" (does nothing)

We also have a couple functions to find the prefixes or the suffixes, please see our README.

ensure-wrapped-in (start/end s)

Ensure that s both starts and ends with start/end.

Return a new string with the necessary added bits, if required.

It simply calls str:ensure-suffix followed by str:ensure-prefix.

See also str:wrapped-in-p and uiop:string-enclosed-p prefix s suffix.

(str:ensure-wrapped-in "/" "abc") ;; => "/abc/"  (added both a prefix and a suffix)
(str:ensure-wrapped-in "/" "/abc/") ;; => "/abc/" (does nothing)

ensure (s &key wrapped-in prefix suffix)

This str:ensure function looks for the following key parameters, in order:

  • :wrapped-in: if non nil, call str:ensure-wrapped-in. This checks that s both starts and ends with the supplied string or character.
  • :prefix and :suffix: if both are supplied and non-nil, call str:ensure-suffix followed by str:ensure-prefix.
  • :prefix: call str:ensure-prefix
  • :suffix: call str:ensure-suffix.


(str:ensure "abc" :wrapped-in "/")  ;; => "/abc/"
(str:ensure "/abc" :prefix "/")  ;; => "/abc"  => no change, still one "/"
(str:ensure "/abc" :suffix "/")  ;; => "/abc/" => added a "/" suffix.

These functions accept strings and characters:

(str:ensure "/abc" :prefix #\/)

warn: if both :wrapped-in and :prefix (and/or :suffix) are supplied together, :wrapped-in takes precedence and :prefix (and/or :suffix) is ignored.

:char-bag parameter to trim, trim-left, trim-right

This was added in January.

str:trim removes all characters in char-bag (default: whitespaces) at the beginning and end of s.

If supplied, char-bag has to be a sequence (e.g. string or list of characters).

(str:trim "cdoooh" :char-bag (str:concat "c" "d" "h")) => "ooo"

fit a string to some length

This is older, it was added in February of 2022.

Fit this string to the given length:

  • if it’s too long, shorten it (showing the ellipsis),
  • if it’s too short, add paddding (to the side pad-side, adding the character pad-char).

As such, it accepts the same key arguments as str:shorten and str:pad: ellipsis, pad-side, pad-char

CL-USER> (str:fit 10 "hello" :pad-char "+")

CL-USER> (str:fit 10 "hello world" :ellipsis "…")
"hello wor…"

If, like me, you want to print a list of data as a table, see:

CL-USER> (ql:quickload "cl-ansi-term")
CL-USER> (term:table '(("name" "age" "email")
              ("me" 7 "some@blah")
              ("me" 7 "some@with-some-longer.email"))
             :column-width '(10 4 20))
|name     |age|email              |
|me       |7  |some@blah          |
|me       |7  |some@with-some-l(…)|
CL-USER> (ql:quickload "cl-ascii-table")
CL-USER> (let ((table (ascii-table:make-table '("Id" "Name" "Amount") :header "Infos")))
  (ascii-table:add-row table '(1 "Bob" 150))
  (ascii-table:add-row table '(2 "Joe" 200))
  (ascii-table:add-separator table)
  (ascii-table:add-row table '("" "Total" 350))
  (ascii-table:display table))

|        Infos        |
| Id | Name  | Amount |
|  1 | Bob   |    150 |
|  2 | Joe   |    200 |
|    | Total |    350 |

fixed string-case

The str:string-case macro was missing an implicit progn, so with more than one s-expression in the clauses, it didn’t fail… but it didn’t work as expected either.

Fixed for LispWorks

Characters are named differently, like #\NewLine. We are still awaiting input on one issue.

We reimplemented str:replace-using to fix it on LispWorks.


We added type declaration e.g. for concat, join.

str:ends-with-p now works with a character.

Small breaking change: fixed str:prefixp when used with a smaller prefix: “f” was not recognized as a prefix of “foobar” and “foobuz”, only “foo” was. Now it is fixed. Same for str:suffixp.

We added str:ascii-p and str:ascii-char-p (in 2021).

More functions now work with characters as well.

We sped up str:join (measured: 4x). We use with-output-to-string and a loop instead of format’s iteration directive.

We use uninterned symbols in defpackage.

We deprecated predicates ending with “?” (but they are still there).

We made casing-functions consistent to inbuilt cl casing functions (we use cl-change-case, but the functions also allow symbols and characters (not only strings) and return NIL when given NIL).

We added :ignore-case to str:count-substring.

We switched the testing framework from prove to fiveam (that was grunt work by the new maintainer yay o/ )

That’s it, thanks again for helping make this lil’ lib useful since day 1.

The “str” library defines many more functions. Look at our table of content on the README: https://github.com/vindarel/cl-str

Install it with

(ql:quickload "str")