• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

asciimoth / pasta / 26626415405

29 May 2026 08:18AM UTC coverage: 77.855% (-0.3%) from 78.117%
26626415405

push

github

asciimoth
fix(std): fix formular menu handling by some nodes

20 of 72 new or added lines in 12 files covered. (27.78%)

1 existing line in 1 file now uncovered.

5467 of 7022 relevant lines covered (77.86%)

323.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.57
/pasta/std/node_select.go
1
package std
2

3
import (
4
        "github.com/asciimoth/configer/configer"
5
        "github.com/asciimoth/formular"
6
        "github.com/asciimoth/pasta/pasta"
7
)
8

9
// NodeTypeSelect is the class name for SelectClass.
10
const NodeTypeSelect = "pasta/Select"
11

12
// SelectClass creates a bidirectional selector node.
13
//
14
// Selector is a pasta/bool input. In 0, In 1, and Out start as any/any data
15
// ports. The first typed link attached to a data port derives one shared data
16
// type for all three data ports and the node primary type. When Selector is
17
// false, events are relayed between In 0 and Out; when true, events are relayed
18
// between In 1 and Out. When Select needs the active input value again, it sends
19
// RequestValue over both sides of the active data path. Payloads implementing
20
// ClosablePayload are tracked while passing through the active path and closed
21
// when Select switches to another path.
22
type SelectClass struct{}
23

24
func (SelectClass) ClassName() string        { return NodeTypeSelect }
198✔
25
func (SelectClass) ShortDescription() string { return "Select one of two inputs" }
179✔
26
func (SelectClass) LongDescription() string {
×
27
        return "Relays events between Out and In 0 when Selector is false, or between Out and In 1 when Selector is true. The data ports derive a shared type from the first typed data link."
×
28
}
×
29
func (SelectClass) DefaultNodeParams() pasta.NodeClassParams {
204✔
30
        return pasta.NodeClassParams{InitialPorts: []pasta.Port{
204✔
31
                {Direction: "right", Name: "Out", Types: []string{pasta.AnyType}},
204✔
32
                {Direction: "left", Name: "Selector", Types: []string{TypeBool}},
204✔
33
                {Direction: "left", Name: "In 0", Types: []string{pasta.AnyType}},
204✔
34
                {Direction: "left", Name: "In 1", Types: []string{pasta.AnyType}},
204✔
35
        }}
204✔
36
}
204✔
37
func (SelectClass) NewNode(_ configer.Config, previous ...*pasta.NodeClassState) (pasta.Node, error) {
2✔
38
        n := newSelectNode()
2✔
39
        if state := firstState(previous); state != nil {
2✔
40
                n.dataType = state.PrimaryType
×
41
        }
×
42
        return n, nil
2✔
43
}
44

45
type selectNode struct {
46
        pasta.BasicNode
47

48
        selector bool
49
        dataType string
50

51
        w  *pasta.Workspace
52
        id uint64
53

54
        out          uint64
55
        selectorPort uint64
56
        in0          uint64
57
        in1          uint64
58

59
        payloads []ClosablePayload
60
}
61

62
func newSelectNode() *selectNode {
2✔
63
        return &selectNode{}
2✔
64
}
2✔
65

66
func (n *selectNode) OnInit(w *pasta.Workspace, _ pasta.Logger, id uint64, _ string, restored *pasta.NodeInitData, _, _, _, _ bool) error {
2✔
67
        n.w = w
2✔
68
        n.id = id
2✔
69
        if restored != nil {
4✔
70
                for _, port := range restored.RightPorts {
4✔
71
                        snapshot, ok := w.PortSnapshot(port)
2✔
72
                        if ok && snapshot.Name == "Out" {
4✔
73
                                n.out = port
2✔
74
                        }
2✔
75
                }
76
                for _, port := range restored.LeftPorts {
8✔
77
                        snapshot, ok := w.PortSnapshot(port)
6✔
78
                        if !ok {
6✔
79
                                continue
×
80
                        }
81
                        switch snapshot.Name {
6✔
82
                        case "Selector":
2✔
83
                                n.selectorPort = port
2✔
84
                        case "In 0":
2✔
85
                                n.in0 = port
2✔
86
                        case "In 1":
2✔
87
                                n.in1 = port
2✔
88
                        }
89
                }
90
        }
91
        if n.dataType != "" {
2✔
92
                if err := n.applyDataType(n.dataType); err != nil {
×
93
                        return err
×
94
                }
×
95
        }
96
        if err := n.updateLabel(); err != nil {
2✔
97
                return err
×
98
        }
×
99
        n.sendMenuSnapshot()
2✔
100
        return nil
2✔
101
}
102

103
func (n *selectNode) OnReady() error {
2✔
104
        n.requestActivePath()
2✔
105
        return nil
2✔
106
}
2✔
107

