Skip to content

Commit 2dd81c4

Browse files
committed
chore: wip
1 parent c0a1c49 commit 2dd81c4

28 files changed

Lines changed: 5296 additions & 13 deletions

CONFIG_FEATURES.md

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
# Configuration System Documentation
2+
3+
## Overview
4+
5+
zig-cli now includes a powerful configuration system supporting three popular formats:
6+
- **TOML** - Simple, readable configuration format
7+
- **JSONC** - JSON with Comments (also handles standard JSON)
8+
- **JSON5** - JSON with extended syntax (more JavaScript-like)
9+
10+
## Features
11+
12+
### 1. Multiple Format Support
13+
14+
Each format has its own strengths:
15+
16+
**TOML:**
17+
- Simple, INI-like syntax
18+
- Great for human editing
19+
- Native support for nested tables
20+
- Comments with `#`
21+
22+
**JSONC:**
23+
- JSON with `//` and `/* */` comments
24+
- Trailing commas allowed
25+
- Familiar to JavaScript developers
26+
- Works with standard JSON files too
27+
28+
**JSON5:**
29+
- Unquoted object keys
30+
- Single and double quotes for strings
31+
- Trailing commas
32+
- Hexadecimal numbers (`0x755`)
33+
- Leading/trailing decimal points (`.5`, `50.`)
34+
- Special values: `Infinity`, `-Infinity`, `NaN`
35+
- Multi-line strings
36+
37+
### 2. Auto-discovery
38+
39+
The config system can automatically discover configuration files:
40+
41+
```zig
42+
var config = try cli.config.discover(allocator, "myapp");
43+
```
44+
45+
Search locations (in order):
46+
1. `./myapp.{toml,json5,jsonc,json}`
47+
2. `./.config/myapp.{toml,json5,jsonc,json}`
48+
3. `~/.config/myapp/myapp.{toml,json5,jsonc,json}`
49+
50+
First found file is loaded.
51+
52+
### 3. Type-safe Access
53+
54+
```zig
55+
// Typed getters with optional returns
56+
const name = config.getString("name"); // ?[]const u8
57+
const port = config.getInt("port"); // ?i64
58+
const debug = config.getBool("debug"); // ?bool
59+
const timeout = config.getFloat("timeout"); // ?f64
60+
61+
// Raw value access for complex types
62+
const value = config.get("database"); // ?*Value
63+
```
64+
65+
### 4. Nested Configuration
66+
67+
All formats support nested structures:
68+
69+
```toml
70+
[database]
71+
host = "localhost"
72+
port = 5432
73+
74+
[database.pool]
75+
size = 10
76+
timeout = 30
77+
```
78+
79+
```jsonc
80+
{
81+
"database": {
82+
"host": "localhost",
83+
"port": 5432,
84+
"pool": {
85+
"size": 10,
86+
"timeout": 30
87+
}
88+
}
89+
}
90+
```
91+
92+
### 5. Format Auto-detection
93+
94+
Auto-detect based on file extension:
95+
96+
```zig
97+
try config.loadFromFile("config.toml", .auto); // Detects TOML
98+
try config.loadFromFile("config.json5", .auto); // Detects JSON5
99+
try config.loadFromFile("config.jsonc", .auto); // Detects JSONC
100+
try config.loadFromFile("config.json", .auto); // Treats as JSONC
101+
```
102+
103+
Or specify explicitly:
104+
105+
```zig
106+
try config.loadFromFile("myfile", .toml);
107+
try config.loadFromFile("myfile", .jsonc);
108+
try config.loadFromFile("myfile", .json5);
109+
```
110+
111+
## Implementation Details
112+
113+
### Parser Architecture
114+
115+
Each format has its own dedicated parser:
116+
117+
1. **TomlParser.zig** (~240 lines)
118+
- Section-based parsing
119+
- Support for tables and arrays
120+
- String and numeric values
121+
- Comment handling
122+
123+
2. **JsoncParser.zig** (~330 lines)
124+
- Standard JSON parser
125+
- `//` and `/* */` comment support
126+
- Trailing comma support
127+
- Escape sequence handling
128+
129+
3. **Json5Parser.zig** (~420 lines)
130+
- Extended JSON syntax
131+
- Unquoted keys
132+
- Single-quoted strings
133+
- Hexadecimal numbers
134+
- Infinity/NaN support
135+
- More flexible number syntax
136+
137+
### Value Types
138+
139+
Unified `Value` type across all formats:
140+
141+
```zig
142+
pub const Value = union(enum) {
143+
null_value: void,
144+
boolean: bool,
145+
integer: i64,
146+
float: f64,
147+
string: []const u8,
148+
array: []Value,
149+
table: std.StringHashMap(Value),
150+
};
151+
```
152+
153+
### Config Manager
154+
155+
The `Config` type provides the high-level API:
156+
157+
```zig
158+
pub const Config = struct {
159+
allocator: std.mem.Allocator,
160+
data: std.StringHashMap(Value),
161+
162+
// Loading
163+
pub fn loadFromFile(path, format) !void
164+
pub fn loadFromString(content, format) !void
165+
pub fn discover(allocator, app_name) !Config
166+
167+
// Accessing
168+
pub fn get(key) ?*Value
169+
pub fn getString(key) ?[]const u8
170+
pub fn getInt(key) ?i64
171+
pub fn getBool(key) ?bool
172+
pub fn getFloat(key) ?f64
173+
174+
// Merging
175+
pub fn merge(other) !void
176+
};
177+
```
178+
179+
## Usage Examples
180+
181+
### Basic Usage
182+
183+
```zig
184+
const std = @import("std");
185+
const cli = @import("zig-cli");
186+
187+
pub fn main() !void {
188+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
189+
defer _ = gpa.deinit();
190+
const allocator = gpa.allocator();
191+
192+
// Load config
193+
var config = try cli.config.load(allocator, "config.toml");
194+
defer config.deinit();
195+
196+
// Use config values
197+
const port = config.getInt("port") orelse 8080;
198+
const host = config.getString("host") orelse "localhost";
199+
200+
std.debug.print("Server: {s}:{d}\n", .{host, port});
201+
}
202+
```
203+
204+
### With CLI Integration
205+
206+
```zig
207+
fn serverAction(ctx: *cli.Command.ParseContext) !void {
208+
const allocator = ctx.allocator;
209+
210+
// Load config
211+
var config = cli.config.discover(allocator, "myserver") catch |err| {
212+
if (err == error.FileNotFound) {
213+
// No config file, use defaults
214+
std.debug.print("No config found, using defaults\n", .{});
215+
return;
216+
}
217+
return err;
218+
};
219+
defer config.deinit();
220+
221+
// CLI options override config
222+
const port = if (ctx.getOption("port")) |p|
223+
try std.fmt.parseInt(u16, p, 10)
224+
else
225+
@intCast(config.getInt("port") orelse 8080);
226+
227+
std.debug.print("Starting server on port {d}\n", .{port});
228+
}
229+
```
230+
231+
### Config with Nested Values
232+
233+
```zig
234+
var config = try cli.config.load(allocator, "config.toml");
235+
defer config.deinit();
236+
237+
// Access nested database config
238+
if (config.get("database")) |db_value| {
239+
if (db_value.* == .table) {
240+
const db_table = &db_value.table;
241+
242+
const host = if (db_table.get("host")) |h|
243+
if (h == .string) h.string else "localhost"
244+
else
245+
"localhost";
246+
247+
const port = if (db_table.get("port")) |p|
248+
if (p == .integer) p.integer else 5432
249+
else
250+
5432;
251+
252+
std.debug.print("Database: {s}:{d}\n", .{host, port});
253+
}
254+
}
255+
```
256+
257+
## File Examples
258+
259+
See `examples/configs/` for complete examples:
260+
261+
- `example.toml` - TOML with all features
262+
- `example.jsonc` - JSONC with comments and trailing commas
263+
- `example.json5` - JSON5 with extended syntax
264+
265+
Run `examples/config.zig` for a live demonstration.
266+
267+
## Performance Considerations
268+
269+
- **Parsing is done once at startup** - negligible impact
270+
- **No runtime overhead** - parsed values are stored in memory
271+
- **Memory efficient** - uses arena allocation for config data
272+
- **File size limits** - 10MB default maximum (configurable)
273+
274+
## Error Handling
275+
276+
All config operations use Zig error unions:
277+
278+
```zig
279+
pub const ParseError = error{
280+
UnexpectedEndOfFile,
281+
InvalidSyntax,
282+
InvalidEscape,
283+
InvalidNumber,
284+
InvalidUnicode,
285+
OutOfMemory,
286+
};
287+
```
288+
289+
Errors are descriptive and can be handled gracefully:
290+
291+
```zig
292+
var config = cli.config.load(allocator, "config.toml") catch |err| {
293+
switch (err) {
294+
error.FileNotFound => {
295+
std.debug.print("Config not found, using defaults\n", .{});
296+
// Continue with defaults
297+
},
298+
error.InvalidSyntax => {
299+
std.debug.print("Config file has invalid syntax\n", .{});
300+
return err;
301+
},
302+
else => return err,
303+
}
304+
};
305+
```
306+
307+
## Testing
308+
309+
Test the config system:
310+
311+
```bash
312+
# Run config example
313+
zig build-exe examples/config.zig --dep zig-cli --mod zig-cli src/root.zig
314+
./config
315+
```
316+
317+
## Future Enhancements
318+
319+
Potential additions:
320+
- [ ] YAML support
321+
- [ ] Environment variable expansion
322+
- [ ] Config validation schemas
323+
- [ ] Hot-reload support
324+
- [ ] Config migration tools
325+
- [ ] Deep merge for complex configs
326+
- [ ] Dotted path access (`database.host`)
327+
328+
## Summary
329+
330+
The configuration system adds approximately:
331+
- **~1,200 lines of code** across 5 files
332+
- **3 format parsers** with full feature support
333+
- **Type-safe API** for accessing configuration
334+
- **Auto-discovery** for easy setup
335+
- **Zero runtime dependencies** - pure Zig stdlib
336+
337+
This brings zig-cli to feature parity with popular CLI frameworks while maintaining Zig's philosophy of explicitness and safety.

0 commit comments

Comments
 (0)