GitRoot

craft your forge, build your project, grow your community freely
 1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
 2//
 3// SPDX-License-Identifier: MIT
 4
 5package glob
 6
 7import (
 8	"regexp"
 9	"strings"
10)
11
12// inspired by https://github.com/golang/go/issues/11862#issuecomment-1517792571
13
14var replaces = regexp.MustCompile(`(\.)|(\*\*\/)|(\*)|([^\/\*]+)|(\/)`)
15
16func toRegexp(pattern string) string {
17	pat := replaces.ReplaceAllStringFunc(pattern, func(s string) string {
18		switch s {
19		case "/":
20			return "\\/"
21		case ".":
22			return "\\."
23		case "**/":
24			return ".*"
25		case "*":
26			return "[^/]*"
27		default:
28			return s
29		}
30	})
31	return "^" + pat + "$"
32}
33
34type Glob struct {
35	reg    *regexp.Regexp
36	regNeg *regexp.Regexp
37}
38
39func NewGlob(path string) (*Glob, error) {
40	parts := strings.Split(path, "|")
41	norm := []string{}
42	neg := []string{}
43	for _, part := range parts {
44		if strings.HasPrefix(part, "!") {
45			neg = append(neg, strings.TrimPrefix(part, "!"))
46		} else {
47			norm = append(norm, part)
48		}
49	}
50	reg, err := regexp.Compile(toRegexp(strings.Join(norm, "|")))
51	if err != nil {
52		return nil, err
53	}
54	var regNeg *regexp.Regexp
55	if len(neg) > 0 {
56		regNeg, err = regexp.Compile(toRegexp(strings.Join(neg, "|")))
57		if err != nil {
58			return nil, err
59		}
60	}
61	return &Glob{
62		reg:    reg,
63		regNeg: regNeg,
64	}, nil
65}
66
67func (g *Glob) Match(path string) bool {
68	if g.regNeg == nil {
69		return g.reg.MatchString(path)
70	} else {
71		return g.reg.MatchString(path) && !g.regNeg.MatchString(path)
72	}
73}