github.com/diamondburned/arikawa@v1.3.14/bot/extras/shellwords/shellwords.go (about)

     1  package shellwords
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  )
     7  
     8  // WordOffset is the offset from the position cursor to print on the error.
     9  const WordOffset = 7
    10  
    11  var escaper = strings.NewReplacer(
    12  	"`", "\\`",
    13  	"@", "\\@",
    14  	"\\", "\\\\",
    15  )
    16  
    17  type ErrParse struct {
    18  	Position int
    19  	Words    string // joined
    20  }
    21  
    22  func (e ErrParse) Error() string {
    23  	// Magic number 5.
    24  	var a = max(0, e.Position-WordOffset)
    25  	var b = min(len(e.Words), e.Position+WordOffset)
    26  	var word = e.Words[a:b]
    27  	var uidx = e.Position - a
    28  
    29  	errstr := strings.Builder{}
    30  	errstr.WriteString("Unexpected quote or escape")
    31  
    32  	// Do a bound check.
    33  	if uidx+1 > len(word) {
    34  		// Invalid.
    35  		errstr.WriteString(".")
    36  		return errstr.String()
    37  	}
    38  
    39  	// Write the pre-underline part.
    40  	fmt.Fprintf(
    41  		&errstr, ": %s__%s__",
    42  		escaper.Replace(word[:uidx]),
    43  		escaper.Replace(string(word[uidx:])),
    44  	)
    45  
    46  	return errstr.String()
    47  }
    48  
    49  // Parse parses the given text to a slice of words.
    50  func Parse(line string) ([]string, error) {
    51  	var args []string
    52  	var escaped, doubleQuoted, singleQuoted bool
    53  
    54  	var buf strings.Builder
    55  	buf.Grow(len(line))
    56  
    57  	got := false
    58  	cursor := 0
    59  
    60  	runes := []rune(line)
    61  
    62  	for _, r := range runes {
    63  		if escaped {
    64  			buf.WriteRune(r)
    65  			escaped = false
    66  			continue
    67  		}
    68  
    69  		if r == '\\' {
    70  			if singleQuoted {
    71  				buf.WriteRune(r)
    72  			} else {
    73  				escaped = true
    74  			}
    75  			continue
    76  		}
    77  
    78  		if isSpace(r) {
    79  			switch {
    80  			case singleQuoted, doubleQuoted:
    81  				buf.WriteRune(r)
    82  			case got:
    83  				cursor += buf.Len()
    84  				args = append(args, buf.String())
    85  				buf.Reset()
    86  				got = false
    87  			}
    88  			continue
    89  		}
    90  
    91  		switch r {
    92  		case '"':
    93  			if !singleQuoted {
    94  				if doubleQuoted {
    95  					got = true
    96  				}
    97  				doubleQuoted = !doubleQuoted
    98  				continue
    99  			}
   100  		case '\'', '`':
   101  			if !doubleQuoted {
   102  				if singleQuoted {
   103  					got = true
   104  				}
   105  
   106  				singleQuoted = !singleQuoted
   107  				continue
   108  			}
   109  		}
   110  
   111  		got = true
   112  		buf.WriteRune(r)
   113  	}
   114  
   115  	if got {
   116  		args = append(args, buf.String())
   117  	}
   118  
   119  	if escaped || singleQuoted || doubleQuoted {
   120  		return args, &ErrParse{
   121  			Position: cursor + buf.Len(),
   122  			Words:    strings.Join(args, " "),
   123  		}
   124  	}
   125  
   126  	return args, nil
   127  }
   128  
   129  func isSpace(r rune) bool {
   130  	switch r {
   131  	case ' ', '\t', '\r', '\n', ' ':
   132  		return true
   133  	}
   134  	return false
   135  }
   136  
   137  func min(i, j int) int {
   138  	if i < j {
   139  		return i
   140  	}
   141  	return j
   142  }
   143  
   144  func max(i, j int) int {
   145  	if i < j {
   146  		return j
   147  	}
   148  	return i
   149  }