blob: 05e95c580b866aa34f7e112ef7debbbbf87b8837 [file] [log] [blame]
// Copyright 2020 CUE Authors
//
// 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 export
import (
"strconv"
"strings"
"unicode/utf8"
)
// quote quotes the given string.
func quote(str string, quote byte) string {
if strings.IndexByte(str, '\n') < 0 {
buf := []byte{quote}
buf = appendEscaped(buf, str, quote, true)
buf = append(buf, quote)
return string(buf)
}
buf := []byte{quote, quote, quote}
buf = append(buf, multiSep...)
buf = appendEscapeMulti(buf, str, quote)
buf = append(buf, quote, quote, quote)
return string(buf)
}
// TODO: consider the best indent strategy.
const multiSep = "\n "
func appendEscapeMulti(buf []byte, str string, quote byte) []byte {
// TODO(perf)
a := strings.Split(str, "\n")
for _, s := range a {
buf = appendEscaped(buf, s, quote, true)
buf = append(buf, multiSep...)
}
return buf
}
const lowerhex = "0123456789abcdef"
func appendEscaped(buf []byte, s string, quote byte, graphicOnly bool) []byte {
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, graphicOnly)
}
return buf
}
func appendEscapedRune(buf []byte, r rune, quote byte, 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
}
// TODO(perf): IsGraphic calls IsPrint.
if strconv.IsPrint(r) || graphicOnly && strconv.IsGraphic(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 r < ' ':
// Invalid for strings, only bytes.
buf = append(buf, `\x`...)
buf = append(buf, lowerhex[byte(r)>>4])
buf = append(buf, lowerhex[byte(r)&0xF])
case r > utf8.MaxRune:
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
}