package global import ( "strconv" "unicode/utf8" ) const ( lowerhex = "0123456789abcdef" upperhex = "0123456789ABCDEF" ) // Quote returns a double-quoted Go string literal representing s. The // returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for // control characters and non-printable characters as defined by // IsPrint. func Quote(s string) string { return quoteWith(s, '"', false, false) } func quoteWith(s string, quote byte, asciiOnly, graphicOnly bool) string { return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote, asciiOnly, graphicOnly)) } func appendQuotedWith(buf []byte, s string, quote byte, asciiOnly, graphicOnly bool) []byte { // Often called with big strings, so preallocate. If there's quoting, // this is conservative but still helps a lot. if cap(buf)-len(buf) < len(s) { nBuf := make([]byte, len(buf), len(buf)+1+len(s)+1) copy(nBuf, buf) buf = nBuf } buf = append(buf, quote) for width := 0; len(s) > 0; s = s[width:] { r := rune(s[0]) width = 1 if r >= utf8.RuneSelf { r, width = utf8.DecodeRuneInString(s) } if width == 1 && r == utf8.RuneError { buf = append(buf, `\x`...) buf = append(buf, lowerhex[s[0]>>4]) buf = append(buf, lowerhex[s[0]&0xF]) continue } buf = appendEscapedRune(buf, r, quote, asciiOnly, graphicOnly) } buf = append(buf, quote) return buf } func appendEscapedRune(buf []byte, r rune, quote byte, asciiOnly, graphicOnly bool) []byte { var runeTmp [utf8.UTFMax]byte if r == rune(quote) || r == '\\' { // always backslashed buf = append(buf, '\\') buf = append(buf, byte(r)) return buf } if asciiOnly { if r < utf8.RuneSelf && strconv.IsPrint(r) { buf = append(buf, byte(r)) return buf } } else if strconv.IsPrint(r) || graphicOnly && isInGraphicList(r) { n := utf8.EncodeRune(runeTmp[:], r) buf = append(buf, runeTmp[:n]...) return buf } switch r { case '\a': buf = append(buf, `\a`...) case '\b': buf = append(buf, `\b`...) case '\f': buf = append(buf, `\f`...) case '\n': buf = append(buf, `\n`...) case '\r': buf = append(buf, `\r`...) case '\t': buf = append(buf, `\t`...) case '\v': buf = append(buf, `\v`...) default: switch { case !utf8.ValidRune(r): r = 0xFFFD fallthrough case r < 0x10000: buf = append(buf, `\u`...) for s := 12; s >= 0; s -= 4 { buf = append(buf, lowerhex[r>>uint(s)&0xF]) } default: buf = append(buf, `\U`...) for s := 28; s >= 0; s -= 4 { buf = append(buf, lowerhex[r>>uint(s)&0xF]) } } } return buf } func isInGraphicList(r rune) bool { // We know r must fit in 16 bits - see makeisprint.go. if r > 0xFFFF { return false } rr := uint16(r) i := bsearch16(isGraphic, rr) return i < len(isGraphic) && rr == isGraphic[i] } // bsearch16 returns the smallest i such that a[i] >= x. // If there is no such i, bsearch16 returns len(a). func bsearch16(a []uint16, x uint16) int { i, j := 0, len(a) for i < j { h := i + (j-i)>>1 if a[h] < x { i = h + 1 } else { j = h } } return i } // isGraphic lists the graphic runes not matched by IsPrint. var isGraphic = []uint16{ 0x00a0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a, 0x202f, 0x205f, 0x3000, }