#!/bin/python # dict (rfc2229) client, by mechiel lukkien , 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)