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

asciimoth / pasta / 29293620075

13 Jul 2026 11:37PM UTC coverage: 78.004% (-0.06%) from 78.067%
29293620075

push

github

asciimoth
feat(std): add variables

553 of 724 new or added lines in 2 files covered. (76.38%)

8823 of 11311 relevant lines covered (78.0%)

578.55 hits per line

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

76.02
/pasta/std/node_variable.go
1
package std
2

3
import (
4
        "sync"
5

6
        "github.com/asciimoth/configer/configer"
7
        "github.com/asciimoth/formular"
8
        "github.com/asciimoth/pasta/pasta"
9
)
10

11
const (
12
        // NodeTypeBoolGet is the class name for BoolGetClass.
13
        NodeTypeBoolGet = "pasta/BoolGet"
14
        // NodeTypeBoolSet is the class name for BoolSetClass.
15
        NodeTypeBoolSet = "pasta/BoolSet"
16
        // NodeTypeIntGet is the class name for IntGetClass.
17
        NodeTypeIntGet = "pasta/IntGet"
18
        // NodeTypeIntSet is the class name for IntSetClass.
19
        NodeTypeIntSet = "pasta/IntSet"
20
        // NodeTypeFloatGet is the class name for FloatGetClass.
21
        NodeTypeFloatGet = "pasta/FloatGet"
22
        // NodeTypeFloatSet is the class name for FloatSetClass.
23
        NodeTypeFloatSet = "pasta/FloatSet"
24
        // NodeTypeStringGet is the class name for StringGetClass.
25
        NodeTypeStringGet = "pasta/StringGet"
26
        // NodeTypeStringSet is the class name for StringSetClass.
27
        NodeTypeStringSet = "pasta/StringSet"
28
        // NodeTypeObjectGet is the class name for ObjectGetClass.
29
        NodeTypeObjectGet = "pasta/ObjectGet"
30
        // NodeTypeObjectSet is the class name for ObjectSetClass.
31
        NodeTypeObjectSet = "pasta/ObjectSet"
32
)
33

34
const (
35
        defaultVariableName = "var"
36
        variableMenuBlock   = "state"
37
)
38

39
// BoolGetClass creates nodes that read a named bool variable when triggered.
40
type BoolGetClass struct{ store *variableStore }
41

42
func (c BoolGetClass) ClassName() string        { return NodeTypeBoolGet }
507✔
43
func (c BoolGetClass) ShortDescription() string { return "Get boolean variable" }
460✔
NEW
44
func (c BoolGetClass) LongDescription() string {
×
NEW
45
        return "Reads a named pasta/bool variable only when triggered, sends that value on Value, then emits Trigger."
×
NEW
46
}
×
47
func (c BoolGetClass) DefaultNodeParams() pasta.NodeClassParams {
513✔
48
        return variableGetParams(TypeBool)
513✔
49
}
513✔
50
func (c BoolGetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
2✔
51
        return newVariableGetNode(TypeBool, c.variableStore(), readVariableName(cfg)), nil
2✔
52
}
2✔
53
func (c BoolGetClass) variableStore() *variableStore { return variableClassStore(c.store, TypeBool) }
2✔
54

55
// BoolSetClass creates nodes that write a named bool variable when triggered.
56
type BoolSetClass struct{ store *variableStore }
57

58
func (c BoolSetClass) ClassName() string        { return NodeTypeBoolSet }
507✔
59
func (c BoolSetClass) ShortDescription() string { return "Set boolean variable" }
460✔
NEW
60
func (c BoolSetClass) LongDescription() string {
×
NEW
61
        return "Stores the latest pasta/bool input locally and writes it to a named variable only when triggered, then emits Trigger."
×
NEW
62
}
×
63
func (c BoolSetClass) DefaultNodeParams() pasta.NodeClassParams {
513✔
64
        return variableSetParams(TypeBool)
513✔
65
}
513✔
66
func (c BoolSetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
2✔
67
        return newVariableSetNode(TypeBool, c.variableStore(), readVariableName(cfg), readVariableValue(cfg, TypeBool)), nil
2✔
68
}
2✔
69
func (c BoolSetClass) variableStore() *variableStore { return variableClassStore(c.store, TypeBool) }
2✔
70

71
// IntGetClass creates nodes that read a named int variable when triggered.
72
type IntGetClass struct{ store *variableStore }
73

74
func (c IntGetClass) ClassName() string        { return NodeTypeIntGet }
507✔
75
func (c IntGetClass) ShortDescription() string { return "Get integer variable" }
460✔
NEW
76
func (c IntGetClass) LongDescription() string {
×
NEW
77
        return "Reads a named pasta/int variable only when triggered, sends that value on Value, then emits Trigger."
×
NEW
78
}
×
79
func (c IntGetClass) DefaultNodeParams() pasta.NodeClassParams {
538✔
80
        return variableGetParams(TypeInt)
538✔
81
}
538✔
82
func (c IntGetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
10✔
83
        return newVariableGetNode(TypeInt, c.variableStore(), readVariableName(cfg)), nil
10✔
84
}
10✔
85
func (c IntGetClass) variableStore() *variableStore { return variableClassStore(c.store, TypeInt) }
10✔
86

87
// IntSetClass creates nodes that write a named int variable when triggered.
88
type IntSetClass struct{ store *variableStore }
89

