| // Copyright 2010 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // Package dict implements the Dictionary Server Protocol | |
| // as defined in RFC 2229. | |
| package dict | |
| import ( | |
| "net/textproto" | |
| "strconv" | |
| "strings" | |
| ) | |
| // A Client represents a client connection to a dictionary server. | |
| type Client struct { | |
| text *textproto.Conn | |
| } | |
| // Dial returns a new client connected to a dictionary server at | |
| // addr on the given network. | |
| func Dial(network, addr string) (*Client, error) { | |
| text, err := textproto.Dial(network, addr) | |
| if err != nil { | |
| return nil, err | |
| } | |
| _, _, err = text.ReadCodeLine(220) | |
| if err != nil { | |
| text.Close() | |
| return nil, err | |
| } | |
| return &Client{text: text}, nil | |
| } | |
| // Close closes the connection to the dictionary server. | |
| func (c *Client) Close() error { | |
| return c.text.Close() | |
| } | |
| // A Dict represents a dictionary available on the server. | |
| type Dict struct { | |
| Name string // short name of dictionary | |
| Desc string // long description | |
| } | |
| // Dicts returns a list of the dictionaries available on the server. | |
| func (c *Client) Dicts() ([]Dict, error) { | |
| id, err := c.text.Cmd("SHOW DB") | |
| if err != nil { | |
| return nil, err | |
| } | |
| c.text.StartResponse(id) | |
| defer c.text.EndResponse(id) | |
| _, _, err = c.text.ReadCodeLine(110) | |
| if err != nil { | |
| return nil, err | |
| } | |
| lines, err := c.text.ReadDotLines() | |
| if err != nil { | |
| return nil, err | |
| } | |
| _, _, err = c.text.ReadCodeLine(250) | |
| dicts := make([]Dict, len(lines)) | |
| for i := range dicts { | |
| d := &dicts[i] | |
| a, _ := fields(lines[i]) | |
| if len(a) < 2 { | |
| return nil, textproto.ProtocolError("invalid dictionary: " + lines[i]) | |
| } | |
| d.Name = a[0] | |
| d.Desc = a[1] | |
| } | |
| return dicts, err | |
| } | |
| // A Defn represents a definition. | |
| type Defn struct { | |
| Dict Dict // Dict where definition was found | |
| Word string // Word being defined | |
| Text []byte // Definition text, typically multiple lines | |
| } | |
| // Define requests the definition of the given word. | |
| // The argument dict names the dictionary to use, | |
| // the Name field of a Dict returned by Dicts. | |
| // | |
| // The special dictionary name "*" means to look in all the | |
| // server's dictionaries. | |
| // The special dictionary name "!" means to look in all the | |
| // server's dictionaries in turn, stopping after finding the word | |
| // in one of them. | |
| func (c *Client) Define(dict, word string) ([]*Defn, error) { | |
| id, err := c.text.Cmd("DEFINE %s %q", dict, word) | |
| if err != nil { | |
| return nil, err | |
| } | |
| c.text.StartResponse(id) | |
| defer c.text.EndResponse(id) | |
| _, line, err := c.text.ReadCodeLine(150) | |
| if err != nil { | |
| return nil, err | |
| } | |
| a, _ := fields(line) | |
| if len(a) < 1 { | |
| return nil, textproto.ProtocolError("malformed response: " + line) | |
| } | |
| n, err := strconv.Atoi(a[0]) | |
| if err != nil { | |
| return nil, textproto.ProtocolError("invalid definition count: " + a[0]) | |
| } | |
| def := make([]*Defn, n) | |
| for i := 0; i < n; i++ { | |
| _, line, err = c.text.ReadCodeLine(151) | |
| if err != nil { | |
| return nil, err | |
| } | |
| a, _ := fields(line) | |
| if len(a) < 3 { | |
| // skip it, to keep protocol in sync | |
| i-- | |
| n-- | |
| def = def[0:n] | |
| continue | |
| } | |
| d := &Defn{Word: a[0], Dict: Dict{a[1], a[2]}} | |
| d.Text, err = c.text.ReadDotBytes() | |
| if err != nil { | |
| return nil, err | |
| } | |
| def[i] = d | |
| } | |
| _, _, err = c.text.ReadCodeLine(250) | |
| return def, err | |
| } | |
| // Fields returns the fields in s. | |
| // Fields are space separated unquoted words | |
| // or quoted with single or double quote. | |
| func fields(s string) ([]string, error) { | |
| var v []string | |
| i := 0 | |
| for { | |
| for i < len(s) && (s[i] == ' ' || s[i] == '\t') { | |
| i++ | |
| } | |
| if i >= len(s) { | |
| break | |
| } | |
| if s[i] == '"' || s[i] == '\'' { | |
| q := s[i] | |
| // quoted string | |
| var j int | |
| for j = i + 1; ; j++ { | |
| if j >= len(s) { | |
| return nil, textproto.ProtocolError("malformed quoted string") | |
| } | |
| if s[j] == '\\' { | |
| j++ | |
| continue | |
| } | |
| if s[j] == q { | |
| j++ | |
| break | |
| } | |
| } | |
| v = append(v, unquote(s[i+1:j-1])) | |
| i = j | |
| } else { | |
| // atom | |
| var j int | |
| for j = i; j < len(s); j++ { | |
| if s[j] == ' ' || s[j] == '\t' || s[j] == '\\' || s[j] == '"' || s[j] == '\'' { | |
| break | |
| } | |
| } | |
| v = append(v, s[i:j]) | |
| i = j | |
| } | |
| if i < len(s) { | |
| c := s[i] | |
| if c != ' ' && c != '\t' { | |
| return nil, textproto.ProtocolError("quotes not on word boundaries") | |
| } | |
| } | |
| } | |
| return v, nil | |
| } | |
| func unquote(s string) string { | |
| if strings.Index(s, "\\") < 0 { | |
| return s | |
| } | |
| b := []byte(s) | |
| w := 0 | |
| for r := 0; r < len(b); r++ { | |
| c := b[r] | |
| if c == '\\' { | |
| r++ | |
| c = b[r] | |
| } | |
| b[w] = c | |
| w++ | |
| } | |
| return string(b[0:w]) | |
| } |