Skip to content

Commit 2fcdfd7

Browse files
authored
Merge pull request #49 from mgomes/mgomes/v0-18-0
v0.18.0: standard library expansion
2 parents c869929 + b59f24c commit 2fcdfd7

20 files changed

+2338
-18
lines changed

ROADMAP.md

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -281,35 +281,35 @@ Goal: reduce host-side boilerplate for common scripting tasks.
281281

282282
### Core Utilities
283283

284-
- [ ] Add JSON parse/stringify built-ins.
285-
- [ ] Add regex matching/replacement helpers.
286-
- [ ] Add UUID/random identifier utilities with deterministic test hooks.
287-
- [ ] Add richer date/time parsing helpers for common layouts.
288-
- [ ] Add safer numeric conversions and clamp/round helpers.
284+
- [x] Add JSON parse/stringify built-ins.
285+
- [x] Add regex matching/replacement helpers.
286+
- [x] Add UUID/random identifier utilities with deterministic test hooks.
287+
- [x] Add richer date/time parsing helpers for common layouts.
288+
- [x] Add safer numeric conversions and clamp/round helpers.
289289

290290
### Collections and Strings
291291

292-
- [ ] Expand hash helpers for nested transforms and key remapping.
293-
- [ ] Expand array helpers for chunking/windowing and stable group operations.
294-
- [ ] Add string helpers for common normalization and templating tasks.
292+
- [x] Expand hash helpers for nested transforms and key remapping.
293+
- [x] Expand array helpers for chunking/windowing and stable group operations.
294+
- [x] Add string helpers for common normalization and templating tasks.
295295

296296
### Compatibility and Safety
297297

298-
- [ ] Define deterministic behavior for locale-sensitive operations.
299-
- [ ] Add quotas/guards around potentially expensive operations.
300-
- [ ] Ensure new stdlib functions are capability-safe where required.
298+
- [x] Define deterministic behavior for locale-sensitive operations.
299+
- [x] Add quotas/guards around potentially expensive operations.
300+
- [x] Ensure new stdlib functions are capability-safe where required.
301301

302302
### Testing and Docs
303303

304-
- [ ] Add comprehensive docs pages and examples for each new family.
305-
- [ ] Add negative tests for malformed JSON/regex patterns.
306-
- [ ] Add benchmark coverage for hot stdlib paths.
304+
- [x] Add comprehensive docs pages and examples for each new family.
305+
- [x] Add negative tests for malformed JSON/regex patterns.
306+
- [x] Add benchmark coverage for hot stdlib paths.
307307

308308
### v0.18.0 Definition of Done
309309

310-
- [ ] New stdlib is documented and example-backed.
311-
- [ ] Runtime behavior is deterministic across supported OSes.
312-
- [ ] Security/performance guardrails are validated by tests.
310+
- [x] New stdlib is documented and example-backed.
311+
- [x] Runtime behavior is deterministic across supported OSes.
312+
- [x] Security/performance guardrails are validated by tests.
313313

314314
---
315315

docs/arrays.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Common enumerable helpers include:
1919
- `sum` to total numeric arrays.
2020
- `compact` to drop `nil` entries.
2121
- `flatten(depth = nil)` to collapse nested arrays (defaults to fully flattening).
22+
- `chunk(size)` to split into fixed-size slices.
23+
- `window(size)` to build overlapping windows.
2224
- `join(sep = "")` to produce a string.
2325

2426
Example:
@@ -33,6 +35,11 @@ def total_by_multiplier(values, multiplier)
3335
end
3436
```
3537

38+
```vibe
39+
[1, 2, 3, 4, 5].chunk(2) # [[1,2], [3,4], [5]]
40+
[1, 2, 3, 4].window(3) # [[1,2,3], [2,3,4]]
41+
```
42+
3643
## Search and predicates
3744

3845
- `include?(value)` for membership checks.
@@ -55,8 +62,12 @@ end
5562
- `reverse`, `sort`, and `sort_by`.
5663
- `partition` to split into matching and non-matching arrays.
5764
- `group_by` to collect values by key.
65+
- `group_by_stable` to collect values by key while preserving group order.
5866
- `tally` to count symbol/string occurrences.
5967

68+
Sorting of strings/symbols uses deterministic codepoint ordering (locale
69+
collation is not applied).
70+
6071
```vibe
6172
def summarize(players)
6273
grouped = players.group_by { |p| p[:status] }

docs/builtins.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,88 @@ end
5656

5757
For time manipulation in VibeScript, use the `Time` object (`Time.now`, `Time.parse`, `Time.utc`, etc.). See `docs/time.md`.
5858

