mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 14:52:30 -05:00 
			
		
		
		
	Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.4 to 1.7.6. - [Release notes](https://github.com/yuin/goldmark/releases) - [Commits](https://github.com/yuin/goldmark/compare/v1.7.4...v1.7.6) --- updated-dependencies: - dependency-name: github.com/yuin/goldmark dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
		
			
				
	
	
		
			988 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			988 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package util provides utility functions for the goldmark.
 | |
| package util
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"io"
 | |
| 	"net/url"
 | |
| 	"regexp"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"unicode"
 | |
| 	"unicode/utf8"
 | |
| )
 | |
| 
 | |
| // A CopyOnWriteBuffer is a byte buffer that copies buffer when
 | |
| // it need to be changed.
 | |
| type CopyOnWriteBuffer struct {
 | |
| 	buffer []byte
 | |
| 	copied bool
 | |
| }
 | |
| 
 | |
| // NewCopyOnWriteBuffer returns a new CopyOnWriteBuffer.
 | |
| func NewCopyOnWriteBuffer(buffer []byte) CopyOnWriteBuffer {
 | |
| 	return CopyOnWriteBuffer{
 | |
| 		buffer: buffer,
 | |
| 		copied: false,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Write writes given bytes to the buffer.
 | |
| // Write allocate new buffer and clears it at the first time.
 | |
| func (b *CopyOnWriteBuffer) Write(value []byte) {
 | |
| 	if !b.copied {
 | |
| 		b.buffer = make([]byte, 0, len(b.buffer)+20)
 | |
| 		b.copied = true
 | |
| 	}
 | |
| 	b.buffer = append(b.buffer, value...)
 | |
| }
 | |
| 
 | |
| // WriteString writes given string to the buffer.
 | |
| // WriteString allocate new buffer and clears it at the first time.
 | |
| func (b *CopyOnWriteBuffer) WriteString(value string) {
 | |
| 	b.Write(StringToReadOnlyBytes(value))
 | |
| }
 | |
| 
 | |
| // Append appends given bytes to the buffer.
 | |
| // Append copy buffer at the first time.
 | |
| func (b *CopyOnWriteBuffer) Append(value []byte) {
 | |
| 	if !b.copied {
 | |
| 		tmp := make([]byte, len(b.buffer), len(b.buffer)+20)
 | |
| 		copy(tmp, b.buffer)
 | |
| 		b.buffer = tmp
 | |
| 		b.copied = true
 | |
| 	}
 | |
| 	b.buffer = append(b.buffer, value...)
 | |
| }
 | |
| 
 | |
| // AppendString appends given string to the buffer.
 | |
| // AppendString copy buffer at the first time.
 | |
| func (b *CopyOnWriteBuffer) AppendString(value string) {
 | |
| 	b.Append(StringToReadOnlyBytes(value))
 | |
| }
 | |
| 
 | |
| // WriteByte writes the given byte to the buffer.
 | |
| // WriteByte allocate new buffer and clears it at the first time.
 | |
| func (b *CopyOnWriteBuffer) WriteByte(c byte) error {
 | |
| 	if !b.copied {
 | |
| 		b.buffer = make([]byte, 0, len(b.buffer)+20)
 | |
| 		b.copied = true
 | |
| 	}
 | |
| 	b.buffer = append(b.buffer, c)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // AppendByte appends given bytes to the buffer.
 | |
| // AppendByte copy buffer at the first time.
 | |
| func (b *CopyOnWriteBuffer) AppendByte(c byte) {
 | |
| 	if !b.copied {
 | |
| 		tmp := make([]byte, len(b.buffer), len(b.buffer)+20)
 | |
| 		copy(tmp, b.buffer)
 | |
| 		b.buffer = tmp
 | |
| 		b.copied = true
 | |
| 	}
 | |
| 	b.buffer = append(b.buffer, c)
 | |
| }
 | |
| 
 | |
| // Bytes returns bytes of this buffer.
 | |
| func (b *CopyOnWriteBuffer) Bytes() []byte {
 | |
| 	return b.buffer
 | |
| }
 | |
| 
 | |
| // IsCopied returns true if buffer has been copied, otherwise false.
 | |
| func (b *CopyOnWriteBuffer) IsCopied() bool {
 | |
| 	return b.copied
 | |
| }
 | |
| 
 | |
| // IsEscapedPunctuation returns true if character at a given index i
 | |
| // is an escaped punctuation, otherwise false.
 | |
| func IsEscapedPunctuation(source []byte, i int) bool {
 | |
| 	return source[i] == '\\' && i < len(source)-1 && IsPunct(source[i+1])
 | |
| }
 | |
| 
 | |
| // ReadWhile read the given source while pred is true.
 | |
| func ReadWhile(source []byte, index [2]int, pred func(byte) bool) (int, bool) {
 | |
| 	j := index[0]
 | |
| 	ok := false
 | |
| 	for ; j < index[1]; j++ {
 | |
| 		c1 := source[j]
 | |
| 		if pred(c1) {
 | |
| 			ok = true
 | |
| 			continue
 | |
| 		}
 | |
| 		break
 | |
| 	}
 | |
| 	return j, ok
 | |
| }
 | |
| 
 | |
| // IsBlank returns true if the given string is all space characters.
 | |
| func IsBlank(bs []byte) bool {
 | |
| 	for _, b := range bs {
 | |
| 		if !IsSpace(b) {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // VisualizeSpaces visualize invisible space characters.
 | |
| func VisualizeSpaces(bs []byte) []byte {
 | |
| 	bs = bytes.Replace(bs, []byte(" "), []byte("[SPACE]"), -1)
 | |
| 	bs = bytes.Replace(bs, []byte("\t"), []byte("[TAB]"), -1)
 | |
| 	bs = bytes.Replace(bs, []byte("\n"), []byte("[NEWLINE]\n"), -1)
 | |
| 	bs = bytes.Replace(bs, []byte("\r"), []byte("[CR]"), -1)
 | |
| 	bs = bytes.Replace(bs, []byte("\v"), []byte("[VTAB]"), -1)
 | |
| 	bs = bytes.Replace(bs, []byte("\x00"), []byte("[NUL]"), -1)
 | |
| 	bs = bytes.Replace(bs, []byte("\ufffd"), []byte("[U+FFFD]"), -1)
 | |
| 	return bs
 | |
| }
 | |
| 
 | |
| // TabWidth calculates actual width of a tab at the given position.
 | |
| func TabWidth(currentPos int) int {
 | |
| 	return 4 - currentPos%4
 | |
| }
 | |
| 
 | |
| // IndentPosition searches an indent position with the given width for the given line.
 | |
| // If the line contains tab characters, paddings may be not zero.
 | |
| // currentPos==0 and width==2:
 | |
| //
 | |
| //	position: 0    1
 | |
| //	          [TAB]aaaa
 | |
| //	width:    1234 5678
 | |
| //
 | |
| // width=2 is in the tab character. In this case, IndentPosition returns
 | |
| // (pos=1, padding=2).
 | |
| func IndentPosition(bs []byte, currentPos, width int) (pos, padding int) {
 | |
| 	return IndentPositionPadding(bs, currentPos, 0, width)
 | |
| }
 | |
| 
 | |
| // IndentPositionPadding searches an indent position with the given width for the given line.
 | |
| // This function is mostly same as IndentPosition except this function
 | |
| // takes account into additional paddings.
 | |
| func IndentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, padding int) {
 | |
| 	if width == 0 {
 | |
| 		return 0, paddingv
 | |
| 	}
 | |
| 	w := 0
 | |
| 	i := 0
 | |
| 	l := len(bs)
 | |
| 	p := paddingv
 | |
| 	for ; i < l; i++ {
 | |
| 		if p > 0 {
 | |
| 			p--
 | |
| 			w++
 | |
| 			continue
 | |
| 		}
 | |
| 		if bs[i] == '\t' && w < width {
 | |
| 			w += TabWidth(currentPos + w)
 | |
| 		} else if bs[i] == ' ' && w < width {
 | |
| 			w++
 | |
| 		} else {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if w >= width {
 | |
| 		return i - paddingv, w - width
 | |
| 	}
 | |
| 	return -1, -1
 | |
| }
 | |
| 
 | |
| // DedentPosition dedents lines by the given width.
 | |
| //
 | |
| // Deprecated: This function has bugs. Use util.IndentPositionPadding and util.FirstNonSpacePosition.
 | |
| func DedentPosition(bs []byte, currentPos, width int) (pos, padding int) {
 | |
| 	if width == 0 {
 | |
| 		return 0, 0
 | |
| 	}
 | |
| 	w := 0
 | |
| 	l := len(bs)
 | |
| 	i := 0
 | |
| 	for ; i < l; i++ {
 | |
| 		if bs[i] == '\t' {
 | |
| 			w += TabWidth(currentPos + w)
 | |
| 		} else if bs[i] == ' ' {
 | |
| 			w++
 | |
| 		} else {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if w >= width {
 | |
| 		return i, w - width
 | |
| 	}
 | |
| 	return i, 0
 | |
| }
 | |
| 
 | |
| // DedentPositionPadding dedents lines by the given width.
 | |
| // This function is mostly same as DedentPosition except this function
 | |
| // takes account into additional paddings.
 | |
| //
 | |
| // Deprecated: This function has bugs. Use util.IndentPositionPadding and util.FirstNonSpacePosition.
 | |
| func DedentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, padding int) {
 | |
| 	if width == 0 {
 | |
| 		return 0, paddingv
 | |
| 	}
 | |
| 
 | |
| 	w := 0
 | |
| 	i := 0
 | |
| 	l := len(bs)
 | |
| 	for ; i < l; i++ {
 | |
| 		if bs[i] == '\t' {
 | |
| 			w += TabWidth(currentPos + w)
 | |
| 		} else if bs[i] == ' ' {
 | |
| 			w++
 | |
| 		} else {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if w >= width {
 | |
| 		return i - paddingv, w - width
 | |
| 	}
 | |
| 	return i - paddingv, 0
 | |
| }
 | |
| 
 | |
| // IndentWidth calculate an indent width for the given line.
 | |
| func IndentWidth(bs []byte, currentPos int) (width, pos int) {
 | |
| 	l := len(bs)
 | |
| 	for i := 0; i < l; i++ {
 | |
| 		b := bs[i]
 | |
| 		if b == ' ' {
 | |
| 			width++
 | |
| 			pos++
 | |
| 		} else if b == '\t' {
 | |
| 			width += TabWidth(currentPos + width)
 | |
| 			pos++
 | |
| 		} else {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // FirstNonSpacePosition returns a position line that is a first nonspace
 | |
| // character.
 | |
| func FirstNonSpacePosition(bs []byte) int {
 | |
| 	i := 0
 | |
| 	for ; i < len(bs); i++ {
 | |
| 		c := bs[i]
 | |
| 		if c == ' ' || c == '\t' {
 | |
| 			continue
 | |
| 		}
 | |
| 		if c == '\n' {
 | |
| 			return -1
 | |
| 		}
 | |
| 		return i
 | |
| 	}
 | |
| 	return -1
 | |
| }
 | |
| 
 | |
| // FindClosure returns a position that closes the given opener.
 | |
| // If codeSpan is set true, it ignores characters in code spans.
 | |
| // If allowNesting is set true, closures correspond to nested opener will be
 | |
| // ignored.
 | |
| //
 | |
| // Deprecated: This function can not handle newlines. Many elements
 | |
| // can be existed over multiple lines(e.g. link labels).
 | |
| // Use text.Reader.FindClosure.
 | |
| func FindClosure(bs []byte, opener, closure byte, codeSpan, allowNesting bool) int {
 | |
| 	i := 0
 | |
| 	opened := 1
 | |
| 	codeSpanOpener := 0
 | |
| 	for i < len(bs) {
 | |
| 		c := bs[i]
 | |
| 		if codeSpan && codeSpanOpener != 0 && c == '`' {
 | |
| 			codeSpanCloser := 0
 | |
| 			for ; i < len(bs); i++ {
 | |
| 				if bs[i] == '`' {
 | |
| 					codeSpanCloser++
 | |
| 				} else {
 | |
| 					i--
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if codeSpanCloser == codeSpanOpener {
 | |
| 				codeSpanOpener = 0
 | |
| 			}
 | |
| 		} else if codeSpanOpener == 0 && c == '\\' && i < len(bs)-1 && IsPunct(bs[i+1]) {
 | |
| 			i += 2
 | |
| 			continue
 | |
| 		} else if codeSpan && codeSpanOpener == 0 && c == '`' {
 | |
| 			for ; i < len(bs); i++ {
 | |
| 				if bs[i] == '`' {
 | |
| 					codeSpanOpener++
 | |
| 				} else {
 | |
| 					i--
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		} else if (codeSpan && codeSpanOpener == 0) || !codeSpan {
 | |
| 			if c == closure {
 | |
| 				opened--
 | |
| 				if opened == 0 {
 | |
| 					return i
 | |
| 				}
 | |
| 			} else if c == opener {
 | |
| 				if !allowNesting {
 | |
| 					return -1
 | |
| 				}
 | |
| 				opened++
 | |
| 			}
 | |
| 		}
 | |
| 		i++
 | |
| 	}
 | |
| 	return -1
 | |
| }
 | |
| 
 | |
| // TrimLeft trims characters in the given s from head of the source.
 | |
| // bytes.TrimLeft offers same functionalities, but bytes.TrimLeft
 | |
| // allocates new buffer for the result.
 | |
| func TrimLeft(source, b []byte) []byte {
 | |
| 	i := 0
 | |
| 	for ; i < len(source); i++ {
 | |
| 		c := source[i]
 | |
| 		found := false
 | |
| 		for j := 0; j < len(b); j++ {
 | |
| 			if c == b[j] {
 | |
| 				found = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return source[i:]
 | |
| }
 | |
| 
 | |
| // TrimRight trims characters in the given s from tail of the source.
 | |
| func TrimRight(source, b []byte) []byte {
 | |
| 	i := len(source) - 1
 | |
| 	for ; i >= 0; i-- {
 | |
| 		c := source[i]
 | |
| 		found := false
 | |
| 		for j := 0; j < len(b); j++ {
 | |
| 			if c == b[j] {
 | |
| 				found = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return source[:i+1]
 | |
| }
 | |
| 
 | |
| // TrimLeftLength returns a length of leading specified characters.
 | |
| func TrimLeftLength(source, s []byte) int {
 | |
| 	return len(source) - len(TrimLeft(source, s))
 | |
| }
 | |
| 
 | |
| // TrimRightLength returns a length of trailing specified characters.
 | |
| func TrimRightLength(source, s []byte) int {
 | |
| 	return len(source) - len(TrimRight(source, s))
 | |
| }
 | |
| 
 | |
| // TrimLeftSpaceLength returns a length of leading space characters.
 | |
| func TrimLeftSpaceLength(source []byte) int {
 | |
| 	i := 0
 | |
| 	for ; i < len(source); i++ {
 | |
| 		if !IsSpace(source[i]) {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return i
 | |
| }
 | |
| 
 | |
| // TrimRightSpaceLength returns a length of trailing space characters.
 | |
| func TrimRightSpaceLength(source []byte) int {
 | |
| 	l := len(source)
 | |
| 	i := l - 1
 | |
| 	for ; i >= 0; i-- {
 | |
| 		if !IsSpace(source[i]) {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if i < 0 {
 | |
| 		return l
 | |
| 	}
 | |
| 	return l - 1 - i
 | |
| }
 | |
| 
 | |
| // TrimLeftSpace returns a subslice of the given string by slicing off all leading
 | |
| // space characters.
 | |
| func TrimLeftSpace(source []byte) []byte {
 | |
| 	return TrimLeft(source, spaces)
 | |
| }
 | |
| 
 | |
| // TrimRightSpace returns a subslice of the given string by slicing off all trailing
 | |
| // space characters.
 | |
| func TrimRightSpace(source []byte) []byte {
 | |
| 	return TrimRight(source, spaces)
 | |
| }
 | |
| 
 | |
| // DoFullUnicodeCaseFolding performs full unicode case folding to given bytes.
 | |
| func DoFullUnicodeCaseFolding(v []byte) []byte {
 | |
| 	var rbuf []byte
 | |
| 	cob := NewCopyOnWriteBuffer(v)
 | |
| 	n := 0
 | |
| 	for i := 0; i < len(v); i++ {
 | |
| 		c := v[i]
 | |
| 		if c < 0xb5 {
 | |
| 			if c >= 0x41 && c <= 0x5a {
 | |
| 				// A-Z to a-z
 | |
| 				cob.Write(v[n:i])
 | |
| 				_ = cob.WriteByte(c + 32)
 | |
| 				n = i + 1
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if !utf8.RuneStart(c) {
 | |
| 			continue
 | |
| 		}
 | |
| 		r, length := utf8.DecodeRune(v[i:])
 | |
| 		if r == utf8.RuneError {
 | |
| 			continue
 | |
| 		}
 | |
| 		folded, ok := unicodeCaseFoldings[r]
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		cob.Write(v[n:i])
 | |
| 		if rbuf == nil {
 | |
| 			rbuf = make([]byte, 4)
 | |
| 		}
 | |
| 		for _, f := range folded {
 | |
| 			l := utf8.EncodeRune(rbuf, f)
 | |
| 			cob.Write(rbuf[:l])
 | |
| 		}
 | |
| 		i += length - 1
 | |
| 		n = i + 1
 | |
| 	}
 | |
| 	if cob.IsCopied() {
 | |
| 		cob.Write(v[n:])
 | |
| 	}
 | |
| 	return cob.Bytes()
 | |
| }
 | |
| 
 | |
| // ReplaceSpaces replaces sequence of spaces with the given repl.
 | |
| func ReplaceSpaces(source []byte, repl byte) []byte {
 | |
| 	var ret []byte
 | |
| 	start := -1
 | |
| 	for i, c := range source {
 | |
| 		iss := IsSpace(c)
 | |
| 		if start < 0 && iss {
 | |
| 			start = i
 | |
| 			continue
 | |
| 		} else if start >= 0 && iss {
 | |
| 			continue
 | |
| 		} else if start >= 0 {
 | |
| 			if ret == nil {
 | |
| 				ret = make([]byte, 0, len(source))
 | |
| 				ret = append(ret, source[:start]...)
 | |
| 			}
 | |
| 			ret = append(ret, repl)
 | |
| 			start = -1
 | |
| 		}
 | |
| 		if ret != nil {
 | |
| 			ret = append(ret, c)
 | |
| 		}
 | |
| 	}
 | |
| 	if start >= 0 && ret != nil {
 | |
| 		ret = append(ret, repl)
 | |
| 	}
 | |
| 	if ret == nil {
 | |
| 		return source
 | |
| 	}
 | |
| 	return ret
 | |
| }
 | |
| 
 | |
| // ToRune decode given bytes start at pos and returns a rune.
 | |
| func ToRune(source []byte, pos int) rune {
 | |
| 	i := pos
 | |
| 	for ; i >= 0; i-- {
 | |
| 		if utf8.RuneStart(source[i]) {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	r, _ := utf8.DecodeRune(source[i:])
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // ToValidRune returns 0xFFFD if the given rune is invalid, otherwise v.
 | |
| func ToValidRune(v rune) rune {
 | |
| 	if v == 0 || !utf8.ValidRune(v) {
 | |
| 		return rune(0xFFFD)
 | |
| 	}
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| // ToLinkReference converts given bytes into a valid link reference string.
 | |
| // ToLinkReference performs unicode case folding, trims leading and trailing spaces,  converts into lower
 | |
| // case and replace spaces with a single space character.
 | |
| func ToLinkReference(v []byte) string {
 | |
| 	v = TrimLeftSpace(v)
 | |
| 	v = TrimRightSpace(v)
 | |
| 	v = DoFullUnicodeCaseFolding(v)
 | |
| 	return string(ReplaceSpaces(v, ' '))
 | |
| }
 | |
| 
 | |
| var htmlEscapeTable = [256][]byte{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("""), nil, nil, nil, []byte("&"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("<"), nil, []byte(">"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil} //nolint:golint,lll
 | |
| 
 | |
| // EscapeHTMLByte returns HTML escaped bytes if the given byte should be escaped,
 | |
| // otherwise nil.
 | |
| func EscapeHTMLByte(b byte) []byte {
 | |
| 	return htmlEscapeTable[b]
 | |
| }
 | |
| 
 | |
| // EscapeHTML escapes characters that should be escaped in HTML text.
 | |
| func EscapeHTML(v []byte) []byte {
 | |
| 	cob := NewCopyOnWriteBuffer(v)
 | |
| 	n := 0
 | |
| 	for i := 0; i < len(v); i++ {
 | |
| 		c := v[i]
 | |
| 		escaped := htmlEscapeTable[c]
 | |
| 		if escaped != nil {
 | |
| 			cob.Write(v[n:i])
 | |
| 			cob.Write(escaped)
 | |
| 			n = i + 1
 | |
| 		}
 | |
| 	}
 | |
| 	if cob.IsCopied() {
 | |
| 		cob.Write(v[n:])
 | |
| 	}
 | |
| 	return cob.Bytes()
 | |
| }
 | |
| 
 | |
| // UnescapePunctuations unescapes blackslash escaped punctuations.
 | |
| func UnescapePunctuations(source []byte) []byte {
 | |
| 	cob := NewCopyOnWriteBuffer(source)
 | |
| 	limit := len(source)
 | |
| 	n := 0
 | |
| 	for i := 0; i < limit; {
 | |
| 		c := source[i]
 | |
| 		if i < limit-1 && c == '\\' && IsPunct(source[i+1]) {
 | |
| 			cob.Write(source[n:i])
 | |
| 			_ = cob.WriteByte(source[i+1])
 | |
| 			i += 2
 | |
| 			n = i
 | |
| 			continue
 | |
| 		}
 | |
| 		i++
 | |
| 	}
 | |
| 	if cob.IsCopied() {
 | |
| 		cob.Write(source[n:])
 | |
| 	}
 | |
| 	return cob.Bytes()
 | |
| }
 | |
| 
 | |
| // ResolveNumericReferences resolve numeric references like 'Ӓ" .
 | |
| func ResolveNumericReferences(source []byte) []byte {
 | |
| 	cob := NewCopyOnWriteBuffer(source)
 | |
| 	buf := make([]byte, 6)
 | |
| 	limit := len(source)
 | |
| 	var ok bool
 | |
| 	n := 0
 | |
| 	for i := 0; i < limit; i++ {
 | |
| 		if source[i] == '&' {
 | |
| 			pos := i
 | |
| 			next := i + 1
 | |
| 			if next < limit && source[next] == '#' {
 | |
| 				nnext := next + 1
 | |
| 				if nnext < limit {
 | |
| 					nc := source[nnext]
 | |
| 					// code point like #x22;
 | |
| 					if nnext < limit && nc == 'x' || nc == 'X' {
 | |
| 						start := nnext + 1
 | |
| 						i, ok = ReadWhile(source, [2]int{start, limit}, IsHexDecimal)
 | |
| 						if ok && i < limit && source[i] == ';' {
 | |
| 							v, _ := strconv.ParseUint(BytesToReadOnlyString(source[start:i]), 16, 32)
 | |
| 							cob.Write(source[n:pos])
 | |
| 							n = i + 1
 | |
| 							runeSize := utf8.EncodeRune(buf, ToValidRune(rune(v)))
 | |
| 							cob.Write(buf[:runeSize])
 | |
| 							continue
 | |
| 						}
 | |
| 						// code point like #1234;
 | |
| 					} else if nc >= '0' && nc <= '9' {
 | |
| 						start := nnext
 | |
| 						i, ok = ReadWhile(source, [2]int{start, limit}, IsNumeric)
 | |
| 						if ok && i < limit && i-start < 8 && source[i] == ';' {
 | |
| 							v, _ := strconv.ParseUint(BytesToReadOnlyString(source[start:i]), 0, 32)
 | |
| 							cob.Write(source[n:pos])
 | |
| 							n = i + 1
 | |
| 							runeSize := utf8.EncodeRune(buf, ToValidRune(rune(v)))
 | |
| 							cob.Write(buf[:runeSize])
 | |
| 							continue
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			i = next - 1
 | |
| 		}
 | |
| 	}
 | |
| 	if cob.IsCopied() {
 | |
| 		cob.Write(source[n:])
 | |
| 	}
 | |
| 	return cob.Bytes()
 | |
| }
 | |
| 
 | |
| // ResolveEntityNames resolve entity references like 'ö" .
 | |
| func ResolveEntityNames(source []byte) []byte {
 | |
| 	cob := NewCopyOnWriteBuffer(source)
 | |
| 	limit := len(source)
 | |
| 	var ok bool
 | |
| 	n := 0
 | |
| 	for i := 0; i < limit; i++ {
 | |
| 		if source[i] == '&' {
 | |
| 			pos := i
 | |
| 			next := i + 1
 | |
| 			if !(next < limit && source[next] == '#') {
 | |
| 				start := next
 | |
| 				i, ok = ReadWhile(source, [2]int{start, limit}, IsAlphaNumeric)
 | |
| 				if ok && i < limit && source[i] == ';' {
 | |
| 					name := BytesToReadOnlyString(source[start:i])
 | |
| 					entity, ok := LookUpHTML5EntityByName(name)
 | |
| 					if ok {
 | |
| 						cob.Write(source[n:pos])
 | |
| 						n = i + 1
 | |
| 						cob.Write(entity.Characters)
 | |
| 						continue
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			i = next - 1
 | |
| 		}
 | |
| 	}
 | |
| 	if cob.IsCopied() {
 | |
| 		cob.Write(source[n:])
 | |
| 	}
 | |
| 	return cob.Bytes()
 | |
| }
 | |
| 
 | |
| var htmlSpace = []byte("%20")
 | |
| 
 | |
| // URLEscape escape the given URL.
 | |
| // If resolveReference is set true:
 | |
| //  1. unescape punctuations
 | |
| //  2. resolve numeric references
 | |
| //  3. resolve entity references
 | |
| //
 | |
| // URL encoded values (%xx) are kept as is.
 | |
| func URLEscape(v []byte, resolveReference bool) []byte {
 | |
| 	if resolveReference {
 | |
| 		v = UnescapePunctuations(v)
 | |
| 		v = ResolveNumericReferences(v)
 | |
| 		v = ResolveEntityNames(v)
 | |
| 	}
 | |
| 	cob := NewCopyOnWriteBuffer(v)
 | |
| 	limit := len(v)
 | |
| 	n := 0
 | |
| 
 | |
| 	for i := 0; i < limit; {
 | |
| 		c := v[i]
 | |
| 		if urlEscapeTable[c] == 1 {
 | |
| 			i++
 | |
| 			continue
 | |
| 		}
 | |
| 		if c == '%' && i+2 < limit && IsHexDecimal(v[i+1]) && IsHexDecimal(v[i+1]) {
 | |
| 			i += 3
 | |
| 			continue
 | |
| 		}
 | |
| 		u8len := utf8lenTable[c]
 | |
| 		if u8len == 99 { // invalid utf8 leading byte, skip it
 | |
| 			i++
 | |
| 			continue
 | |
| 		}
 | |
| 		if c == ' ' {
 | |
| 			cob.Write(v[n:i])
 | |
| 			cob.Write(htmlSpace)
 | |
| 			i++
 | |
| 			n = i
 | |
| 			continue
 | |
| 		}
 | |
| 		if int(u8len) > len(v) {
 | |
| 			u8len = int8(len(v) - 1)
 | |
| 		}
 | |
| 		if u8len == 0 {
 | |
| 			i++
 | |
| 			n = i
 | |
| 			continue
 | |
| 		}
 | |
| 		cob.Write(v[n:i])
 | |
| 		stop := i + int(u8len)
 | |
| 		if stop > len(v) {
 | |
| 			i++
 | |
| 			n = i
 | |
| 			continue
 | |
| 		}
 | |
| 		cob.Write(StringToReadOnlyBytes(url.QueryEscape(string(v[i:stop]))))
 | |
| 		i += int(u8len)
 | |
| 		n = i
 | |
| 	}
 | |
| 	if cob.IsCopied() && n < limit {
 | |
| 		cob.Write(v[n:])
 | |
| 	}
 | |
| 	return cob.Bytes()
 | |
| }
 | |
| 
 | |
| // FindURLIndex returns a stop index value if the given bytes seem an URL.
 | |
| // This function is equivalent to [A-Za-z][A-Za-z0-9.+-]{1,31}:[^<>\x00-\x20]* .
 | |
| func FindURLIndex(b []byte) int {
 | |
| 	i := 0
 | |
| 	if !(len(b) > 0 && urlTable[b[i]]&7 == 7) {
 | |
| 		return -1
 | |
| 	}
 | |
| 	i++
 | |
| 	for ; i < len(b); i++ {
 | |
| 		c := b[i]
 | |
| 		if urlTable[c]&4 != 4 {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if i == 1 || i > 33 || i >= len(b) {
 | |
| 		return -1
 | |
| 	}
 | |
| 	if b[i] != ':' {
 | |
| 		return -1
 | |
| 	}
 | |
| 	i++
 | |
| 	for ; i < len(b); i++ {
 | |
| 		c := b[i]
 | |
| 		if urlTable[c]&1 != 1 {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return i
 | |
| }
 | |
| 
 | |
| var emailDomainRegexp = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*`) //nolint:golint,lll
 | |
| 
 | |
| // FindEmailIndex returns a stop index value if the given bytes seem an email address.
 | |
| func FindEmailIndex(b []byte) int {
 | |
| 	// TODO: eliminate regexps
 | |
| 	i := 0
 | |
| 	for ; i < len(b); i++ {
 | |
| 		c := b[i]
 | |
| 		if emailTable[c]&1 != 1 {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if i == 0 {
 | |
| 		return -1
 | |
| 	}
 | |
| 	if i >= len(b) || b[i] != '@' {
 | |
| 		return -1
 | |
| 	}
 | |
| 	i++
 | |
| 	if i >= len(b) {
 | |
| 		return -1
 | |
| 	}
 | |
| 	match := emailDomainRegexp.FindSubmatchIndex(b[i:])
 | |
| 	if match == nil {
 | |
| 		return -1
 | |
| 	}
 | |
| 	return i + match[1]
 | |
| }
 | |
| 
 | |
| var spaces = []byte(" \t\n\x0b\x0c\x0d")
 | |
| 
 | |
| var spaceTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //nolint:golint,lll
 | |
| 
 | |
| var punctTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //nolint:golint,lll
 | |
| 
 | |
| // a-zA-Z0-9, ;/?:@&=+$,-_.!~*'()#
 | |
| 
 | |
| var urlEscapeTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //nolint:golint,lll
 | |
| 
 | |
| var utf8lenTable = [256]int8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 99, 99, 99, 99, 99, 99, 99, 99} //nolint:golint,lll
 | |
| 
 | |
| var urlTable = [256]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 5, 5, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 0, 1, 0, 1, 1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} //nolint:golint,lll
 | |
| 
 | |
| var emailTable = [256]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //nolint:golint,lll
 | |
| 
 | |
| // UTF8Len returns a byte length of the utf-8 character.
 | |
| func UTF8Len(b byte) int8 {
 | |
| 	return utf8lenTable[b]
 | |
| }
 | |
| 
 | |
| // IsPunct returns true if the given character is a punctuation, otherwise false.
 | |
| func IsPunct(c byte) bool {
 | |
| 	return punctTable[c] == 1
 | |
| }
 | |
| 
 | |
| // IsPunctRune returns true if the given rune is a punctuation, otherwise false.
 | |
| func IsPunctRune(r rune) bool {
 | |
| 	return unicode.IsSymbol(r) || unicode.IsPunct(r)
 | |
| }
 | |
| 
 | |
| // IsSpace returns true if the given character is a space, otherwise false.
 | |
| func IsSpace(c byte) bool {
 | |
| 	return spaceTable[c] == 1
 | |
| }
 | |
| 
 | |
| // IsSpaceRune returns true if the given rune is a space, otherwise false.
 | |
| func IsSpaceRune(r rune) bool {
 | |
| 	return int32(r) <= 256 && IsSpace(byte(r)) || unicode.IsSpace(r)
 | |
| }
 | |
| 
 | |
| // IsNumeric returns true if the given character is a numeric, otherwise false.
 | |
| func IsNumeric(c byte) bool {
 | |
| 	return c >= '0' && c <= '9'
 | |
| }
 | |
| 
 | |
| // IsHexDecimal returns true if the given character is a hexdecimal, otherwise false.
 | |
| func IsHexDecimal(c byte) bool {
 | |
| 	return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'
 | |
| }
 | |
| 
 | |
| // IsAlphaNumeric returns true if the given character is a alphabet or a numeric, otherwise false.
 | |
| func IsAlphaNumeric(c byte) bool {
 | |
| 	return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
 | |
| }
 | |
| 
 | |
| // A BufWriter is a subset of the bufio.Writer .
 | |
| type BufWriter interface {
 | |
| 	io.Writer
 | |
| 	Available() int
 | |
| 	Buffered() int
 | |
| 	Flush() error
 | |
| 	WriteByte(c byte) error
 | |
| 	WriteRune(r rune) (size int, err error)
 | |
| 	WriteString(s string) (int, error)
 | |
| }
 | |
| 
 | |
| // A PrioritizedValue struct holds pair of an arbitrary value and a priority.
 | |
| type PrioritizedValue struct {
 | |
| 	// Value is an arbitrary value that you want to prioritize.
 | |
| 	Value interface{}
 | |
| 	// Priority is a priority of the value.
 | |
| 	Priority int
 | |
| }
 | |
| 
 | |
| // PrioritizedSlice is a slice of the PrioritizedValues.
 | |
| type PrioritizedSlice []PrioritizedValue
 | |
| 
 | |
| // Sort sorts the PrioritizedSlice in ascending order.
 | |
| func (s PrioritizedSlice) Sort() {
 | |
| 	sort.Slice(s, func(i, j int) bool {
 | |
| 		return s[i].Priority < s[j].Priority
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Remove removes the given value from this slice.
 | |
| func (s PrioritizedSlice) Remove(v interface{}) PrioritizedSlice {
 | |
| 	i := 0
 | |
| 	found := false
 | |
| 	for ; i < len(s); i++ {
 | |
| 		if s[i].Value == v {
 | |
| 			found = true
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if !found {
 | |
| 		return s
 | |
| 	}
 | |
| 	return append(s[:i], s[i+1:]...)
 | |
| }
 | |
| 
 | |
| // Prioritized returns a new PrioritizedValue.
 | |
| func Prioritized(v interface{}, priority int) PrioritizedValue {
 | |
| 	return PrioritizedValue{v, priority}
 | |
| }
 | |
| 
 | |
| func bytesHash(b []byte) uint64 {
 | |
| 	var hash uint64 = 5381
 | |
| 	for _, c := range b {
 | |
| 		hash = ((hash << 5) + hash) + uint64(c)
 | |
| 	}
 | |
| 	return hash
 | |
| }
 | |
| 
 | |
| // BytesFilter is a efficient data structure for checking whether bytes exist or not.
 | |
| // BytesFilter is thread-safe.
 | |
| type BytesFilter interface {
 | |
| 	// Add adds given bytes to this set.
 | |
| 	Add([]byte)
 | |
| 
 | |
| 	// Contains return true if this set contains given bytes, otherwise false.
 | |
| 	Contains([]byte) bool
 | |
| 
 | |
| 	// Extend copies this filter and adds given bytes to new filter.
 | |
| 	Extend(...[]byte) BytesFilter
 | |
| }
 | |
| 
 | |
| type bytesFilter struct {
 | |
| 	chars     [256]uint8
 | |
| 	threshold int
 | |
| 	slots     [][][]byte
 | |
| }
 | |
| 
 | |
| // NewBytesFilter returns a new BytesFilter.
 | |
| func NewBytesFilter(elements ...[]byte) BytesFilter {
 | |
| 	s := &bytesFilter{
 | |
| 		threshold: 3,
 | |
| 		slots:     make([][][]byte, 64),
 | |
| 	}
 | |
| 	for _, element := range elements {
 | |
| 		s.Add(element)
 | |
| 	}
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| func (s *bytesFilter) Add(b []byte) {
 | |
| 	l := len(b)
 | |
| 	m := s.threshold
 | |
| 	if l < s.threshold {
 | |
| 		m = l
 | |
| 	}
 | |
| 	for i := 0; i < m; i++ {
 | |
| 		s.chars[b[i]] |= 1 << uint8(i)
 | |
| 	}
 | |
| 	h := bytesHash(b) % uint64(len(s.slots))
 | |
| 	slot := s.slots[h]
 | |
| 	if slot == nil {
 | |
| 		slot = [][]byte{}
 | |
| 	}
 | |
| 	s.slots[h] = append(slot, b)
 | |
| }
 | |
| 
 | |
| func (s *bytesFilter) Extend(bs ...[]byte) BytesFilter {
 | |
| 	newFilter := NewBytesFilter().(*bytesFilter)
 | |
| 	newFilter.chars = s.chars
 | |
| 	newFilter.threshold = s.threshold
 | |
| 	for k, v := range s.slots {
 | |
| 		newSlot := make([][]byte, len(v))
 | |
| 		copy(newSlot, v)
 | |
| 		newFilter.slots[k] = v
 | |
| 	}
 | |
| 	for _, b := range bs {
 | |
| 		newFilter.Add(b)
 | |
| 	}
 | |
| 	return newFilter
 | |
| }
 | |
| 
 | |
| func (s *bytesFilter) Contains(b []byte) bool {
 | |
| 	l := len(b)
 | |
| 	m := s.threshold
 | |
| 	if l < s.threshold {
 | |
| 		m = l
 | |
| 	}
 | |
| 	for i := 0; i < m; i++ {
 | |
| 		if (s.chars[b[i]] & (1 << uint8(i))) == 0 {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	h := bytesHash(b) % uint64(len(s.slots))
 | |
| 	slot := s.slots[h]
 | |
| 	if len(slot) == 0 {
 | |
| 		return false
 | |
| 	}
 | |
| 	for _, element := range slot {
 | |
| 		if bytes.Equal(element, b) {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 |