90
func (c IntSetClass) ClassName() string        { return NodeTypeIntSet }
507✔
91
func (c IntSetClass) ShortDescription() string { return "Set integer variable" }
460✔
NEW
92
func (c IntSetClass) LongDescription() string {
×
NEW
93
        return "Stores the latest pasta/int input locally and writes it to a named variable only when triggered, then emits Trigger."
×
NEW
94
}
×
95
func (c IntSetClass) DefaultNodeParams() pasta.NodeClassParams {
535✔
96
        return variableSetParams(TypeInt)
535✔
97
}
535✔
98
func (c IntSetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
9✔
99
        return newVariableSetNode(TypeInt, c.variableStore(), readVariableName(cfg), readVariableValue(cfg, TypeInt)), nil
9✔
100
}
9✔
101
func (c IntSetClass) variableStore() *variableStore { return variableClassStore(c.store, TypeInt) }
9✔
102

103
// FloatGetClass creates nodes that read a named float variable when triggered.
104
type FloatGetClass struct{ store *variableStore }
105

106
func (c FloatGetClass) ClassName() string        { return NodeTypeFloatGet }
507✔
107
func (c FloatGetClass) ShortDescription() string { return "Get float variable" }
460✔
NEW
108
func (c FloatGetClass) LongDescription() string {
×
NEW
109
        return "Reads a named pasta/float variable only when triggered, sends that value on Value, then emits Trigger."
×
NEW
110
}
×
111
func (c FloatGetClass) DefaultNodeParams() pasta.NodeClassParams {
510✔
112
        return variableGetParams(TypeFloat)
510✔
113
}
510✔
114
func (c FloatGetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
1✔
115
        return newVariableGetNode(TypeFloat, c.variableStore(), readVariableName(cfg)), nil
1✔
116
}
1✔
117
func (c FloatGetClass) variableStore() *variableStore { return variableClassStore(c.store, TypeFloat) }
1✔
118

119
// FloatSetClass creates nodes that write a named float variable when triggered.
120
type FloatSetClass struct{ store *variableStore }
121

122
func (c FloatSetClass) ClassName() string        { return NodeTypeFloatSet }
507✔
123
func (c FloatSetClass) ShortDescription() string { return "Set float variable" }
460✔
NEW
124
func (c FloatSetClass) LongDescription() string {
×
NEW
125
        return "Stores the latest pasta/float input locally and writes it to a named variable only when triggered, then emits Trigger."
×
NEW
126
}
×
127
func (c FloatSetClass) DefaultNodeParams() pasta.NodeClassParams {
510✔
128
        return variableSetParams(TypeFloat)
510✔
129
}
510✔
130
func (c FloatSetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
1✔
131
        return newVariableSetNode(TypeFloat, c.variableStore(), readVariableName(cfg), readVariableValue(cfg, TypeFloat)), nil
1✔
132
}
1✔
133
func (c FloatSetClass) variableStore() *variableStore { return variableClassStore(c.store, TypeFloat) }
1✔
134

135
// StringGetClass creates nodes that read a named string variable when triggered.
136
type StringGetClass struct{ store *variableStore }
137

138
func (c StringGetClass) ClassName() string        { return NodeTypeStringGet }
507✔
139
func (c StringGetClass) ShortDescription() string { return "Get string variable" }
460✔
NEW
140
func (c StringGetClass) LongDescription() string {
×
NEW
141
        return "Reads a named pasta/string variable only when triggered, sends that value on Value, then emits Trigger."
×
NEW
142
}
×
143
func (c StringGetClass) DefaultNodeParams() pasta.NodeClassParams {
522✔
144
        return variableGetParams(TypeString)
522✔
145
}
522✔
146
func (c StringGetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
5✔
147
        return newVariableGetNode(TypeString, c.variableStore(), readVariableName(cfg)), nil
5✔
148
}
5✔
149
func (c StringGetClass) variableStore() *variableStore {
5✔
150
        return variableClassStore(c.store, TypeString)
5✔
151
}
5✔
152

153
// StringSetClass creates nodes that write a named string variable when triggered.
154
type StringSetClass struct{ store *variableStore }
155

156
func (c StringSetClass) ClassName() string        { return NodeTypeStringSet }
507✔
157
func (c StringSetClass) ShortDescription() string { return "Set string variable" }
460✔
NEW
158
func (c StringSetClass) LongDescription() string {
×
NEW
159
        return "Stores the latest pasta/string input locally and writes it to a named variable only when triggered, then emits Trigger."
×
NEW
160
}
×
161
func (c StringSetClass) DefaultNodeParams() pasta.NodeClassParams {
525✔
162
        return variableSetParams(TypeString)
525✔
163
}
525✔
164
func (c StringSetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
6✔
165
        return newVariableSetNode(TypeString, c.variableStore(), readVariableName(cfg), readVariableValue(cfg, TypeString)), nil
6✔
166
}
6✔
167
func (c StringSetClass) variableStore() *variableStore {
6✔
168
        return variableClassStore(c.store, TypeString)
6✔
169
}
6✔
170

171
// ObjectGetClass creates nodes that read a named object variable when triggered.
172
type ObjectGetClass struct{ store *variableStore }
173

174
func (c ObjectGetClass) ClassName() string        { return NodeTypeObjectGet }
507✔
175
func (c ObjectGetClass) ShortDescription() string { return "Get object variable" }
460✔
NEW
176
func (c ObjectGetClass) LongDescription() string {
×
NEW
177
        return "Reads a named pasta/object variable only when triggered, sends that value on Value, then emits Trigger."
×
NEW
178
}
×
179
func (c ObjectGetClass) DefaultNodeParams() pasta.NodeClassParams {
510✔
180
        return variableGetParams(TypeObject)
510✔
181
}
510✔
182
func (c ObjectGetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
1✔
183
        return newVariableGetNode(TypeObject, c.variableStore(), readVariableName(cfg)), nil
1✔
184
}
1✔
185
func (c ObjectGetClass) variableStore() *variableStore {
1✔
186
        return variableClassStore(c.store, TypeObject)
1✔
187
}
1✔
188

