github.com/diamondburned/arikawa/v2@v2.1.0/bot/extras/shellwords/shellwords.go (about)

     1  package shellwords
     2  
     3  import (
     4  	"strings"
     5  )
     6  
     7  var escaper = strings.NewReplacer(
     8  	"__", "\\_\\_",
     9  	"\\", "\\\\",
    10  )
    11  
    12  // ErrMissingClose is returned when the parsed line is missing a closing quote.
    13  type ErrMissingClose struct {
    14  	Position int
    15  	Words    string // joined
    16  }
    17  
    18  func (e ErrMissingClose) Error() string {
    19  	// Underline 7 characters around.
    20  	var start = e.Position
    21  
    22  	errstr := strings.Builder{}
    23  	errstr.WriteString("missing quote close")
    24  
    25  	if e.Words[start:] != "" {
    26  		errstr.WriteString(": ")
    27  		errstr.WriteString(escaper.Replace(e.Words[:start]))
    28  		errstr.WriteString("__")
    29  		errstr.WriteString(escaper.Replace(e.Words[start:]))
    30  		errstr.WriteString("__")
    31  	}
    32  
    33  	return errstr.String()
    34  }
    35  
    36  // Parse parses the given text to a slice of words.
    37  func Parse(line string) ([]string, error) {
    38  	var args []string
    39  	var escaped, doubleQuoted, singleQuoted bool
    40  
    41  	var buf strings.Builder
    42  	buf.Grow(len(line))
    43  
    44  	got := false
    45  	cursor := 0
    46  
    47  	runes := []rune(line)
    48  
    49  	for _, r := range runes {
    50  		if escaped {
    51  			buf.WriteRune(r)
    52  			escaped = false
    53  			continue
    54  		}
    55  
    56  		if r == '\\' {
    57  			if singleQuoted {
    58  				buf.WriteRune(r)
    59  			} else {
    60  				escaped = true
    61  			}
    62  			continue
    63  		}
    64  
    65  		if isSpace(r) {
    66  			switch {
    67  			case singleQuoted, doubleQuoted:
    68  				buf.WriteRune(r)
    69  			case got:
    70  				cursor += buf.Len()
    71  				args = append(args, buf.String())
    72  				buf.Reset()
    73  				got = false
    74  			}
    75  			continue
    76  		}
    77  
    78  		switch r {
    79  		case '"', '“', '”':
    80  			if !singleQuoted {
    81  				if doubleQuoted {
    82  					got = true
    83  				}
    84  				doubleQuoted = !doubleQuoted
    85  				continue
    86  			}
    87  		case '\'', '`', '‘', '’':
    88  			if !doubleQuoted {
    89  				if singleQuoted {
    90  					got = true
    91  				}
    92  
    93  				singleQuoted = !singleQuoted
    94  				continue
    95  			}
    96  		}
    97  
    98  		got = true
    99  		buf.WriteRune(r)
   100  	}
   101  
   102  	if got {
   103  		args = append(args, buf.String())
   104  	}
   105  
   106  	if escaped || singleQuoted || doubleQuoted {
   107  		return args, ErrMissingClose{
   108  			Position: cursor + buf.Len(),
   109  			Words:    strings.Join(args, " "),
   110  		}
   111  	}
   112  
   113  	return args, nil
   114  }
   115  
   116  func isSpace(r rune) bool {
   117  	switch r {
   118  	case ' ', '\t', '\r', '\n', ' ':
   119  		return true
   120  	}
   121  	return false
   122  }