kelora 0.7.2

A command-line log analysis tool with embedded Rhai scripting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# Rhai Cheatsheet

Quick reference for Rhai scripting in Kelora. For detailed tutorials, see [Scripting Transforms](../tutorials/scripting-transforms.md). For practical examples: `kelora --help-examples`. For function reference: `kelora --help-functions`.

## Variables & Types

```rhai
let x = 42;                          // Integer (i64)
let price = 19.99;                   // Float (f64)
let name = "alice";                  // String (double quotes only!)
let active = true;                   // Boolean (true/false)
let tags = [1, "two", 3.0];          // Array (mixed types ok)
let user = #{name: "bob", age: 30};  // Map/object literal
let empty = ();                      // Unit type (Rhai's "nothing")

type_of(x)                           // Returns: "i64", "string", "array", "map", "()"
x = "hello";                         // Dynamic typing: can change type
```

**Key Points:**

- `let` required for new variables (no implicit declaration)
- Double quotes only for strings (`"text"` not `'text'`)
- Unit type `()` represents "nothing" (not null/undefined)
- Arrays and maps are reference types (modifying copies affects original)

## Operators

```rhai
// Arithmetic
a + b    a - b    a * b    a / b    a % b    a ** b  // power: 2**3 == 8

// Comparison
a == b   a != b   a < b    a > b    a <= b   a >= b

// Logical
a && b   a || b   !a

// Bitwise
a & b    a | b    a ^ b    a << b   a >> b

// Assignment
a = b    a += b   a -= b   a *= b   a /= b   a %= b
a &= b   a |= b   a ^= b   a <<= b  a >>= b

// Ranges (for loops only)
1..5     // Exclusive: 1, 2, 3, 4
1..=5    // Inclusive: 1, 2, 3, 4, 5

// Membership
"key" in map                         // Check if key exists
```

## Control Flow

### If-Else

```rhai
if x > 10 {
    print("big");
} else if x > 5 {
    print("medium");
} else {
    print("small");
}

// Ternary-style (if is an expression)
let category = if x > 10 { "big" } else { "small" };
```

### Switch

```rhai
let category = switch x {
    1 => "one",
    2 | 3 => "two or three",          // Multiple cases
    4..=6 => "four to six",            // Range matching
    _ => "other"                       // Default (underscore)
};
```

### Loops

```rhai
// Range loops
for i in 0..10 { print(i); }          // 0 to 9
for i in 0..=10 { print(i); }         // 0 to 10

// Array iteration
for item in array { print(item); }

// Map iteration
for (key, value) in map {
    print(`${key} = ${value}`);
}

// While loop
while condition {
    if done { break; }
    if skip { continue; }
}

// Infinite loop
loop {
    if should_stop { break; }
}
```

## Functions & Closures

```rhai
// Function definition
fn add(a, b) {
    a + b                             // Last expr is return value
}

fn greet(name) {
    return "Hello, " + name;          // Explicit return
}

// Closures
let double = |x| x * 2;
let add = |a, b| a + b;

// Closures in array methods
[1, 2, 3].map(|x| x * 2)              // [2, 4, 6]
[1, 2, 3].filter(|x| x > 1)           // [2, 3]
```

## Rhai Special Feature: Function-as-Method

Rhai allows calling any function as a method on its first argument:

```rhai
// These are equivalent:
extract_re(e.line, r"\d+")            // Function call style
e.line.extract_re(r"\d+")             // Method call style

// Use method style for chaining:
e.domain = e.url
    .extract_domain()
    .to_lower()
    .strip();

// Both styles work for all functions:
to_int(e.port)                        // Function style
e.port.to_int()                       // Method style (more readable)
```

## Kelora Event Access

The global variable `e` represents the current event in `--filter` and `--exec` stages:

```rhai
// Direct field access
e.level                               // Top-level field
e.user.name                           // Nested field (maps)
e.scores[1]                           // Array indexing (0-based)
e.scores[-1]                          // Negative indexing (last element)
e.headers["user-agent"]               // Bracket notation for special chars

// Field existence checking
"field" in e                          // Check top-level field exists
e.has_path("user.role")               // Check nested path exists

// Safe field access with defaults
e.get_path("user.role", "guest")      // Get nested with fallback
e.get_path("scores[0]", 0)            // Works with array paths

// Field removal
e.password = ()                       // Remove field (unit assignment)
e.ssn = ()                            // Remove another field
e = ()                                // Remove entire event (filtered out)
```

## Array & Map Operations

JSON arrays become native Rhai arrays with full functionality:

```rhai
// Array transformations
sorted(e.scores)                      // Sort numerically/lexicographically
reversed(e.items)                     // Reverse order
unique(e.tags)                        // Remove duplicates
dedup(e.values)                       // Remove consecutive duplicates
sorted_by(e.users, "age")             // Sort objects by field

// Array methods
e.tags.len()                          // Length
e.tags.is_empty()                     // Check if empty
e.tags.join(", ")                     // Join to string
e.scores.sum()                        // Sum numbers
e.scores.min()                        // Minimum value
e.scores.max()                        // Maximum value

// Array access patterns
if e.items.len() > 0 {
    e.first = e.items[0];
    e.last = e.items[-1];
}

// Fan-out: convert array elements to separate events
emit_each(e.items)                    // Each element becomes an event
emit_each(e.items, #{ctx: "value"})   // Add base fields to each

// Map operations
for (key, val) in e {
    print(`${key} = ${val}`);
}
```

## Type Conversions

