4 # :title: DICT (RFC 2229) Protocol Client Plugin for rbot
6 # Author:: Yaohan Chen <yaohan.chen@gmail.com>
7 # Copyright:: (C) 2007 Yaohan Chen
10 # Looks up words on a DICT server. DEFINE and MATCH commands, as well as listing of
11 # databases and strategies are supported.
14 # Improve output format
17 # requires Ruby/DICT <http://www.caliban.org/ruby/ruby-dict.shtml>
21 # Returns a new string truncated to length 'to'
22 # If ellipsis is not given, that will just be the first n characters,
23 # Else it will return a string in the form <head><ellipsis><tail>
24 # The total length of that string will not exceed 'to'.
25 # If tail is an Integer, the tail will be exactly 'tail' characters,
26 # if it is a Float/Rational tails length will be (to*tail).ceil.
28 # Contributed by apeiros
29 def truncate(to=32, ellipsis='…', tail=0.3)
31 return str.first(to).join('') if !ellipsis or str.length <= to
32 to -= ellipsis.split(//).length
33 tail = (tail*to).ceil unless Integer === tail
35 "#{str.first(to)}#{ellipsis}#{str.last(tail)}"
45 definition[1..-1].join.gsub(/\s+/, ' ').strip
49 class DictClientPlugin < Plugin
50 BotConfig.register BotConfigStringValue.new('dictclient.server',
51 :default => 'dict.org',
52 :desc => _('Hostname or hostname:port of the DICT server used to lookup words'))
53 BotConfig.register BotConfigIntegerValue.new('dictclient.max_defs_before_collapse',
55 :desc => _('When multiple databases reply a number of definitions that above this limit, only the database names will be listed. Otherwise, the full definitions from each database are replied'))
56 BotConfig.register BotConfigIntegerValue.new('dictclient.max_length_per_def',
58 :desc => _('Each definition is truncated to this length'))
59 BotConfig.register BotConfigStringValue.new('dictclient.headword_format',
60 :default => "#{Bold}<headword>#{Bold}",
61 :desc => _('Format of headwords; <word> will be replaced with the actual word'))
62 BotConfig.register BotConfigStringValue.new('dictclient.database_format',
63 :default => "#{Underline}<database>#{Underline}",
64 :desc => _('Format of database names; <database> will be replaced with the database name'))
65 BotConfig.register BotConfigStringValue.new('dictclient.definition_format',
66 :default => '<headword>: <definition> -<database>',
67 :desc => _('Format of definitions. <word> will be replaced with the formatted headword, <def> will be replaced with the truncated definition, and <database> with the formatted database name'))
68 BotConfig.register BotConfigStringValue.new('dictclient.match_format',
69 :default => '<matches>––<database>',
70 :desc => _('Format of match results. <matches> will be replaced with the formatted headwords, <database> with the formatted database name'))
76 # create a DICT object, which is passed to the block. after the block finishes,
77 # the DICT object is automatically disconnected. the return value of the block
78 # is returned from this method.
79 # if an IRC message argument is passed, the error message will be replied
80 def with_dict(m=nil &block)
81 server, port = @bot.config['dictclient.server'].split ':' if @bot.config['dictclient.server']
83 port ||= DICT::DEFAULT_PORT
86 dict = DICT.new(server, port)
90 m.reply _('An error occured connecting to the DICT server. Check the dictclient.server configuration or retry later') if m
92 m.reply _('A protocol error occured') if m
94 m.reply _('An error occured') if m
99 def format_headword(w)
100 @bot.config['dictclient.headword_format'].gsub '<headword>', w
103 def format_database(d)
104 @bot.config['dictclient.database_format'].gsub '<database>', d
107 def cmd_define(m, params)
108 phrase = params[:phrase].to_s
109 results = with_dict(m) {|d| d.define(params[:database], params[:phrase])}
112 # only list database headers if definitions come from different databases and
113 # the number of definitions is above dictclient.max_defs_before_collapse
114 if results.any? {|r| r.database != results[0].database} &&
115 results.length > @bot.config['dictclient.max_defs_before_collapse']
116 _("Many definitions for %{phrase} were found in %{databases}. Use 'define <phrase> from <database> to view a definition.") %
117 { :phrase => format_headword(phrase),
118 :databases => results.collect {|r| r.database}.uniq.
119 collect {|d| format_database d}.join(', ') }
120 # otherwise display the definitions
123 @bot.config['dictclient.definition_format'].gsub(
124 '<headword>', format_headword(r.headword)
126 '<database>', format_database(r.database)
128 '<definition>', r.body.truncate(@bot.config['dictclient.max_length_per_def'])
133 _("No definition for %{phrase} found from %{database}.") %
134 { :phrase => format_headword(phrase),
135 :database => format_database(params[:database]) }
140 def cmd_match(m, params)
141 phrase = params[:phrase].to_s
142 results = with_dict(m) {|d| d.match(params[:database],
143 params[:strategy], phrase)}
146 results.collect {|database, matches|
147 @bot.config['dictclient.match_format'].gsub(
148 '<matches>', matches.collect {|m| format_headword m}.join(', ')
150 '<database>', format_database(database)
154 _("Nothing matched %{query} from %{database} using %{strategy}") %
155 { :query => format_headword(phrase),
156 :database => format_database(params[:database]),
157 :strategy => params[:strategy] }
162 def cmd_databases(m, params)
164 m.reply _("Databases: %{list}") % {
165 :list => d.show_db.collect {|db, des| "#{format_database db}: #{des}"}.join('; ')
170 def cmd_strategies(m, params)
172 m.reply _("Strategies: %{list}") % {
173 :list => d.show_strat.collect {|s, des| "#{s}: #{des}"}.join('; ')
178 def help(plugin, topic='')
179 _("define <phrase> [from <database>] => Show definition of a phrase; match <phrase> [using <strategy>] [from <database>] => Show matching phrases; dictclient databases => List databases; dictclient strategies => List strategies")
183 plugin = DictClientPlugin.new
185 plugin.map 'define *phrase [from :database]',
186 :action => 'cmd_define',
187 :defaults => {:database => DICT::ALL_DATABASES}
189 plugin.map 'match *phrase [using :strategy] [from :database]',
190 :action => 'cmd_match',
191 :defaults => {:database => DICT::ALL_DATABASES,
192 :strategy => DICT::DEFAULT_MATCH_STRATEGY }
194 plugin.map 'dictclient databases', :action => 'cmd_databases'
195 plugin.map 'dictclient strategies', :action => 'cmd_strategies'