mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-15 02:01:43 +03:00
140 lines
2.4 KiB
Go
140 lines
2.4 KiB
Go
package query
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
type tokenKind int
|
|
|
|
const (
|
|
_ tokenKind = iota
|
|
tokenKindKV
|
|
tokenKindSearch
|
|
)
|
|
|
|
type token struct {
|
|
kind tokenKind
|
|
|
|
// KV
|
|
qualifier string
|
|
value string
|
|
|
|
// Search
|
|
term string
|
|
}
|
|
|
|
func newTokenKV(qualifier, value string) token {
|
|
return token{
|
|
kind: tokenKindKV,
|
|
qualifier: qualifier,
|
|
value: value,
|
|
}
|
|
}
|
|
|
|
func newTokenSearch(term string) token {
|
|
return token{
|
|
kind: tokenKindSearch,
|
|
term: term,
|
|
}
|
|
}
|
|
|
|
// tokenize parse and break a input into tokens ready to be
|
|
// interpreted later by a parser to get the semantic.
|
|
func tokenize(query string) ([]token, error) {
|
|
fields, err := splitQuery(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var tokens []token
|
|
for _, field := range fields {
|
|
split := strings.Split(field, ":")
|
|
|
|
// full text search
|
|
if len(split) == 1 {
|
|
tokens = append(tokens, newTokenSearch(removeQuote(field)))
|
|
continue
|
|
}
|
|
|
|
if len(split) != 2 {
|
|
return nil, fmt.Errorf("can't tokenize \"%s\"", field)
|
|
}
|
|
|
|
if len(split[0]) == 0 {
|
|
return nil, fmt.Errorf("can't tokenize \"%s\": empty qualifier", field)
|
|
}
|
|
if len(split[1]) == 0 {
|
|
return nil, fmt.Errorf("empty value for qualifier \"%s\"", split[0])
|
|
}
|
|
|
|
tokens = append(tokens, newTokenKV(split[0], removeQuote(split[1])))
|
|
}
|
|
return tokens, nil
|
|
}
|
|
|
|
// split the query into chunks by splitting on whitespaces but respecting
|
|
// quotes
|
|
func splitQuery(query string) ([]string, error) {
|
|
lastQuote := rune(0)
|
|
inQuote := false
|
|
|
|
isToken := func(r rune) bool {
|
|
switch {
|
|
case !inQuote && isQuote(r):
|
|
lastQuote = r
|
|
inQuote = true
|
|
return true
|
|
case inQuote && r == lastQuote:
|
|
lastQuote = rune(0)
|
|
inQuote = false
|
|
return true
|
|
case inQuote:
|
|
return true
|
|
default:
|
|
return !unicode.IsSpace(r)
|
|
}
|
|
}
|
|
|
|
var result []string
|
|
var token strings.Builder
|
|
for _, r := range query {
|
|
if isToken(r) {
|
|
token.WriteRune(r)
|
|
} else {
|
|
if token.Len() > 0 {
|
|
result = append(result, token.String())
|
|
token.Reset()
|
|
}
|
|
}
|
|
}
|
|
|
|
if inQuote {
|
|
return nil, fmt.Errorf("unmatched quote")
|
|
}
|
|
|
|
if token.Len() > 0 {
|
|
result = append(result, token.String())
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func isQuote(r rune) bool {
|
|
return r == '"' || r == '\''
|
|
}
|
|
|
|
func removeQuote(field string) string {
|
|
runes := []rune(field)
|
|
if len(runes) >= 2 {
|
|
r1 := runes[0]
|
|
r2 := runes[len(runes)-1]
|
|
|
|
if r1 == r2 && isQuote(r1) {
|
|
return string(runes[1 : len(runes)-1])
|
|
}
|
|
}
|
|
return field
|
|
}
|