mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-12-05 02:54:46 +01:00
263 lines
6.2 KiB
Go
263 lines
6.2 KiB
Go
|
// Copyright 2015 go-swagger maintainers
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package swag
|
||
|
|
||
|
import (
|
||
|
"unicode"
|
||
|
)
|
||
|
|
||
|
var nameReplaceTable = map[rune]string{
|
||
|
'@': "At ",
|
||
|
'&': "And ",
|
||
|
'|': "Pipe ",
|
||
|
'$': "Dollar ",
|
||
|
'!': "Bang ",
|
||
|
'-': "",
|
||
|
'_': "",
|
||
|
}
|
||
|
|
||
|
type (
|
||
|
splitter struct {
|
||
|
postSplitInitialismCheck bool
|
||
|
initialisms []string
|
||
|
}
|
||
|
|
||
|
splitterOption func(*splitter) *splitter
|
||
|
)
|
||
|
|
||
|
// split calls the splitter; splitter provides more control and post options
|
||
|
func split(str string) []string {
|
||
|
lexems := newSplitter().split(str)
|
||
|
result := make([]string, 0, len(lexems))
|
||
|
|
||
|
for _, lexem := range lexems {
|
||
|
result = append(result, lexem.GetOriginal())
|
||
|
}
|
||
|
|
||
|
return result
|
||
|
|
||
|
}
|
||
|
|
||
|
func (s *splitter) split(str string) []nameLexem {
|
||
|
return s.toNameLexems(str)
|
||
|
}
|
||
|
|
||
|
func newSplitter(options ...splitterOption) *splitter {
|
||
|
splitter := &splitter{
|
||
|
postSplitInitialismCheck: false,
|
||
|
initialisms: initialisms,
|
||
|
}
|
||
|
|
||
|
for _, option := range options {
|
||
|
splitter = option(splitter)
|
||
|
}
|
||
|
|
||
|
return splitter
|
||
|
}
|
||
|
|
||
|
// withPostSplitInitialismCheck allows to catch initialisms after main split process
|
||
|
func withPostSplitInitialismCheck(s *splitter) *splitter {
|
||
|
s.postSplitInitialismCheck = true
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
type (
|
||
|
initialismMatch struct {
|
||
|
start, end int
|
||
|
body []rune
|
||
|
complete bool
|
||
|
}
|
||
|
initialismMatches []*initialismMatch
|
||
|
)
|
||
|
|
||
|
func (s *splitter) toNameLexems(name string) []nameLexem {
|
||
|
nameRunes := []rune(name)
|
||
|
matches := s.gatherInitialismMatches(nameRunes)
|
||
|
return s.mapMatchesToNameLexems(nameRunes, matches)
|
||
|
}
|
||
|
|
||
|
func (s *splitter) gatherInitialismMatches(nameRunes []rune) initialismMatches {
|
||
|
matches := make(initialismMatches, 0)
|
||
|
|
||
|
for currentRunePosition, currentRune := range nameRunes {
|
||
|
newMatches := make(initialismMatches, 0, len(matches))
|
||
|
|
||
|
// check current initialism matches
|
||
|
for _, match := range matches {
|
||
|
if keepCompleteMatch := match.complete; keepCompleteMatch {
|
||
|
newMatches = append(newMatches, match)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// drop failed match
|
||
|
currentMatchRune := match.body[currentRunePosition-match.start]
|
||
|
if !s.initialismRuneEqual(currentMatchRune, currentRune) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// try to complete ongoing match
|
||
|
if currentRunePosition-match.start == len(match.body)-1 {
|
||
|
// we are close; the next step is to check the symbol ahead
|
||
|
// if it is a small letter, then it is not the end of match
|
||
|
// but beginning of the next word
|
||
|
|
||
|
if currentRunePosition < len(nameRunes)-1 {
|
||
|
nextRune := nameRunes[currentRunePosition+1]
|
||
|
if newWord := unicode.IsLower(nextRune); newWord {
|
||
|
// oh ok, it was the start of a new word
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
match.complete = true
|
||
|
match.end = currentRunePosition
|
||
|
}
|
||
|
|
||
|
newMatches = append(newMatches, match)
|
||
|
}
|
||
|
|
||
|
// check for new initialism matches
|
||
|
for _, initialism := range s.initialisms {
|
||
|
initialismRunes := []rune(initialism)
|
||
|
if s.initialismRuneEqual(initialismRunes[0], currentRune) {
|
||
|
newMatches = append(newMatches, &initialismMatch{
|
||
|
start: currentRunePosition,
|
||
|
body: initialismRunes,
|
||
|
complete: false,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
matches = newMatches
|
||
|
}
|
||
|
|
||
|
return matches
|
||
|
}
|
||
|
|
||
|
func (s *splitter) mapMatchesToNameLexems(nameRunes []rune, matches initialismMatches) []nameLexem {
|
||
|
nameLexems := make([]nameLexem, 0)
|
||
|
|
||
|
var lastAcceptedMatch *initialismMatch
|
||
|
for _, match := range matches {
|
||
|
if !match.complete {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if firstMatch := lastAcceptedMatch == nil; firstMatch {
|
||
|
nameLexems = append(nameLexems, s.breakCasualString(nameRunes[:match.start])...)
|
||
|
nameLexems = append(nameLexems, s.breakInitialism(string(match.body)))
|
||
|
|
||
|
lastAcceptedMatch = match
|
||
|
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if overlappedMatch := match.start <= lastAcceptedMatch.end; overlappedMatch {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
middle := nameRunes[lastAcceptedMatch.end+1 : match.start]
|
||
|
nameLexems = append(nameLexems, s.breakCasualString(middle)...)
|
||
|
nameLexems = append(nameLexems, s.breakInitialism(string(match.body)))
|
||
|
|
||
|
lastAcceptedMatch = match
|
||
|
}
|
||
|
|
||
|
// we have not found any accepted matches
|
||
|
if lastAcceptedMatch == nil {
|
||
|
return s.breakCasualString(nameRunes)
|
||
|
}
|
||
|
|
||
|
if lastAcceptedMatch.end+1 != len(nameRunes) {
|
||
|
rest := nameRunes[lastAcceptedMatch.end+1:]
|
||
|
nameLexems = append(nameLexems, s.breakCasualString(rest)...)
|
||
|
}
|
||
|
|
||
|
return nameLexems
|
||
|
}
|
||
|
|
||
|
func (s *splitter) initialismRuneEqual(a, b rune) bool {
|
||
|
return a == b
|
||
|
}
|
||
|
|
||
|
func (s *splitter) breakInitialism(original string) nameLexem {
|
||
|
return newInitialismNameLexem(original, original)
|
||
|
}
|
||
|
|
||
|
func (s *splitter) breakCasualString(str []rune) []nameLexem {
|
||
|
segments := make([]nameLexem, 0)
|
||
|
currentSegment := ""
|
||
|
|
||
|
addCasualNameLexem := func(original string) {
|
||
|
segments = append(segments, newCasualNameLexem(original))
|
||
|
}
|
||
|
|
||
|
addInitialismNameLexem := func(original, match string) {
|
||
|
segments = append(segments, newInitialismNameLexem(original, match))
|
||
|
}
|
||
|
|
||
|
addNameLexem := func(original string) {
|
||
|
if s.postSplitInitialismCheck {
|
||
|
for _, initialism := range s.initialisms {
|
||
|
if upper(initialism) == upper(original) {
|
||
|
addInitialismNameLexem(original, initialism)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
addCasualNameLexem(original)
|
||
|
}
|
||
|
|
||
|
for _, rn := range string(str) {
|
||
|
if replace, found := nameReplaceTable[rn]; found {
|
||
|
if currentSegment != "" {
|
||
|
addNameLexem(currentSegment)
|
||
|
currentSegment = ""
|
||
|
}
|
||
|
|
||
|
if replace != "" {
|
||
|
addNameLexem(replace)
|
||
|
}
|
||
|
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if !unicode.In(rn, unicode.L, unicode.M, unicode.N, unicode.Pc) {
|
||
|
if currentSegment != "" {
|
||
|
addNameLexem(currentSegment)
|
||
|
currentSegment = ""
|
||
|
}
|
||
|
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if unicode.IsUpper(rn) {
|
||
|
if currentSegment != "" {
|
||
|
addNameLexem(currentSegment)
|
||
|
}
|
||
|
currentSegment = ""
|
||
|
}
|
||
|
|
||
|
currentSegment += string(rn)
|
||
|
}
|
||
|
|
||
|
if currentSegment != "" {
|
||
|
addNameLexem(currentSegment)
|
||
|
}
|
||
|
|
||
|
return segments
|
||
|
}
|