```rhai
// Strict conversions (return () on error)
to_int(e.port)                        // String → integer
to_float(e.price)                     // String → float
to_bool(e.active)                     // String → boolean

// Safe conversions with defaults
to_int_or(e.port, 8080)               // Use default if conversion fails
to_float_or(e.price, 0.0)
to_bool_or(e.active, false)

// String conversions
to_string(42)                         // Any → string
e.value.to_int()                      // Method style

// Type checking
type_of(e.field)                      // Get type as string
type_of(e.field) != "()"              // Check if field has value
```

## Common Patterns

### Safe Nested Access

```rhai
// With default fallback
let role = e.get_path("user.role", "guest");
let port = to_int_or(e.port, 8080);

// With existence check
if e.has_path("user.profile.avatar") {
    e.avatar = e.user.profile.avatar;
}

// Safe array access
if e.items.len() > 0 {
    e.first_item = e.items[0];
}
```

### Conditional Field Removal

```rhai
// Remove debug fields in production
if e.level != "DEBUG" {
    e.stack_trace = ();
    e.debug_info = ();
}

// Remove entire event conditionally
if e.status < 400 { e = (); }         // Only keep errors
```

### Method Chaining

```rhai
// Extract and normalize domain
e.domain = e.url
    .extract_domain()
    .to_lower()
    .strip();

// Parse and extract from structured text
e.error_line = e.stack_trace
    .extract_re(r"line (\d+)", 1)
    .to_int_or(0);
```

### Array Processing

```rhai
// Get top N scores
e.top_3 = sorted(e.scores)[-3:];

// Extract names from sorted users
e.winners = sorted_by(e.users, "score")
    .reverse()
    .map(|u| u.name);

// Filter and count
e.active_items = e.items.filter(|i| i.status == "active");
e.active_count = e.active_items.len();
```

### Multi-Level Fan-Out

```rhai
# First exec: batches → separate events
--exec 'emit_each(e.batches)'

# Second exec: items → separate events with context
--exec 'let ctx = #{batch_id: e.id}; emit_each(e.items, ctx)'

# Filter the final events
--filter 'e.status == "active"'
```

## Global Context

```rhai
conf                                  // Global config map (read-only after --begin)
metrics                               // Global metrics map (from track_* calls)
meta                                  // Event metadata (filename, line numbers, raw line)
get_env("VAR", "default")             // Environment variable access

// meta attributes:
meta.line                             // Original raw line (always available)
meta.line_num                         // Line number, 1-based (available with files)
meta.filename                         // Source filename (multi-file processing)

// Example usage:
--begin 'conf.env = get_env("ENVIRONMENT", "dev")'
--filter 'conf.env == "prod" || e.level == "ERROR"'

// Multi-file tracking
--exec 'if e.level == "ERROR" { track_count(meta.filename) }'

// Debugging with line numbers
--exec 'eprint("Error at " + meta.filename + ":" + meta.line_num)'
```

## Error Handling Modes

**Default (resilient):**

- Parse errors → skip line, continue
- Filter errors → treat as false, drop event
- Exec errors → rollback, keep original event

**Strict mode (`--strict`):**

- Any error → abort immediately with exit code 1

## Rhai Quirks & Gotchas

| Coming from | Watch out for |
|------------|---------------|
| **JavaScript** | No `null`/`undefined` (use `()`), no single quotes, `let` required |
| **Python** | Braces required, no `:` after if/for, `!=` not `<>`, double quotes only |
| **Rust** | More permissive syntax, semicolons mostly optional, dynamic typing |

**Common mistakes:**

```rhai
// ❌ Wrong
x = 1                                 // Error: x not declared
let name = 'alice';                   // Error: single quotes not allowed
if x > 5: print("big")                // Error: colon not allowed, braces required
"5" + 3                               // Error: no implicit conversion

// ✅ Correct
let x = 1;                            // Declare with let
let name = "alice";                   // Double quotes
if x > 5 { print("big"); }            // Braces required
"5".to_int() + 3                      // Explicit conversion
```

**Special behaviors:**

- Last expression in block is return value (no `return` needed)
- Semicolons recommended but often optional
- Function calls without args: `e.len` same as `e.len()`
- No implicit type conversion (use `to_int()`, `to_float()`, etc.)

## Quick Reference

```rhai
// Event manipulation
e.field = value                       // Set field
e.field = ()                          // Remove field
e = ()                                // Remove event

// Type checking
type_of(e.field)                      // Get type
"field" in e                          // Field exists

// Safe access
e.get_path("a.b.c", default)          // Nested with fallback
e.has_path("a.b.c")                   // Check nested exists

// Conversions
to_int_or(val, 0)                     // Safe int conversion
to_float_or(val, 0.0)                 // Safe float conversion
to_bool_or(val, false)                // Safe bool conversion

// Arrays
e.items.len()                         // Length
e.items.is_empty()                    // Check empty
sorted(e.items)                       // Sort
unique(e.items)                       // Deduplicate
emit_each(e.items)                    // Fan out to events

// Strings
e.text.to_lower()                     // Lowercase
e.text.to_upper()                     // Uppercase
e.text.strip()                        // Trim whitespace
e.text.contains("word")               // Substring check
e.text.extract_re(r"(\d+)", 1)        // Regex extraction

// Environment & Context
get_env("VAR", "default")             // Get env var
conf.key                              // Read config (from --begin)
metrics.key                           // Read metrics (in --end)
meta.filename                         // Current source filename
meta.line_num                         // Current line number (1-based)
meta.line                             // Original raw line
```

## See Also

- [Scripting Transforms Tutorial]../tutorials/scripting-transforms.md - Detailed walkthrough with examples
- `kelora --help-functions` - Complete function catalogue
- `kelora --help-examples` - Practical log analysis patterns
- `kelora --help-rhai` - Language guide (this cheatsheet's source)
- [Rhai Documentation]https://rhai.rs - Full Rhai language reference