comparison cmd/sievemgr/internal/config/scanner.go @ 5:4dff4c3f0fbb

Introduce configuration file where account information is specified Introduce a configuration file where account information must be specified instead of passing it with each invocation on the command line. Each account has a name by which it can be selected and one may be specified as the default account. This is intended to improve usability for productive usage. Enforce strict permissions since passwords may be specified for non-interactive usage. Remove command-line flags for passing account information.
author Guido Berhoerster <guido+sievemgr@berhoerster.name>
date Tue, 03 Nov 2020 23:44:45 +0100
parents
children
comparison
equal deleted inserted replaced
4:f925f15d8ce5 5:4dff4c3f0fbb
1 // Copyright (C) 2020 Guido Berhoerster <guido+sievemgr@berhoerster.name>
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 package config
23
24 import (
25 "bufio"
26 "fmt"
27 "io"
28 "strings"
29 "unicode"
30 )
31
32 type token int
33
34 const (
35 tokenEOF token = -(iota + 1)
36 tokenIllegal
37 tokenIdent
38 tokenNumber
39 tokenString
40 )
41
42 func (tok token) String() string {
43 switch tok {
44 case tokenIllegal:
45 return "illegal"
46 case tokenEOF:
47 return "EOF"
48 case tokenIdent:
49 return "identifier"
50 case tokenNumber:
51 return "number"
52 case tokenString:
53 return "string"
54 default:
55 return "unknown"
56 }
57 }
58
59 func isWhitespaceRune(r rune) bool {
60 return r == ' ' || r == '\t' || r == '\r' || r == '\n'
61 }
62
63 func isIdentRune(r rune) bool {
64 return r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z'
65 }
66
67 func isNumberRune(r rune) bool {
68 return r >= '0' && r <= '9'
69 }
70
71 type scanner struct {
72 br *bufio.Reader
73 line int // line number for error messages
74 r rune // last read rune
75 rdSize int // last read size
76 rdOffset int // offset from beginning of file
77 }
78
79 func newScanner(br *bufio.Reader) *scanner {
80 return &scanner{line: 1, br: br}
81 }
82
83 func (s *scanner) read() error {
84 var size int
85 again:
86 r, size, err := s.br.ReadRune()
87 if err != nil {
88 return err
89 }
90
91 // skip over BOM at the beginning of the file
92 if r == bom && s.rdOffset == 0 {
93 s.rdOffset += size
94 goto again
95 }
96
97 if s.r == '\n' {
98 s.line++
99 }
100
101 s.r = r
102 s.rdOffset += size
103 s.rdSize = size
104
105 if r == unicode.ReplacementChar && size == 1 {
106 return fmt.Errorf("illegal UTF-8 sequence")
107 } else if r == 0 {
108 return fmt.Errorf("illegal nul byte")
109 }
110 return nil
111 }
112
113 func (s *scanner) unread() {
114 if s.br.UnreadRune() != nil {
115 return
116 }
117
118 if b, _ := s.br.Peek(1); b[0] == '\n' {
119 // moved back to the previous line
120 s.line--
121 }
122
123 s.r = 0
124 s.rdOffset -= s.rdSize
125 s.rdSize = 0
126 }
127
128 func (s *scanner) skipWhitespace() error {
129 for {
130 if err := s.read(); err == io.EOF {
131 break
132 } else if err != nil {
133 return err
134 } else if !isWhitespaceRune(s.r) {
135 s.unread()
136 break
137 }
138 }
139 return nil
140 }
141
142 func (s *scanner) scanIdent() (token, string, error) {
143 var sb strings.Builder
144 for {
145 if err := s.read(); err == io.EOF {
146 break
147 } else if err != nil {
148 return tokenIllegal, "", err
149 }
150 if isIdentRune(s.r) {
151 sb.WriteRune(s.r)
152 } else if isWhitespaceRune(s.r) {
153 s.unread()
154 break
155 } else {
156 return tokenIllegal, "", fmt.Errorf("illegal rune in identifier: %q", s.r)
157 }
158 }
159 if sb.Len() == 0 {
160 return tokenIllegal, "", fmt.Errorf("expected identifier, got %q", s.r)
161 }
162 return tokenIdent, sb.String(), nil
163 }
164
165 func (s *scanner) scanNumber() (token, string, error) {
166 var sb strings.Builder
167 for {
168 if err := s.read(); err == io.EOF {
169 break
170 } else if err != nil {
171 return tokenIllegal, "", err
172 }
173 if isNumberRune(s.r) {
174 sb.WriteRune(s.r)
175 } else if isWhitespaceRune(s.r) {
176 s.unread()
177 break
178 } else {
179 return tokenIllegal, "", fmt.Errorf("illegal rune in number: %q", s.r)
180 }
181 }
182 if sb.Len() == 0 {
183 return tokenIllegal, "", fmt.Errorf("expected number, got %q", s.r)
184 }
185 return tokenNumber, sb.String(), nil
186 }
187
188 func (s *scanner) scanString() (token, string, error) {
189 if err := s.read(); err == io.EOF {
190 return tokenIllegal, "", fmt.Errorf("unexpected EOF")
191 } else if err != nil {
192 return tokenIllegal, "", err
193 }
194
195 if s.r != '"' {
196 return tokenIllegal, "",
197 fmt.Errorf("expected '\"', got %q", s.r)
198 }
199
200 var sb strings.Builder
201 var inEscape bool
202 for {
203 if err := s.read(); err == io.EOF {
204 return tokenIllegal, "", fmt.Errorf("unterminated string")
205 } else if err != nil {
206 return tokenIllegal, "", err
207 }
208
209 if s.r == '\\' && !inEscape {
210 inEscape = true
211 } else if s.r == '"' && !inEscape {
212 break
213 } else {
214 sb.WriteRune(s.r)
215 inEscape = false
216 }
217 }
218
219 return tokenString, sb.String(), nil
220 }
221
222 func (s *scanner) scan() (token, string, error) {
223 if err := s.skipWhitespace(); err != nil {
224 return tokenIllegal, "", err
225 }
226
227 if err := s.read(); err == io.EOF {
228 return tokenEOF, "", nil
229 } else if err != nil {
230 return tokenIllegal, "", err
231 }
232 r := s.r
233 s.unread()
234
235 switch {
236 case isIdentRune(r):
237 return s.scanIdent()
238 case isNumberRune(r):
239 return s.scanNumber()
240 case r == '"':
241 return s.scanString()
242 }
243 return tokenIllegal, string(r), nil
244 }