🏡 index : ironmagma/quintodrome.git

package shellquote

import (
	"errors"
	"strings"
)

var (
	ErrUnterminatedSingleQuote = errors.New("unterminated single-quoted string")
	ErrUnterminatedDoubleQuote = errors.New("unterminated double-quoted string")
	ErrUnterminatedEscape      = errors.New("unterminated backslash-escape")
)

type state int

const (
	stateUnquoted state = iota
	stateSingleQuoted
	stateDoubleQuoted
)

// Split splits a string into words following POSIX-like shell quoting rules.
// It handles single quotes, double quotes, and backslash escapes.
func Split(input string) ([]string, error) {
	var words []string
	var word strings.Builder
	inWord := false
	parseState := stateUnquoted

	i := 0
	for i < len(input) {
		ch := input[i]

		switch parseState {
		case stateUnquoted:
			switch {
			case ch == '\\':
				if i+1 >= len(input) {
					return nil, ErrUnterminatedEscape
				}
				if input[i+1] == '\n' {
					// Line continuation: skip both backslash and newline
					i += 2
					continue
				}
				i++
				word.WriteByte(input[i])
				inWord = true
			case ch == '\'':
				parseState = stateSingleQuoted
				inWord = true
			case ch == '"':
				parseState = stateDoubleQuoted
				inWord = true
			case ch == ' ' || ch == '\t' || ch == '\n':
				if inWord {
					words = append(words, word.String())
					word.Reset()
					inWord = false
				}
			default:
				word.WriteByte(ch)
				inWord = true
			}

		case stateSingleQuoted:
			if ch == '\'' {
				parseState = stateUnquoted
			} else {
				word.WriteByte(ch)
			}

		case stateDoubleQuoted:
			switch {
			case ch == '"':
				parseState = stateUnquoted
			case ch == '\\':
				if i+1 >= len(input) {
					return nil, ErrUnterminatedEscape
				}
				next := input[i+1]
				// In double quotes, backslash only escapes: $ ` " \n \
				if next == '$' || next == '`' || next == '"' || next == '\n' || next == '\\' {
					if next == '\n' {
						// Line continuation: skip both backslash and newline
						i += 2
						continue
					}
					i++
					word.WriteByte(next)
				} else {
					// Backslash is literal for other characters
					word.WriteByte(ch)
				}
			default:
				word.WriteByte(ch)
			}
		}

		i++
	}

	switch parseState {
	case stateSingleQuoted:
		return nil, ErrUnterminatedSingleQuote
	case stateDoubleQuoted:
		return nil, ErrUnterminatedDoubleQuote
	}

	if inWord {
		words = append(words, word.String())
	}

	return words, nil
}