Lists
September 3, 2026 · View on GitHub
███████╗████████╗██████╗ ██╗ ██╗██╗ ██╗███████╗
██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝██║ ██╔╝██╔════╝
███████╗ ██║ ██████╔╝ ╚████╔╝ █████╔╝ █████╗
╚════██║ ██║ ██╔══██╗ ╚██╔╝ ██╔═██╗ ██╔══╝
███████║ ██║ ██║ ██║ ██║ ██║ ██╗███████╗
╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝
[ u t i l s ]
[BOUNDARY HELPERS // EVERYTHING ELSE IS A STRYKE BUILTIN]
"149 composites the language doesn't already ship. Cross-checked against
%b— zero overlap."
stryke-utils is a small pure-stryke utility library: six sublibraries (String, List, Hash, Num, Time, Path) of higher-level composites that aren't already in stryke core. No [ffi] table, no cdylib, no helper binary — just .stk modules loaded on use Utils. Created by MenkeTechnologies.
strykelang · MenkeTechnologiesMeta · stryke-arrow · stryke-demo
Read the Docs · Engineering Report
Table of Contents
- [0x00] Why a Package, Not Core
- [0x01] Install
- [0x02] Quick Start
- [0x03] Sublibraries
- [0x04] What's NOT in Here
- [0x05] CLI
- [0x06] Tests
- [0x07] Layout
- [0xFF] License
[0x00] Why a Package, Not Core
stryke core absorbed most of the obvious composites — slugify,
snake_case, truncate, levenshtein, chunk, uniq, group_by,
deep_merge, pick, omit, clamp, format_bytes, format_duration,
now_ms, basename, common_prefix, and roughly seventy more landed
in %b. This package shrunk in response. What's left here is the long
tail: helpers that are still useful glue but live below the bar for core
inclusion.
| Tier | Properties | Examples |
|---|---|---|
stryke core (%b, 10k+ entries) | builtins everyone needs everywhere | length, keys, uc, sort, time, sprintf, slugify, chunk, deep_merge, format_bytes, levenshtein, basename, … |
stryke-utils (opt-in, 149 fns) | path-aware variants, n-ary wrappers, regex composites, the long tail | deep_merge_all, parse_duration, compound_ext, round_to_multiple, pad_center, escape_shell, mask_middle, unwrap, windows, difference/intersection/union |
Every function in this repo is cross-checked against %b at build time
— zero name collisions with builtins. The two exceptions, Utils::Path::join
and Utils::Path::normalize, are intentionally distinct: stryke's join
is the array builtin and stryke's normalize is a vector op.
[0x01] Install
From a release:
s pkg install -g github.com/MenkeTechnologies/stryke-utils
From a local checkout:
cd ~/projects/stryke-utils
s pkg install -g . # installs into ~/.stryke/store/stryke-utils@<version>/
Or:
make install
No cargo step. No cdylib. The store directory ends up containing
stryke.toml + lib/*.stk only.
[0x02] Quick Start
use Utils # pulls all six sublibraries
# Strings — composites that aren't in core
Utils::String::ltrim(" hello") # "hello"
Utils::String::pad_center("hi", 8, ".") # "...hi..."
Utils::String::squeeze("aaabbc") # "abc"
Utils::String::compact_whitespace("a b\t c") # "a b c"
Utils::String::partition("a/b/c", "/") # ("a", "/", "b/c")
Utils::String::rpartition("a/b/c", "/") # ("a/b", "/", "c")
Utils::String::mask_middle("4111232233334444", 4, 4) # "4111********4444"
Utils::String::escape_shell("it's \$foo") # "'it'\\''s \$foo'"
Utils::String::unwrap('"quoted"', '"') # "quoted"
# Lists — set ops + sliding windows
Utils::List::difference([1,2,3,4], [2,4]) # [1,3]
Utils::List::intersection([1,2,3], [2,3,5]) # [2,3]
Utils::List::union([1,2], [2,3]) # [1,2,3]
Utils::List::windows([1,2,3,4], 2) # [[1,2],[2,3],[3,4]]
Utils::List::transpose([[1,2,3],[4,5,6]]) # [[1,4],[2,5],[3,6]]
# Hashes — variadic merge + dot-path access
val $cfg = Utils::Hash::deep_merge_all(
$defaults, $from_env, $runtime)
Utils::Hash::deep_get($cfg, "db.pool.max") # nested read by dot path
Utils::Hash::deep_set($cfg, "db.pool.min", 5) # autovivifying write
Utils::Hash::deep_has($cfg, "db.pool.min") # missing-vs-undef differentiator
# Numbers — long tail
Utils::Num::ordinal(21) # "21st"
Utils::Num::round_to_multiple(13, 5) # 15
# Time — parsing + relative phrasing + ISO formatting
Utils::Time::parse_duration("1h30m") # 5400
Utils::Time::ago(time() - 90) # "1 minute ago"
Utils::Time::format_iso8601() # "2026-06-10T14:23:05Z"
# Paths — string-only path arithmetic
Utils::Path::compound_ext("archive.tar.gz") # "tar.gz"
Utils::Path::set_ext("a/b.csv", "parquet") # "a/b.parquet"
Utils::Path::normalize("/a/b/../c/./d") # "/a/c/d"
Utils::Path::relative("/a/x", "/a/b/c") # "../../x"
Utils::Path::join("/a", "b/", "/c") # "/a/b/c"
For everything else — slugify, chunk, uniq, deep_merge,
format_bytes, levenshtein, basename, … — just call the stryke
builtin directly. No wrapper indirection.
Pull only what you need:
use Utils::Path
use Utils::Time
Utils::Path::compound_ext("a.tar.gz")
Utils::Time::parse_duration("90m")
[0x03] Sublibraries
| Module | use | fns | Surface |
|---|---|---|---|
| String | use Utils::String | 33 | ltrim · rtrim · pad_center · visible_width · count_occurrences · reverse_chars · squeeze · compact_whitespace · partition · rpartition · mask_middle · escape_shell · expand_tabs · unexpand_tabs · unwrap · ellipsize · truncate_words · nth_index · splitn · capitalize_first · uncapitalize · titleize · normalize_newlines · collapse_blank_lines · strip_quotes · repeat_to · remove_prefix · remove_suffix · ensure_prefix · ensure_suffix · prefix_lines · nth_line · count_lines_nonblank |
| List | use Utils::List | 20 | difference · intersection · union · symmetric_difference · is_disjoint · windows · chunk · cartesian · rle_encode · rle_decode · top_n · bottom_n · count_where · transpose · running_sum · running_product · round_robin · deltas · rotate_left · index_of_max |
| Hash | use Utils::Hash | 26 | deep_merge_all · deep_get · deep_set · deep_has · map_keys · map_values · all_hashes · rename_keys · flatten_keys · unflatten_keys · deep_delete · defaults · deep_keys · deep_values · count_values · map_entries · merge_with · pick_by · omit_by · hash_diff · invert_multi · deep_count · pluck_paths · to_query_string (+ _vstr, _urlenc helpers) |
| Num | use Utils::Num | 26 | round_to_multiple · floor_to_multiple · ceil_to_multiple · ordinal · percent_change · weighted_avg · percentile_of · digit_sum · digit_count · digital_root · pct_of · mean_abs_dev · is_close · to_radians · to_degrees · round_sig · clamp01 · remap · wrap · gcd · round_half_even · clamp_to · mod_floor · next_power_of_two · midrange · divisors_of |
| Time | use Utils::Time | 18 | parse_duration · ago · format_iso8601 · format_date · format_time · timed · day_of_week · format_human · format_clock · add_duration · sub_duration · parse_iso8601 · quarter · is_same_day · truncate_to_hour · next_weekday · prev_weekday (+ _days_from_civil helper) |
| Path | use Utils::Path | 26 | ext · compound_ext · without_ext · set_ext · splitext · join · normalize · is_absolute · is_bare · relative · with_name · add_suffix · strip_trailing_slash · ensure_trailing_slash · segments · depth · is_under · is_hidden · expand_user · sibling · ancestors · with_stem · common_ancestor · is_relative · with_parent · strip_extension_all |
149 functions total. Every sublibrary stands alone — no FFI, no required
environment, no state between calls. Drop a single lib/*.stk into
another project and it works.
[0x04] What's NOT in Here
By design — these are stryke builtins, so we don't re-wrap them. Every
omission below was present in stryke-utils at one point and got
deleted when the corresponding builtin landed in core:
| Category | Builtins (call directly) |
|---|---|
| String case/slug | trim · slugify · snake_case · kebab_case · camel_case · pascal_case · title_case · swap_case · indent · dedent · rot13 |
| String predicates / distance | contains · starts_with · ends_with · is_blank · is_palindrome · levenshtein · hamming · dice_coefficient · find_all_indices · common_prefix · common_suffix |
| String padding / shaping | pad_left · pad_right · truncate · strip_ansi · word_wrap |
| List | chunk · compact · uniq · uniq_by · flatten · group_by · count_by · index_by · partition · pluck · sum · mean · median · min_by · max_by · sort_by · zip · take · drop · range |
| Hash | deep_merge · pick · omit · invert · filter · from_pairs · to_pairs · is_empty |
| Num | clamp · between · lerp · round_to · format_number · format_bytes · format_percent · gcd · lcm · sign · is_even · is_odd |
| Time | now_ms · now_us · format_duration · elapsed |
| Path | basename · dirname · common_prefix |
| Core | uc/lc/length/sprintf/substr/index/rindex/split · push/pop/shift/unshift/splice/sort/reverse/map/grep/join/keys/values/exists/defined/scalar/wantarray · abs/int/sqrt/time/localtime/gmtime/sleep · mkdir/unlink/rmdir/opendir/readdir/-e/-d/-f/-s · to_json/from_json · the .. range and x repetition operators |
If a function here can be replaced with one builtin call, it's a bug — file an issue.
[0x05] CLI
bin/utils.stk is a thin dispatcher exposing the surviving lib fns
plus convenience routes to a few common builtins:
s bin/utils.stk pad-center "hi" 8 . # ...hi...
s bin/utils.stk squeeze "aaabbc" # abc
s bin/utils.stk mask 4111232233334444 4 4 # 4111********4444
s bin/utils.stk escape-shell "it's \$foo" # 'it'\''s $foo'
s bin/utils.stk unwrap '"quoted"' '"' # quoted
s bin/utils.stk ordinal 21 # 21st
s bin/utils.stk round-multiple 13 5 # 15
s bin/utils.stk parse-duration 1h30m # 5400
s bin/utils.stk ago $((now - 90)) # 1 minute ago
s bin/utils.stk iso # 2026-06-10T14:23:05Z
s bin/utils.stk compound-ext archive.tar.gz # tar.gz
s bin/utils.stk normalize a/./b/../c # a/c
s bin/utils.stk relative /a/x /a/b/c # ../../x
s bin/utils.stk help
For builtins (slugify, chunk, deep_merge, format_bytes, …), call
stryke one-liner-style — no wrapper needed:
s -e 'print slugify($ARGV[0])' -- "Hello, World!"
[0x06] Tests
s test t/ # assertions across every public function
make check-counts # verify every doc count matches the source
t/test_utils.stk covers every public function with at least one
round-trip / boundary assertion and exits TAP-style via test_run.
scripts/check-counts.sh derives the per-sublib fn counts, the total,
the assertion count, and the example count straight from lib/, t/,
and examples/, then fails if any number quoted in README.md or
docs/*.html disagrees — so the hand-written doc numbers can't silently
rot. It runs in CI on every push.
[0x07] Layout
stryke-utils/
stryke.toml # pure-stryke package manifest (no [ffi])
Makefile # test / check-counts / install / clean
LICENSE # MIT
scripts/
check-counts.sh # doc-count invariant (CI-enforced)
lib/
Utils.stk # `use Utils` — pulls all six sublibs
String.stk # `use Utils::String`
List.stk # `use Utils::List`
Hash.stk # `use Utils::Hash`
Num.stk # `use Utils::Num`
Time.stk # `use Utils::Time`
Path.stk # `use Utils::Path`
bin/
utils.stk # CLI front-end
t/
test_utils.stk # all-surface assertions
test_extra.stk # assertions for the later sublib additions
examples/
discover.stk # one call per sublib
word_frequency.stk # builtin-powered text pipeline
config_merge.stk # layered config via deep_merge_all + deep_get
deploy_digest.stk # cross-module pipeline (Time + String + Num + List + Path)
[0xFF] License
MIT.