189
// ObjectSetClass creates nodes that write a named object variable when triggered.
190
type ObjectSetClass struct{ store *variableStore }
191

192
func (c ObjectSetClass) ClassName() string        { return NodeTypeObjectSet }
507✔
193
func (c ObjectSetClass) ShortDescription() string { return "Set object variable" }
460✔
NEW
194
func (c ObjectSetClass) LongDescription() string {
×
NEW
195
        return "Stores the latest pasta/object input locally and writes it to a named variable only when triggered, then emits Trigger."
×
NEW
196
}
×
197
func (c ObjectSetClass) DefaultNodeParams() pasta.NodeClassParams {
510✔
198
        return variableSetParams(TypeObject)
510✔
199
}
510✔
200
func (c ObjectSetClass) NewNode(cfg configer.Config, _ ...*pasta.NodeClassState) (pasta.Node, error) {
1✔
201
        return newVariableSetNode(TypeObject, c.variableStore(), readVariableName(cfg), readVariableValue(cfg, TypeObject)), nil
1✔
202
}
1✔
203
func (c ObjectSetClass) variableStore() *variableStore {
1✔
204
        return variableClassStore(c.store, TypeObject)
1✔
205
}
1✔
206

207
type variableClassStores struct {
208
        boolStore   *variableStore
209
        intStore    *variableStore
210
        floatStore  *variableStore
211
        stringStore *variableStore
212
        objectStore *variableStore
213
}
214

215
func newVariableClassStores() *variableClassStores {
47✔
216
        return &variableClassStores{
47✔
217
                boolStore:   newVariableStore(TypeBool),
47✔
218
                intStore:    newVariableStore(TypeInt),
47✔
219
                floatStore:  newVariableStore(TypeFloat),
47✔
220
                stringStore: newVariableStore(TypeString),
47✔
221
                objectStore: newVariableStore(TypeObject),
47✔
222
        }
47✔
223
}
47✔
224

225
func (s *variableClassStores) get(typ string) *variableStore {
470✔
226
        if s == nil {
470✔
NEW
227
                return fallbackVariableClassStores.get(typ)
×
NEW
228
        }
×
229
        switch typ {
470✔
230
        case TypeBool:
94✔
231
                return s.boolStore
94✔
232
        case TypeInt:
94✔
233
                return s.intStore
94✔
234
        case TypeFloat:
94✔
235
                return s.floatStore
94✔
236
        case TypeString:
94✔
237
                return s.stringStore
94✔
238
        case TypeObject:
94✔
239
                return s.objectStore
94✔
NEW
240
        default:
×
NEW
241
                return nil
×
242
        }
243
}
244

245
var fallbackVariableClassStores = &variableClassStores{
246
        boolStore:   newVariableStore(TypeBool),
247
        intStore:    newVariableStore(TypeInt),
248
        floatStore:  newVariableStore(TypeFloat),
249
        stringStore: newVariableStore(TypeString),
250
        objectStore: newVariableStore(TypeObject),
251
}
252

253
func variableClassStore(store *variableStore, typ string) *variableStore {
38✔
254
        if store != nil {
76✔
255
                return store
38✔
256
        }
38✔
NEW
257
        return fallbackVariableClassStores.get(typ)
×
258
}
259

260
type variableStore struct {
261
        mu    sync.Mutex
262
        typ   string
263
        items map[string]variableEntry
264
}
265

266
type variableEntry struct {
267
        value any
268
        refs  int
269
}
270

271
func newVariableStore(typ string) *variableStore {
240✔
272
        return &variableStore{typ: typ, items: map[string]variableEntry{}}
240✔
273
}
240✔
274

275
func (s *variableStore) acquire(name string) {
72✔
276
        if s == nil {
72✔
NEW
277
                return
×
NEW
278
        }
×
279
        s.mu.Lock()
72✔
280
        defer s.mu.Unlock()
72✔
281
        entry, ok := s.items[name]
72✔
282
        if !ok {
109✔
283
                entry = variableEntry{value: variableDefaultValue(s.typ)}
37✔
284
        }
37✔
285
        entry.refs++
72✔
286
        s.items[name] = entry
72✔
287
}
288

289
func (s *variableStore) release(name string) {
38✔
290
        if s == nil {
38✔
NEW
291
                return
×
NEW
292
        }
×
293
        s.mu.Lock()
38✔
294
        defer s.mu.Unlock()
38✔
295
        entry, ok := s.items[name]
38✔
296
        if !ok {
38✔
NEW
297
                return
×
NEW
298
        }
×
299
        entry.refs--
38✔
300
        if entry.refs <= 0 {
57✔
301
                delete(s.items, name)
19✔
302
                return
19✔
303
        }
19✔
304
        s.items[name] = entry
19✔
305
}
306

307
func (s *variableStore) set(name string, value any) {
20✔
308
        if s == nil {
20✔
NEW
309
                return
×
NEW
310
        }
×
311
        s.mu.Lock()
20✔
312
        defer s.mu.Unlock()
20✔
313
        entry, ok := s.items[name]
20✔
314
        if !ok {
20✔
NEW
315
                entry = variableEntry{}
×
NEW
316
        }
×
317
        entry.value = value
20✔
318
        s.items[name] = entry
20✔
319
}
320

321
func (s *variableStore) get(name string) any {
24✔
322
        if s == nil {
24✔
NEW
323
                return nil
×
NEW
324
        }
×
325
        s.mu.Lock()
24✔
326
        defer s.mu.Unlock()
24✔
327
        entry, ok := s.items[name]
24✔
328
        if !ok {
24✔
NEW
329
                return variableDefaultValue(s.typ)
×
NEW
330
        }
×
331
        return entry.value
24✔
332
}
333

