Merging in graft is designed to be intuitive. Files to merge are listed in-order on the command line. The first file serves as the base, and subsequent files are merged on top, adding new keys and replacing existing ones.
graft processes files through multiple phases to handle various operations:
The first file is loaded as the root document. Each subsequent file is merged on top, overwriting, appending, and deleting as specified.
Array operators are evaluated as each new document is merged. This allows greater control over how arrays are merged (append, prepend, insert, merge, replace).
If any (( prune )) operators are defined, the objects they apply to are marked for pruning but remain unmodified. No other operators are evaluated at this time.
In this phase, (( inject )) operators are evaluated to flesh out the root document.
Since (( inject )) happens after array operators, you cannot use array operators when overriding arrays provided via (( inject )). Currently, data will be appended to arrays.
The document is scanned for (( param )) operators. If any exist, it means a required property wasn't overridden by later files. graft will print errors for missing parameters and exit.
Unless --skip-eval is specified, graft scans for operators, generates a dependency graph, and evaluates them in order. Each operator modifies the document. All remaining operators are evaluated at this stage.
Any parts marked for pruning via (( prune )) operators or the --prune flag are deleted.
If --cherry-pick is specified, only the requested data structures are extracted from the document.
Any errors from the Eval Phase or Pruning/Cherry Picking are displayed. If successful, graft formats the document as YAML and outputs it.
Arrays require special handling because order matters. graft provides array operators to control merging:
(( append ))- Adds data to the end of the array(( prepend ))- Inserts data at the beginning(( insert ))- Inserts data at a specific position(( merge ))- Merges based on a common key(( inline ))- Merges based on array indices(( replace ))- Replaces the entire array(( delete ))- Deletes specific elements
Without explicit operators, arrays merge according to:
-
If all elements are objects with a
namekey,(( merge on name ))is implied. Elements with matching names are merged; new elements are appended. -
If merge-by-name isn't possible, an
(( inline ))merge is performed. With--fallback-append, an(( append ))merge is used instead.
Maps (objects) are merged recursively:
- New keys are added
- Existing keys have their values replaced or merged (if both are maps)
- Keys can be deleted with
nullvalues or(( delete ))operator
Operators are evaluated in dependency order:
- References
(( grab ))are resolved first - Dependent operators wait for their dependencies
- Circular dependencies cause errors
Environment variables can be used in references:
$VARsyntax in grab paths(( grab $ENV.path ))for dynamic references- Defaults with
||operator
# base.yml
name: my-app
port: 8080
features:
auth: true
cache: false
# override.yml
port: 9090
features:
cache: true
logging: true
# Result: graft merge base.yml override.yml
name: my-app
port: 9090
features:
auth: true
cache: true
logging: true# base.yml
servers:
- name: web-1
ip: 10.0.0.1
- name: web-2
ip: 10.0.0.2
# add-server.yml
servers:
- (( append ))
- name: web-3
ip: 10.0.0.3
# Result shows web-3 appended# config.yml
domain: example.com
url: (( concat "https://" domain ))
credentials:
password: (( vault "secret/app:password" ))-
Order files from general to specific - Base configurations first, environment-specific overrides last
-
Use explicit array operators - Don't rely on default behavior for critical merges
-
Validate parameters - Use
(( param ))for required values -
Organize operators - Group related operators together for clarity
-
Test merges - Use
graft diffto verify merge results
- Array Merging - Detailed array operator documentation
- Operators Reference - Complete operator list
- Environment Variables - Using environment variables