59+
## Random IDs
60+
61+
### `uuid()`
62+
63+
Returns an RFC 4122 version 4 UUID string:
64+
65+
```vibe
66+
event_id = uuid()
67+
```
68+
69+
### `random_id(length = 16)`
70+
71+
Returns an alphanumeric random identifier string:
72+
73+
```vibe
74+
short = random_id(8)
75+
token = random_id()
76+
```
77+
78+
## Numeric Conversion
79+
80+
### `to_int(value)`
81+
82+
Converts `int`, integral `float`, or base-10 numeric `string` values into `int`.
83+
84+
### `to_float(value)`
85+
86+
Converts `int`, `float`, or numeric `string` values into `float`.
87+
88+
```vibe
89+
count = to_int("42")
90+
ratio = to_float("1.25")
91+
```
92+
93+
## JSON
94+
95+
### `JSON.parse(string)`
96+
97+
Parses a JSON string into VibeScript values (`hash`, `array`, `string`, `int`,
98+
`float`, `bool`, `nil`):
99+
100+
```vibe
101+
payload = JSON.parse("{\"id\":\"p-1\",\"score\":10}")
102+
payload[:score] # 10
103+
```
104+
105+
`JSON.parse` enforces a 1 MiB input limit.
106+
107+
### `JSON.stringify(value)`
108+
109+
Serializes supported values (`hash`/`object`, `array`, scalar primitives) into
110+
a JSON string:
111+
112+
```vibe
113+
raw = JSON.stringify({ id: "p-1", score: 10, tags: ["a", "b"] })
114+
```
115+
116+
`JSON.stringify` enforces a 1 MiB output limit.
117+
118+
## Regex
119+
120+
### `Regex.match(pattern, text)`
121+
122+
Returns the first match string or `nil` when no match exists.
123+
124+
### `Regex.replace(text, pattern, replacement)`
125+
126+
Replaces the first regex match in `text`.
127+
128+
### `Regex.replace_all(text, pattern, replacement)`
129+
130+
Replaces all regex matches in `text`.
131+
132+
```vibe
133+
Regex.match("ID-[0-9]+", "ID-12 ID-34") # "ID-12"
134+
Regex.replace("ID-12 ID-34", "ID-[0-9]+", "X") # "X ID-34"
135+
Regex.replace_all("ID-12 ID-34", "ID-[0-9]+", "X") # "X X"
136+
Regex.replace("ID-12", "ID-([0-9]+)", "X-$1") # "X-12"
137+
```
138+
139+
Regex helpers enforce input guards (max pattern size 16 KiB, max text size 1 MiB).
140+
59141
## Module Loading
60142

61143
### `require(module_name, as: alias?)`

docs/hashes.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ end
4545
- `except(*keys)` removes selected keys.
4646
- `select` / `reject` with a block.
4747
- `transform_keys` / `transform_values` with a block.
48+
- `deep_transform_keys` for recursive key mapping across nested hashes/arrays.
49+
- `remap_keys(mapping_hash)` for direct key rename maps.
4850

4951
```vibe
5052
def public_profile(record)
@@ -54,6 +56,21 @@ def public_profile(record)
5456
end
5557
```
5658

59+
```vibe
60+
payload = { player_id: 7, profile: { total_raised: 12 } }
61+
payload.deep_transform_keys do |k|
62+
if k == :player_id
63+
:playerId
64+
elsif k == :total_raised
65+
:totalRaised
66+
else
67+
k
68+
end
69+
end
70+
71+
{ first_name: "Alex" }.remap_keys({ first_name: :name })
72+
```
73+
5774
## Iteration helpers
5875

5976
- `keys` and `values`

docs/introduction.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ dives on specific topics.
3030
behavior (exports, aliasing, policy hooks).
3131
- `examples/module_require.md` – practical example showing how to share
3232
helpers with `require` and module search paths.
33+
- `stdlib_core_utilities.md` – examples for JSON, regex, random IDs, numeric
34+
conversion, and common time parsing helpers.