334
type variableSetNode struct {
335
        pasta.BasicNode
336

337
        typ   string
338
        store *variableStore
339
        name  string
340
        value any
341

342
        w          *pasta.Workspace
343
        id         uint64
344
        triggerIn  uint64
345
        valueIn    uint64
346
        triggerOut uint64
347
        acquired   bool
348
}
349

350
func newVariableSetNode(typ string, store *variableStore, name string, value any) *variableSetNode {
19✔
351
        if name == "" {
19✔
NEW
352
                name = defaultVariableName
×
NEW
353
        }
×
354
        if value == nil {
19✔
NEW
355
                value = variableDefaultValue(typ)
×
NEW
356
        }
×
357
        return &variableSetNode{typ: typ, store: store, name: name, value: value}
19✔
358
}
359

360
func (n *variableSetNode) OnInit(w *pasta.Workspace, _ pasta.Logger, id uint64, _ string, restored *pasta.NodeInitData, _, _, _, _ bool) error {
19✔
361
        n.w = w
19✔
362
        n.id = id
19✔
363
        n.findPorts(restored)
19✔
364
        if err := n.w.SetNodePrimaryLocked(n.id, n.typ); err != nil {
19✔
NEW
365
                return err
×
NEW
366
        }
×
367
        n.acquireName(n.name)
19✔
368
        if err := n.updateLabel(); err != nil {
19✔
NEW
369
                return err
×
NEW
370
        }
×
371
        n.sendMenuSnapshot(false)
19✔
372
        return nil
19✔
373
}
374

375
func (n *variableSetNode) OnStop() {
2✔
376
        if n.acquired {
4✔
377
                n.store.release(n.name)
2✔
378
                n.acquired = false
2✔
379
        }
2✔
380
}
381

382
func (n *variableSetNode) PreLinkAdd(port uint64, linkType, portDirection string) error {
39✔
383
        switch port {
39✔
384
        case n.triggerIn:
12✔
385
                if portDirection == "left" && linkType == TypeTrigger {
24✔
386
                        return nil
12✔
387
                }
12✔
388
        case n.valueIn:
13✔
389
                if portDirection == "left" && linkType == n.typ {
26✔
390
                        snapshot, ok := n.w.PortSnapshotLocked(port)
13✔
391
                        if ok && len(snapshot.Links) > 0 {
13✔
NEW
392
                                return pasta.ErrLinkDup
×
NEW
393
                        }
×
394
                        return nil
13✔
395
                }
396
        case n.triggerOut:
14✔
397
                if portDirection == "right" && linkType == TypeTrigger {
28✔
398
                        return nil
14✔
399
                }
14✔
400
        }
NEW
401
        return pasta.LinkTypeErr(linkType)
×
402
}
403

404
func (n *variableSetNode) OnLinkRemoved(_ uint64, port uint64, _ string, _ string) error {
1✔
405
        if port == n.valueIn {
2✔
406
                n.value = variableDefaultValue(n.typ)
1✔
407
                if err := n.updateLabel(); err != nil {
1✔
NEW
408
                        return err
×
NEW
409
                }
×
410
                n.sendMenuBlock()
1✔
411
        }
412
        return nil
1✔
413
}
414

415
func (n *variableSetNode) OnEvent(event pasta.Event, linkType string, _ []string, receiverPortDirection string) error {
28✔
416
        switch event.ReceiverPort {
28✔
417
        case n.triggerIn:
12✔
418
                if receiverPortDirection == "left" && linkType == TypeTrigger && !IsRequest(event.Payload) {
24✔
419
                        n.run()
12✔
420
                }
12✔
421
        case n.valueIn:
16✔
422
                if receiverPortDirection != "left" || linkType != n.typ || isValueRequest(event.Payload) {
16✔
NEW
423
                        return nil
×
NEW
424
                }
×
425
                value, ok := variableValueFromPayload(n.typ, event.Payload)
16✔
426
                if !ok {
16✔
NEW
427
                        return nil
×
NEW
428
                }
×
429
                n.value = value
16✔
430
                if err := n.updateLabel(); err != nil {
16✔
NEW
431
                        return err
×
NEW
432
                }
×
433
                n.sendMenuBlock()
16✔
434
        }
435
        return nil
28✔
436
}
437

438
func (n *variableSetNode) OnFormularMsg(message any) error {
17✔
439
        msg, ok := message.(formular.FormApplyMessage)
17✔
440
        if !ok || msg.MenuID != pasta.NodeMenuID(n.id) || msg.BlockID != variableMenuBlock {
17✔
NEW
441
                return nil
×
NEW
442
        }
×
443
        nextName, ok := parseStringAny(msg.Values["name"])
17✔
444
        if !ok {
17✔
NEW
445
                n.sendMenuSnapshot(true)
×
NEW
446
                return nil
×
NEW
447
        }
×
448
        nextValue, ok := variableValueFromMenu(n.typ, msg.Values["value"])
17✔
449
        if !ok {
17✔
NEW
450
                n.sendMenuSnapshot(true)
×
NEW
451
                return nil
×
NEW
452
        }
×
453
        if nextName == "" {
17✔
NEW
454
                nextName = defaultVariableName
×
NEW
455
        }
×
456
        if nextName != n.name {
34✔
457
                n.acquireName(nextName)
17✔
458
        }
17✔
459
        n.value = nextValue
17✔
460
        if err := n.updateLabel(); err != nil {
17✔
NEW
461
                return err
×
NEW
462
        }
×
463
        n.sendMenuBlock()
17✔
464
        return nil
17✔
465
}
466

