package linedb import "strings" type stringMatchMode int const ( stringMatchEqual stringMatchMode = iota stringMatchContains stringMatchHasPrefix stringMatchHasSuffix ) func matchStringByPattern(value string, pattern string, strictCompare bool) bool { mode, lit := parsePercentPattern(pattern) if !strictCompare { // For substring checks we use ToLower (close enough to EqualFold intent). value = strings.ToLower(value) lit = strings.ToLower(lit) } switch mode { case stringMatchContains: return strings.Contains(value, lit) case stringMatchHasPrefix: return strings.HasPrefix(value, lit) case stringMatchHasSuffix: return strings.HasSuffix(value, lit) default: if strictCompare { return value == lit } return strings.EqualFold(value, lit) } } // parsePercentPattern interprets unescaped % at the start/end of pattern: // - %foo% => contains "foo" // - foo% => hasPrefix "foo" // - %foo => hasSuffix "foo" // - foo => equal "foo" // Escaping: \% means literal %, \\ means literal \ (only affects escape processing). func parsePercentPattern(pattern string) (stringMatchMode, string) { if pattern == "" { return stringMatchEqual, "" } leading := pattern[0] == '%' trailing := len(pattern) > 0 && pattern[len(pattern)-1] == '%' && !isEscapedAt(pattern, len(pattern)-1) start := 0 end := len(pattern) if leading { start = 1 } if trailing && end > start { end-- } lit := unescapePercents(pattern[start:end]) switch { case leading && trailing: return stringMatchContains, lit case trailing: return stringMatchHasPrefix, lit case leading: return stringMatchHasSuffix, lit default: return stringMatchEqual, unescapePercents(pattern) } } func isEscapedAt(s string, idx int) bool { // idx is escaped if preceded by an odd number of backslashes. if idx <= 0 || idx >= len(s) { return false } n := 0 for i := idx - 1; i >= 0 && s[i] == '\\'; i-- { n++ } return n%2 == 1 } func unescapePercents(s string) string { if s == "" { return "" } var b strings.Builder b.Grow(len(s)) esc := false for i := 0; i < len(s); i++ { ch := s[i] if esc { // Only unescape % and \; keep backslash for other chars. if ch == '%' || ch == '\\' { b.WriteByte(ch) } else { b.WriteByte('\\') b.WriteByte(ch) } esc = false continue } if ch == '\\' { esc = true continue } b.WriteByte(ch) } if esc { // dangling backslash b.WriteByte('\\') } return b.String() }