docs/stdlib_core_utilities.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Core Stdlib Utilities
2+
3+
This page covers the core utility helpers added in v0.18.
4+
5+
## JSON
6+
7+
```vibe
8+
def parse_payload(raw)
9+
JSON.parse(raw)
10+
end
11+
12+
def emit_payload(record)
13+
JSON.stringify(record)
14+
end
15+
```
16+
17+
## Regex
18+
19+
```vibe
20+
def normalize_ids(text)
21+
Regex.replace_all(text, "ID-[0-9]+", "X")
22+
end
23+
24+
def first_id(text)
25+
Regex.match("ID-[0-9]+", text)
26+
end
27+
```
28+
29+
## Random IDs
30+
31+
```vibe
32+
def new_event_id()
33+
uuid()
34+
end
35+
36+
def short_token()
37+
random_id(8)
38+
end
39+
```
40+
41+
## Numeric Conversion
42+
43+
```vibe
44+
def parse_score(raw_score)
45+
to_int(raw_score)
46+
end
47+
48+
def parse_ratio(raw_ratio)
49+
to_float(raw_ratio)
50+
end
51+
```
52+
53+
## Common Time Parsing
54+
55+
`Time.parse` accepts common formats without manually passing a layout:
56+
57+
- RFC3339 / RFC3339Nano
58+
- RFC1123 / RFC1123Z
59+
- `YYYY-MM-DD` and `YYYY-MM-DD HH:MM:SS`
60+
- `YYYY/MM/DD` and `YYYY/MM/DD HH:MM:SS`
61+
- `MM/DD/YYYY` and `MM/DD/YYYY HH:MM:SS`
62+
63+
```vibe
64+
def parse_seen_at(raw)
65+
Time.parse(raw, in: "UTC")
66+
end
67+
```
68+
69+
For a runnable end-to-end sample, see `examples/stdlib/core_utilities.vibe`.

docs/strings.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
VibeScript provides several methods for string manipulation.
44

5+
## Locale Behavior
6+
7+
String transforms are locale-insensitive and deterministic across supported
8+
platforms. Methods like `upcase`, `downcase`, and `capitalize` use Unicode
9+
case mapping rules and do not depend on host locale settings.
10+
511
## Basic Methods
612

713
### `length`
@@ -58,6 +64,15 @@ end
5864
clean_input(" hello ") # "hello"
5965
```
6066

67+
### `squish`
68+
69+
Trims leading/trailing whitespace and collapses internal whitespace runs to a
70+
single space:
71+
72+
```vibe
73+
" hello \n\t world ".squish # "hello world"
74+
```
75+
6176
### `lstrip`
6277

6378
Removes leading whitespace:
@@ -269,6 +284,7 @@ The following methods are supported as aliases and return transformed strings.
269284
When there is no change, bang methods return `nil`.
270285

271286
- `strip!`, `lstrip!`, `rstrip!`, `chomp!`
287+
- `squish!`
272288
- `delete_prefix!`, `delete_suffix!`
273289
- `upcase!`, `downcase!`, `capitalize!`, `swapcase!`, `reverse!`
274290
- `sub!`, `gsub!`
@@ -292,6 +308,23 @@ Splits a string into an array of strings.
292308
"path/to/file".split("/") # ["path", "to", "file"]
293309
```
294310

311+
## Templating
312+
313+
### `template(context, strict: false)`
314+
315+
Interpolates `{{name}}` placeholders from a hash context. Dot paths can access
316+
nested hashes (`{{user.name}}`).
317+
318+
```vibe
319+
tpl = "Player {{user.name}} scored {{user.score}}"
320+
ctx = { user: { name: "Alex", score: 42 } }
321+
322+
tpl.template(ctx) # "Player Alex scored 42"
323+
```
324+
325+
When `strict: true`, missing placeholders raise an error instead of being left
326+
unchanged.
327+
295328
## Example: Text Processing
296329

297330
```vibe

docs/time.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ end
1818
- `Time.parse(string, layout=nil, in: zone)`
1919

2020
Zones accept Go-style names (e.g. `"America/New_York"`), `"UTC"`/`"GMT"`, `"LOCAL"`, or numeric offsets like `"+05:30"`.
21+
Without an explicit `layout`, `Time.parse` accepts common formats such as RFC3339/RFC1123, `YYYY-MM-DD`, `YYYY/MM/DD`, `YYYY-MM-DD HH:MM:SS`, and `MM/DD/YYYY` (with optional time).
2122

2223
## Formatting
2324

examples/arrays/extras.vibe

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,17 @@ end
114114
def tally_statuses(statuses)
115115
statuses.tally
116116
end
117+
118+
def chunk_values(values, size)
119+
values.chunk(size)
120+
end
121+
122+
def window_values(values, size)
123+
values.window(size)
124+
end
125+
126+
def group_by_stable_status(players)
127+
players.group_by_stable do |player|
128+
player[:status]
129+
end
130+
end

examples/hashes/transformations.vibe

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,21 @@ def non_zero(record)
5555
value == 0
5656
end
5757
end
58+
59+
def remap_profile(record)
60+
record.remap_keys({ first_name: :name, total_raised: :raised })
61+
end
62+
63+
def deep_transform_profile(record)
64+
record.deep_transform_keys do |key|
65+
if key == :player_id
66+
:playerId
67+
elsif key == :total_raised
68+
:totalRaised
69+
elsif key == :amount_cents
70+
:amountCents
71+
else
72+
key
73+
end
74+
end
75+
end

0 commit comments

Comments
 (0)