mirror of
https://github.com/siyuan-note/siyuan.git
synced 2026-01-01 14:28:49 +01:00
204 lines
4.9 KiB
Go
204 lines
4.9 KiB
Go
// SiYuan - Build Your Eternal Digital Garden
|
||
// Copyright (c) 2020-present, b3log.org
|
||
//
|
||
// This program is free software: you can redistribute it and/or modify
|
||
// it under the terms of the GNU Affero General Public License as published by
|
||
// the Free Software Foundation, either version 3 of the License, or
|
||
// (at your option) any later version.
|
||
//
|
||
// This program is distributed in the hope that it will be useful,
|
||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
// GNU Affero General Public License for more details.
|
||
//
|
||
// You should have received a copy of the GNU Affero General Public License
|
||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
||
package util
|
||
|
||
// 该文件代码来自 https://github.com/go-git/go-git 项目,Apache-2.0 license
|
||
|
||
import (
|
||
"path/filepath"
|
||
"strings"
|
||
)
|
||
|
||
// https://github.com/go-git/go-git/blob/master/plumbing/format/gitignore/matcher.go
|
||
|
||
// Matcher defines a global multi-pattern matcher for gitignore patterns
|
||
type Matcher interface {
|
||
// Match matches patterns in the order of priorities. As soon as an inclusion or
|
||
// exclusion is found, not further matching is performed.
|
||
Match(path []string, isDir bool) bool
|
||
}
|
||
|
||
// NewMatcher constructs a new global matcher. Patterns must be given in the order of
|
||
// increasing priority. That is most generic settings files first, then the content of
|
||
// the repo .gitignore, then content of .gitignore down the path or the repo and then
|
||
// the content command line arguments.
|
||
func NewMatcher(ps []Pattern) Matcher {
|
||
return &matcher{ps}
|
||
}
|
||
|
||
type matcher struct {
|
||
patterns []Pattern
|
||
}
|
||
|
||
func (m *matcher) Match(path []string, isDir bool) bool {
|
||
n := len(m.patterns)
|
||
for i := n - 1; i >= 0; i-- {
|
||
if match := m.patterns[i].Match(path, isDir); match > NoMatch {
|
||
return match == Exclude
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// https://github.com/go-git/go-git/blob/master/plumbing/format/gitignore/pattern.go
|
||
|
||
// MatchResult defines outcomes of a match, no match, exclusion or inclusion.
|
||
type MatchResult int
|
||
|
||
const (
|
||
// NoMatch defines the no match outcome of a match check
|
||
NoMatch MatchResult = iota
|
||
// Exclude defines an exclusion of a file as a result of a match check
|
||
Exclude
|
||
// Include defines an explicit inclusion of a file as a result of a match check
|
||
Include
|
||
)
|
||
|
||
const (
|
||
inclusionPrefix = "!"
|
||
zeroToManyDirs = "**"
|
||
patternDirSep = "/"
|
||
)
|
||
|
||
// Pattern defines a single gitignore pattern.
|
||
type Pattern interface {
|
||
// Match matches the given path to the pattern.
|
||
Match(path []string, isDir bool) MatchResult
|
||
}
|
||
|
||
type pattern struct {
|
||
domain []string
|
||
pattern []string
|
||
inclusion bool
|
||
dirOnly bool
|
||
isGlob bool
|
||
}
|
||
|
||
// ParsePattern parses a gitignore pattern string into the Pattern structure.
|
||
func ParsePattern(p string, domain []string) Pattern {
|
||
res := pattern{domain: domain}
|
||
|
||
if strings.HasPrefix(p, inclusionPrefix) {
|
||
res.inclusion = true
|
||
p = p[1:]
|
||
}
|
||
|
||
if !strings.HasSuffix(p, "\\ ") {
|
||
p = strings.TrimRight(p, " ")
|
||
}
|
||
|
||
if strings.HasSuffix(p, patternDirSep) {
|
||
res.dirOnly = true
|
||
p = p[:len(p)-1]
|
||
}
|
||
|
||
if strings.Contains(p, patternDirSep) {
|
||
res.isGlob = true
|
||
}
|
||
|
||
res.pattern = strings.Split(p, patternDirSep)
|
||
return &res
|
||
}
|
||
|
||
func (p *pattern) Match(path []string, isDir bool) MatchResult {
|
||
if len(path) <= len(p.domain) {
|
||
return NoMatch
|
||
}
|
||
for i, e := range p.domain {
|
||
if path[i] != e {
|
||
return NoMatch
|
||
}
|
||
}
|
||
|
||
path = path[len(p.domain):]
|
||
if p.isGlob && !p.globMatch(path, isDir) {
|
||
return NoMatch
|
||
} else if !p.isGlob && !p.simpleNameMatch(path, isDir) {
|
||
return NoMatch
|
||
}
|
||
|
||
if p.inclusion {
|
||
return Include
|
||
} else {
|
||
return Exclude
|
||
}
|
||
}
|
||
|
||
func (p *pattern) simpleNameMatch(path []string, isDir bool) bool {
|
||
for i, name := range path {
|
||
if match, err := filepath.Match(p.pattern[0], name); err != nil {
|
||
return false
|
||
} else if !match {
|
||
continue
|
||
}
|
||
if p.dirOnly && !isDir && i == len(path)-1 {
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (p *pattern) globMatch(path []string, isDir bool) bool {
|
||
matched := false
|
||
canTraverse := false
|
||
for i, pattern := range p.pattern {
|
||
if pattern == "" {
|
||
canTraverse = false
|
||
continue
|
||
}
|
||
if pattern == zeroToManyDirs {
|
||
if i == len(p.pattern)-1 {
|
||
break
|
||
}
|
||
canTraverse = true
|
||
continue
|
||
}
|
||
if strings.Contains(pattern, zeroToManyDirs) {
|
||
return false
|
||
}
|
||
if len(path) == 0 {
|
||
return false
|
||
}
|
||
if canTraverse {
|
||
canTraverse = false
|
||
for len(path) > 0 {
|
||
e := path[0]
|
||
path = path[1:]
|
||
if match, err := filepath.Match(pattern, e); err != nil {
|
||
return false
|
||
} else if match {
|
||
matched = true
|
||
break
|
||
} else if len(path) == 0 {
|
||
// if nothing left then fail
|
||
matched = false
|
||
}
|
||
}
|
||
} else {
|
||
if match, err := filepath.Match(pattern, path[0]); err != nil || !match {
|
||
return false
|
||
}
|
||
matched = true
|
||
path = path[1:]
|
||
}
|
||
}
|
||
if matched && p.dirOnly && !isDir && len(path) == 0 {
|
||
matched = false
|
||
}
|
||
return matched
|
||
}
|