467
func (n *variableSetNode) OnTrigger() error {
8✔
468
        n.run()
8✔
469
        return nil
8✔
470
}
8✔
471

472
func (n *variableSetNode) OnSave(cfg configer.Config) error {
4✔
473
        if err := pasta.DeleteNodeOwnedConfigKeys(cfg); err != nil {
4✔
NEW
474
                return err
×
NEW
475
        }
×
476
        if err := cfg.Set(configer.Path{"name"}, n.name); err != nil {
4✔
NEW
477
                return err
×
NEW
478
        }
×
479
        value, err := variableValueToConfigValue(n.typ, n.value)
4✔
480
        if err != nil {
4✔
NEW
481
                return err
×
NEW
482
        }
×
483
        return cfg.Set(configer.Path{"value"}, value)
4✔
484
}
485

486
func (n *variableSetNode) findPorts(restored *pasta.NodeInitData) {
19✔
487
        if restored == nil {
19✔
NEW
488
                return
×
NEW
489
        }
×
490
        for _, port := range restored.LeftPorts {
57✔
491
                snapshot, ok := n.w.PortSnapshotLocked(port)
38✔
492
                if !ok {
38✔
NEW
493
                        continue
×
494
                }
495
                switch snapshot.Name {
38✔
496
                case "Trigger":
19✔
497
                        n.triggerIn = port
19✔
498
                case "Value":
19✔
499
                        n.valueIn = port
19✔
500
                }
501
        }
502
        for _, port := range restored.RightPorts {
38✔
503
                snapshot, ok := n.w.PortSnapshotLocked(port)
19✔
504
                if ok && snapshot.Name == "Trigger" {
38✔
505
                        n.triggerOut = port
19✔
506
                }
19✔
507
        }
508
}
509

510
func (n *variableSetNode) acquireName(next string) {
36✔
511
        if n.store == nil {
36✔
NEW
512
                n.store = variableClassStore(nil, n.typ)
×
NEW
513
        }
×
514
        if n.acquired {
53✔
515
                n.store.release(n.name)
17✔
516
        }
17✔
517
        n.name = next
36✔
518
        n.store.acquire(n.name)
36✔
519
        n.acquired = true
36✔
520
}
521

522
func (n *variableSetNode) run() {
20✔
523
        n.store.set(n.name, n.value)
20✔
524
        n.emitTrigger()
20✔
525
}
20✔
526

527
func (n *variableSetNode) emitTrigger() {
20✔
528
        port, ok := n.w.PortSnapshotLocked(n.triggerOut)
20✔
529
        if !ok {
20✔
NEW
530
                return
×
NEW
531
        }
×
532
        for _, link := range port.Links {
35✔
533
                snapshot, ok := n.w.LinkSnapshotLocked(link)
15✔
534
                if !ok {
15✔
NEW
535
                        continue
×
536
                }
537
                receiverNode, receiverPort := otherEndpoint(snapshot, n.triggerOut)
15✔
538
                n.w.SendEventLocked(pasta.Event{SenderNode: n.id, SenderPort: n.triggerOut, ReceiverNode: receiverNode, ReceiverPort: receiverPort, Payload: Trigger{}})
15✔
539
        }
540
}
541

542
func (n *variableSetNode) updateLabel() error {
53✔
543
        return n.w.SetNodeLabelLocked(n.id, n.name)
53✔
544
}
53✔
545

546
func (n *variableSetNode) sendMenuSnapshot(force bool) {
19✔
547
        n.w.SendNodeMenuMsgLocked(n.id, formular.MenuSnapshotMessage{
19✔
548
                MessageBase: formular.MessageBase{Type: formular.MessageMenuSnapshot, MenuID: pasta.NodeMenuID(n.id), MenuGeneration: 1},
19✔
549
                Force:       force,
19✔
550
                Blocks:      []formular.Block{n.menuBlock()},
19✔
551
        })
19✔
552
}
19✔
553

554
func (n *variableSetNode) sendMenuBlock() {
34✔
555
        n.w.SendNodeMenuMsgLocked(n.id, formular.BlockSnapshotMessage{
34✔
556
                MessageBase: formular.MessageBase{Type: formular.MessageBlockSnapshot, MenuID: pasta.NodeMenuID(n.id), MenuGeneration: 1, BlockGeneration: 1},
34✔
557
                Block:       n.menuBlock(),
34✔
558
        })
34✔
559
}
34✔
560

561
func (n *variableSetNode) menuBlock() formular.Block {
53✔
562
        return formular.Block{
53✔
563
                ID: variableMenuBlock, Order: 10, Generation: 1, Form: true,
53✔
564
                Items: []formular.Item{
53✔
565
                        {Type: formular.ItemField, ID: "name", Label: "Name", Field: &formular.Field{Kind: formular.FieldText, Value: n.name, Required: true}},
53✔
566
                        {Type: formular.ItemField, ID: "value", Label: "Value", Field: variableField(n.typ, n.value)},
53✔
567
                },
53✔
568
        }
53✔
569
}
53✔
570

571
type variableGetNode struct {
572
        pasta.BasicNode
573

574
        typ   string
575
        store *variableStore
576
        name  string
577
        sent  map[uint64]any
578

579
        w          *pasta.Workspace
580
        id         uint64
581
        triggerIn  uint64
582
        triggerOut uint64
583
        valueOut   uint64
584
        acquired   bool
585
}
586

587
func newVariableGetNode(typ string, store *variableStore, name string) *variableGetNode {
19✔
588
        if name == "" {
19✔
NEW
589
                name = defaultVariableName
×
NEW
590
        }
×
591
        return &variableGetNode{typ: typ, store: store, name: name, sent: map[uint64]any{}}
19✔
592
}
593

