root/data/rbot/plugins/dictclient.rb

Revision 783ffa4235330029d661752b1023db635b26f2b3, 8.0 KB (checked in by Raine Virta <rane@…>, 19 months ago)

remove whitespace

  • Property mode set to 100644
Line 
1#-- vim:sw=2:et
2#++
3#
4# :title: DICT (RFC 2229) Protocol Client Plugin for rbot
5#
6# Author:: Yaohan Chen <yaohan.chen@gmail.com>
7# Copyright:: (C) 2007 Yaohan Chen
8# License:: GPL v2
9#
10# Looks up words on a DICT server. DEFINE and MATCH commands, as well as listing of
11# databases and strategies are supported.
12#
13# TODO
14# Improve output format
15
16
17# requires Ruby/DICT <http://www.caliban.org/ruby/ruby-dict.shtml>
18begin
19  require 'dict'
20rescue LoadError
21  raise LoadError, "Ruby/DICT not found, grab it from http://www.caliban.org/ruby/ruby-dict.shtml"
22end
23
24class ::String
25  # Returns a new string truncated to length 'to'
26  # If ellipsis is not given, that will just be the first n characters,
27  # Else it will return a string in the form <head><ellipsis><tail>
28  # The total length of that string will not exceed 'to'.
29  # If tail is an Integer, the tail will be exactly 'tail' characters,
30  # if it is a Float/Rational tails length will be (to*tail).ceil.
31  #
32  # Contributed by apeiros
33  def truncate(to=32, ellipsis='…', tail=0.3)
34    str  = split(//)
35    return str.first(to).join('') if !ellipsis or str.length <= to
36    to  -= ellipsis.split(//).length
37    tail = (tail*to).ceil unless Integer === tail
38    to  -= tail
39    "#{str.first(to)}#{ellipsis}#{str.last(tail)}"
40  end
41end
42
43class ::Definition
44  def headword
45    definition[0].strip
46  end
47
48  def body
49    # two or more consecutive newlines are replaced with double spaces, while single
50    # newlines are replaced with single spaces
51    lb = /\r?\n/
52    definition[1..-1].join.
53      gsub(/\s*(:#{lb}){2,}\s*/, '  ').
54      gsub(/\s*#{lb}\s*/, ' ').strip
55  end
56end
57
58class DictClientPlugin < Plugin
59  Config.register Config::StringValue.new('dictclient.server',
60    :default => 'dict.org',
61    :desc => _('Hostname or hostname:port of the DICT server used to lookup words'))
62  Config.register Config::IntegerValue.new('dictclient.max_defs_before_collapse',
63    :default => 4,
64    :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'))
65  Config.register Config::IntegerValue.new('dictclient.max_length_per_def',
66    :default => 200,
67    :desc => _('Each definition is truncated to this length'))
68  Config.register Config::StringValue.new('dictclient.headword_format',
69    :default => "#{Bold}<headword>#{Bold}",
70    :desc => _('Format of headwords; <word> will be replaced with the actual word'))
71  Config.register Config::StringValue.new('dictclient.database_format',
72    :default => "#{Underline}<database>#{Underline}",
73    :desc => _('Format of database names; <database> will be replaced with the database name'))
74  Config.register Config::StringValue.new('dictclient.definition_format',
75    :default => '<headword>: <definition> -<database>',
76    :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'))
77  Config.register Config::StringValue.new('dictclient.match_format',
78    :default => '<matches>––<database>',
79    :desc => _('Format of match results. <matches> will be replaced with the formatted headwords, <database> with the formatted database name'))
80
81  def initialize
82    super
83  end
84
85  # create a DICT object, which is passed to the block. after the block finishes,
86  # the DICT object is automatically disconnected. the return value of the block
87  # is returned from this method.
88  # if an IRC message argument is passed, the error message will be replied
89  def with_dict(m=nil &block)
90    server, port = @bot.config['dictclient.server'].split ':' if @bot.config['dictclient.server']
91    server ||= 'dict.org'
92    port ||= DICT::DEFAULT_PORT
93    ret = nil
94    begin
95      dict = DICT.new(server, port)
96      ret = yield dict
97      dict.disconnect
98    rescue ConnectError
99      m.reply _('An error occured connecting to the DICT server. Check the dictclient.server configuration or retry later') if m
100    rescue ProtocolError
101      m.reply _('A protocol error occured') if m
102    rescue DICTError
103      m.reply _('An error occured') if m
104    end
105    ret
106  end
107
108  def format_headword(w)
109    @bot.config['dictclient.headword_format'].gsub '<headword>', w
110  end
111
112  def format_database(d)
113    @bot.config['dictclient.database_format'].gsub '<database>', d
114  end
115
116  def cmd_define(m, params)
117    phrase = params[:phrase].to_s
118    results = with_dict(m) {|d| d.define(params[:database], params[:phrase])}
119    m.reply(
120      if results
121        # only list database headers if definitions come from different databases and
122        # the number of definitions is above dictclient.max_defs_before_collapse
123        if results.any? {|r| r.database != results[0].database} &&
124           results.length > @bot.config['dictclient.max_defs_before_collapse']
125          _("Many definitions for %{phrase} were found in %{databases}. Use 'define <phrase> from <database> to view a definition.") %
126          { :phrase => format_headword(phrase),
127            :databases => results.collect {|r| r.database}.uniq.
128                                  collect {|d| format_database d}.join(', ') }
129        # otherwise display the definitions
130        else
131          results.collect {|r|
132            @bot.config['dictclient.definition_format'].gsub(
133              '<headword>', format_headword(r.headword)
134            ).gsub(
135              '<database>', format_database(r.database)
136            ).gsub(
137              '<definition>', r.body.truncate(@bot.config['dictclient.max_length_per_def'])
138            )
139          }.join ' | '
140        end
141      else
142        _("No definition for %{phrase} found from %{database}.") %
143          { :phrase => format_headword(phrase),
144            :database => format_database(params[:database]) }
145      end
146    )
147  end
148
149  def cmd_match(m, params)
150    phrase = params[:phrase].to_s
151    results = with_dict(m) {|d| d.match(params[:database],
152                                        params[:strategy], phrase)}
153    m.reply(
154      if results
155        results.collect {|database, matches|
156          @bot.config['dictclient.match_format'].gsub(
157            '<matches>', matches.collect {|m| format_headword m}.join(', ')
158          ).gsub(
159            '<database>', format_database(database)
160          )
161        }.join ' '
162      else
163        _("Nothing matched %{query} from %{database} using %{strategy}") %
164        { :query => format_headword(phrase),
165          :database => format_database(params[:database]),
166          :strategy => params[:strategy] }
167      end
168    )
169  end
170
171  def cmd_databases(m, params)
172    with_dict(m) do |d|
173      m.reply _("Databases: %{list}") % {
174        :list => d.show_db.collect {|db, des| "#{format_database db}: #{des}"}.join(' | ')
175      }
176    end
177  end
178
179  def cmd_strategies(m, params)
180    with_dict(m) do |d|
181      m.reply _("Strategies: %{list}") % {
182        :list => d.show_strat.collect {|s, des| "#{s}: #{des}"}.join(' | ')
183      }
184    end
185  end
186
187  def help(plugin, topic='')
188    case topic
189    when 'define'
190      _('define <phrase> [from <database>] => Show definition of a phrase')
191    when 'match'
192      _('match <phrase> [using <strategy>] [from <database>] => Show phrases matching the given pattern')
193    when 'server information'
194      _('dictclient databases => List databases; dictclient strategies => List strategies')
195    else
196      _('look up phrases on the configured DICT server. topics: define, match, server information')
197    end
198  end
199end
200
201plugin = DictClientPlugin.new
202
203plugin.map 'define *phrase [from :database]',
204           :action => 'cmd_define',
205           :defaults => {:database => DICT::ALL_DATABASES},
206           :threaded => true
207
208plugin.map 'match *phrase [using :strategy] [from :database]',
209           :action => 'cmd_match',
210           :defaults => {:database => DICT::ALL_DATABASES,
211                         :strategy => DICT::DEFAULT_MATCH_STRATEGY },
212           :threaded => true
213
214plugin.map 'dictclient databases', :action => 'cmd_databases', :thread => true
215plugin.map 'dictclient strategies', :action => 'cmd_strategies', :thread => true
Note: See TracBrowser for help on using the browser.