ponder 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,14 +3,12 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
3
  require 'ponder'
4
4
 
5
5
  # This Thaum will parrot all channel messages.
6
- @ponder = Ponder::Thaum.new
7
-
8
- @ponder.configure do |c|
9
- c.server = 'chat.freenode.org'
10
- c.port = 6667
11
- c.nick = 'Ponder'
12
- c.verbose = true
13
- c.logging = false
6
+ @ponder = Ponder::Thaum.new do |t|
7
+ t.server = 'chat.freenode.org'
8
+ t.port = 6667
9
+ t.nick = 'Ponder'
10
+ t.verbose = true
11
+ t.logging = false
14
12
  end
15
13
 
16
14
  @ponder.on :connect do
@@ -32,14 +32,12 @@ def remember(lowercase_nick, nick, user, host, channel, action,
32
32
  'updated_at', Time.now.to_i)
33
33
  end
34
34
 
35
- @ponder = Ponder::Thaum.new
36
-
37
- @ponder.configure do |c|
38
- c.server = 'chat.freenode.org'
39
- c.port = 6667
40
- c.nick = 'Ponder'
41
- c.verbose = true
42
- c.logging = false
35
+ @ponder = Ponder::Thaum.new do |t|
36
+ t.server = 'chat.freenode.org'
37
+ t.port = 6667
38
+ t.nick = 'Ponder'
39
+ t.verbose = true
40
+ t.logging = false
43
41
  end
44
42
 
45
43
  @ponder.on :connect do
@@ -124,16 +122,24 @@ end
124
122
  @ponder.message event_data[:channel], "Too many results (#{results})."
125
123
  end
126
124
  # single search
127
- elsif @redis.exists nick
128
- msg = last_seen(nick)
129
- if online_nick = @ponder.whois(nick)
130
- msg = "#{online_nick[:nick]} is online. (#{msg})"
131
- end
132
- @ponder.message event_data[:channel], msg
133
- elsif online_nick = @ponder.whois(nick)
134
- @ponder.message event_data[:channel], "#{online_nick[:nick]} is online."
135
125
  else
136
- @ponder.message event_data[:channel], "#{nick} not found."
126
+ @ponder.whois(nick).callback do |result|
127
+ message = if @redis.exists nick
128
+ if result
129
+ "#{result[:nick]} is online. (#{last_seen(nick)})"
130
+ else
131
+ last_seen(nick)
132
+ end
133
+ else
134
+ if result
135
+ "#{result[:nick]} is online."
136
+ else
137
+ "#{nick} not found."
138
+ end
139
+ end
140
+
141
+ @ponder.message event_data[:channel], message
142
+ end
137
143
  end
138
144
  end
139
145
 
@@ -0,0 +1,9 @@
1
+ class Array
2
+ def extract_options!
3
+ if last.is_a?(Hash)
4
+ pop
5
+ else
6
+ {}
7
+ end
8
+ end
9
+ end
@@ -3,9 +3,18 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
3
  module Ponder
4
4
  ROOT = File.dirname($0)
5
5
 
6
- autoload :Filter, 'ponder/filter'
6
+ autoload :AsyncIRC, 'ponder/async_irc'
7
+ autoload :Callback, 'ponder/callback'
8
+ autoload :Connection, 'ponder/connection'
9
+ autoload :Filter, 'ponder/filter'
7
10
  autoload :Formatting, 'ponder/formatting'
8
- autoload :Thaum, 'ponder/thaum'
9
- autoload :VERSION, 'ponder/version'
11
+ autoload :IRC, 'ponder/irc'
12
+ autoload :Thaum, 'ponder/thaum'
13
+ autoload :VERSION, 'ponder/version'
14
+
15
+ module Logger
16
+ autoload :Twoflogger, 'ponder/logger/twoflogger'
17
+ autoload :BlindIo, 'ponder/logger/blind_io'
18
+ end
10
19
  end
11
20
 
@@ -1,120 +1,151 @@
1
1
  require 'thread'
2
2
  require 'timeout'
3
+ require 'eventmachine'
3
4
 
4
5
  module Ponder
5
6
  module AsyncIRC
6
- TIMEOUT = 30
7
+ class Topic
8
+ # number of seconds the deferrable will wait for a response before failing
9
+ TIMEOUT = 15
7
10
 
