mirror of
https://github.com/miniflux/v2.git
synced 2024-08-30 08:07:20 +02:00
211 lines
4.5 KiB
Go
211 lines
4.5 KiB
Go
|
// 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])
|
||
|
}
|