-
-
Notifications
You must be signed in to change notification settings - Fork 36
/
enrich.go
227 lines (183 loc) · 5.89 KB
/
enrich.go
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
package datamodel
import (
"strings"
"cuelang.org/go/cue"
"github.com/hofstadter-io/hof/lib/hof"
)
func (dm *Datamodel) EnrichValue() error {
// fmt.Println("dm.EnrichValue()", dm.Hof.Path)
// val := dm.Value()
// build a hof.Node tree from the gen.CueValue
//nodes, err := hof.FindHofs(val)
//if err != nil {
//return err
//}
//nodes := dm.Node
// fmt.Println(G.Hof.Path, len(dms), len(dms[0].Node.T.History()), len(gNs))
// assert that there is only 1 gN
//if len(nodes) != 1 {
// return fmt.Errorf("%s at %s created multiple $hof Nodes, you should not mix things like generators and datamodels withing the (exactly) same value, nesting is ok", dm.Hof.Label, dm.Hof.Path)
//}
//node := nodes[0]
// gN.Print()
err := dm.enrichR(dm.Node)
if err != nil {
return err
}
return nil
}
func (dm *Datamodel) enrichR(hn *hof.Node[Value]) error {
// recursion into children
for _, c := range hn.Children {
err := dm.enrichR(c)
if err != nil {
return err
}
}
// we do all the real work post-order recursion
// so that parent enrichments include child enrichments
// fmt.Println("DM.enrichR", dm.Hof.Path, hn.Hof.Path)
// here, we need to inject any datamodel history(s)
// this is going to need some fancy, recursive processing
// so we will call out to a helper of some kind
if hn.Hof.Datamodel.History {
err := dm.enrichHistory(hn)
if err != nil {
return err
}
}
// here we create an ordered version of the node at the same level
//if hn.Hof.Datamodel.Ordered {
// err := dm.enrichOrdered(hn)
// if err != nil {
// return err
// }
//}
return nil
}
func (dm *Datamodel) enrichHistory(hn *hof.Node[Value]) error {
// if G.Verbosity > 0 {
// fmt.Println("found @history at: ", hn.Hof.Path, hn.Hof.Metadata.ID, dm.Hof.Path, dm.Hof.Metadata.ID)
// }
// We want to walk the root node tree to find where it aligns with the current hn.
// In this way, we can write code that is ignorant of where in the node tree it is.
match := findHistoryMatchR(hn, dm.Node)
// this should not happen because we already verified that we are in the root
// so return an error
if match == nil {
// TODO return error
// fmt.Println(" no match found")
return nil
}
// get & check history
hist := match.T.History()
// if G.Verbosity > 0 {
// fmt.Println("injecting hist at: ", hn.Hof.Metadata.ID, match.Hof.Metadata.ID, len(hist), hist[0].Timestamp)
// }
// build up the label
p := hn.Hof.Path
// trim the datamodel label since we are already in there via G.Value
start := dm.Hof.Label + "."
p = strings.TrimPrefix(p, start)
// This is the current snapshot, outside the history object
// Inject the CurrDiff, if the model is dirty
if match.T.Snapshot.Lense.CurrDiff.Exists() {
s, err := snapshotToData(match.T.Snapshot)
if err != nil {
return err
}
dm.Value = dm.Value.FillPath(cue.ParsePath(p+".Snapshot"), s)
}
// fmt.Println(start, p)
// Datafy each snapshot
snaps := []any{}
for _, h := range hist {
s, err := snapshotToData(h)
if err != nil {
return err
}
snaps = append(snaps, s)
}
// Inject the value at the current path as "History" list
p += ".History"
// XXX TODO XXX inject refrence rather than value
dm.Value = dm.Value.FillPath(cue.ParsePath(p), snaps)
// fmt.Println(G.CueValue)
return nil
}
func findHistoryMatchR(hn *hof.Node[Value], root *hof.Node[Value]) *hof.Node[Value] {
// are we currently there?
// TODO: make this check better
// current limitation is no shared names in the same datamodel tree
// (ID really, but when not set, then ID = name)
// so this could suffice for a while if we tell users to set the ID in this case
// maybe we can just force this by having a check somewhere during loading
if root.Hof.Metadata.ID == hn.Hof.Metadata.ID {
return root
}
// recurse if not there yet, we fully unwind on first match
// (which is where the naming issue above comes from)
for _, c := range root.Children {
m := findHistoryMatchR(hn, c)
if m != nil {
return m
}
}
return nil
}
func snapshotToData(snap *Snapshot) (any, error) {
s := make(map[string]any)
// true for history snapshot entries
// false for the snapshot we use on the current value to hold the current diff (dirty datamodel)
if snap.Timestamp != "" {
s["Timestamp"] = snap.Timestamp
s["Pos"] = snap.Pos
s["Data"] = snap.Data
}
// check to see if this snapshot has a diff
// (true for all but the "first" (in time)
if snap.Lense.CurrDiff.Exists() {
// TODO, add more diff types & formats here
s["CurrDiff"] = snap.Lense.CurrDiff
s["PrevDiff"] = snap.Lense.PrevDiff
}
return s, nil
}
// the point of this is to have a stable order for cue values specified as a struct
// since they get turned into Go maps, which have random order during iteration
// generated code can shift around while being the "same"
// This is where we auto-fill from @ordered(), but users can also do this manually
// Note | XXX, CUE's order may change between versions, they are working towards defining a stable order
// at which point we will use the same for consistency. We should be backwards compatible at this point
// but there is risk until then
func (dm *Datamodel) enrichOrdered(hn *hof.Node[any]) error {
// if G.Verbosity > 0 {
// fmt.Println("found @ordered at: ", hn.Hof.Path)
// }
path := hn.Hof.Path
path = strings.TrimPrefix(path, dm.Hof.Metadata.Name + ".")
value := dm.Value.LookupPath(cue.ParsePath(path))
iter, err := value.Fields()
if err != nil {
return err
}
var ordered []cue.Value
for iter.Next() {
sel := iter.Selector().String()
if sel == "#hof" {
continue
}
if sel == "$hof" {
continue
}
val := iter.Value()
ordered = append(ordered, val)
// fmt.Printf("%s: %v\n", sel, iter.Value())
}
// fmt.Printf("%# v\n", val)
// construct new ordered list for fields
l := value.Context().NewList(ordered...)
// fill into Gen value
dm.Value = dm.Value.FillPath(cue.ParsePath(path + "Ordered"), l)
return nil
}