594
func (n *variableGetNode) OnInit(w *pasta.Workspace, _ pasta.Logger, id uint64, _ string, restored *pasta.NodeInitData, _, _, _, _ bool) error {
19✔
595
        n.w = w
19✔
596
        n.id = id
19✔
597
        n.findPorts(restored)
19✔
598
        if n.sent == nil {
19✔
NEW
599
                n.sent = map[uint64]any{}
×
NEW
600
        }
×
601
        if err := n.w.SetNodePrimaryLocked(n.id, n.typ); err != nil {
19✔
NEW
602
                return err
×
NEW
603
        }
×
604
        n.acquireName(n.name)
19✔
605
        if err := n.updateLabel(); err != nil {
19✔
NEW
606
                return err
×
NEW
607
        }
×
608
        n.sendMenuSnapshot(false)
19✔
609
        return nil
19✔
610
}
611

612
func (n *variableGetNode) OnStop() {
2✔
613
        if n.acquired {
4✔
614
                n.store.release(n.name)
2✔
615
                n.acquired = false
2✔
616
        }
2✔
617
}
618

619
func (n *variableGetNode) PreLinkAdd(port uint64, linkType, portDirection string) error {
40✔
620
        switch port {
40✔
621
        case n.triggerIn:
14✔
622
                if portDirection == "left" && linkType == TypeTrigger {
28✔
623
                        return nil
14✔
624
                }
14✔
NEW
625
        case n.triggerOut:
×
NEW
626
                if portDirection == "right" && linkType == TypeTrigger {
×
NEW
627
                        return nil
×
NEW
628
                }
×
629
        case n.valueOut:
26✔
630
                if portDirection == "right" && linkType == n.typ {
52✔
631
                        return nil
26✔
632
                }
26✔
633
        }
NEW
634
        return pasta.LinkTypeErr(linkType)
×
635
}
636

637
func (n *variableGetNode) OnLinkRemoved(link uint64, port uint64, _ string, _ string) error {
3✔
638
        if port == n.valueOut {
4✔
639
                delete(n.sent, link)
1✔
640
        }
1✔
641
        return nil
3✔
642
}
643

644
func (n *variableGetNode) OnEvent(event pasta.Event, linkType string, _ []string, receiverPortDirection string) error {
47✔
645
        switch event.ReceiverPort {
47✔
646
        case n.triggerIn:
15✔
647
                if receiverPortDirection == "left" && linkType == TypeTrigger && !IsRequest(event.Payload) {
30✔
648
                        n.run()
15✔
649
                }
15✔
650
        case n.valueOut:
32✔
651
                if receiverPortDirection == "right" && linkType == n.typ && isValueRequest(event.Payload) {
64✔
652
                        if value, ok := n.sent[event.Link]; ok {
33✔
653
                                n.w.SendEventLocked(pasta.Event{SenderNode: n.id, SenderPort: n.valueOut, ReceiverNode: event.SenderNode, ReceiverPort: event.SenderPort, Payload: value})
1✔
654
                        }
1✔
655
                }
656
        }
657
        return nil
47✔
658
}
659

660
func (n *variableGetNode) OnFormularMsg(message any) error {
17✔
661
        msg, ok := message.(formular.FormApplyMessage)
17✔
662
        if !ok || msg.MenuID != pasta.NodeMenuID(n.id) || msg.BlockID != variableMenuBlock {
17✔
NEW
663
                return nil
×
NEW
664
        }
×
665
        nextName, ok := parseStringAny(msg.Values["name"])
17✔
666
        if !ok {
17✔
NEW
667
                n.sendMenuSnapshot(true)
×
NEW
668
                return nil
×
NEW
669
        }
×
670
        if nextName == "" {
17✔
NEW
671
                nextName = defaultVariableName
×
NEW
672
        }
×
673
        if nextName != n.name {
34✔
674
                n.acquireName(nextName)
17✔
675
                n.sent = map[uint64]any{}
17✔
676
        }
17✔
677
        if err := n.updateLabel(); err != nil {
17✔
NEW
678
                return err
×
NEW
679
        }
×
680
        n.sendMenuBlock()
17✔
681
        return nil
17✔
682
}
683

684
func (n *variableGetNode) OnTrigger() error {
9✔
685
        n.run()
9✔
686
        return nil
9✔
687
}
9✔
688

689
func (n *variableGetNode) OnSave(cfg configer.Config) error {
4✔
690
        if err := pasta.DeleteNodeOwnedConfigKeys(cfg); err != nil {
4✔
NEW
691
                return err
×
NEW
692
        }
×
693
        return cfg.Set(configer.Path{"name"}, n.name)
4✔
694
}
695

696
func (n *variableGetNode) findPorts(restored *pasta.NodeInitData) {
19✔
697
        if restored == nil {
19✔
NEW
698
                return
×
NEW
699
        }
×
700
        for _, port := range restored.LeftPorts {
38✔
701
                snapshot, ok := n.w.PortSnapshotLocked(port)
19✔
702
                if ok && snapshot.Name == "Trigger" {
38✔
703
                        n.triggerIn = port
19✔
704
                }
19✔
705
        }
706
        for _, port := range restored.RightPorts {
57✔
707
                snapshot, ok := n.w.PortSnapshotLocked(port)
38✔
708
                if !ok {
38✔
NEW
709
                        continue
×
710
                }
711
                switch snapshot.Name {
38✔
712
                case "Trigger":
19✔
713
                        n.triggerOut = port
19✔
714
                case "Value":
19✔
715
                        n.valueOut = port
19✔
716
                }
717
        }
718
}
719

