diff --git a/CHANGELOG.md b/CHANGELOG.md index b6c0f6b..00ac862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project are documented in this file. +## [1.2.0] - 2025-12-20 +### Changed +- `reverse/1` now uses the BEAM stdlib grapheme segmentation (`string.to_graphemes`) for better Unicode correctness and consistency across the library. + +### Performance +- Optimized `count/3` internals to avoid repeated `list.length` calls inside recursive loops, improving performance on long strings. + +### CI +- Pinned Gleam version in CI and fixed build cache path/key for more reproducible and faster runs. + +Contributed by: Daniele (`lupodevelop`) + ## [1.1.1] - 2025-11-30 ### Fixed - Robustness fixes for grapheme-aware utilities; resolved parity issues in `ends_with/2` for complex ZWJ sequences. diff --git a/gleam.toml b/gleam.toml index 773d060..dc062b1 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "str" -version = "1.1.1" +version = "1.2.0" # Project metadata (fill or replace placeholders before publishing) description = "Unicode-aware string utilities for Gleam: grapheme-safe operations, pragmatic ASCII transliteration, and slug generation." diff --git a/src/str/core.gleam b/src/str/core.gleam index 127a623..4c395f5 100644 --- a/src/str/core.gleam +++ b/src/str/core.gleam @@ -12,7 +12,6 @@ import gleam/int import gleam/list import gleam/string -import str/tokenize /// Detects if a grapheme cluster likely contains emoji components. /// @@ -195,24 +194,51 @@ fn count_loop( overlapping: Bool, acc: Int, ) -> Int { - case list.length(hs) < nd_len { + count_loop_with_len(hs, list.length(hs), nd, nd_len, overlapping, acc) +} + +fn count_loop_with_len( + hs: List(String), + hs_len: Int, + nd: List(String), + nd_len: Int, + overlapping: Bool, + acc: Int, +) -> Int { + case hs_len < nd_len { True -> acc False -> case list.take(hs, nd_len) == nd { True -> case overlapping { True -> - count_loop(list.drop(hs, 1), nd, nd_len, overlapping, acc + 1) + count_loop_with_len( + list.drop(hs, 1), + hs_len - 1, + nd, + nd_len, + overlapping, + acc + 1, + ) False -> - count_loop( + count_loop_with_len( list.drop(hs, nd_len), + hs_len - nd_len, nd, nd_len, overlapping, acc + 1, ) } - False -> count_loop(list.drop(hs, 1), nd, nd_len, overlapping, acc) + False -> + count_loop_with_len( + list.drop(hs, 1), + hs_len - 1, + nd, + nd_len, + overlapping, + acc, + ) } } } @@ -413,10 +439,10 @@ pub fn truncate_default(text: String, max_len: Int) -> String { /// reverse("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ") -> "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ" /// pub fn reverse(text: String) -> String { - let clusters = tokenize.chars(text) - clusters + text + |> string.to_graphemes |> list.reverse - |> list.fold("", fn(acc, s) { acc <> s }) + |> string.concat } // ============================================================================