mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: Add pmg setup edit command (#235)
* feat: Add pmg setup edit command * fix: Code review fixes
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
// Package shellwords provides POSIX-like splitting of shell command strings
|
||||
// into argv tokens. It is intentionally minimal: it handles the cases that
|
||||
// arise from environment variables like $VISUAL or $EDITOR, where users may
|
||||
// quote paths with spaces or pass flags. It is not a full shell parser — it
|
||||
// does not perform variable expansion, command substitution, or globbing.
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Split splits s into tokens using POSIX-like rules:
|
||||
//
|
||||
// - Whitespace (space, tab, newline) separates tokens.
|
||||
// - Single quotes preserve their contents verbatim; no escapes are honored.
|
||||
// - Double quotes preserve whitespace; backslash escapes only ", \, $, `.
|
||||
// - Outside quotes a backslash escapes the next character literally.
|
||||
// - Empty quoted strings ("" or '') produce an empty token.
|
||||
//
|
||||
// Split returns an error for unterminated quoted strings or a trailing
|
||||
// backslash with nothing to escape.
|
||||
func Split(s string) ([]string, error) {
|
||||
var (
|
||||
out []string
|
||||
buf strings.Builder
|
||||
inSingle bool
|
||||
inDouble bool
|
||||
escaped bool
|
||||
hasToken bool
|
||||
)
|
||||
|
||||
flush := func() {
|
||||
if hasToken {
|
||||
out = append(out, buf.String())
|
||||
buf.Reset()
|
||||
hasToken = false
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
switch {
|
||||
case escaped:
|
||||
buf.WriteByte(c)
|
||||
hasToken = true
|
||||
escaped = false
|
||||
case inSingle:
|
||||
if c == '\'' {
|
||||
inSingle = false
|
||||
} else {
|
||||
buf.WriteByte(c)
|
||||
}
|
||||
hasToken = true
|
||||
case inDouble:
|
||||
switch c {
|
||||
case '"':
|
||||
inDouble = false
|
||||
case '\\':
|
||||
if i+1 >= len(s) {
|
||||
return nil, fmt.Errorf("dangling backslash")
|
||||
}
|
||||
next := s[i+1]
|
||||
// Inside double quotes, backslash only escapes a few
|
||||
// characters; otherwise it is literal.
|
||||
if next == '"' || next == '\\' || next == '$' || next == '`' {
|
||||
buf.WriteByte(next)
|
||||
i++
|
||||
} else {
|
||||
buf.WriteByte(c)
|
||||
}
|
||||
default:
|
||||
buf.WriteByte(c)
|
||||
}
|
||||
hasToken = true
|
||||
default:
|
||||
switch c {
|
||||
case '\'':
|
||||
inSingle = true
|
||||
hasToken = true
|
||||
case '"':
|
||||
inDouble = true
|
||||
hasToken = true
|
||||
case '\\':
|
||||
escaped = true
|
||||
case ' ', '\t', '\n':
|
||||
flush()
|
||||
default:
|
||||
buf.WriteByte(c)
|
||||
hasToken = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inSingle || inDouble {
|
||||
return nil, fmt.Errorf("unterminated quoted string")
|
||||
}
|
||||
if escaped {
|
||||
return nil, fmt.Errorf("dangling backslash")
|
||||
}
|
||||
flush()
|
||||
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user