|  | // 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 "golang.org/x/net/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]) | 
|  | } |