source: code/trunk/vendor/gopkg.in/irc.v3/parser.go@ 822

Last change on this file since 822 was 822, checked in by yakumo.izuru, 22 months ago

Prefer immortal.run over runit and rc.d, use vendored modules
for convenience.

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 8.6 KB
RevLine 
[822]1package irc
2
3import (
4 "bytes"
5 "errors"
6 "strings"
7)
8
9var tagDecodeSlashMap = map[rune]rune{
10 ':': ';',
11 's': ' ',
12 '\\': '\\',
13 'r': '\r',
14 'n': '\n',
15}
16
17var tagEncodeMap = map[rune]string{
18 ';': "\\:",
19 ' ': "\\s",
20 '\\': "\\\\",
21 '\r': "\\r",
22 '\n': "\\n",
23}
24
25var (
26 // ErrZeroLengthMessage is returned when parsing if the input is
27 // zero-length.
28 ErrZeroLengthMessage = errors.New("irc: Cannot parse zero-length message")
29
30 // ErrMissingDataAfterPrefix is returned when parsing if there is
31 // no message data after the prefix.
32 ErrMissingDataAfterPrefix = errors.New("irc: No message data after prefix")
33
34 // ErrMissingDataAfterTags is returned when parsing if there is no
35 // message data after the tags.
36 ErrMissingDataAfterTags = errors.New("irc: No message data after tags")
37
38 // ErrMissingCommand is returned when parsing if there is no
39 // command in the parsed message.
40 ErrMissingCommand = errors.New("irc: Missing message command")
41)
42
43// TagValue represents the value of a tag.
44type TagValue string
45
46// ParseTagValue parses a TagValue from the connection. If you need to
47// set a TagValue, you probably want to just set the string itself, so
48// it will be encoded properly.
49func ParseTagValue(v string) TagValue {
50 ret := &bytes.Buffer{}
51
52 input := bytes.NewBufferString(v)
53
54 for {
55 c, _, err := input.ReadRune()
56 if err != nil {
57 break
58 }
59
60 if c == '\\' {
61 c2, _, err := input.ReadRune()
62
63 // If we got a backslash then the end of the tag value, we should
64 // just ignore the backslash.
65 if err != nil {
66 break
67 }
68
69 if replacement, ok := tagDecodeSlashMap[c2]; ok {
70 ret.WriteRune(replacement)
71 } else {
72 ret.WriteRune(c2)
73 }
74 } else {
75 ret.WriteRune(c)
76 }
77 }
78
79 return TagValue(ret.String())
80}
81
82// Encode converts a TagValue to the format in the connection.
83func (v TagValue) Encode() string {
84 ret := &bytes.Buffer{}
85
86 for _, c := range v {
87 if replacement, ok := tagEncodeMap[c]; ok {
88 ret.WriteString(replacement)
89 } else {
90 ret.WriteRune(c)
91 }
92 }
93
94 return ret.String()
95}
96
97// Tags represents the IRCv3 message tags.
98type Tags map[string]TagValue
99
100// ParseTags takes a tag string and parses it into a tag map. It will
101// always return a tag map, even if there are no valid tags.
102func ParseTags(line string) Tags {
103 ret := Tags{}
104
105 tags := strings.Split(line, ";")
106 for _, tag := range tags {
107 parts := strings.SplitN(tag, "=", 2)
108 if len(parts) < 2 {
109 ret[parts[0]] = ""
110 continue
111 }
112
113 ret[parts[0]] = ParseTagValue(parts[1])
114 }
115
116 return ret
117}
118
119// GetTag is a convenience method to look up a tag in the map.
120func (t Tags) GetTag(key string) (string, bool) {
121 ret, ok := t[key]
122 return string(ret), ok
123}
124
125// Copy will create a new copy of all IRC tags attached to this
126// message.
127func (t Tags) Copy() Tags {
128 ret := Tags{}
129
130 for k, v := range t {
131 ret[k] = v
132 }
133
134 return ret
135}
136
137// String ensures this is stringable
138func (t Tags) String() string {
139 buf := &bytes.Buffer{}
140
141 for k, v := range t {
142 buf.WriteByte(';')
143 buf.WriteString(k)
144 if v != "" {
145 buf.WriteByte('=')
146 buf.WriteString(v.Encode())
147 }
148 }
149
150 // We don't need the first byte because that's an extra ';'
151 // character.
152 buf.ReadByte()
153
154 return buf.String()
155}
156
157// Prefix represents the prefix of a message, generally the user who sent it
158type Prefix struct {
159 // Name will contain the nick of who sent the message, the
160 // server who sent the message, or a blank string
161 Name string
162
163 // User will either contain the user who sent the message or a blank string
164 User string
165
166 // Host will either contain the host of who sent the message or a blank string
167 Host string
168}
169
170// ParsePrefix takes an identity string and parses it into an
171// identity struct. It will always return an Prefix struct and never
172// nil.
173func ParsePrefix(line string) *Prefix {
174 // Start by creating an Prefix with nothing but the host
175 id := &Prefix{
176 Name: line,
177 }
178
179 uh := strings.SplitN(id.Name, "@", 2)
180 if len(uh) == 2 {
181 id.Name, id.Host = uh[0], uh[1]
182 }
183
184 nu := strings.SplitN(id.Name, "!", 2)
185 if len(nu) == 2 {
186 id.Name, id.User = nu[0], nu[1]
187 }
188
189 return id
190}
191
192// Copy will create a new copy of an Prefix
193func (p *Prefix) Copy() *Prefix {
194 if p == nil {
195 return nil
196 }
197
198 newPrefix := &Prefix{}
199
200 *newPrefix = *p
201
202 return newPrefix
203}
204
205// String ensures this is stringable
206func (p *Prefix) String() string {
207 buf := &bytes.Buffer{}
208 buf.WriteString(p.Name)
209
210 if p.User != "" {
211 buf.WriteString("!")
212 buf.WriteString(p.User)
213 }
214
215 if p.Host != "" {
216 buf.WriteString("@")
217 buf.WriteString(p.Host)
218 }
219
220 return buf.String()
221}
222
223// Message represents a line parsed from the server
224type Message struct {
225 // Each message can have IRCv3 tags
226 Tags
227
228 // Each message can have a Prefix
229 *Prefix
230
231 // Command is which command is being called.
232 Command string
233
234 // Params are all the arguments for the command.
235 Params []string
236}
237
238// MustParseMessage calls ParseMessage and either returns the message
239// or panics if an error is returned.
240func MustParseMessage(line string) *Message {
241 m, err := ParseMessage(line)
242 if err != nil {
243 panic(err.Error())
244 }
245 return m
246}
247
248// ParseMessage takes a message string (usually a whole line) and
249// parses it into a Message struct. This will return nil in the case
250// of invalid messages.
251func ParseMessage(line string) (*Message, error) {
252 // Trim the line and make sure we have data
253 line = strings.TrimRight(line, "\r\n")
254 if len(line) == 0 {
255 return nil, ErrZeroLengthMessage
256 }
257
258 c := &Message{
259 Tags: Tags{},
260 Prefix: &Prefix{},
261 }
262
263 if line[0] == '@' {
264 loc := strings.Index(line, " ")
265 if loc == -1 {
266 return nil, ErrMissingDataAfterTags
267 }
268
269 c.Tags = ParseTags(line[1:loc])
270 line = line[loc+1:]
271 }
272
273 if line[0] == ':' {
274 loc := strings.Index(line, " ")
275 if loc == -1 {
276 return nil, ErrMissingDataAfterPrefix
277 }
278
279 // Parse the identity, if there was one
280 c.Prefix = ParsePrefix(line[1:loc])
281 line = line[loc+1:]
282 }
283
284 // Split out the trailing then the rest of the args. Because
285 // we expect there to be at least one result as an arg (the
286 // command) we don't need to special case the trailing arg and
287 // can just attempt a split on " :"
288 split := strings.SplitN(line, " :", 2)
289 c.Params = strings.FieldsFunc(split[0], func(r rune) bool {
290 return r == ' '
291 })
292
293 // If there are no args, we need to bail because we need at
294 // least the command.
295 if len(c.Params) == 0 {
296 return nil, ErrMissingCommand
297 }
298
299 // If we had a trailing arg, append it to the other args
300 if len(split) == 2 {
301 c.Params = append(c.Params, split[1])
302 }
303
304 // Because of how it's parsed, the Command will show up as the
305 // first arg.
306 c.Command = strings.ToUpper(c.Params[0])
307 c.Params = c.Params[1:]
308
309 // If there are no params, set it to nil, to make writing tests and other
310 // things simpler.
311 if len(c.Params) == 0 {
312 c.Params = nil
313 }
314
315 return c, nil
316}
317
318// Param returns the i'th argument in the Message or an empty string
319// if the requested arg does not exist
320func (m *Message) Param(i int) string {
321 if i < 0 || i >= len(m.Params) {
322 return ""
323 }
324 return m.Params[i]
325}
326
327// Trailing returns the last argument in the Message or an empty string
328// if there are no args
329func (m *Message) Trailing() string {
330 if len(m.Params) < 1 {
331 return ""
332 }
333
334 return m.Params[len(m.Params)-1]
335}
336
337// Copy will create a new copy of an message
338func (m *Message) Copy() *Message {
339 // Create a new message
340 newMessage := &Message{}
341
342 // Copy stuff from the old message
343 *newMessage = *m
344
345 // Copy any IRcv3 tags
346 newMessage.Tags = m.Tags.Copy()
347
348 // Copy the Prefix
349 newMessage.Prefix = m.Prefix.Copy()
350
351 // Copy the Params slice
352 newMessage.Params = append(make([]string, 0, len(m.Params)), m.Params...)
353
354 // Similar to parsing, if Params is empty, set it to nil
355 if len(newMessage.Params) == 0 {
356 newMessage.Params = nil
357 }
358
359 return newMessage
360}
361
362// String ensures this is stringable
363func (m *Message) String() string {
364 buf := &bytes.Buffer{}
365
366 // Write any IRCv3 tags if they exist in the message
367 if len(m.Tags) > 0 {
368 buf.WriteByte('@')
369 buf.WriteString(m.Tags.String())
370 buf.WriteByte(' ')
371 }
372
373 // Add the prefix if we have one
374 if m.Prefix != nil && m.Prefix.Name != "" {
375 buf.WriteByte(':')
376 buf.WriteString(m.Prefix.String())
377 buf.WriteByte(' ')
378 }
379
380 // Add the command since we know we'll always have one
381 buf.WriteString(m.Command)
382
383 if len(m.Params) > 0 {
384 args := m.Params[:len(m.Params)-1]
385 trailing := m.Params[len(m.Params)-1]
386
387 if len(args) > 0 {
388 buf.WriteByte(' ')
389 buf.WriteString(strings.Join(args, " "))
390 }
391
392 // If trailing is zero-length, contains a space or starts with
393 // a : we need to actually specify that it's trailing.
394 if len(trailing) == 0 || strings.ContainsRune(trailing, ' ') || trailing[0] == ':' {
395 buf.WriteString(" :")
396 } else {
397 buf.WriteString(" ")
398 }
399 buf.WriteString(trailing)
400 }
401
402 return buf.String()
403}
Note: See TracBrowser for help on using the repository browser.