Skip to content

Commit

Permalink
binding access in dynaml
Browse files Browse the repository at this point in the history
  • Loading branch information
mandelsoft committed Mar 18, 2021
1 parent ef36d4c commit 005a5fe
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 21 deletions.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ Contents:
- [Scope References](#scope-references)
- [_](#_)
- [__](#__)
- [___](#___)
- [__ctx.OUTER](#__ctxouter)
- [Special Literals](#special-literals)
- [Access to evaluation context](#access-to-evaluation-context)
Expand Down Expand Up @@ -4779,6 +4780,48 @@ result:
bob: static
```


### `___`

The special reference `___` can be used to lookup references in the outer most
scope. It can therefore be used to access processing bindings specified for a
document processing via command line or API. If no bindings are specified
the document root is used.

Calling `spiff merge template.yaml --bindings bindings.yaml` with a binding of

**bindings.yaml**
```yaml
input1: binding1
input2: binding2
```

and the template

**template.yaml**
```yaml
input1: top1
map:
input: map
input1: map1
results:
frommap: (( input1 ))
fromroot: (( .input1 ))
frombinding1: (( ___.input1 ))
frombinding2: (( input2 ))
```

evaluates `map.results` to

```yaml
results:
frombinding1: binding1
frombinding2: binding2
frommap: map1
fromroot: top1
```

### `__ctx.OUTER`

The context field `OUTER` is used for nested [merges](#-mergemap1-map2-).
Expand Down Expand Up @@ -4831,6 +4874,9 @@ The following fields are supported:
| `PATHNAME` | string | path name of actually processed field |
| `PATH` | list[string] | path name as component list |
| `OUTER` | yaml doc | outer documents for nested [merges](#-mergemap1-map2-), index 0 is the next outer document |
| `BINDINGS` | yaml doc | the external bindings for the actual processing (see also [___](#___)) |

If external bindings are specified they are the last elements in `OUTER`.

e.g.:

Expand Down
78 changes: 57 additions & 21 deletions flow/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ type DefaultEnvironment struct {
static map[string]yaml.Node
outer dynaml.Binding

active bool
active bool
binding bool
}

func keys(s map[string]yaml.Node) string {
Expand Down Expand Up @@ -146,24 +147,35 @@ func (e DefaultEnvironment) FindFromRoot(path []string) (yaml.Node, bool) {
return yaml.FindR(true, yaml.NewNode(e.scope.root.local, "scope"), path...)
}

func FindInScopes(nodescope *Scope, path []string) (yaml.Node, bool) {
if len(path) > 0 {
scope := nodescope
for scope != nil {
val := scope.local[path[0]]
if val != nil {
return yaml.FindR(true, val, path[1:]...)
}
scope = scope.next
}
return nil, false
}
return yaml.FindR(true, node(nodescope.local), path...)
}

func (e DefaultEnvironment) FindReference(path []string) (yaml.Node, bool) {
root, found, nodescope := resolveSymbol(&e, path[0], e.scope)
if !found {
if path[0] == yaml.ROOT {
var outer dynaml.Binding = e
for outer.Outer() != nil {
outer = outer.Outer()
}
return yaml.FindR(true, node(outer.GetRootBinding()), path[1:]...)
}
//fmt.Printf("FIND %s: %s\n", strings.Join(path,"."), e)
//fmt.Printf("FOUND %s: %v\n", strings.Join(path,"."), keys(nodescope))
if path[0] == yaml.DOCNODE && nodescope != nil {
if len(path) > 1 {
scope := nodescope
for scope != nil {
val := scope.local[path[1]]
if val != nil {
return yaml.FindR(true, val, path[2:]...)
}
scope = scope.next
}
return nil, false
}
return yaml.FindR(true, node(nodescope.local), path[1:]...)
return FindInScopes(nodescope, path[1:])
}
if e.outer != nil {
return e.outer.FindReference(path)
Expand Down Expand Up @@ -290,7 +302,7 @@ func NewEnvironment(stubs []yaml.Node, source string, optstate ...*State) dynaml
if state == nil {
state = NewState(os.Getenv("SPIFF_ENCRYPTION_KEY"), MODE_OS_ACCESS|MODE_FILE_ACCESS)
}
return DefaultEnvironment{state: state, stubs: stubs, sourceName: source, currentSourceName: source, outer: nil, active: true}
return DefaultEnvironment{state: state, stubs: stubs, sourceName: source, currentSourceName: source, outer: nil, active: true, binding: true}
}

func NewProcessLocalEnvironment(stubs []yaml.Node, source string) dynaml.Binding {
Expand Down Expand Up @@ -379,6 +391,33 @@ func resolveSymbol(env *DefaultEnvironment, name string, scope *Scope) (yaml.Nod
return nil, false, nodescope
}

func getOuters(env *DefaultEnvironment) (yaml.Node, yaml.Node) {
var bindings dynaml.Binding
var list []yaml.Node

if outer := env.Outer(); outer != nil {
for outer != nil {
if e, ok := outer.(DefaultEnvironment); ok && e.binding {
bindings = outer
}
list = append(list, node(outer.GetRootBinding()))
outer = outer.Outer()
}
}
if list == nil {
if bindings != nil {
return nil, node(bindings.GetRootBinding())
} else {
return nil, yaml.NewNode([]map[string]interface{}{}, "context")
}
}
if bindings != nil {
return node(list), node(bindings.GetRootBinding())
} else {
return node(list), yaml.NewNode([]map[string]interface{}{}, "context")
}
}

func createContext(env *DefaultEnvironment) yaml.Node {
ctx := make(map[string]yaml.Node)

Expand All @@ -403,14 +442,11 @@ func createContext(env *DefaultEnvironment) yaml.Node {
path[i] = node(v)
}
ctx["STUBPATH"] = node(path)
if outer := env.Outer(); outer != nil {
list := []yaml.Node{}
for outer != nil {
list = append(list, node(outer.GetRootBinding()))
outer = outer.Outer()
}
ctx["OUTER"] = node(list)
list, bindings := getOuters(env)
if list != nil {
ctx["OUTER"] = list
}
ctx["BINDINGS"] = bindings
return node(ctx)
}

Expand Down
42 changes: 42 additions & 0 deletions spiffing/spiffing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,46 @@ var _ = Describe("Spiffing", func() {
Expect(string(result)).To(Equal("- 25\n- 26\n"))
})
})

Context("Bindings", func() {
ctx, err := New().WithValues(map[string]interface{}{
"values": map[string]interface{}{
"alice": 25,
"bob": 26,
},
})
Expect(err).To(Succeed())

It("Handles simple bindings", func() {
templ, err := ctx.Unmarshal("test", []byte(`
data: (( values.alice ))
`))
Expect(err).To(Succeed())
result, err := ctx.Cascade(templ, nil)
Expect(err).To(Succeed())
data, err := ctx.Marshal(result)
Expect(err).To(Succeed())
Expect(string(data)).To(Equal(
`data: 25
`))
})

It("Handles override bindings", func() {
templ, err := ctx.Unmarshal("test", []byte(`
values: other
data: (( ___.values.alice ))
orig: (( values ))
`))
Expect(err).To(Succeed())
result, err := ctx.Cascade(templ, nil)
Expect(err).To(Succeed())
data, err := ctx.Marshal(result)
Expect(err).To(Succeed())
Expect(string(data)).To(Equal(
`data: 25
orig: other
values: other
`))
})
})
})
1 change: 1 addition & 0 deletions yaml/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

const SELF = "_"
const DOCNODE = "__"
const ROOT = "___"
const MERGEKEY = "<<<"

type RefResolver interface {
Expand Down

0 comments on commit 005a5fe

Please sign in to comment.