108
func (n *selectNode) PreLinkAdd(port uint64, linkType, portDirection string) error {
9✔
109
        if portDirection == "left" {
16✔
110
                snapshot, ok := n.w.PortSnapshot(port)
7✔
111
                if ok && len(snapshot.Links) > 0 {
7✔
112
                        return pasta.ErrLinkDup
×
113
                }
×
114
        }
115
        if port == n.selectorPort {
12✔
116
                if linkType != TypeBool {
3✔
NEW
117
                        return pasta.LinkTypeErr(linkType)
×
118
                }
×
119
                return nil
3✔
120
        }
121
        if !n.isDataPort(port) {
6✔
NEW
122
                return pasta.LinkTypeErr(linkType)
×
123
        }
×
124
        if n.dataType != "" && linkType != n.dataType && linkType != pasta.AnyType {
6✔
NEW
125
                return pasta.LinkTypeErr(linkType)
×
126
        }
×
127
        return nil
6✔
128
}
129

130
func (n *selectNode) OnLinkAdd(link uint64, port uint64, linkType, _ string) error {
9✔
131
        if n.isDataPort(port) && n.dataType == "" && linkType != pasta.AnyType {
11✔
132
                if err := n.applyDataType(linkType); err != nil {
2✔
133
                        return err
×
134
                }
×
135
        }
136
        if port == n.selectorPort || port == n.activeInput() {
14✔
137
                n.requestLink(link, port)
5✔
138
        }
5✔
139
        if port == n.out {
11✔
140
                n.requestActivePath()
2✔
141
        }
2✔
142
        return nil
9✔
143
}
144

145
func (n *selectNode) OnLinkRemoved(_ uint64, port uint64, _ string, _ string) error {
1✔
146
        if port == n.activeInput() {
1✔
147
                n.requestActivePath()
×
148
        }
×
149
        return nil
1✔
150
}
151

152
func (n *selectNode) OnEvent(event pasta.Event, linkType string, _ []string, receiverPortDirection string) error {
18✔
153
        if event.ReceiverPort == n.selectorPort {
24✔
154
                value, ok := event.Payload.(bool)
6✔
155
                if !ok {
6✔
156
                        return nil
×
157
                }
×
158
                changed := n.selector != value
6✔
159
                n.selector = value
6✔
160
                _ = n.updateLabel()
6✔
161
                n.sendMenuBlock()
6✔
162
                if changed {
8✔
163
                        n.closePayloads()
2✔
164
                        n.requestActivePath()
2✔
165
                }
2✔
166
                return nil
6✔
167
        }
168
        if n.isDataPort(event.ReceiverPort) && n.dataType == "" && linkType != pasta.AnyType {
12✔
169
                if err := n.applyDataType(linkType); err != nil {
×
170
                        return err
×
171
                }
×
172
        }
173
        if receiverPortDirection == "left" {
23✔
174
                if event.ReceiverPort == n.activeInput() {
20✔
175
                        n.trackPayload(event.Payload)
9✔
176
                        n.forwardToOut(event.Payload)
9✔
177
                }
9✔
178
                return nil
11✔
179
        }
180
        if event.ReceiverPort == n.out {
2✔
181
                n.trackPayload(event.Payload)
1✔
182
                n.forwardToActiveInput(event.Payload)
1✔
183
        }
1✔
184
        return nil
1✔
185
}
186

187
func (n *selectNode) applyDataType(typ string) error {
2✔
188
        n.dataType = typ
2✔
189
        if err := n.w.SetNodePrimary(n.id, typ); err != nil {
2✔
190
                return err
×
191
        }
×
192
        for _, port := range []uint64{n.in0, n.in1, n.out} {
8✔
193
                if port == 0 {
6✔
194
                        continue
×
195
                }
196
                if err := n.w.SetPortTypes(port, []string{typ}); err != nil {
6✔
197
                        return err
×
198
                }
×
199
        }
200
        return nil
2✔
201
}
202

203
func (n *selectNode) activeInput() uint64 {
25✔
204
        if n.selector {
29✔
205
                return n.in1
4✔
206
        }
4✔
207
        return n.in0
21✔
208
}
209

210
func (n *selectNode) isDataPort(port uint64) bool {
27✔
211
        return port == n.in0 || port == n.in1 || port == n.out
27✔
212
}
27✔
213

214
func (n *selectNode) requestActivePath() {
6✔
215
        n.requestPort(n.activeInput())
6✔
216
        n.requestPort(n.out)
6✔
217
}
6✔
218

219
func (n *selectNode) requestPort(port uint64) {
12✔
220
        snapshot, ok := n.w.PortSnapshot(port)
12✔
221
        if !ok || len(snapshot.Links) == 0 {
18✔
222
                return
6✔
223
        }
6✔
224
        for _, link := range snapshot.Links {
12✔
225
                n.requestLink(link, port)
6✔
226
        }
6✔
227
}
228

