feat(rules.on): support & as logical AND

This commit is contained in:
yusing 2025-05-05 19:15:35 +08:00
parent ad60f377ba
commit 1c182b5a7d
2 changed files with 122 additions and 5 deletions

View file

@ -2,6 +2,7 @@ package rules
import (
"net/http"
"strings"
"slices"
@ -230,19 +231,80 @@ var checkers = map[string]struct {
},
}
var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
var andSeps = [256]uint8{'&': 1, '\n': 1}
func indexAnd(s string) int {
for i, c := range s {
if andSeps[c] != 0 {
return i
}
}
return -1
}
func countAnd(s string) int {
n := 0
for _, c := range s {
if andSeps[c] != 0 {
n++
}
}
return n
}
// splitAnd splits a string by "&" and "\n" with all spaces removed.
// empty strings are not included in the result.
func splitAnd(s string) []string {
if s == "" {
return []string{}
}
n := countAnd(s)
a := make([]string, n+1)
i := 0
for i < n {
end := indexAnd(s)
if end == -1 {
break
}
beg := 0
// trim leading spaces
for beg < end && asciiSpace[s[beg]] != 0 {
beg++
}
// trim trailing spaces
next := end + 1
for end-1 > beg && asciiSpace[s[end-1]] != 0 {
end--
}
// skip empty segments
if end > beg {
a[i] = s[beg:end]
i++
}
s = s[next:]
}
s = strings.TrimSpace(s)
if s != "" {
a[i] = s
i++
}
return a[:i]
}
// Parse implements strutils.Parser.
func (on *RuleOn) Parse(v string) error {
on.raw = v
lines := strutils.SplitLine(v)
checkAnd := make(CheckMatchAll, 0, len(lines))
rules := splitAnd(v)
checkAnd := make(CheckMatchAll, 0, len(rules))
errs := gperr.NewBuilder("rule.on syntax errors")
for i, line := range lines {
if line == "" {
for i, rule := range rules {
if rule == "" {
continue
}
parsed, err := parseOn(line)
parsed, err := parseOn(rule)
if err != nil {
errs.Add(err.Subjectf("line %d", i+1))
continue

View file

@ -12,6 +12,61 @@ import (
"golang.org/x/crypto/bcrypt"
)
func TestSplitAnd(t *testing.T) {
tests := []struct {
name string
input string
want []string
}{
{
name: "empty",
input: "",
want: []string{},
},
{
name: "single",
input: "rule",
want: []string{"rule"},
},
{
name: "multiple",
input: "rule1 & rule2",
want: []string{"rule1", "rule2"},
},
{
name: "multiple_newline",
input: "rule1\n\nrule2",
want: []string{"rule1", "rule2"},
},
{
name: "multiple_newline_and",
input: "rule1\nrule2 & rule3",
want: []string{"rule1", "rule2", "rule3"},
},
{
name: "empty segment",
input: "rule1\n& &rule2& rule3",
want: []string{"rule1", "rule2", "rule3"},
},
{
name: "double_and",
input: "rule1\nrule2 && rule3",
want: []string{"rule1", "rule2", "rule3"},
},
{
name: "spaces_around",
input: " rule1\nrule2 & rule3 ",
want: []string{"rule1", "rule2", "rule3"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := splitAnd(tt.input)
ExpectEqual(t, got, tt.want)
})
}
}
func TestParseOn(t *testing.T) {
tests := []struct {
name string