github.com/mithrandie/csvq@v1.18.1/lib/excmd/args_splitter.go (about) 1 package excmd 2 3 import ( 4 "bytes" 5 "errors" 6 "unicode" 7 8 "github.com/mithrandie/csvq/lib/parser" 9 ) 10 11 type ArgsSplitter struct { 12 src []rune 13 srcPos int 14 text bytes.Buffer 15 16 err error 17 } 18 19 func (s *ArgsSplitter) Init(src string) *ArgsSplitter { 20 s.src = []rune(src) 21 s.srcPos = 0 22 s.text.Reset() 23 s.err = nil 24 return s 25 } 26 27 func (s *ArgsSplitter) Err() error { 28 return s.err 29 } 30 31 func (s *ArgsSplitter) Text() string { 32 return s.text.String() 33 } 34 35 func (s *ArgsSplitter) peek() rune { 36 if len(s.src) <= s.srcPos { 37 return EOF 38 } 39 40 return s.src[s.srcPos] 41 } 42 43 func (s *ArgsSplitter) next() rune { 44 ch := s.peek() 45 if ch == EOF { 46 return ch 47 } 48 49 s.srcPos++ 50 return ch 51 } 52 53 func (s *ArgsSplitter) Scan() bool { 54 for unicode.IsSpace(s.peek()) { 55 s.next() 56 } 57 58 ch := s.next() 59 s.text.Reset() 60 61 switch ch { 62 case EOF: 63 return false 64 case parser.VariableSign: 65 s.text.WriteRune(ch) 66 67 if s.peek() == parser.EnvironmentVariableSign { 68 s.text.WriteRune(s.next()) 69 if s.peek() == '`' { 70 s.text.WriteRune(s.next()) 71 s.scanQuotedVariable('`') 72 } else { 73 s.scanString() 74 } 75 } else { 76 s.scanString() 77 } 78 case parser.ExternalCommandSign: 79 s.text.WriteRune(ch) 80 if s.peek() != parser.BeginExpression { 81 s.err = errors.New("invalid command symbol") 82 } else { 83 s.text.WriteRune(s.next()) 84 s.scanExternalCommand() 85 } 86 case '"', '\'': 87 s.text.WriteRune(ch) 88 s.scanQuotedString(ch) 89 default: 90 s.text.WriteRune(ch) 91 s.scanString() 92 } 93 94 return s.err == nil 95 } 96 97 func (s *ArgsSplitter) scanQuotedVariable(quote rune) { 98 for { 99 ch := s.next() 100 if ch == EOF { 101 s.err = errors.New("environment variable not terminated") 102 break 103 } 104 105 s.text.WriteRune(ch) 106 107 if ch == quote { 108 break 109 } 110 111 if ch == '\\' { 112 switch s.peek() { 113 case '\\', quote: 114 s.text.WriteRune(s.next()) 115 } 116 } 117 } 118 } 119 120 func (s *ArgsSplitter) scanQuotedString(quote rune) { 121 for { 122 ch := s.next() 123 if ch == EOF { 124 s.err = errors.New("string not terminated") 125 break 126 } 127 128 if ch == '\\' { 129 switch s.peek() { 130 case '\\', quote: 131 s.text.WriteRune(ch) 132 ch = s.next() 133 } 134 } else if ch == quote { 135 s.text.WriteRune(ch) 136 if s.peek() == quote { 137 ch = s.next() 138 } else { 139 break 140 } 141 } 142 143 s.text.WriteRune(ch) 144 } 145 } 146 147 func (s *ArgsSplitter) scanString() { 148 for { 149 if s.peek() == EOF || unicode.IsSpace(s.peek()) { 150 break 151 } 152 s.text.WriteRune(s.next()) 153 } 154 } 155 156 func (s *ArgsSplitter) scanExternalCommand() { 157 for { 158 ch := s.next() 159 if ch == EOF { 160 s.err = errors.New("command not terminated") 161 break 162 } 163 164 if ch == parser.EndExpression { 165 s.text.WriteRune(ch) 166 break 167 } 168 169 if ch == '\\' { 170 switch s.peek() { 171 case parser.BeginExpression, parser.EndExpression: 172 ch = s.next() 173 } 174 } 175 s.text.WriteRune(ch) 176 } 177 }