229
func (n *selectNode) requestLink(link, port uint64) {
11✔
230
        linkSnapshot, ok := n.w.LinkSnapshot(link)
11✔
231
        if !ok {
11✔
232
                return
×
233
        }
×
234
        receiverNode, receiverPort := otherEndpoint(linkSnapshot, port)
11✔
235
        n.w.SendEvent(pasta.Event{SenderNode: n.id, SenderPort: port, ReceiverNode: receiverNode, ReceiverPort: receiverPort, Payload: RequestValue{}})
11✔
236
}
237

238
func (n *selectNode) forwardToOut(payload any) {
9✔
239
        snapshot, ok := n.w.PortSnapshot(n.out)
9✔
240
        if !ok {
9✔
241
                return
×
242
        }
×
243
        for _, link := range snapshot.Links {
14✔
244
                linkSnapshot, ok := n.w.LinkSnapshot(link)
5✔
245
                if !ok {
5✔
246
                        continue
×
247
                }
248
                receiverNode, receiverPort := otherEndpoint(linkSnapshot, n.out)
5✔
249
                n.w.SendEvent(pasta.Event{SenderNode: n.id, SenderPort: n.out, ReceiverNode: receiverNode, ReceiverPort: receiverPort, Payload: payload})
5✔
250
        }
251
}
252

253
func (n *selectNode) forwardToActiveInput(payload any) {
1✔
254
        port := n.activeInput()
1✔
255
        snapshot, ok := n.w.PortSnapshot(port)
1✔
256
        if !ok || len(snapshot.Links) == 0 {
1✔
257
                return
×
258
        }
×
259
        linkSnapshot, ok := n.w.LinkSnapshot(snapshot.Links[0])
1✔
260
        if !ok {
1✔
261
                return
×
262
        }
×
263
        receiverNode, receiverPort := otherEndpoint(linkSnapshot, port)
1✔
264
        n.w.SendEvent(pasta.Event{SenderNode: n.id, SenderPort: port, ReceiverNode: receiverNode, ReceiverPort: receiverPort, Payload: payload})
1✔
265
}
266

267
func (n *selectNode) trackPayload(payload any) {
10✔
268
        closable, ok := payload.(ClosablePayload)
10✔
269
        if !ok || closable == nil {
20✔
270
                return
10✔
271
        }
10✔
272
        n.payloads = append(n.payloads, closable)
×
273
}
274

275
func (n *selectNode) closePayloads() {
2✔
276
        for _, payload := range n.payloads {
2✔
277
                if payload != nil {
×
278
                        _ = payload.Close()
×
279
                }
×
280
        }
281
        n.payloads = nil
2✔
282
}
283

284
func (n *selectNode) updateLabel() error {
8✔
285
        if n.selector {
12✔
286
                return n.w.SetNodeLabel(n.id, "in 1 -> out")
4✔
287
        }
4✔
288
        return n.w.SetNodeLabel(n.id, "in 0 -> out")
4✔
289
}
290

291
func (n *selectNode) sendMenuSnapshot() {
2✔
292
        n.w.SendNodeMenuMsg(n.id, formular.MenuSnapshotMessage{
2✔
293
                MessageBase: formular.MessageBase{Type: formular.MessageMenuSnapshot, MenuID: pasta.NodeMenuID(n.id), MenuGeneration: 1},
2✔
294
                Blocks:      []formular.Block{n.menuBlock()},
2✔
295
        })
2✔
296
}
2✔
297

298
func (n *selectNode) sendMenuBlock() {
6✔
299
        if n.w == nil || n.id == 0 {
6✔
300
                return
×
301
        }
×
302
        n.w.SendNodeMenuMsg(n.id, formular.BlockSnapshotMessage{
6✔
303
                MessageBase: formular.MessageBase{Type: formular.MessageBlockSnapshot, MenuID: pasta.NodeMenuID(n.id), MenuGeneration: 1, BlockGeneration: 1},
6✔
304
                Block:       n.menuBlock(),
6✔
305
        })
6✔
306
}
307

308
func (n *selectNode) menuBlock() formular.Block {
8✔
309
        return formular.Block{
8✔
310
                ID: "state", Order: 10, Generation: 1,
8✔
311
                Items: []formular.Item{{
8✔
312
                        Type:  formular.ItemField,
8✔
313
                        ID:    "selector",
8✔
314
                        Label: "Selector",
8✔
315
                        Field: &formular.Field{Kind: formular.FieldCheckbox, Value: n.selector, Readonly: true},
8✔
316
                }},
8✔
317
        }
8✔
318
}
8✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc