github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/parse/quote.go (about) 1 package parse 2 3 import ( 4 "bytes" 5 "unicode" 6 ) 7 8 // Quote returns a valid Elvish expression that evaluates to the given string. 9 // If s is a valid bareword, it is returned as is; otherwise it is quoted, 10 // preferring the use of single quotes. 11 func Quote(s string) string { 12 s, _ = QuoteAs(s, Bareword) 13 return s 14 } 15 16 // QuoteVariableName is like Quote, but quotes s if it contains any character 17 // that may not appear unquoted in variable names. 18 func QuoteVariableName(s string) string { 19 if s == "" { 20 return "''" 21 } 22 23 // Keep track of whether it is a valid (unquoted) variable name. 24 bare := true 25 for _, r := range s { 26 if !unicode.IsPrint(r) { 27 // Contains unprintable character; force double quote. 28 return quoteDouble(s) 29 } 30 if !allowedInVariableName(r) { 31 bare = false 32 break 33 } 34 } 35 36 if bare { 37 return s 38 } 39 return quoteSingle(s) 40 } 41 42 // QuoteAs returns a representation of s in elvish syntax, preferring the syntax 43 // specified by q, which must be one of Bareword, SingleQuoted, or DoubleQuoted. 44 // It returns the quoted string and the actual quoting. 45 func QuoteAs(s string, q PrimaryType) (string, PrimaryType) { 46 if q == DoubleQuoted { 47 // Everything can be quoted using double quotes, return directly. 48 return quoteDouble(s), DoubleQuoted 49 } 50 if s == "" { 51 return "''", SingleQuoted 52 } 53 54 // Keep track of whether it is a valid bareword. 55 bare := s[0] != '~' 56 for _, r := range s { 57 if !unicode.IsPrint(r) { 58 // Contains unprintable character; force double quote. 59 return quoteDouble(s), DoubleQuoted 60 } 61 if !allowedInBareword(r, strictExpr) { 62 bare = false 63 } 64 } 65 66 if q == Bareword && bare { 67 return s, Bareword 68 } 69 return quoteSingle(s), SingleQuoted 70 } 71 72 func quoteSingle(s string) string { 73 var buf bytes.Buffer 74 buf.WriteByte('\'') 75 for _, r := range s { 76 buf.WriteRune(r) 77 if r == '\'' { 78 buf.WriteByte('\'') 79 } 80 } 81 buf.WriteByte('\'') 82 return buf.String() 83 } 84 85 func rtohex(r rune, w int) []byte { 86 bytes := make([]byte, w) 87 for i := w - 1; i >= 0; i-- { 88 d := byte(r % 16) 89 r /= 16 90 if d <= 9 { 91 bytes[i] = '0' + d 92 } else { 93 bytes[i] = 'a' + d - 10 94 } 95 } 96 return bytes 97 } 98 99 func quoteDouble(s string) string { 100 var buf bytes.Buffer 101 buf.WriteByte('"') 102 for _, r := range s { 103 if e, ok := doubleUnescape[r]; ok { 104 // Takes care of " and \ as well. 105 buf.WriteByte('\\') 106 buf.WriteRune(e) 107 } else if !unicode.IsPrint(r) { 108 buf.WriteByte('\\') 109 if r <= 0xff { 110 buf.WriteByte('x') 111 buf.Write(rtohex(r, 2)) 112 } else if r <= 0xffff { 113 buf.WriteByte('u') 114 buf.Write(rtohex(r, 4)) 115 } else { 116 buf.WriteByte('U') 117 buf.Write(rtohex(r, 8)) 118 } 119 } else { 120 buf.WriteRune(r) 121 } 122 } 123 buf.WriteByte('"') 124 return buf.String() 125 }