parse: Add QuoteAs, move [qQ]uote* to quote.go.

This commit is contained in:
Qi Xiao 2016-02-24 13:00:11 +01:00
parent 4d63220e14
commit a657a2989a
4 changed files with 126 additions and 102 deletions

View File

@ -1038,84 +1038,7 @@ func isSpaceOrNewline(r rune) bool {
return isSpace(r) || r == '\n'
}
// Helpers.
func addChild(p Node, ch Node) {
p.n().children = append(p.n().children, ch)
ch.n().parent = p
}
// Quote returns a representation of s in elvish syntax. Bareword is tried
// first, then single quoted string and finally double quoted string.
func Quote(s string) string {
if s == "" {
return `""`
}
bare := s[0] != '~'
for _, r := range s {
if !unicode.IsPrint(r) && r != '\n' {
return quoteDouble(s)
}
if !allowedInBareword(r) {
bare = false
}
}
if bare {
return s
}
return quoteSingle(s)
}
func quoteSingle(s string) string {
var buf bytes.Buffer
buf.WriteByte('\'')
for _, r := range s {
buf.WriteRune(r)
if r == '\'' {
buf.WriteByte('\'')
}
}
buf.WriteByte('\'')
return buf.String()
}
func rtohex(r rune, w int) []byte {
bytes := make([]byte, w)
for i := w - 1; i >= 0; i-- {
d := byte(r % 16)
r /= 16
if d <= 9 {
bytes[i] = '0' + d
} else {
bytes[i] = 'a' + d - 10
}
}
return bytes
}
func quoteDouble(s string) string {
var buf bytes.Buffer
buf.WriteByte('"')
for _, r := range s {
if r == '\\' || r == '"' {
buf.WriteByte('\\')
buf.WriteRune(r)
} else if !unicode.IsPrint(r) {
buf.WriteByte('\\')
if r <= 0xff {
buf.WriteByte('x')
buf.Write(rtohex(r, 2))
} else if r <= 0xffff {
buf.WriteByte('u')
buf.Write(rtohex(r, 4))
} else {
buf.WriteByte('U')
buf.Write(rtohex(r, 8))
}
} else {
buf.WriteRune(r)
}
}
buf.WriteByte('"')
return buf.String()
}

View File

@ -350,28 +350,3 @@ func TestParseError(t *testing.T) {
}
}
}
var quoteTests = []struct {
text, quoted string
}{
// Empty string is quoted with double quote.
{"", `""`},
// Bareword when possible.
{"x-y,z@h/d", "x-y,z@h/d"},
// Single quote when there is special char but no unprintable.
{"x$y[]\nef'", "'x$y[]\nef'''"},
// Tilde needs quoting only when appearing at the beginning
{"~x", "'~x'"},
{"x~", "x~"},
// Double quote when there is unprintable char.
{"\x1b\"\\", `"\x1b\"\\"`},
}
func TestQuote(t *testing.T) {
for _, tc := range quoteTests {
got := Quote(tc.text)
if got != tc.quoted {
t.Errorf("Quote(%q) => %s, want %s", tc.text, got, tc.quoted)
}
}
}

98
parse/quote.go Normal file
View File

@ -0,0 +1,98 @@
package parse
import (
"bytes"
"unicode"
)
// Quote returns a representation of s in elvish syntax. Bareword is tried
// first, then single quoted string and finally double quoted string.
func Quote(s string) string {
s, _ = QuoteAs(s, Bareword)
return s
}
// QuoteAs returns a representation of s in elvish syntax, using the syntax
// specified by q, which must be one of Bareword, SingleQuoted, or
// DoubleQuoted. It returns the quoted string and the actual quoting.
func QuoteAs(s string, q PrimaryType) (string, PrimaryType) {
if q == DoubleQuoted {
// Everything can be quoted using double quotes, return directly.
return quoteDouble(s), DoubleQuoted
}
if s == "" {
return "''", SingleQuoted
}
// Keep track of whether it is a valid bareword.
bare := s[0] != '~'
for _, r := range s {
if !unicode.IsPrint(r) && r != '\n' {
// Contains unprintable character that is not a newline. Force
// double quote.
return quoteDouble(s), DoubleQuoted
}
if !allowedInBareword(r) {
bare = false
}
}
if q == Bareword && bare {
return s, Bareword
}
return quoteSingle(s), SingleQuoted
}
func quoteSingle(s string) string {
var buf bytes.Buffer
buf.WriteByte('\'')
for _, r := range s {
buf.WriteRune(r)
if r == '\'' {
buf.WriteByte('\'')
}
}
buf.WriteByte('\'')
return buf.String()
}
func rtohex(r rune, w int) []byte {
bytes := make([]byte, w)
for i := w - 1; i >= 0; i-- {
d := byte(r % 16)
r /= 16
if d <= 9 {
bytes[i] = '0' + d
} else {
bytes[i] = 'a' + d - 10
}
}
return bytes
}
func quoteDouble(s string) string {
var buf bytes.Buffer
buf.WriteByte('"')
for _, r := range s {
if r == '\\' || r == '"' {
buf.WriteByte('\\')
buf.WriteRune(r)
} else if !unicode.IsPrint(r) {
buf.WriteByte('\\')
if r <= 0xff {
buf.WriteByte('x')
buf.Write(rtohex(r, 2))
} else if r <= 0xffff {
buf.WriteByte('u')
buf.Write(rtohex(r, 4))
} else {
buf.WriteByte('U')
buf.Write(rtohex(r, 8))
}
} else {
buf.WriteRune(r)
}
}
buf.WriteByte('"')
return buf.String()
}

28
parse/quote_test.go Normal file
View File

@ -0,0 +1,28 @@
package parse
import "testing"
var quoteTests = []struct {
text, quoted string
}{
// Empty string is quoted with single quote.
{"", `''`},
// Bareword when possible.
{"x-y,z@h/d", "x-y,z@h/d"},
// Single quote when there is special char but no unprintable.
{"x$y[]\nef'", "'x$y[]\nef'''"},
// Tilde needs quoting only when appearing at the beginning
{"~x", "'~x'"},
{"x~", "x~"},
// Double quote when there is unprintable char.
{"\x1b\"\\", `"\x1b\"\\"`},
}
func TestQuote(t *testing.T) {
for _, tc := range quoteTests {
got := Quote(tc.text)
if got != tc.quoted {
t.Errorf("Quote(%q) => %s, want %s", tc.text, got, tc.quoted)
}
}
}