func TrimRightLength(source, s []byte) int {
	return len(source) - len(TrimRight(source, s))
}
TrimRightLength returns a length of trailing specified characters.
package util
import (
	"bytes"
	"io"
	"net/url"
	"regexp"
	"sort"
	"strconv"
	"unicode"
	"unicode/utf8"
)
type CopyOnWriteBuffer struct {
	buffer []byte
	copied bool
}
func NewCopyOnWriteBuffer(buffer []byte) CopyOnWriteBuffer {
	return CopyOnWriteBuffer{
		buffer: buffer,
		copied: false,
	}
}
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...)
}
func (b *CopyOnWriteBuffer) WriteString(value string) {
	b.Write(StringToReadOnlyBytes(value))
}
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...)
}
func (b *CopyOnWriteBuffer) AppendString(value string) {
	b.Append(StringToReadOnlyBytes(value))
}
func (b *CopyOnWriteBuffer) WriteByte(c byte) {
	if !b.copied {
		b.buffer = make([]byte, 0, len(b.buffer)+20)
		b.copied = true
	}
	b.buffer = append(b.buffer, c)
}
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)
}
func (b *CopyOnWriteBuffer) Bytes() []byte {
	return b.buffer
}
func (b *CopyOnWriteBuffer) IsCopied() bool {
	return b.copied
}
func IsEscapedPunctuation(source []byte, i int) bool {
	return source[i] == '\\' && i < len(source)-1 && IsPunct(source[i+1])
}
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
}
func IsBlank(bs []byte) bool {
	for _, b := range bs {
		if !IsSpace(b) {
			return false
		}
	}
	return true
}
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
}
func TabWidth(currentPos int) int {
	return 4 - currentPos%4
}
func IndentPosition(bs []byte, currentPos, width int) (pos, padding int) {
	return IndentPositionPadding(bs, currentPos, 0, width)
}
func IndentPositionPadding(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 < 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
}
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
}
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
}
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
}
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:]
}
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]
}
func TrimLeftLength(source, s []byte) int {
	return len(source) - len(TrimLeft(source, s))
}