#!/usr/bin/ruby -w # # rdict - a DICT protocol client (see RFC 2229) # # $Id: rdict,v 1.30 2007/05/20 00:01:36 ianmacd Exp $ # # Version : 0.9.4 # Author : Ian Macdonald # # Copyright (C) 2002-2007 Ian Macdonald # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. =begin = NAME rdict - a DICT protocol client = SYNOPSIS rdict [-h|--host server] [-p|--port service] [-d|--database dbname] [-m|--match] [-s|--strategy strategy] [-C|--nocorrect] [-D|--dbs] [-S|--strats] [-H|--serverhelp] [-i|--info dbname] [-I|--serverinfo] [-T|--status] [-b|--debug] [-u|--user user] [-k|--key key] [-v|--verbose] word1 [word2 ... wordn] rdict [--help|-v|--version] = DESCRIPTION rdict is an (()) compliant Dictionary Server Protocol (DICT) client that provides access to dictionary definitions from a set of natural language dictionary databases. = OPTIONS : -h ((*server*)) or --host ((*server*)) Specifies the hostname for the DICT server. If no servers are specified, the default behavior is to try dict.org, followed by alt0.dict.org. : -p ((*port*)) or --port ((*port*)) Specifies the port (e.g. 2628) or service (e.g. dict) for connections. The default is 2628, as specified in the DICT Protocol RFC. : -d ((*dbname*)) or --database ((*dbname*)) Specifies a specific database to search. The default is to search all databases (a '*' from the DICT protocol). Note that a '!' in the DICT protocol means to search all of the databases until a match is found, and then stop searching. : -m or --match Instead of printing a definition, perform a match using the specified strategy. : -s ((*strategy*)) or --strategy ((*strategy*)) Specify a matching strategy. By default, the server default match strategy is used. This is usually 'exact' for definitions, and a server-defined optimal spelling correction strategy for matches ('.' from the DICT protocol). The available strategies are dependent on the server implementation. For a list of available strategies, see the -S or --strats option. : -C or --nocorrect Usually, if a definition is requested and the word cannot be found, spelling correction is requested from the server, and a list of possible words are provided. This option disables the generation of this list. : -D or --dbs Query the server and display a list of available databases. : -S or --strats Query the server and display a list of available search strategies. : -H or --serverhelp Query the server and display the help information that it provides. : -i ((*dbname*)) or --info ((*dbname*)) Request information on the specified database (usually the server will provide origination, descriptive or other information about the database or its contents). : -I or --serverinfo Query the server and display information about the server. : -T or --status Query the server for status information. : -u ((*user*)) or --user ((*user*)) Specifies the username for authentication. : -k ((*key*)) or --key ((*key*)) Specifies the shared secret for authentication. : -V or --version Display version information. : --help Display help information. : -v or --verbose Be verbose. : -b or --debug Display debugging information. This is long-winded, as the entire protocol exchange will be dumped. = EXAMPLES : $ rdict -D This will provide you with a list of databases you can query. : $ rdict -S This will provide you with a list of strategies you can employ to match words. : $ rdict -m -s prefix foo This shows you a list of all words that begin with 'foo' in all of the databases. : $ rdict -s re '^(cu|ke)rb$' This shows you all the definitions relating to both 'curb' and 'kerb' from all the databases. The 're' strategy allows regular expression matching. : $ rdict -m -s suffix fix This shows a list of all words that end in 'fix' in all of the databases. : $ rdict -d jargon -m -s prefix '' This displays a list of all the entries in the 'jargon' database. = AUTHOR Written by Ian Macdonald = COPYRIGHT Copyright (C) 2002-2007 Ian Macdonald This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. = SEE ALSO * ((<"Ruby/DICT home page - http://www.caliban.org/ruby/"|URL:http://www.caliban.org/ruby/>)) * (()) * ((<"The DICT development group - http://www.dict.org/"|URL:http://www.dict.org/>)) * ((<"RFC 2229 - ftp://ftp.isi.edu/in-notes/rfc2229.txt"|URL:ftp://ftp.isi.edu/in-notes/rfc2229.txt>)) = BUGS * MIME is not implemented * command pipelining is not implemented * URL parsing is not implemented =end require 'dict' require 'getoptlong' DEFAULT_SERVERS = %w(dict.org alt0.dict.org) DEFAULT_STRATEGY = 'exact' PROGRAM_NAME = File::basename($0) PROGRAM_VERSION = '0.9.4' class Optlist attr_reader :correct, :database, :dbs, :debug, :host, :key, :info, :match, :port, :serverhelp, :serverinfo, :status, :strategy, :strats, :user, :match_strategy, :verbose def initialize begin @correct = true @database = DICT::ALL_DATABASES @dbs = false @debug = false @host = DEFAULT_SERVERS @key = nil @info = nil @match = false @match_strategy = DICT::DEFAULT_MATCH_STRATEGY @port = DICT::DEFAULT_PORT @serverhelp = false @serverinfo = false @status = false @strategy = DEFAULT_STRATEGY @strats = false @user = nil @verbose = false opt = GetoptLong.new( [ '--debug', '-b', GetoptLong::NO_ARGUMENT ], [ '--database', '-d', GetoptLong::REQUIRED_ARGUMENT ], [ '--dbs', '-D', GetoptLong::NO_ARGUMENT ], [ '--help', GetoptLong::NO_ARGUMENT ], [ '--host', '-h', GetoptLong::REQUIRED_ARGUMENT ], [ '--info', '-i', GetoptLong::REQUIRED_ARGUMENT ], [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT ], [ '--match', '-m', GetoptLong::NO_ARGUMENT ], [ '--nocorrect', '-C', GetoptLong::NO_ARGUMENT ], [ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ], [ '--serverhelp', '-H', GetoptLong::NO_ARGUMENT ], [ '--serverinfo', '-I', GetoptLong::NO_ARGUMENT ], [ '--status', '-T', GetoptLong::NO_ARGUMENT ], [ '--strategy', '-s', GetoptLong::REQUIRED_ARGUMENT ], [ '--strats', '-S', GetoptLong::NO_ARGUMENT ], [ '--user', '-u', GetoptLong::REQUIRED_ARGUMENT ], [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], [ '--version', '-V', GetoptLong::NO_ARGUMENT ] ) opt.each_option do |name, arg| case name when '--debug' then @debug = true when '--database' then @database = arg when '--dbs' then @dbs = true when '--help' then usage when '--host' then @host = arg when '--key' then @key = arg when '--info' then @info = arg when '--match' then @match = true when '--nocorrect' then @correct = false when '--port' then @port = arg when '--serverhelp' then @serverhelp = true when '--serverinfo' then @serverinfo = true when '--status' then @status = true when '--strategy' then @strategy = arg; @match_strategy = arg when '--strats' then @strats = true when '--user' then @user = arg when '--verbose' then @verbose = true when '--version' then version end end rescue GetoptLong::InvalidOption, GetoptLong::MissingArgument usage(1) end end end # display usage message and exit # def usage(code = 0) $stderr.puts < This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, to the extent permitted by law. EOF exit end # tabulate databases and strategies # def tabulate(data_type, data) printf("%d %s available:\n", data.size, data_type) data.each { |k, v| printf(" %-11s%s\n", k, v) } end # show matches # def show_matches(match, header = nil) puts header if header yield if block_given? screen_width = 80 match_separator = ' ' match.each do |db, definitions| # quote multi-word matches definitions.each { |definition| definition.sub!(/^(.* .*)$/, "\"\\1\"") } line = "%s: %s\n" % [ db, definitions.join(match_separator) ] # wrap display of definitions at screen_width characters while line.length > screen_width - match_separator.length cutoff = line[0 .. screen_width - 1].rindex(match_separator) puts line[0 .. cutoff - 1] print match_separator line = line[cutoff + 2, line.length - cutoff - 1] end puts line end end # show definitions # def show_definitions(definitions, header = nil) puts header if header definitions.each do |d| printf("\nFrom %s [%s]:\n\n", d.description, d.database) d.definition.each { |line| print ' ', line } end end # take care of pluralising nouns # def pluralise(ending, count) count == 1 ? '' : ending end # check for status code errors # def check_code(code, message) case code when DICT::INVALID_DATABASE die(code, 'Invalid database. Use -D|--dbs for a list.') when DICT::ILLEGAL_PARAMETERS die(code, message) when DICT::INVALID_STRATEGY die(code, "Invalid search strategy. Use -S|--strats for a list.") when DICT::AUTH_DENIED $stderr.puts "Authentication denied" end end # raise an exception and exit # def die(code, message) raise ProtocolError, "(%s) %s" % [ code, message ] end # start of main program option = Optlist.new # connect dict = DICT.new(option.host, option.port, option.debug, option.verbose) # send client info dict.client("%s v%s" % [ PROGRAM_NAME, PROGRAM_VERSION ]) # authorise if possible, but don't die on failure dict.auth(option.user, option.key) if option.user && option.key check_code(dict.code, dict.message) # informational options if option.info resp = dict.show_info(option.info) puts resp unless resp.nil? check_code(dict.code, dict.message) end if option.serverinfo resp = dict.show_server puts resp unless resp.nil? check_code(dict.code, dict.message) end if option.dbs unless (resp = dict.show_db).nil? check_code(dict.code, dict.message) tabulate("databases", resp) end end if option.strats unless (resp = dict.show_strat).nil? check_code(dict.code, dict.message) tabulate("strategies", resp) end end # status option puts dict.status if option.status # server-side help option puts "Server help:", dict.help if option.serverhelp exit if ARGV.empty? && option.dbs || option.strats || option.info || option.serverinfo || option.status || option.serverhelp usage if ARGV.empty? ARGV.each do |word| if option.match # perform match match = dict.match(option.database, option.match_strategy, word) check_code(dict.code, dict.message) printf(%Q(No matches found for "%s"\n), word) unless match if match show_matches(match) do count = 0 match.each_value { |definitions| count += definitions.size } printf(%Q(\n%d definition%s for "%s" found in %d database%s\n\n), count, pluralise('s', count), word, match.size, pluralise('s', match.size)) end end next end # check for non-default matching strategy if option.strategy != DEFAULT_STRATEGY # match to get list of words match = dict.match(option.database, option.match_strategy, word) check_code(dict.code, dict.message) if match match.each do |db, words| # iterate over databases words.each do |w| # iterate over words definitions = dict.define(db, w) show_definitions(definitions) if definitions end end end next end # look up definitions definitions = dict.define(option.database, word) check_code(dict.code, dict.message) if definitions show_definitions(definitions, "%d definition%s found" % [ definitions.size, pluralise('s', definitions.size) ]) else printf('No definitions found for "%s"', word) if option.correct # perform correction match = dict.match(option.database, DICT::DEFAULT_MATCH_STRATEGY, word) show_matches(match, ', perhaps you mean:') if match puts unless match else puts end end end dict.disconnect