You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
4.4 KiB
269 lines
4.4 KiB
package argh
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"sort"
|
|
"unicode"
|
|
)
|
|
|
|
const (
|
|
nul = rune(0)
|
|
eol = rune(-1)
|
|
)
|
|
|
|
var (
|
|
DefaultScannerConfig = &ScannerConfig{
|
|
AssignmentOperator: '=',
|
|
FlagPrefix: '-',
|
|
MultiValueDelim: ',',
|
|
}
|
|
)
|
|
|
|
type Scanner struct {
|
|
r *bufio.Reader
|
|
i int
|
|
cfg *ScannerConfig
|
|
}
|
|
|
|
type ScannerConfig struct {
|
|
AssignmentOperator rune
|
|
FlagPrefix rune
|
|
MultiValueDelim rune
|
|
}
|
|
|
|
// ScannerError is largely borrowed from go/scanner.Error
|
|
type ScannerError struct {
|
|
Pos Position
|
|
Msg string
|
|
}
|
|
|
|
func (e ScannerError) Error() string {
|
|
if e.Pos.IsValid() {
|
|
return e.Pos.String() + ":" + e.Msg
|
|
}
|
|
return e.Msg
|
|
}
|
|
|
|
// ScannerErrorList is largely borrowed from go/scanner.ErrorList
|
|
type ScannerErrorList []*ScannerError
|
|
|
|
func (el *ScannerErrorList) Add(pos Position, msg string) {
|
|
*el = append(*el, &ScannerError{Pos: pos, Msg: msg})
|
|
}
|
|
|
|
func (el *ScannerErrorList) Reset() { *el = (*el)[0:0] }
|
|
|
|
func (el ScannerErrorList) Len() int { return len(el) }
|
|
|
|
func (el ScannerErrorList) Swap(i, j int) { el[i], el[j] = el[j], el[i] }
|
|
|
|
func (el ScannerErrorList) Less(i, j int) bool {
|
|
e := &el[i].Pos
|
|
f := &el[j].Pos
|
|
|
|
if e.Column != f.Column {
|
|
return e.Column < f.Column
|
|
}
|
|
|
|
return el[i].Msg < el[j].Msg
|
|
}
|
|
|
|
func (el ScannerErrorList) Sort() {
|
|
sort.Sort(el)
|
|
}
|
|
|
|
func (el ScannerErrorList) Error() string {
|
|
switch len(el) {
|
|
case 0:
|
|
return "no errors"
|
|
case 1:
|
|
return el[0].Error()
|
|
}
|
|
return fmt.Sprintf("%s (and %d more errors)", el[0], len(el)-1)
|
|
}
|
|
|
|
func (el ScannerErrorList) Err() error {
|
|
if len(el) == 0 {
|
|
return nil
|
|
}
|
|
return el
|
|
}
|
|
|
|
func PrintScannerError(w io.Writer, err error) {
|
|
if list, ok := err.(ScannerErrorList); ok {
|
|
for _, e := range list {
|
|
fmt.Fprintf(w, "%s\n", e)
|
|
}
|
|
} else if err != nil {
|
|
fmt.Fprintf(w, "%s\n", err)
|
|
}
|
|
}
|
|
|
|
func NewScanner(r io.Reader, cfg *ScannerConfig) *Scanner {
|
|
if cfg == nil {
|
|
cfg = DefaultScannerConfig
|
|
}
|
|
|
|
return &Scanner{
|
|
r: bufio.NewReader(r),
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) Scan() (Token, string, Pos) {
|
|
ch, pos := s.read()
|
|
|
|
if s.isBlankspace(ch) {
|
|
_ = s.unread()
|
|
return s.scanBlankspace()
|
|
}
|
|
|
|
if s.isAssignmentOperator(ch) {
|
|
return ASSIGN, string(ch), pos
|
|
}
|
|
|
|
if s.isMultiValueDelim(ch) {
|
|
return MULTI_VALUE_DELIMITER, string(ch), pos
|
|
}
|
|
|
|
if ch == eol {
|
|
return EOL, "", pos
|
|
}
|
|
|
|
if ch == nul {
|
|
return ARG_DELIMITER, string(ch), pos
|
|
}
|
|
|
|
if unicode.IsGraphic(ch) {
|
|
_ = s.unread()
|
|
return s.scanArg()
|
|
}
|
|
|
|
return ILLEGAL, string(ch), pos
|
|
}
|
|
|
|
func (s *Scanner) read() (rune, Pos) {
|
|
ch, _, err := s.r.ReadRune()
|
|
s.i++
|
|
|
|
if errors.Is(err, io.EOF) {
|
|
return eol, Pos(s.i)
|
|
} else if err != nil {
|
|
log.Printf("unknown scanner error=%+v", err)
|
|
return eol, Pos(s.i)
|
|
}
|
|
|
|
return ch, Pos(s.i)
|
|
}
|
|
|
|
func (s *Scanner) unread() Pos {
|
|
_ = s.r.UnreadRune()
|
|
s.i--
|
|
return Pos(s.i)
|
|
}
|
|
|
|
func (s *Scanner) isBlankspace(ch rune) bool {
|
|
return ch == ' ' || ch == '\t' || ch == '\n'
|
|
}
|
|
|
|
func (s *Scanner) isUnderscore(ch rune) bool {
|
|
return ch == '_'
|
|
}
|
|
|
|
func (s *Scanner) isFlagPrefix(ch rune) bool {
|
|
return ch == s.cfg.FlagPrefix
|
|
}
|
|
|
|
func (s *Scanner) isMultiValueDelim(ch rune) bool {
|
|
return ch == s.cfg.MultiValueDelim
|
|
}
|
|
|
|
func (s *Scanner) isAssignmentOperator(ch rune) bool {
|
|
return ch == s.cfg.AssignmentOperator
|
|
}
|
|
|
|
func (s *Scanner) scanBlankspace() (Token, string, Pos) {
|
|
buf := &bytes.Buffer{}
|
|
ch, pos := s.read()
|
|
buf.WriteRune(ch)
|
|
|
|
for {
|
|
ch, pos = s.read()
|
|
|
|
if ch == eol {
|
|
break
|
|
} else if !s.isBlankspace(ch) {
|
|
pos = s.unread()
|
|
break
|
|
} else {
|
|
_, _ = buf.WriteRune(ch)
|
|
}
|
|
}
|
|
|
|
return BS, buf.String(), pos
|
|
}
|
|
|
|
func (s *Scanner) scanArg() (Token, string, Pos) {
|
|
buf := &bytes.Buffer{}
|
|
ch, pos := s.read()
|
|
buf.WriteRune(ch)
|
|
|
|
for {
|
|
ch, pos = s.read()
|
|
|
|
if ch == eol || ch == nul || s.isAssignmentOperator(ch) || s.isMultiValueDelim(ch) {
|
|
pos = s.unread()
|
|
break
|
|
}
|
|
|
|
_, _ = buf.WriteRune(ch)
|
|
}
|
|
|
|
str := buf.String()
|
|
|
|
if len(str) == 0 {
|
|
return EMPTY, str, pos
|
|
}
|
|
|
|
ch0 := rune(str[0])
|
|
|
|
if len(str) == 1 {
|
|
if s.isFlagPrefix(ch0) {
|
|
return STDIN_FLAG, str, pos
|
|
}
|
|
|
|
if s.isAssignmentOperator(ch0) {
|
|
return ASSIGN, str, pos
|
|
}
|
|
|
|
return IDENT, str, pos
|
|
}
|
|
|
|
ch1 := rune(str[1])
|
|
|
|
if len(str) == 2 {
|
|
if str == string(s.cfg.FlagPrefix)+string(s.cfg.FlagPrefix) {
|
|
return STOP_FLAG, str, pos
|
|
}
|
|
|
|
if s.isFlagPrefix(ch0) {
|
|
return SHORT_FLAG, str, pos
|
|
}
|
|
}
|
|
|
|
if s.isFlagPrefix(ch0) {
|
|
if s.isFlagPrefix(ch1) {
|
|
return LONG_FLAG, str, pos
|
|
}
|
|
|
|
return COMPOUND_SHORT_FLAG, str, pos
|
|
}
|
|
|
|
return IDENT, str, pos
|
|
}
|