8
- def get_topic(channel)
9
- queue = Queue.new
10
- @observer_queues[queue] = [/:\S+ (331|332|403|442) \S+ #{Regexp.escape(channel)} :/i]
11
- raw "TOPIC #{channel}"
11
+ include EventMachine::Deferrable
12
12
 
13
- topic = begin
14
- Timeout::timeout(TIMEOUT) do
15
- response = queue.pop
16
- raw_numeric = response.scan(/^:\S+ (\d{3})/)[0][0]
13
+ def initialize(channel, timeout_after, thaum)
14
+ @channel = channel
15
+ @thaum = thaum
17
16
 
18
- case raw_numeric
17
+ self.timeout(timeout_after)
18
+ self.errback { @thaum.deferrables.delete self }
19
+
20
+ @thaum.deferrables.add self
21
+ @thaum.raw "TOPIC #{@channel}"
22
+ end
23
+
24
+ def try(message)
25
+ if message =~ /:\S+ (331|332|403|442) \S+ #{Regexp.escape(@channel)} :/i
26
+ case $1
19
27
  when '331'
20
- {:raw_numeric => 331, :message => 'No topic is set'}
28
+ succeed({:raw_numeric => 331, :message => 'No topic is set'})
21
29
  when '332'
22
- {:raw_numeric => 332, :message => response.scan(/ :(.*)/)[0][0]}
30
+ succeed({:raw_numeric => 332, :message => message.scan(/ :(.*)/)[0][0]})
23
31
  when '403'
24
- {:raw_numeric => 403, :message => 'No such channel'}
32
+ succeed({:raw_numeric => 403, :message => 'No such channel'})
25
33
  when '442'
26
- {:raw_numeric => 442, :message => "You're not on that channel"}
34
+ succeed({:raw_numeric => 442, :message => "You're not on that channel"})
27
35
  end
28
36
  end
29
- rescue Timeout::Error
30
- false
31
37
  end
32
38
 
33
- @observer_queues.delete queue
34
- return topic
39
+ def succeed(*args)
40
+ @thaum.deferrables.delete self
41
+ set_deferred_status :succeeded, *args
42
+ end
35
43
  end
36
44
 
37
- def channel_info(channel)
38
- queue = Queue.new
39
- @observer_queues[queue] = [/:\S+ (324|329|403|442) \S+ #{Regexp.escape(channel)}/i]
40
- raw "MODE #{channel}"
41
- information = {}
42
- running = true
43
-
44
- begin
45
- Timeout::timeout(TIMEOUT) do
46
- while running
47
- response = queue.pop
48
- raw_numeric = response.scan(/^:\S+ (\d{3})/)[0][0]
49
-
50
- case raw_numeric
51
- when '324'
52
- information[:modes] = response.scan(/^:\S+ 324 \S+ \S+ \+(\w*)/)[0][0].split('')
53
- limit = response.scan(/^:\S+ 324 \S+ \S+ \+\w* (\w*)/)[0]
54
- information[:channel_limit] = limit[0].to_i if limit
55
- when '329'
56
- information[:created_at] = Time.at(response.scan(/^:\S+ 329 \S+ \S+ (\d+)/)[0][0].to_i)
57
- running = false
58
- when '403', '442'
59
- information = false
60
- running = false
45
+ class Whois
46
+ # number of seconds the deferrable will wait for a response before failing
47
+ TIMEOUT = 15
48
+
49
+ include EventMachine::Deferrable
50
+
51
+ def initialize(nick, timeout_after, thaum)
52
+ @nick = nick
53
+ @thaum = thaum
54
+ @whois_data = {}
55
+
56
+ self.timeout(timeout_after)
57
+ self.errback { @thaum.deferrables.delete self }
58
+
59
+ @thaum.deferrables.add self
60
+ @thaum.raw "WHOIS #{@nick}"
61
+ end
62
+
63
+ def try(message)
64
+ if message =~ /^:\S+ (307|311|312|318|319|330|401) \S+ #{Regexp.escape(@nick)}/i
65
+ case $1
66
+ when '307', '330'
67
+ @whois_data[:registered] = true
68
+ when '311'
69
+ message = message.scan(/^:\S+ 311 \S+ (\S+) :?(\S+) (\S+) \* :(.*)$/)[0]
70
+ @whois_data[:nick] = message[0]
71
+ @whois_data[:username] = message[1]
72
+ @whois_data[:host] = message[2]
73
+ @whois_data[:real_name] = message[3]
74
+ when '312'
75
+ message = message.scan(/^:\S+ 312 \S+ \S+ (\S+) :(.*)/)[0]
76
+ @whois_data[:server] = {:address => message[0], :name => message[1]}
77
+ when '318'
78
+ succeed @whois_data
79
+ when '319'
80
+ channels_with_mode = message.scan(/^:\S+ 319 \S+ \S+ :(.*)/)[0][0].split(' ')
81
+ @whois_data[:channels] = {}
82
+ channels_with_mode.each do |c|
83
+ @whois_data[:channels][c.scan(/(.)?(#\S+)/)[0][1]] = c.scan(/(.)?(#\S+)/)[0][0]
61
84
  end
85
+ when '401'
86
+ succeed false
62
87
  end
63
88
  end
64
- rescue Timeout::Error
65
- information = false
66
89
  end
67
90
 
68
- @observer_queues.delete queue
69
- return information
91
+ def succeed(*args)
92
+ @thaum.deferrables.delete self
93
+ set_deferred_status :succeeded, *args
94
+ end
70
95
  end
71
96
 
72
- def whois(nick)
73
- queue = Queue.new
74
- @observer_queues[queue] = [/^:\S+ (307|311|312|318|319|330|401) \S+ #{Regexp.escape(nick)}/i]
75
- raw "WHOIS #{nick}"
76
- whois = {}
77
- running = true
78
-
79
- begin
80
- Timeout::timeout(TIMEOUT) do
81
- while running
82
- response = queue.pop
83
- raw_numeric = response.scan(/^:\S+ (\d{3})/)[0][0]
84
-
85
- case raw_numeric
86
- when '307', '330'
87
- whois[:registered] = true
88
- when '311'
89
- response = response.scan(/^:\S+ 311 \S+ (\S+) :?(\S+) (\S+) \* :(.*)$/)[0]
90
- whois[:nick] = response[0]
91
- whois[:username] = response[1]
92
- whois[:host] = response[2]
93
- whois[:real_name] = response[3]
94
- when '312'
95
- response = response.scan(/^:\S+ 312 \S+ \S+ (\S+) :(.*)/)[0]
96
- whois[:server] = {:address => response[0], :name => response[1]}
97
- when '318'
98
- running = false
99
- when '319'
100
- channels_with_mode = response.scan(/^:\S+ 319 \S+ \S+ :(.*)/)[0][0].split(' ')
101
- whois[:channels] = {}
102
- channels_with_mode.each do |c|
103
- whois[:channels][c.scan(/(.)?(#\S+)/)[0][1]] = c.scan(/(.)?(#\S+)/)[0][0]
104
- end
105
- when '401'
106
- whois = false
107
- running = false
108
- end
97
+ class Channel
98
+ # number of seconds the deferrable will wait for a response before failing
99
+ TIMEOUT = 15
100
+
101
+ include EventMachine::Deferrable
102
+
103
+ def initialize(channel, timeout_after, thaum)
104
+ @channel = channel
105
+ @thaum = thaum
106
+ @channel_information = {}
107
+
108
+ self.timeout(timeout_after)
109
+ self.errback { @ponder.deferrables.delete self }
110
+
111
+ @thaum.deferrables.add self
112
+ @thaum.raw "MODE #{@channel}"
113
+ end
114
+
115
+ def try(message)
116
+ if message =~ /:\S+ (324|329|403|442) \S+ #{Regexp.escape(@channel)}/i
117
+ case $1
118
+ when '324'
119
+ @channel_information[:modes] = message.scan(/^:\S+ 324 \S+ \S+ \+(\w*)/)[0][0].split('')
120
+ limit = message.scan(/^:\S+ 324 \S+ \S+ \+\w* (\w*)/)[0]
121
+ @channel_information[:channel_limit] = limit[0].to_i if limit
122
+ when '329'
123
+ @channel_information[:created_at] = Time.at(message.scan(/^:\S+ 329 \S+ \S+ (\d+)/)[0][0].to_i)
124
+ succeed @channel_information
125
+ when '403', '442'
126
+ succeed false
109
127
  end
110
128
  end
111
- rescue Timeout::Error
112
- nil
113
129
  end
114
130
 
115
- @observer_queues.delete queue
116
- return whois
131
+ def succeed(*args)
132
+ @thaum.deferrables.delete self
133
+ set_deferred_status :succeeded, *args
134
+ end
135
+ end
136
+
137
+ module Delegate
138
+ def get_topic(channel, timeout_after = AsyncIRC::Topic::TIMEOUT)
139
+ AsyncIRC::Topic.new(channel, timeout_after, self)
140
+ end
141
+
142
+ def whois(nick, timeout_after = AsyncIRC::Whois::TIMEOUT)
143
+ AsyncIRC::Whois.new(nick, timeout_after, self)
144
+ end
145
+
146
+ def channel_info(channel, timeout_after = AsyncIRC::Channel::TIMEOUT)
147
+ AsyncIRC::Channel.new(channel, timeout_after, self)
148
+ end
117
149
  end
118
150
  end
119
151
  end
120
-
@@ -1,16 +1,19 @@
1
1
  module Ponder
2
2
  class Callback
3
3
  LISTENED_TYPES = [:connect, :channel, :query, :join, :part, :quit, :nickchange, :kick, :topic, :disconnect] # + 3-digit numbers
4
-
5
- def initialize(event_type = :channel, match = //, proc = Proc.new {})
4
+
5
+ attr_reader :options
6
+
7
+ def initialize(event_type = :channel, match = //, options = {}, proc = Proc.new {})
6
8
  unless self.class::LISTENED_TYPES.include?(event_type) || event_type.is_a?(Integer)
7
9
  raise TypeError, "#{event_type} is an unsupported event-type"
8
10
  end
9
-
11
+
10
12
  self.match = match
11
13
  self.proc = proc
14
+ @options = options
12
15
  end
13
-
16
+
14
17
  def call(event_type, event_data = {})
15
18
  if (event_type == :channel) || (event_type == :query)
16
19
  @proc.call(event_data) if event_data[:message] =~ @match
@@ -20,9 +23,9 @@ module Ponder
20
23
  @proc.call(event_data)
21
24
  end
22
25
  end
23
-
26
+
24
27
  private
25
-
28
+
26
29
  def match=(match)
27
30
  if match.is_a?(Regexp)
28
31
  @match = match
@@ -30,7 +33,7 @@ module Ponder
30
33
  raise TypeError, "#{match} must be a Regexp"
31
34
  end
32
35
  end
33
-
36
+
34
37
  def proc=(proc)
35
38
  if proc.is_a?(Proc)
36
39
  @proc = proc
@@ -1,24 +1,26 @@
1
1
  module Ponder
2
- class BlindIo
3
- def debug(*args, &block)
4
- end
2
+ module Logger
3
+ class BlindIo
4
+ def debug(*args, &block)
5
+ end
5
6
 
6
- def info(*args, &block)
7
- end
7
+ def info(*args, &block)
8
+ end
8
9
 
9
- def warn(*args, &block)
10
- end
10
+ def warn(*args, &block)
11
+ end
11
12
 
12
- def error(*args, &block)
13
- end
13
+ def error(*args, &block)
14
+ end
14
15
 
15
- def fatal(*args, &block)
16
- end
16
+ def fatal(*args, &block)
17
+ end
17
18
 
18
- def unknown(*args, &block)
19
- end
19
+ def unknown(*args, &block)
20
+ end
20
21
 
21
- def method_missing(*args, &block)
22
+ def method_missing(*args, &block)
23
+ end
22
24
  end
23
25
  end
24
26
  end
@@ -1,11 +1,13 @@
1
1
  require 'logger'
2
2
 
3
3
  module Ponder
4
- class Twoflogger < Logger
5
- def initialize(*args)
6
- super(*args)
7
- self.formatter = proc do |severity, datetime, progname, msg|
8
- "#{severity} #{datetime.strftime('%Y-%m-%d %H:%M:%S')} #{msg}\n"
4
+ module Logger
5
+ class Twoflogger < ::Logger
6
+ def initialize(*args)
7
+ super(*args)
8
+ self.formatter = proc do |severity, datetime, progname, msg|
9
+ "#{severity} #{datetime.strftime('%Y-%m-%d %H:%M:%S')} #{msg}\n"
10
+ end
9
11
  end
10
12
  end
11
13
  end
@@ -1,21 +1,25 @@
1
+ require 'core_ext/array'
2
+ require 'fileutils'
3
+ require 'ostruct'
4
+ require 'set'
1
5
  require 'ponder/async_irc'
2
6
  require 'ponder/callback'
3
7
  require 'ponder/connection'
8
+ require 'ponder/filter'
4
9
  require 'ponder/irc'
5
10
  require 'ponder/logger/twoflogger'
6
11
  require 'ponder/logger/blind_io'
7
- require 'ostruct'
8
- autoload :FileUtils, 'fileutils'
9
12
 
10
13
  module Ponder
11
14
  class Thaum
12
15
  include IRC
13
- include AsyncIRC
16
+ include AsyncIRC::Delegate
14
17
 
15
18
  attr_reader :config, :callbacks
16
- attr_accessor :connected, :logger, :console_logger
19
+ attr_accessor :connected, :logger, :console_logger, :deferrables
17
20
 
18
- def initialize
21
+ def initialize(&block)
22
+ # default settings
19
23
  @config = OpenStruct.new(
20
24
  :server => 'localhost',
21
25
  :port => 6667,
@@ -28,13 +32,34 @@ module Ponder
28
32
  :reconnect_interval => 30
29
33
  )
30
34
 
31
- @logger = BlindIo.new
32
- @console_logger = Twoflogger.new($stdout)
35
+ # custom settings
36
+ block.call(@config) if block_given?
37
+
38
+ # setting up loggers
39
+ @console_logger = if @config.verbose
40
+ Logger::Twoflogger.new($stdout)
41
+ else
42
+ Logger::BlindIo.new
43
+ end
44
+
45
+ @logger = if @config.logging
46
+ if @config.logger
47
+ @config.logger
48
+ else
49
+ log_path = File.join(ROOT, 'logs', 'log.log')
50
+ log_dir = File.dirname(log_path)
51
+ FileUtils.mkdir_p(log_dir) unless File.exist?(log_dir)
52
+ Logger::Twoflogger.new(log_path, File::WRONLY | File::APPEND)
53
+ end
54
+ else
55
+ Logger::BlindIo.new
56
+ end
33
57
 
34
- @observer_queues = {}
58
+ # when using methods like #get_topic or #whois, a Deferrable object will wait
59
+ # for the response and call a callback. these Deferrables are stored in this Set
60
+ @deferrables = Set.new
35
61
 
36
62
  @connected = false
37
- @reloading = false
38
63
 
39
64
  # user callbacks
40
65
  @callbacks = Hash.new { |hash, key| hash[key] = [] }
@@ -58,36 +83,13 @@ module Ponder
58
83
  @after_filters = Hash.new { |hash, key| hash[key] = [] }
59
84
  end
60
85
 
61
- def configure(&block)
62
- unless @reloading
63
- block.call(@config)
64
-
65
- # logger changes (if differing from initialize)
66
- if @config.verbose
67
- @console_logger = @config.console_logger if @config.console_logger
68
- else
69
- @console_logger = BlindIo.new
70
- end
71
-
72
- if @config.logging
73
- log_path = @config.log_path || File.join(ROOT, 'logs', 'log.log')
74
- log_dir = File.dirname(log_path)
75
- FileUtils.mkdir_p(log_dir) unless File.exist?(log_dir)
86
+ def on(event_types = [:channel], match = //, *options, &block)
87
+ options = options.extract_options!
76
88
 
77
- if @config.logger
78
- @logger = @config.logger
79
- else
80
- @logger = Twoflogger.new(log_path, File::WRONLY | File::APPEND)
81
- end
82
- end
83
- end
84
- end
85
-
86
- def on(event_types = [:channel], match = //, &block)
87
89
  if event_types.is_a?(Array)
88
- callbacks = event_types.map { |event_type| Callback.new(event_type, match, block) }
90
+ callbacks = event_types.map { |event_type| Callback.new(event_type, match, options, block) }
89
91
  else
90
- callbacks = [Callback.new(event_types, match, block)]
92
+ callbacks = [Callback.new(event_types, match, options, block)]
91
93
  event_types = [event_types]
92
94
  end
93
95
 
@@ -97,27 +99,14 @@ module Ponder
97
99
  end
98
100
 
99
101
  def connect
100
- unless @reloading
101
- @logger.info '-- Starting Ponder'
102
- @console_logger.info '-- Starting Ponder'
102
+ @logger.info '-- Starting Ponder'
103
+ @console_logger.info '-- Starting Ponder'
103
104
 
104
- EventMachine::run do
105
- @connection = EventMachine::connect(@config.server, @config.port, Connection, self)
106
- end
105
+ EventMachine::run do
106
+ @connection = EventMachine::connect(@config.server, @config.port, Connection, self)
107
107
  end
108
108
  end
109
109
 
110
- def reload!
111
- @reloading = true
112
- @callbacks.clear
113
- load $0
114
- @reloading = false
115
- end
116
-
117
- def reloading?
118
- @reloading
119
- end
120
-
121
110
  # parsing incoming traffic
122
111
  def parse(message)
123
112
  message.chomp!
@@ -128,7 +117,7 @@ module Ponder
128
117
  when /^PING \S+$/
129
118
  raw message.sub(/PING/, 'PONG')
130
119
 
131
- when /^:\S+ (\d\d\d) /
120
+ when /^(?:\:\S+ )?(\d\d\d) /
132
121
  number = $1.to_i
133
122
  parse_event(number, :type => number, :params => $')
134
123
 
@@ -157,51 +146,52 @@ module Ponder
157
146
  parse_event(:topic, :type => :topic, :nick => $1, :user => $2, :host => $3, :channel => $4, :topic => $')
158
147
  end
159
148
 
160
- @observer_queues.each do |queue, regexps|
161
- regexps.each do |regexp|
162
- if message =~ regexp
163
- queue << message
164
- end
165
- end
166
- end
149
+ # if there are pending deferrabels, check if the message suits their matching pattern
150
+ @deferrables.each { |d| d.try(message) }
167
151
  end
168
152
 
169
153
  # process callbacks with its begin; rescue; end
170
154
  def process_callbacks(event_type, event_data)
171
155
  @callbacks[event_type].each do |callback|
172
- EM.defer(
173
- Proc.new do
174
- begin
175
- stop_running = false
176
-
177
- # before filters (specific filters first, then :all)
178
- (@before_filters[event_type] + @before_filters[:all]).each do |filter|
179
- if filter.call(event_type, event_data) == false
180
- stop_running = true
181
- break
182
- end
156
+ # process chain of before_filters, callback handling and after_filters
157
+ process = proc do
158
+ begin
159
+ stop_running = false
160
+
161
+ # before filters (specific filters first, then :all)
162
+ (@before_filters[event_type] + @before_filters[:all]).each do |filter|
163
+ if filter.call(event_type, event_data) == false
164
+ stop_running = true
165
+ break
183
166
  end
167
+ end
184
168
 
185
- unless stop_running
186
- # handling
187
- callback.call(event_type, event_data)
169
+ unless stop_running
170
+ # handling
171
+ callback.call(event_type, event_data)
188
172
 
189
- # after filters (specific filters first, then :all)
190
- (@after_filters[event_type] + @after_filters[:all]).each do |filter|
191
- filter.call(event_type, event_data)
192
- end
193
- end
194
- rescue => e
195
- [@logger, @console_logger].each do |logger|
196
- logger.error("-- #{e.class}: #{e.message}")
197
- e.backtrace.each { |line| logger.error("-- #{line}") }
173
+ # after filters (specific filters first, then :all)
174
+ (@after_filters[event_type] + @after_filters[:all]).each do |filter|
175
+ filter.call(event_type, event_data)
198
176
  end
199
177
  end
178
+ rescue => e
179
+ [@logger, @console_logger].each do |logger|
180
+ logger.error("-- #{e.class}: #{e.message}")
181
+ e.backtrace.each { |line| logger.error("-- #{line}") }
182
+ end
200
183
  end
201
- )
184
+ end
185
+
186
+ # defer the whole process
187
+ if callback.options[:defer]
188
+ EM.defer(process)
189
+ else
190
+ process.call
191
+ end
202
192
  end
203
193
  end
204
-
194
+
205
195
  def before_filter(event_types = :all, match = //, &block)
206
196
  filter(@before_filters, event_types, match, block)
207
197
  end
@@ -225,10 +215,10 @@ module Ponder
225
215
  def filter(filter_type, event_types = :all, match = //, block = Proc.new)
226
216
  if event_types.is_a?(Array)
227
217
  event_types.each do |event_type|
228
- filter_type[event_type] << Filter.new(event_type, match, block)
218
+ filter_type[event_type] << Filter.new(event_type, match, {}, block)
229
219
  end
230
220
  else
231
- filter_type[event_types] << Filter.new(event_types, match, block)
221
+ filter_type[event_types] << Filter.new(event_types, match, {}, block)
232
222
  end
233
223
  end
234
224
  end
@@ -1,4 +1,4 @@
1
1
  module Ponder
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.0'
3
3
  end
4
4
 
@@ -5,13 +5,13 @@ require 'lib/ponder/version'
5
5
  Gem::Specification.new do |s|
6
6
  s.name = 'ponder'
7
7
  s.version = Ponder::VERSION
8
- s.date = '2011-05-19'
8
+ s.date = '2011-08-28'
9
9
  s.summary = 'IRC bot framework'
10
10
  s.description = 'Ponder (Stibbons) is a Domain Specific Language for writing IRC Bots using the EventMachine library.'
11
11
 
12
12
  s.author = 'Tobias Bühlmann'
13
13
  s.email = 'tobias.buehlmann@gmx.de'
14
- s.homepage = 'http://github.com/tbuehlmann/ponder'
14
+ s.homepage = 'https://github.com/tbuehlmann/ponder'
15
15
 
16
16
  s.required_ruby_version = '>= 1.8.6'
17
17
  s.add_dependency('eventmachine', '>= 0.12.10')
@@ -21,8 +21,8 @@ Gem::Specification.new do |s|
21
21
  README.md
22
22
  Rakefile
23
23
  examples/echo.rb
24
- examples/github_blog.rb
25
24
  examples/redis_last_seen.rb
25
+ lib/core_ext/array.rb
26
26
  lib/ponder.rb
27
27
  lib/ponder/async_irc.rb
28
28
  lib/ponder/callback.rb
@@ -2,144 +2,185 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
 
3
3
  require 'spec_helper'
4
4
  require 'ponder/thaum'
5
+ require 'ponder/async_irc'
5
6
 
6
7
  describe Ponder::AsyncIRC do
7
- PERIODIC_TIME = 0.01
8
-
9
8
  before(:each) do
10
- @ponder = Ponder::Thaum.new
11
- @ponder.configure { |c| c.verbose = false }
9
+ @ponder = Ponder::Thaum.new { |c| c.verbose = false }
12
10
  end
13
11
 
14
- context 'tries to get a topic when' do
15
- it 'there is no topic set' do
16
- @ponder.should_receive(:raw).with('TOPIC #channel')
17
- EM.run do
18
- EM.defer(Proc.new { @result = @ponder.get_topic('#channel') }, Proc.new { EM.schedule { EM.stop } })
19
- EM::PeriodicTimer.new(PERIODIC_TIME) { @ponder.parse(":server 331 Ponder #channel :No topic is set.\r\n") }
20
- end
21
- @result.should eql({:raw_numeric => 331, :message => 'No topic is set'})
22
- end
23
-
24
- it 'there is no topic set' do
25
- @ponder.should_receive(:raw).with('TOPIC #channel')
26
- EM.run do
27
- EM.defer(Proc.new { @result = @ponder.get_topic('#channel') }, Proc.new { EM.schedule { EM.stop } })
28
- EM::PeriodicTimer.new(PERIODIC_TIME) { @ponder.parse(":server 332 Ponder #channel :topic content\r\n") }
12
+ describe Ponder::AsyncIRC::Whois do
13
+ context 'tries to get whois information when' do
14
+ it 'there is no such nick' do
15
+ @ponder.should_receive(:raw).with('WHOIS not_online')
16
+ EM.run do
17
+ whois = @ponder.whois('not_online', 1.5)
18
+ whois.callback do |result|
19
+ result.should be_false
20
+ EM.stop
21
+ end
22
+ whois.errback do
23
+ fail 'Wrong Callback called'
24
+ EM.stop
25
+ end
26
+
27
+ EM.next_tick { @ponder.parse(":server 401 Ponder not_online :No such nick\r\n") }
28
+ end
29
29
  end
30
- @result.should eql({:raw_numeric => 332, :message => 'topic content'})
31
- end
32
30
 
33
- it 'there is no such channel' do
34
- @ponder.should_receive(:raw).with('TOPIC #no_channel')
35
- EM.run do
36
- EM.defer(Proc.new { @result = @ponder.get_topic('#no_channel') }, Proc.new { EM.schedule { EM.stop } })
37
- EM::PeriodicTimer.new(PERIODIC_TIME) { @ponder.parse(":server 403 Ponder #no_channel :No such channel\r\n") }
31
+ it 'the user is online' do
32
+ @ponder.should_receive(:raw).with('WHOIS Ridcully')
33
+ EM.run do
34
+ whois = @ponder.whois('Ridcully', 1.5)
35
+ whois.callback do |result|
36
+ result.should be_kind_of(Hash)
37
+ result[:nick].should eql('Ridcully')
38
+ result[:username].should eql('ridc')
39
+ result[:host].should eql('host')
40
+ result[:real_name].should eql('Ridcully the wizard')
41
+
42
+ result[:server].should be_kind_of(Hash)
43
+ result[:server][:address].should eql('foo.host.net')
44
+ result[:server][:name].should eql('That host thing')
45
+
46
+ result[:channels].should be_kind_of(Hash)
47
+ result[:channels]['#foo'].should be_nil
48
+ result[:channels]['##bar'].should be_nil
49
+ result[:channels]['#baz'].should eql('<')
50
+ result[:channels]['#sushi'].should eql('@')
51
+ result[:channels]['#ramen'].should eql('+')
52
+
53
+ result[:registered].should be_true
54
+ EM.stop
55
+ end
56
+ whois.errback do
57
+ fail 'Wrong Callback called'
58
+ EM.stop
59
+ end
60
+
61
+ EM.next_tick do
62
+ @ponder.parse(":server 311 Ponder Ridcully :ridc host * :Ridcully the wizard\r\n")
63
+ @ponder.parse(":server 312 Ponder Ridcully foo.host.net :That host thing\r\n")
64
+ @ponder.parse(":server 319 Ponder Ridcully :#foo ##bar <#baz @#sushi +#ramen\r\n")
65
+ @ponder.parse(":server 330 Ponder Ridcully rid_ :is logged in as\r\n")
66
+ @ponder.parse(":server 318 Ponder Ridcully :End of /WHOIS list.\r\n")
67
+ end
68
+ end
38
69
  end
39
- @result.should eql({:raw_numeric => 403, :message => 'No such channel'})
40
70
  end
41
71
 
42
- it "you're not on that channel" do
43
- @ponder.should_receive(:raw).with('TOPIC #channel')
44
- EM.run do
45
- EM.defer(Proc.new { @result = @ponder.get_topic('#channel') }, Proc.new { EM.schedule { EM.stop } })
46
- EM::PeriodicTimer.new(PERIODIC_TIME) { @ponder.parse(":server 442 Ponder #channel :You're not on that channel\r\n") }
72
+ context 'it tries to get channel information when' do
73
+ it 'there is no such channel' do
74
+ @ponder.should_receive(:raw).with('MODE #no_channel')
75
+
76
+ EM.run do
77
+ channel_info = @ponder.channel_info('#no_channel')
78
+ channel_info.callback do |result|
79
+ result.should be_false
80
+ EM.stop
81
+ end
82
+ channel_info.errback do
83
+ fail 'Wrong Callback called'
84
+ EM.stop
85
+ end
86
+
87
+ EM.next_tick { @ponder.parse(":server 403 Ponder #no_channel :No such channel\r\n") }
88
+ end
47
89
  end
48
- @result.should eql({:raw_numeric => 442, :message => "You're not on that channel"})
49
90
  end
50
91
  end
51
92
 
52
- context 'it tries to get channel information when' do
53
- it 'there is no such channel' do
54
- @ponder.should_receive(:raw).with('MODE #no_channel')
55
- EM.run do
56
- EM.defer(Proc.new { @result = @ponder.channel_info('#no_channel') }, Proc.new { EM.schedule { EM.stop } })
57
- EM::PeriodicTimer.new(PERIODIC_TIME) { @ponder.parse(":server 403 Ponder #no_channel :No such channel\r\n") }
93
+ describe Ponder::AsyncIRC::Topic do
94
+ context 'tries to get a topic' do
95
+ it ', adds the Deferrable object to the Set and deletes if afterwards for a success' do
96
+ @ponder.should_receive(:raw).with('TOPIC #channel')
97
+ EM.run do
98
+ @ponder.deferrables.should be_empty
99
+ topic = @ponder.get_topic('#channel')
100
+ @ponder.deferrables.should include(topic)
101
+ topic.callback do |result|
102
+ @ponder.deferrables.should be_empty
103
+ EM.stop
104
+ end
105
+ topic.errback do
106
+ fail 'Wrong Callback called'
107
+ EM.stop
108
+ end
109
+
110
+ EM.next_tick { @ponder.parse(":server 331 Ponder #channel :No topic is set.\r\n") }
111
+ end
58
112
  end
59
- @result.should be_false
60
- end
61
113
 
62
- it "you're not on that channel" do
63
- @ponder.should_receive(:raw).with('MODE #channel')
64
- EM.run do
65
- EM.defer(Proc.new { @result = @ponder.channel_info('#channel') }, Proc.new { EM.schedule { EM.stop } })
66
- EM::PeriodicTimer.new(PERIODIC_TIME) { @ponder.parse(":server 442 Ponder #channel :You're not on that channel\r\n") }
67
- end
68
- @result.should be_false
69
- end
70
-
71
- it "there are regular channel modes" do
72
- @ponder.should_receive(:raw).with('MODE #channel')
73
- EM.run do
74
- EM.defer(Proc.new { @result = @ponder.channel_info('#channel') }, Proc.new { EM.schedule { EM.stop } })
75
- EM::PeriodicTimer.new(PERIODIC_TIME) do
76
- @ponder.parse(":server 324 Ponder #channel +cnst\r\n")
77
- @ponder.parse(":server 329 Ponder #channel 1233034048\r\n")
114
+ it ', adds the Deferrable object to the Set and deletes if afterwards for a failure (timeout)' do
115
+ @ponder.should_receive(:raw).with('TOPIC #channel')
116
+ EM.run do
117
+ @ponder.deferrables.should be_empty
118
+ topic = @ponder.get_topic('#channel', 1.5)
119
+ @ponder.deferrables.should include(topic)
120
+ topic.callback do |result|
121
+ fail 'Wrong Callback called'
122
+ EM.stop
123
+ end
124
+ topic.errback do
125
+ @ponder.deferrables.should be_empty
126
+ EM.stop
127
+ end
78
128
  end
79
129
  end
80
- @result.should be_kind_of(Hash)
81
- @result[:modes].should include('c', 'n', 's', 't')
82
- @result[:created_at].should eql(Time.at(1233034048))
83
- end
84
130
 
85
- it "there are regular channel modes with a limit" do
86
- @ponder.should_receive(:raw).with('MODE #channel')
87
- EM.run do
88
- EM.defer(Proc.new { @result = @ponder.channel_info('#channel') }, Proc.new { EM.schedule { EM.stop } })
89
- EM::PeriodicTimer.new(PERIODIC_TIME) do
90
- @ponder.parse(":server 324 Ponder #channel +cnstl 8\r\n")
91
- @ponder.parse(":server 329 Ponder #channel 1233034048\r\n")
131
+ it 'when there is no topic set' do
132
+ @ponder.should_receive(:raw).with('TOPIC #channel')
133
+ EM.run do
134
+ @ponder.deferrables.should be_empty
135
+ topic = @ponder.get_topic('#channel', 2)
136
+ @ponder.deferrables.should include(topic)
137
+ topic.callback do |result|
138
+ result.should eql({:raw_numeric => 331, :message => 'No topic is set'})
139
+ @ponder.deferrables.should be_empty
140
+ EM.stop
141
+ end
142
+ topic.errback do
143
+ fail 'Wrong Callback called'
144
+ EM.stop
145
+ end
146
+
147
+ EM.next_tick { @ponder.parse(":server 331 Ponder #channel :No topic is set.\r\n") }
92
148
  end
93
149
  end
94
- @result.should be_kind_of(Hash)
95
- @result[:modes].should include('c', 'n', 's', 't')
96
- @result[:created_at].should eql(Time.at(1233034048))
97
- @result[:channel_limit].should equal(8)
98
- end
99
- end
100
150
 
101
- context 'tries to get whois information when' do
102
- it 'there is no such nick' do
103
- @ponder.should_receive(:raw).with('WHOIS not_online')
104
- EM.run do
105
- EM.defer(Proc.new { @result = @ponder.whois('not_online') }, Proc.new { EM.schedule { EM.stop } })
106
- EM::PeriodicTimer.new(PERIODIC_TIME) { @ponder.parse(":server 401 Ponder not_online :No such nick\r\n") }
151
+ it 'when there is no such channel' do
152
+ @ponder.should_receive(:raw).with('TOPIC #no_channel')
153
+ EM.run do
154
+ topic = @ponder.get_topic('#no_channel')
155
+ topic.callback do |result|
156
+ result.should eql({:raw_numeric => 403, :message => 'No such channel'})
157
+ EM.stop
158
+ end
159
+ topic.errback do
160
+ fail 'Wrong Callback called'
161
+ EM.stop
162
+ end
163
+
164
+ EM.next_tick { @ponder.parse(":server 403 Ponder #no_channel :No such channel\r\n") }
165
+ end
107
166
  end
108
- @result.should be_false
109
- end
110
167
 
111
- it 'the user is online' do
112
- @ponder.should_receive(:raw).with('WHOIS Ridcully')
113
- EM.run do
114
- EM.defer(Proc.new { @result = @ponder.whois('Ridcully') }, Proc.new { EM.schedule { EM.stop } })
115
- EM::PeriodicTimer.new(PERIODIC_TIME) do
116
- @ponder.parse(":server 311 Ponder Ridcully :ridc host * :Ridcully the wizard\r\n")
117
- @ponder.parse(":server 312 Ponder Ridcully foo.host.net :That host thing\r\n")
118
- @ponder.parse(":server 319 Ponder Ridcully :#foo ##bar <#baz @#sushi +#ramen\r\n")
119
- @ponder.parse(":server 330 Ponder Ridcully rid_ :is logged in as\r\n")
120
- @ponder.parse(":server 318 Ponder Ridcully :End of /WHOIS list.\r\n")
168
+ it "you're not on that channel" do
169
+ @ponder.should_receive(:raw).with('TOPIC #channel')
170
+ EM.run do
171
+ topic = @ponder.get_topic('#channel')
172
+ topic.callback do |result|
173
+ result.should eql({:raw_numeric => 442, :message => "You're not on that channel"})
174
+ EM.stop
175
+ end
176
+ topic.errback do
177
+ fail 'Wrong Callback called'
178
+ EM.stop
179
+ end
180
+
181
+ EM.next_tick { @ponder.parse(":server 442 Ponder #channel :You're not on that channel\r\n") }
121
182
  end
122
183
  end
123
-
124
- @result.should be_kind_of(Hash)
125
- @result[:nick].should eql('Ridcully')
126
- @result[:username].should eql('ridc')
127
- @result[:host].should eql('host')
128
- @result[:real_name].should eql('Ridcully the wizard')
129
-
130
- @result[:server].should be_kind_of(Hash)
131
- @result[:server][:address].should eql('foo.host.net')
132
- @result[:server][:name].should eql('That host thing')
133
-
134
- @result[:channels].should be_kind_of(Hash)
135
- @result[:channels]['#foo'].should be_nil
136
- @result[:channels]['##bar'].should be_nil
137
- @result[:channels]['#baz'].should eql('<')
138
- @result[:channels]['#sushi'].should eql('@')
139
- @result[:channels]['#ramen'].should eql('+')
140
-
141
- @result[:registered].should be_true
142
184
  end
143
185
  end
144
186
  end
145
-
@@ -7,8 +7,7 @@ describe Ponder::Callback do
7
7
  before(:all) { @proc = Proc.new { } }
8
8
 
9
9
  before(:each) do
10
- @ponder = Ponder::Thaum.new
11
- @ponder.configure { |c| c.verbose = true }
10
+ @ponder = Ponder::Thaum.new { |c| c.verbose = true }
12
11
  end
13
12
 
14
13
  context 'tries to create a callback' do
@@ -25,12 +24,12 @@ describe Ponder::Callback do
25
24
  end
26
25
 
27
26
  it 'with an invalid proc' do
28
- lambda { Ponder::Callback.new(:channel, /foo/, 8) }.should raise_error(TypeError)
27
+ lambda { Ponder::Callback.new(:channel, /foo/, {}, 8) }.should raise_error(TypeError)
29
28
  end
30
29
  end
31
30
 
32
31
  it "calls the callback's proc on right match" do
33
- callback = Ponder::Callback.new(:channel, /wizzard/, Proc.new { 8 })
32
+ callback = Ponder::Callback.new(:channel, /wizzard/, {}, Proc.new { 8 })
34
33
  callback.call(:channel, {:message => 'I like wizzards'}).should eql(8)
35
34
  end
36
35
 
@@ -5,12 +5,11 @@ require 'ponder/thaum'
5
5
 
6
6
  describe Ponder::IRC do
7
7
  before(:each) do
8
- @ponder = Ponder::Thaum.new
9
- @ponder.configure do |c|
10
- c.nick = 'Ponder'
11
- c.username = 'Ponder'
12
- c.real_name = 'Ponder Stibbons'
13
- c.reconnect = true
8
+ @ponder = Ponder::Thaum.new do |t|
9
+ t.nick = 'Ponder'
10
+ t.username = 'Ponder'
11
+ t.real_name = 'Ponder Stibbons'
12
+ t.reconnect = true
14
13
  end
15
14
  end
16
15
 
@@ -26,10 +25,16 @@ describe Ponder::IRC do
26
25
  end
27
26
 
28
27
  it 'registers with the server with a password' do
28
+ @ponder = Ponder::Thaum.new do |t|
29
+ t.nick = 'Ponder'
30
+ t.username = 'Ponder'
31
+ t.real_name = 'Ponder Stibbons'
32
+ t.reconnect = true
33
+ t.password = 'secret'
34
+ end
29
35
  @ponder.should_receive(:raw).with('NICK Ponder').once
30
36
  @ponder.should_receive(:raw).with('USER Ponder * * :Ponder Stibbons').once
31
37
  @ponder.should_receive(:raw).with('PASS secret').once
32
- @ponder.configure { |c| c.password = 'secret' }
33
38
  @ponder.register
34
39
  end
35
40
 
@@ -8,7 +8,7 @@ describe Ponder::Thaum do
8
8
  @ponder = Ponder::Thaum.new
9
9
  end
10
10
 
11
- it 'sets a default configuration without calling the #configure method' do
11
+ it 'sets a default configuration' do
12
12
  @ponder.config.server.should eql('localhost')
13
13
  @ponder.config.port.should equal(6667)
14
14
  @ponder.config.nick.should eql('Ponder')
@@ -19,14 +19,13 @@ describe Ponder::Thaum do
19
19
  @ponder.config.reconnect.should be_true
20
20
  @ponder.config.reconnect_interval.should equal(30)
21
21
 
22
- @ponder.logger.should be_an_instance_of(Ponder::BlindIo)
23
- @ponder.console_logger.should be_an_instance_of(Ponder::Twoflogger)
22
+ @ponder.logger.should be_an_instance_of(Ponder::Logger::BlindIo)
23
+ @ponder.console_logger.should be_an_instance_of(Ponder::Logger::Twoflogger)
24
24
  end
25
25
 
26
26
  it 'sets the logger correctly' do
27
- FileUtils.stub!(:mkdir_p)
28
- Ponder::Twoflogger.should_receive(:new)
29
- @ponder.configure { |c| c.logging = true }
27
+ Ponder::Logger::Twoflogger.should_receive(:new).twice
28
+ @ponder = Ponder::Thaum.new { |t| t.logging = true }
30
29
  end
31
30
 
32
31
  it 'sets default callbacks' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ponder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-05-19 00:00:00.000000000Z
12
+ date: 2011-08-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
16
- requirement: &16716980 !ruby/object:Gem::Requirement
16
+ requirement: &21592080 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.12.10
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *16716980
24
+ version_requirements: *21592080
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &16716600 !ruby/object:Gem::Requirement
27
+ requirement: &21591680 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *16716600
35
+ version_requirements: *21591680
36
36
  description: Ponder (Stibbons) is a Domain Specific Language for writing IRC Bots
37
37
  using the EventMachine library.
38
38
  email: tobias.buehlmann@gmx.de
@@ -44,8 +44,8 @@ files:
44
44
  - README.md
45
45
  - Rakefile
46
46
  - examples/echo.rb
47
- - examples/github_blog.rb
48
47
  - examples/redis_last_seen.rb
48
+ - lib/core_ext/array.rb
49
49
  - lib/ponder.rb
50
50
  - lib/ponder/async_irc.rb
51
51
  - lib/ponder/callback.rb
@@ -63,7 +63,7 @@ files:
63
63
  - spec/irc_spec.rb
64
64
  - spec/spec_helper.rb
65
65
  - spec/thaum_spec.rb
66
- homepage: http://github.com/tbuehlmann/ponder
66
+ homepage: https://github.com/tbuehlmann/ponder
67
67
  licenses: []
68
68
  post_install_message:
69
69
  rdoc_options: []
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
83
  version: '0'
84
84
  requirements: []
85
85
  rubyforge_project:
86
- rubygems_version: 1.7.2
86
+ rubygems_version: 1.8.6
87
87
  signing_key:
88
88
  specification_version: 3
89
89
  summary: IRC bot framework
@@ -1,31 +0,0 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
-
3
- require 'rubygems'
4
- require 'ponder'
5
- require 'nokogiri'
6
- require 'open-uri'
7
-
8
- # This Thaum answers the channel message "blog?" with the title of the newest github blog entry.
9
- @ponder = Ponder::Thaum.new
10
-
11
- @ponder.configure do |c|
12
- c.server = 'chat.freenode.org'
13
- c.port = 6667
14
- c.nick = 'Ponder'
15
- c.verbose = true
16
- c.logging = false
17
- end
18
-
19
- @ponder.on :connect do
20
- @ponder.join '#ponder'
21
- end
22
-
23
- @ponder.on :channel, /^blog\?$/ do |event_data|
24
- doc = Nokogiri::HTML(open('https://github.com/blog'))
25
- title = doc.xpath('/html/body/div/div[2]/div/div/ul/li/h2/a')[0].text
26
-
27
- @ponder.message event_data[:channel], "Newest Github Blog Post: #{title}"
28
- end
29
-
30
- @ponder.connect
31
-