ponder 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
-