* Fix validate() function to handle errors in embedded anon structs * Implement webhook branch filter See #2025, #3998.
		
			
				
	
	
		
			274 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package lexer
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"github.com/gobwas/glob/util/runes"
 | |
| 	"unicode/utf8"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	char_any           = '*'
 | |
| 	char_comma         = ','
 | |
| 	char_single        = '?'
 | |
| 	char_escape        = '\\'
 | |
| 	char_range_open    = '['
 | |
| 	char_range_close   = ']'
 | |
| 	char_terms_open    = '{'
 | |
| 	char_terms_close   = '}'
 | |
| 	char_range_not     = '!'
 | |
| 	char_range_between = '-'
 | |
| )
 | |
| 
 | |
| var specials = []byte{
 | |
| 	char_any,
 | |
| 	char_single,
 | |
| 	char_escape,
 | |
| 	char_range_open,
 | |
| 	char_range_close,
 | |
| 	char_terms_open,
 | |
| 	char_terms_close,
 | |
| }
 | |
| 
 | |
| func Special(c byte) bool {
 | |
| 	return bytes.IndexByte(specials, c) != -1
 | |
| }
 | |
| 
 | |
| type tokens []Token
 | |
| 
 | |
| func (i *tokens) shift() (ret Token) {
 | |
| 	ret = (*i)[0]
 | |
| 	copy(*i, (*i)[1:])
 | |
| 	*i = (*i)[:len(*i)-1]
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (i *tokens) push(v Token) {
 | |
| 	*i = append(*i, v)
 | |
| }
 | |
| 
 | |
| func (i *tokens) empty() bool {
 | |
| 	return len(*i) == 0
 | |
| }
 | |
| 
 | |
| var eof rune = 0
 | |
| 
 | |
| type lexer struct {
 | |
| 	data string
 | |
| 	pos  int
 | |
| 	err  error
 | |
| 
 | |
| 	tokens     tokens
 | |
| 	termsLevel int
 | |
| 
 | |
| 	lastRune     rune
 | |
| 	lastRuneSize int
 | |
| 	hasRune      bool
 | |
| }
 | |
| 
 | |
| func NewLexer(source string) *lexer {
 | |
| 	l := &lexer{
 | |
| 		data:   source,
 | |
| 		tokens: tokens(make([]Token, 0, 4)),
 | |
| 	}
 | |
| 	return l
 | |
| }
 | |
| 
 | |
| func (l *lexer) Next() Token {
 | |
| 	if l.err != nil {
 | |
| 		return Token{Error, l.err.Error()}
 | |
| 	}
 | |
| 	if !l.tokens.empty() {
 | |
| 		return l.tokens.shift()
 | |
| 	}
 | |
| 
 | |
| 	l.fetchItem()
 | |
| 	return l.Next()
 | |
| }
 | |
| 
 | |
| func (l *lexer) peek() (r rune, w int) {
 | |
| 	if l.pos == len(l.data) {
 | |
| 		return eof, 0
 | |
| 	}
 | |
| 
 | |
| 	r, w = utf8.DecodeRuneInString(l.data[l.pos:])
 | |
| 	if r == utf8.RuneError {
 | |
| 		l.errorf("could not read rune")
 | |
| 		r = eof
 | |
| 		w = 0
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (l *lexer) read() rune {
 | |
| 	if l.hasRune {
 | |
| 		l.hasRune = false
 | |
| 		l.seek(l.lastRuneSize)
 | |
| 		return l.lastRune
 | |
| 	}
 | |
| 
 | |
| 	r, s := l.peek()
 | |
| 	l.seek(s)
 | |
| 
 | |
| 	l.lastRune = r
 | |
| 	l.lastRuneSize = s
 | |
| 
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| func (l *lexer) seek(w int) {
 | |
| 	l.pos += w
 | |
| }
 | |
| 
 | |
| func (l *lexer) unread() {
 | |
| 	if l.hasRune {
 | |
| 		l.errorf("could not unread rune")
 | |
| 		return
 | |
| 	}
 | |
| 	l.seek(-l.lastRuneSize)
 | |
| 	l.hasRune = true
 | |
| }
 | |
| 
 | |
| func (l *lexer) errorf(f string, v ...interface{}) {
 | |
| 	l.err = fmt.Errorf(f, v...)
 | |
| }
 | |
| 
 | |
| func (l *lexer) inTerms() bool {
 | |
| 	return l.termsLevel > 0
 | |
| }
 | |
| 
 | |
| func (l *lexer) termsEnter() {
 | |
| 	l.termsLevel++
 | |
| }
 | |
| 
 | |
| func (l *lexer) termsLeave() {
 | |
| 	l.termsLevel--
 | |
| }
 | |
| 
 | |
| var inTextBreakers = []rune{char_single, char_any, char_range_open, char_terms_open}
 | |
| var inTermsBreakers = append(inTextBreakers, char_terms_close, char_comma)
 | |
| 
 | |
| func (l *lexer) fetchItem() {
 | |
| 	r := l.read()
 | |
| 	switch {
 | |
| 	case r == eof:
 | |
| 		l.tokens.push(Token{EOF, ""})
 | |
| 
 | |
| 	case r == char_terms_open:
 | |
| 		l.termsEnter()
 | |
| 		l.tokens.push(Token{TermsOpen, string(r)})
 | |
| 
 | |
| 	case r == char_comma && l.inTerms():
 | |
| 		l.tokens.push(Token{Separator, string(r)})
 | |
| 
 | |
| 	case r == char_terms_close && l.inTerms():
 | |
| 		l.tokens.push(Token{TermsClose, string(r)})
 | |
| 		l.termsLeave()
 | |
| 
 | |
| 	case r == char_range_open:
 | |
| 		l.tokens.push(Token{RangeOpen, string(r)})
 | |
| 		l.fetchRange()
 | |
| 
 | |
| 	case r == char_single:
 | |
| 		l.tokens.push(Token{Single, string(r)})
 | |
| 
 | |
| 	case r == char_any:
 | |
| 		if l.read() == char_any {
 | |
| 			l.tokens.push(Token{Super, string(r) + string(r)})
 | |
| 		} else {
 | |
| 			l.unread()
 | |
| 			l.tokens.push(Token{Any, string(r)})
 | |
| 		}
 | |
| 
 | |
| 	default:
 | |
| 		l.unread()
 | |
| 
 | |
| 		var breakers []rune
 | |
| 		if l.inTerms() {
 | |
| 			breakers = inTermsBreakers
 | |
| 		} else {
 | |
| 			breakers = inTextBreakers
 | |
| 		}
 | |
| 		l.fetchText(breakers)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (l *lexer) fetchRange() {
 | |
| 	var wantHi bool
 | |
| 	var wantClose bool
 | |
| 	var seenNot bool
 | |
| 	for {
 | |
| 		r := l.read()
 | |
| 		if r == eof {
 | |
| 			l.errorf("unexpected end of input")
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if wantClose {
 | |
| 			if r != char_range_close {
 | |
| 				l.errorf("expected close range character")
 | |
| 			} else {
 | |
| 				l.tokens.push(Token{RangeClose, string(r)})
 | |
| 			}
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if wantHi {
 | |
| 			l.tokens.push(Token{RangeHi, string(r)})
 | |
| 			wantClose = true
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if !seenNot && r == char_range_not {
 | |
| 			l.tokens.push(Token{Not, string(r)})
 | |
| 			seenNot = true
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if n, w := l.peek(); n == char_range_between {
 | |
| 			l.seek(w)
 | |
| 			l.tokens.push(Token{RangeLo, string(r)})
 | |
| 			l.tokens.push(Token{RangeBetween, string(n)})
 | |
| 			wantHi = true
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		l.unread() // unread first peek and fetch as text
 | |
| 		l.fetchText([]rune{char_range_close})
 | |
| 		wantClose = true
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (l *lexer) fetchText(breakers []rune) {
 | |
| 	var data []rune
 | |
| 	var escaped bool
 | |
| 
 | |
| reading:
 | |
| 	for {
 | |
| 		r := l.read()
 | |
| 		if r == eof {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		if !escaped {
 | |
| 			if r == char_escape {
 | |
| 				escaped = true
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			if runes.IndexRune(breakers, r) != -1 {
 | |
| 				l.unread()
 | |
| 				break reading
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		escaped = false
 | |
| 		data = append(data, r)
 | |
| 	}
 | |
| 
 | |
| 	if len(data) > 0 {
 | |
| 		l.tokens.push(Token{Text, string(data)})
 | |
| 	}
 | |
| }
 |