Skip to content

Commit 82dccc7

Browse files
Replace expressions engine (#133)
1 parent 418c708 commit 82dccc7

40 files changed

Lines changed: 6864 additions & 1292 deletions

go.mod

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ require (
2121
github.com/opencontainers/image-spec v1.1.1
2222
github.com/opencontainers/selinux v1.12.0
2323
github.com/pkg/errors v0.9.1
24-
github.com/rhysd/actionlint v1.7.7
2524
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
2625
github.com/sirupsen/logrus v1.9.3
2726
github.com/spf13/cobra v1.10.1
@@ -49,7 +48,6 @@ require (
4948
require (
5049
github.com/Microsoft/go-winio v0.6.2 // indirect
5150
github.com/ProtonMail/go-crypto v1.1.6 // indirect
52-
github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect
5351
github.com/cloudflare/circl v1.6.1 // indirect
5452
github.com/containerd/errdefs/pkg v0.3.0 // indirect
5553
github.com/containerd/log v0.1.0 // indirect
@@ -59,7 +57,6 @@ require (
5957
github.com/docker/docker-credential-helpers v0.8.2 // indirect
6058
github.com/docker/go-units v0.5.0 // indirect
6159
github.com/emirpasic/gods v1.18.1 // indirect
62-
github.com/fatih/color v1.18.0 // indirect
6360
github.com/felixge/httpsnoop v1.0.4 // indirect
6461
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
6562
github.com/go-logr/logr v1.4.2 // indirect
@@ -73,8 +70,6 @@ require (
7370
github.com/kevinburke/ssh_config v1.2.0 // indirect
7471
github.com/klauspost/compress v1.18.0 // indirect
7572
github.com/mattn/go-colorable v0.1.14 // indirect
76-
github.com/mattn/go-runewidth v0.0.16 // indirect
77-
github.com/mattn/go-shellwords v1.0.12 // indirect
7873
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
7974
github.com/moby/docker-image-spec v1.3.1 // indirect
8075
github.com/moby/sys/atomicwriter v0.1.0 // indirect
@@ -86,8 +81,6 @@ require (
8681
github.com/opencontainers/go-digest v1.0.0 // indirect
8782
github.com/pjbgf/sha1cd v0.3.2 // indirect
8883
github.com/pmezard/go-difflib v1.0.0 // indirect
89-
github.com/rivo/uniseg v0.4.7 // indirect
90-
github.com/robfig/cron/v3 v3.0.1 // indirect
9184
github.com/russross/blackfriday/v2 v2.1.0 // indirect
9285
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
9386
github.com/skeema/knownhosts v1.3.1 // indirect

go.sum

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n
1515
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
1616
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
1717
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
18-
github.com/actions-oss/act-cli-actionlint v0.0.0-20250517100532-8f847f29ba36 h1:QnIPcWM4eVfqRUB3B6sLOwEJrMrTa64qrVqzxF5A21U=
19-
github.com/actions-oss/act-cli-actionlint v0.0.0-20250517100532-8f847f29ba36/go.mod h1:AE6I6vJEkNaIfWqC2GNE5spIJNhxf8NCtLEKU4NnUXg=
2018
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
2119
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
2220
github.com/andreaskoch/go-fswatch v1.0.0 h1:la8nP/HiaFCxP2IM6NZNUCoxgLWuyNFgH0RligBbnJU=
@@ -27,8 +25,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
2725
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
2826
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
2927
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
30-
github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ=
31-
github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
3228
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
3329
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
3430
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
@@ -65,8 +61,6 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o
6561
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
6662
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
6763
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
68-
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
69-
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
7064
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
7165
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
7266
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@@ -127,10 +121,6 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg
127121
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
128122
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
129123
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
130-
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
131-
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
132-
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
133-
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
134124
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
135125
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
136126
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@@ -166,11 +156,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
166156
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
167157
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
168158
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
169-
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
170-
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
171-
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
172-
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
173-
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
174159
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
175160
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
176161
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=

internal/eval/functions/format.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package functions
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
)
8+
9+
// Format evaluates a format string with the supplied arguments.
10+
// It behaves like the C# implementation in the repository –
11+
// it supports escaped braces and numeric argument indices.
12+
// Format specifiers (e.g. :D) are recognised but currently ignored.
13+
func Format(formatStr string, args ...interface{}) (string, error) {
14+
var sb strings.Builder
15+
i := 0
16+
for i < len(formatStr) {
17+
lbrace := strings.IndexByte(formatStr[i:], '{')
18+
rbrace := strings.IndexByte(formatStr[i:], '}')
19+
20+
// left brace
21+
if lbrace >= 0 && (rbrace < 0 || rbrace > lbrace) {
22+
l := i + lbrace
23+
24+
sb.WriteString(formatStr[i:l])
25+
26+
// escaped left brace
27+
if l+1 < len(formatStr) && formatStr[l+1] == '{' {
28+
sb.WriteString(formatStr[l : l+1])
29+
i = l + 2
30+
continue
31+
}
32+
33+
// normal placeholder
34+
if rbrace > lbrace+1 {
35+
// read index
36+
idx, endIdx, ok := readArgIndex(formatStr, l+1)
37+
if !ok {
38+
return "", fmt.Errorf("invalid format string: %s", formatStr)
39+
}
40+
// read optional format specifier
41+
spec, r, ok := readFormatSpecifiers(formatStr, endIdx+1)
42+
if !ok {
43+
return "", fmt.Errorf("invalid format string: %s", formatStr)
44+
}
45+
if idx >= len(args) {
46+
return "", fmt.Errorf("argument index %d out of range", idx)
47+
}
48+
// append argument (format specifier is ignored here)
49+
arg := args[idx]
50+
sb.WriteString(fmt.Sprintf("%v", arg))
51+
if spec != "" {
52+
// placeholder for future specifier handling
53+
_ = spec
54+
}
55+
i = r + 1
56+
continue
57+
}
58+
return "", fmt.Errorf("invalid format string: %s", formatStr)
59+
}
60+
61+
// right brace
62+
if rbrace >= 0 {
63+
// escaped right brace
64+
if i+rbrace+1 < len(formatStr) && formatStr[i+rbrace+1] == '}' {
65+
sb.WriteString(formatStr[i : i+rbrace+1])
66+
i += rbrace + 2
67+
continue
68+
}
69+
return "", fmt.Errorf("invalid format string: %s", formatStr)
70+
}
71+
72+
// rest of string
73+
sb.WriteString(formatStr[i:])
74+
break
75+
}
76+
return sb.String(), nil
77+
}
78+
79+
// readArgIndex parses a decimal number starting at pos.
80+
// It returns the parsed value, the index of the last digit and true on success.
81+
func readArgIndex(s string, pos int) (int, int, bool) {
82+
start := pos
83+
for pos < len(s) && s[pos] >= '0' && s[pos] <= '9' {
84+
pos++
85+
}
86+
if start == pos {
87+
return 0, 0, false
88+
}
89+
idx, err := strconv.Atoi(s[start:pos])
90+
if err != nil {
91+
return 0, 0, false
92+
}
93+
return idx, pos - 1, true
94+
}
95+
96+
// readFormatSpecifiers reads an optional format specifier block.
97+
// It returns the specifier string, the index of the closing '}' and true on success.
98+
func readFormatSpecifiers(s string, pos int) (string, int, bool) {
99+
if pos >= len(s) {
100+
return "", 0, false
101+
}
102+
if s[pos] == '}' {
103+
return "", pos, true
104+
}
105+
if s[pos] != ':' {
106+
return "", 0, false
107+
}
108+
pos++ // skip ':'
109+
start := pos
110+
for pos < len(s) {
111+
if s[pos] == '}' {
112+
return s[start:pos], pos, true
113+
}
114+
if s[pos] == '}' && pos+1 < len(s) && s[pos+1] == '}' {
115+
// escaped '}'
116+
pos += 2
117+
continue
118+
}
119+
pos++
120+
}
121+
return "", 0, false
122+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package functions
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestFormat(t *testing.T) {
11+
s, err := Format("Hello {0}, you have {1} new messages", "Alice", 5)
12+
assert.NoError(t, err)
13+
fmt.Println(s) // Hello Alice, you have 5 new messages
14+
}

0 commit comments

Comments
 (0)