#!/bin/python
# dict (rfc2229) client, by mechiel lukkien <mjl@cat-v.org>, public domain
import sys
import os
import getopt
import string
import socket
#from sets import Set
# todo
# - handle escaping that dict protocol can do (i think)
# - remove some quotes (") around matches of non-exact match strategy
def usage(s):
print >>sys.stderr, 'dict:', s
print >>sys.stderr, 'usage: dict [-DIM] [-d db] [-m strategy] [-i db] [word]'
sys.exit(1)
def dial(dialstr):
proto, host, port = string.split(dialstr, '!', 2)
if proto != 'tcp':
raise Exception('Protocols other than tcp not implemented.')
try:
port = int(port)
except:
pass
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
return sock
# no module sets in python2.2+
def unique(l):
r = []
for e in l:
if not e in r:
r.append(e)
return r
class Dict:
def __init__(self, addr, verbose):
self.sock = dial(addr)
self.f = self.sock.makefile("r")
self.verbose = verbose
welcome = self.f.readline()
if welcome[0:4] != '220 ':
raise Exception("server doesn't want you (%s)" % welcome[0:4])
r, _ = self._cmd('CLIENT python client, versionless')
if r != '250':
raise Exception('sending client string failed')
def debug(self, s):
if self.verbose:
print >>sys.stderr, s
def getdbs(self):
r, line = self._cmd('SHOW DB')
if r != '110':
raise Exception('show db failed (%s)' % line)
return [tuple(string.split(line, ' ', 1)) for line in self._readlist()]
def getstrats(self):
r, line = self._cmd('SHOW STRAT')
if r != '111':
raise Exception('show strat failed (%s)' % line)
return [tuple(string.split(line, ' ', 1)) for line in self._readlist()]
def getserverinfo(self):
r, line = self._cmd('SHOW SERVER')
if r != '114':
raise Exception('show server failed (%s)' % line)
return '\n'.join(self._readlist())
def getdbinfo(self, db):
r, line = self._cmd('SHOW INFO %s' % self.quote(db))
if r != '112':
raise Exception('show info failed (%s)' % line)
return '\n'.join(self._readlist())
def match(self, word, db='!', strat='.'):
r, line = self._cmd('MATCH %s %s %s' % (self.quote(db), self.quote(strat), self.quote(word)))
if r == '552':
return []
if r[0] in ['4', '5']:
raise Exception('response to match: %s' % line)
lines = [tuple(self.split(l, ' ', 1)) for l in self._readlist()]
line = self._read()
if line[0:4] != '250 ':
raise Exception('expected code 250 after match (%s)' % line)
return lines
def definition(self, word, db='!'):
r, line = self._cmd('DEFINE %s %s' % (self.quote(db), self.quote(word)))
if r == '552':
return []
if r[0] in ['4', '5']:
raise Exception('response to define: %s' % line)
defs = []
while 1:
line = self._read()
if line[0:4] == '151 ':
_, _, db, dbdescr = self.split(line, ' ', 3)
defs.append((db, dbdescr, '\n'.join(self._readlist())))
else:
break
return defs
def quote(self, word):
if ' ' in word or "'" in word or '"' in word:
return "'%s'" % string.replace(word, "'", "''")
return word
def split(self, line, delim, num):
def unquote(l):
if l[0] in ['"', "'"]:
q = l[0]
offset = 1
while 1:
offset = string.find(l[offset:], q)
if offset == -1:
raise Exception('Invalidly quoted line from server')
if l[offset-1:offset+1] == (r'\%s' % q):
offset += 1
else:
word = string.replace(l[1:offset+1], r'\%s' % q, q)
l = string.lstrip(l[offset+2:])
break
else:
word, l = string.split(l, delim, 1)
return word, l
r = []
l = line
while num != 0:
word, l = unquote(l)
r.append(word)
num -= 1
word, rest = unquote(l)
r.append(word)
return r
def _readlist(self):
lines = []
while 1:
line = self._read()
if line == '.':
break
if line[0:2] == '..':
line = line[1:]
lines.append(line)
return lines
def _read(self):
line = self.f.readline()
if line[-1] == '\n':
line = line[0:-1]
if line[-1] == '\r':
line = line[0:-1]
self.debug('< %s' % line)
return line
def _cmd(self, cmd):
self.sock.sendall(cmd + '\r\n')
self.f.flush()
self.debug('> %s' % cmd)
line = self._read()
code = line[0:3]
return code, line
if __name__ == '__main__':
listdb = liststrats = serverinfo = 0
defaultdatabase = database = '!'
defaultstrat = strat = 'exact'
infodb = None
verbose = 0
try:
optlist, args = getopt.getopt(sys.argv[1:], 'd:i:m:vDIM')
except getopt.GetoptError, s:
usage(s)
for opt, arg in optlist:
if opt == '-d':
database = arg
elif opt == '-i':
infodb = arg
elif opt == '-m':
strat = arg
if database == defaultdatabase:
database = '*'
elif opt == '-D':
listdb = 1
elif opt == '-I':
serverinfo = 1
elif opt == '-M':
liststrats = 1
elif opt == '-v':
verbose = 1
d = Dict('tcp!dict.org!2628', verbose)
if len(args) > 1:
usage('too many arguments')
if liststrats:
strats = d.getstrats()
for name, descr in strats:
print '%-15s %s' % (name, descr)
if listdb:
dbs = d.getdbs()
for name, descr in dbs:
print '%-15s %s' % (name, descr)
if serverinfo:
s = d.getserverinfo()
print s
if infodb:
s = d.getdbinfo(infodb)
print s
if len(args) == 0:
if not (infodb or listdb or serverinfo or liststrats):
usage('no arguments passed')
sys.exit(0)
def quote(s):
if ' ' in s or "'" in s:
return "'%s'" % string.replace(s, "'", "''")
return s
if strat != defaultstrat:
l = d.match(args[0], db=database, strat=strat)
keys = unique([db for db, word in l])
matches = dict([(key, [value for k, value in l if k == key]) for key in keys])
for key in matches.keys():
for m in matches[key]:
print 'dict -d %-8s %-15s # dict:%s:%s' % (key, quote(m), key, quote(m))
else:
print >>sys.stderr, 'no match'
sys.exit(2)
else:
if database == defaultdatabase:
l = d.match(args[0], db=database, strat=strat)
else:
l = [(database, args[0])]
for db, word in l:
defs = d.definition(word, db=db)
if defs == []:
print >>sys.stderr, 'no match'
sys.exit(2)
db, dbdescr, defstr = defs[0]
if db != database:
print '### From %s' % quote(dbdescr)
if len(defs) > 1:
print ''
print '\n\n\n'.join([defstr for _, _, defstr in defs])
sys.exit(0)
|