// SPDX-License-Identifier: GPL-3.0-or-later package plan import ( "fmt" "strconv" "strings" "time" ) // CheckTemplate reports the first reason s's placeholders could never // expand - an unknown placeholder, {mtime} or {now} without a format, an // unknown format code, {0}, {10} and up, an unclosed "{" - or nil. Capture // counts are not checked here; that needs the rule's name tests. func CheckTemplate(s string) error { if _, err := MaxIndex(s); err != nil { return err } facts := Facts{Name: "a.b", Captures: make([]string, 10), ModTime: time.Unix(0, 0), Now: time.Unix(0, 0)} _, err := Expand(s, facts) return err } // MaxIndex returns the highest {N} used in s, 0 when none. It reports the // same errors Expand does for a malformed placeholder: an unclosed // placeholder, or {0} (capture groups are numbered from 1). It shares // Expand's scanner shape: "{{" and "}}" each emit one literal brace, and any // other "{" opens a placeholder that runs to the next "}". func MaxIndex(s string) (int, error) { max := 0 for i := 0; i < len(s); { switch { case s[i] == '{' && i+1 < len(s) && s[i+1] == '{': i += 2 case s[i] == '}' && i+1 < len(s) && s[i+1] == '}': i += 2 case s[i] == '{': end := strings.IndexByte(s[i+1:], '}') if end < 0 { return 0, fmt.Errorf("unclosed placeholder") } body := s[i+1 : i+1+end] if n, err := strconv.Atoi(body); err == nil { if n == 0 { return 0, fmt.Errorf("capture groups are numbered from 1") } if n >= 1 && n <= 9 && n > max { max = n } } i += end + 2 default: i++ } } return max, nil }