720
func (n *variableGetNode) acquireName(next string) {
36✔
721
        if n.store == nil {
36✔
NEW
722
                n.store = variableClassStore(nil, n.typ)
×
NEW
723
        }
×
724
        if n.acquired {
53✔
725
                n.store.release(n.name)
17✔
726
        }
17✔
727
        n.name = next
36✔
728
        n.store.acquire(n.name)
36✔
729
        n.acquired = true
36✔
730
}
731

732
func (n *variableGetNode) run() {
24✔
733
        value := n.store.get(n.name)
24✔
734
        n.sendValueAll(value)
24✔
735
        n.emitTrigger()
24✔
736
}
24✔
737

738
func (n *variableGetNode) sendValueAll(value any) {
24✔
739
        port, ok := n.w.PortSnapshotLocked(n.valueOut)
24✔
740
        if !ok {
24✔
NEW
741
                return
×
NEW
742
        }
×
743
        for _, link := range port.Links {
54✔
744
                n.sendValueToLink(link, value)
30✔
745
        }
30✔
746
}
747

748
func (n *variableGetNode) sendValueToLink(link uint64, value any) {
30✔
749
        snapshot, ok := n.w.LinkSnapshotLocked(link)
30✔
750
        if !ok {
30✔
NEW
751
                return
×
NEW
752
        }
×
753
        receiverNode, receiverPort := otherEndpoint(snapshot, n.valueOut)
30✔
754
        n.sent[link] = value
30✔
755
        n.w.SendEventLocked(pasta.Event{SenderNode: n.id, SenderPort: n.valueOut, ReceiverNode: receiverNode, ReceiverPort: receiverPort, Payload: value})
30✔
756
}
757

758
func (n *variableGetNode) emitTrigger() {
24✔
759
        port, ok := n.w.PortSnapshotLocked(n.triggerOut)
24✔
760
        if !ok {
24✔
NEW
761
                return
×
NEW
762
        }
×
763
        for _, link := range port.Links {
24✔
NEW
764
                snapshot, ok := n.w.LinkSnapshotLocked(link)
×
NEW
765
                if !ok {
×
NEW
766
                        continue
×
767
                }
NEW
768
                receiverNode, receiverPort := otherEndpoint(snapshot, n.triggerOut)
×
NEW
769
                n.w.SendEventLocked(pasta.Event{SenderNode: n.id, SenderPort: n.triggerOut, ReceiverNode: receiverNode, ReceiverPort: receiverPort, Payload: Trigger{}})
×
770
        }
771
}
772

773
func (n *variableGetNode) updateLabel() error {
36✔
774
        return n.w.SetNodeLabelLocked(n.id, n.name)
36✔
775
}
36✔
776

777
func (n *variableGetNode) sendMenuSnapshot(force bool) {
19✔
778
        n.w.SendNodeMenuMsgLocked(n.id, formular.MenuSnapshotMessage{
19✔
779
                MessageBase: formular.MessageBase{Type: formular.MessageMenuSnapshot, MenuID: pasta.NodeMenuID(n.id), MenuGeneration: 1},
19✔
780
                Force:       force,
19✔
781
                Blocks:      []formular.Block{n.menuBlock()},
19✔
782
        })
19✔
783
}
19✔
784

785
func (n *variableGetNode) sendMenuBlock() {
17✔
786
        n.w.SendNodeMenuMsgLocked(n.id, formular.BlockSnapshotMessage{
17✔
787
                MessageBase: formular.MessageBase{Type: formular.MessageBlockSnapshot, MenuID: pasta.NodeMenuID(n.id), MenuGeneration: 1, BlockGeneration: 1},
17✔
788
                Block:       n.menuBlock(),
17✔
789
        })
17✔
790
}
17✔
791

792
func (n *variableGetNode) menuBlock() formular.Block {
36✔
793
        return formular.Block{
36✔
794
                ID: variableMenuBlock, Order: 10, Generation: 1, Form: true,
36✔
795
                Items: []formular.Item{{Type: formular.ItemField, ID: "name", Label: "Name", Field: &formular.Field{Kind: formular.FieldText, Value: n.name, Required: true}}},
36✔
796
        }
36✔
797
}
36✔
798

799
func variableGetParams(typ string) pasta.NodeClassParams {
2,593✔
800
        return pasta.NodeClassParams{PrimaryType: typ, InitialPorts: []pasta.Port{
2,593✔
801
                {Direction: "right", Name: "Trigger", Types: []string{TypeTrigger}},
2,593✔
802
                {Direction: "right", Name: "Value", Types: []string{typ}},
2,593✔
803
                {Direction: "left", Name: "Trigger", Types: []string{TypeTrigger}},
2,593✔
804
        }}
2,593✔
805
}
2,593✔
806

807
func variableSetParams(typ string) pasta.NodeClassParams {
2,593✔
808
        return pasta.NodeClassParams{PrimaryType: typ, InitialPorts: []pasta.Port{
2,593✔
809
                {Direction: "right", Name: "Trigger", Types: []string{TypeTrigger}},
2,593✔
810
                {Direction: "left", Name: "Trigger", Types: []string{TypeTrigger}},
2,593✔
811
                {Direction: "left", Name: "Value", Types: []string{typ}},
2,593✔
812
        }}
2,593✔
813
}
2,593✔
814

815
func readVariableName(cfg configer.Config) string {
38✔
816
        if cfg == nil {
72✔
817
                return defaultVariableName
34✔
818
        }
34✔
819
        raw, err := cfg.Get(configer.Path{"name"})
4✔
820
        if err != nil {
4✔
NEW
821
                return defaultVariableName
×
NEW
822
        }
×
823
        name, ok := parseStringAny(raw)
4✔
824
        if !ok || name == "" {
4✔
NEW
825
                return defaultVariableName
×
NEW
826
        }
×
827
        return name
4✔
828
}
829

