// 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]) }