gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

quote.go (2331B)


      1 package shellquote
      2 
      3 import (
      4 	"bytes"
      5 	"strings"
      6 	"unicode/utf8"
      7 )
      8 
      9 // Join quotes each argument and joins them with a space.
     10 // If passed to /bin/sh, the resulting string will be split back into the
     11 // original arguments.
     12 func Join(args ...string) string {
     13 	var buf bytes.Buffer
     14 	for i, arg := range args {
     15 		if i != 0 {
     16 			buf.WriteByte(' ')
     17 		}
     18 		quote(arg, &buf)
     19 	}
     20 	return buf.String()
     21 }
     22 
     23 const (
     24 	specialChars      = "\\'\"`${[|&;<>()*?!"
     25 	extraSpecialChars = " \t\n"
     26 	prefixChars       = "~"
     27 )
     28 
     29 func quote(word string, buf *bytes.Buffer) {
     30 	// We want to try to produce a "nice" output. As such, we will
     31 	// backslash-escape most characters, but if we encounter a space, or if we
     32 	// encounter an extra-special char (which doesn't work with
     33 	// backslash-escaping) we switch over to quoting the whole word. We do this
     34 	// with a space because it's typically easier for people to read multi-word
     35 	// arguments when quoted with a space rather than with ugly backslashes
     36 	// everywhere.
     37 	origLen := buf.Len()
     38 
     39 	if len(word) == 0 {
     40 		// oops, no content
     41 		buf.WriteString("''")
     42 		return
     43 	}
     44 
     45 	cur, prev := word, word
     46 	atStart := true
     47 	for len(cur) > 0 {
     48 		c, l := utf8.DecodeRuneInString(cur)
     49 		cur = cur[l:]
     50 		if strings.ContainsRune(specialChars, c) || (atStart && strings.ContainsRune(prefixChars, c)) {
     51 			// copy the non-special chars up to this point
     52 			if len(cur) < len(prev) {
     53 				buf.WriteString(prev[0 : len(prev)-len(cur)-l])
     54 			}
     55 			buf.WriteByte('\\')
     56 			buf.WriteRune(c)
     57 			prev = cur
     58 		} else if strings.ContainsRune(extraSpecialChars, c) {
     59 			// start over in quote mode
     60 			buf.Truncate(origLen)
     61 			goto quote
     62 		}
     63 		atStart = false
     64 	}
     65 	if len(prev) > 0 {
     66 		buf.WriteString(prev)
     67 	}
     68 	return
     69 
     70 quote:
     71 	// quote mode
     72 	// Use single-quotes, but if we find a single-quote in the word, we need
     73 	// to terminate the string, emit an escaped quote, and start the string up
     74 	// again
     75 	inQuote := false
     76 	for len(word) > 0 {
     77 		i := strings.IndexRune(word, '\'')
     78 		if i == -1 {
     79 			break
     80 		}
     81 		if i > 0 {
     82 			if !inQuote {
     83 				buf.WriteByte('\'')
     84 				inQuote = true
     85 			}
     86 			buf.WriteString(word[0:i])
     87 		}
     88 		word = word[i+1:]
     89 		if inQuote {
     90 			buf.WriteByte('\'')
     91 			inQuote = false
     92 		}
     93 		buf.WriteString("\\'")
     94 	}
     95 	if len(word) > 0 {
     96 		if !inQuote {
     97 			buf.WriteByte('\'')
     98 		}
     99 		buf.WriteString(word)
    100 		buf.WriteByte('\'')
    101 	}
    102 }