Skip to content

Commit b7c9e78

Browse files
committed
doc: improve README
Better examples and explain more clearly what exactly all of this does and what it's useful for. Signed-off-by: Simon de Vlieger <cmdr@supakeen.com>
1 parent 473c2c4 commit b7c9e78

1 file changed

Lines changed: 149 additions & 24 deletions

File tree

README.md

Lines changed: 149 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,181 @@
1-
# `yamlplus`
1+
# yamlplus
22

3-
This is a small Go library that processes the Abstract Syntax Tree (AST) offered by `go.yaml.in/yaml/v3` to extend it with tags.
3+
A Go library that extends YAML with cross-file references, allowing you to split large configuration files into smaller, reusable pieces.
44

5-
## Usage
5+
## Features
6+
7+
- Reference YAML anchors across multiple files using the `!xref` tag
8+
- Support for standard YAML map merges with cross-file references
9+
- Load files individually, by directory, or recursively
10+
- Built on top of `go.yaml.in/yaml/v3`
11+
- Circular dependency detection
12+
13+
## Installation
14+
15+
```bash
16+
go get github.com/supakeen/yamlplus
17+
```
18+
19+
## Quick Start
20+
21+
Given two YAML files:
22+
23+
**database.yaml:**
24+
```yaml
25+
connection: &db-config
26+
host: localhost
27+
port: 5432
28+
timeout: 30s
29+
```
30+
31+
**app.yaml:**
32+
```yaml
33+
service:
34+
name: myapp
35+
database: !xref "database.yaml#db-config"
36+
```
37+
38+
Load and unmarshal them:
639
740
```go
841
package main
942

1043
import (
44+
"fmt"
1145
"os"
12-
1346
"github.com/supakeen/yamlplus"
1447
)
1548

1649
func main() {
17-
loader := yamlplus.NewLoader(os.DirFS("somepath"))
50+
loader := yamlplus.NewLoader(os.DirFS("config"))
51+
loader.RegisterFile("database.yaml")
52+
53+
var config map[string]any
54+
data, _ := os.ReadFile("config/app.yaml")
55+
loader.Unmarshal(data, &config)
56+
57+
// Access the cross-referenced database configuration
58+
service := config["service"].(map[string]any)
59+
db := service["database"].(map[string]any)
60+
fmt.Printf("Database: %s:%d\n", db["host"], db["port"])
61+
}
62+
```
1863

19-
_ = loader.RegisterFile("one.yaml")
20-
_ = loader.RegisterFile("two.yaml")
64+
## Reference Syntax
2165

22-
_ = loader.RegisterDirectory("dir")
66+
The `!xref` tag supports two forms:
2367

24-
_ = loader.RegisterRecursively("dir")
68+
### Reference a specific anchor
2569

26-
var output map[string]any
70+
```yaml
71+
config: !xref "filename.yaml#anchorname"
72+
```
2773
28-
_ = loader.Unmarshal([]byte(`one: !xref "one.yaml#anchor"`), &output)
29-
}
74+
### Reference an entire file
75+
76+
```yaml
77+
config: !xref "filename.yaml"
3078
```
3179
32-
## Tags
80+
When referencing a file without an anchor, the first document in the file is used.
81+
82+
## Map Merges
3383
34-
### `!xref`
84+
Cross-file references work with YAML's map merge syntax:
3585
36-
The `!xref` tag allows for cross-referencing anchors in other YAML files.
86+
**defaults.yaml:**
87+
```yaml
88+
defaults: &api-defaults
89+
timeout: 30s
90+
retries: 3
91+
log_level: info
92+
```
3793
94+
**service.yaml:**
3895
```yaml
39-
config:
40-
port: &port 3306
96+
production:
97+
<<: !xref "defaults.yaml#api-defaults"
98+
timeout: 60s # Override the default
99+
endpoint: /api/v1
41100
```
42101
102+
Result after unmarshaling:
43103
```yaml
44-
other_config:
45-
port: !xref config.yaml#port
104+
production:
105+
timeout: 60s
106+
retries: 3
107+
log_level: info
108+
endpoint: /api/v1
109+
```
110+
111+
## Loading Files
112+
113+
### Load individual files
114+
115+
```go
116+
loader := yamlplus.NewLoader(os.DirFS("config"))
117+
loader.RegisterFile("base.yaml")
118+
loader.RegisterFile("database.yaml")
46119
```
47120

48-
The syntax is `!xref` followed by the filename of the file the anchor appears in, a `#` and then the name of the anchor. You can reference an entire file by omitting the `#anchor` part. When referencing an entire file but that file contains multiple documents the first one will be used implicitly.
121+
### Load all YAML files in a directory
49122

50-
`!xref` is also available for map merges:
123+
```go
124+
loader.RegisterDirectory("configs")
125+
```
126+
127+
This loads all `.yaml` and `.yml` files in the directory (non-recursive).
128+
129+
### Load files recursively
130+
131+
```go
132+
loader.RegisterRecursively("configs")
133+
```
134+
135+
This walks the directory tree and loads all YAML files.
136+
137+
## Path-Based Namespacing
138+
139+
Files are registered and referenced using their exact path relative to the filesystem root:
140+
141+
```go
142+
loader := yamlplus.NewLoader(os.DirFS("/etc"))
143+
loader.RegisterFile("app/config.yaml")
144+
145+
// Must use the same path in references:
146+
data := []byte(`settings: !xref "app/config.yaml"`)
147+
```
148+
149+
## Circular Dependency Detection
51150

151+
The library detects circular references and returns an error:
152+
153+
**a.yaml:**
154+
```yaml
155+
value: !xref "b.yaml"
156+
```
157+
158+
**b.yaml:**
52159
```yaml
53-
config:
54-
<<: !xref base.yaml#config
55-
port: 3306
160+
value: !xref "a.yaml"
161+
```
162+
163+
Attempting to unmarshal will result in an error: `circular dependency detected`.
164+
165+
## Thread Safety
166+
167+
A `Loader` is safe for concurrent `Unmarshal` calls after all files have been registered. However, `RegisterFile`, `RegisterDirectory`, and `RegisterRecursively` should not be called concurrently with each other or with `Unmarshal`.
168+
169+
## Examples
170+
171+
See the [examples in the documentation](https://pkg.go.dev/github.com/supakeen/yamlplus#pkg-examples) for more usage patterns.
172+
173+
## Testing
174+
175+
```bash
176+
go test ./...
56177
```
178+
179+
## License
180+
181+
MIT

0 commit comments

Comments
 (0)