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  }