830
func readVariableValue(cfg configer.Config, typ string) any {
19✔
831
        if cfg == nil {
36✔
832
                return variableDefaultValue(typ)
17✔
833
        }
17✔
834
        raw, err := cfg.Get(configer.Path{"value"})
2✔
835
        if err != nil {
2✔
NEW
836
                return variableDefaultValue(typ)
×
NEW
837
        }
×
838
        if value, ok := variableValueFromConfig(typ, raw); ok {
4✔
839
                return value
2✔
840
        }
2✔
NEW
841
        return variableDefaultValue(typ)
×
842
}
843

844
func variableDefaultValue(typ string) any {
55✔
845
        switch typ {
55✔
846
        case TypeBool:
6✔
847
                return false
6✔
848
        case TypeInt:
24✔
849
                return Int(0)
24✔
850
        case TypeFloat:
3✔
851
                return Float(0)
3✔
852
        case TypeString:
19✔
853
                return String("")
19✔
854
        case TypeObject:
3✔
855
                return NilObject()
3✔
NEW
856
        default:
×
NEW
857
                return nil
×
858
        }
859
}
860

861
func variableValueFromPayload(typ string, payload any) (any, bool) {
34✔
862
        switch typ {
34✔
863
        case TypeBool:
4✔
864
                return parseBoolAny(payload)
4✔
865
        case TypeInt:
16✔
866
                value, ok := parseIntAny(payload)
16✔
867
                return Int(value), ok
16✔
868
        case TypeFloat:
2✔
869
                value, ok := parseFloatAny(payload)
2✔
870
                return Float(value), ok
2✔
871
        case TypeString:
12✔
872
                value, ok := parseStringAny(payload)
12✔
873
                return String(value), ok
12✔
NEW
874
        case TypeObject:
×
NEW
875
                return ObjectFromPayload(payload)
×
NEW
876
        default:
×
NEW
877
                return nil, false
×
878
        }
879
}
880

881
func variableValueFromMenu(typ string, value any) (any, bool) {
17✔
882
        if typ != TypeObject {
33✔
883
                return variableValueFromPayload(typ, value)
16✔
884
        }
16✔
885
        raw, ok := parseStringAny(value)
1✔
886
        if !ok {
1✔
NEW
887
                return nil, false
×
NEW
888
        }
×
889
        object, err := ObjectFromJSON([]byte(raw))
1✔
890
        return object, err == nil
1✔
891
}
892

893
func variableValueFromConfig(typ string, value any) (any, bool) {
2✔
894
        if typ != TypeObject {
4✔
895
                return variableValueFromPayload(typ, value)
2✔
896
        }
2✔
NEW
897
        return objectFromConfigValue(value)
×
898
}
899

900
func variableValueToConfigValue(typ string, value any) (any, error) {
4✔
901
        switch typ {
4✔
NEW
902
        case TypeBool:
×
NEW
903
                v, _ := parseBoolAny(value)
×
NEW
904
                return v, nil
×
905
        case TypeInt:
4✔
906
                v, _ := parseIntAny(value)
4✔
907
                return v, nil
4✔
NEW
908
        case TypeFloat:
×
NEW
909
                v, _ := parseFloatAny(value)
×
NEW
910
                return v, nil
×
NEW
911
        case TypeString:
×
NEW
912
                v, _ := parseStringAny(value)
×
NEW
913
                return v, nil
×
NEW
914
        case TypeObject:
×
NEW
915
                object, ok := ObjectFromPayload(value)
×
NEW
916
                if !ok {
×
NEW
917
                        object = NilObject()
×
NEW
918
                }
×
NEW
919
                return objectToConfigValue(object)
×
NEW
920
        default:
×
NEW
921
                return nil, nil
×
922
        }
923
}
924

925
func variableField(typ string, value any) *formular.Field {
53✔
926
        field := &formular.Field{Kind: variableFieldKind(typ), Value: variableMenuValue(typ, value)}
53✔
927
        if typ == TypeObject {
55✔
928
                field.Multiline = true
2✔
929
                field.Placeholder = `{"name":"Ada"}`
2✔
930
        }
2✔
931
        return field
53✔
932
}
933

934
func variableFieldKind(typ string) string {
53✔
935
        switch typ {
53✔
936
        case TypeBool:
6✔
937
                return formular.FieldCheckbox
6✔
938
        case TypeFloat:
3✔
939
                return formular.FieldFloat
3✔
940
        case TypeString, TypeObject:
21✔
941
                return formular.FieldText
21✔
942
        default:
23✔
943
                return formular.FieldInt
23✔
944
        }
945
}
946

947
func variableMenuValue(typ string, value any) any {
53✔
948
        switch typ {
53✔
949
        case TypeBool:
6✔
950
                v, _ := parseBoolAny(value)
6✔
951
                return v
6✔
952
        case TypeInt:
23✔
953
                v, _ := parseIntAny(value)
23✔
954
                return v
23✔
955
        case TypeFloat:
3✔
956
                v, _ := parseFloatAny(value)
3✔
957
                return v
3✔
958
        case TypeString:
19✔
959
                v, _ := parseStringAny(value)
19✔
960
                return v
19✔
961
        case TypeObject:
2✔
962
                object, ok := ObjectFromPayload(value)
2✔
963
                if !ok {
2✔
NEW
964
                        object = NilObject()
×
NEW
965
                }
×
966
                return object.PrettyJSONString()
2✔
NEW
967
        default:
×
NEW
968
                return nil
×
969
        }
970
}
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