balotelli 0.4.2.1 → 0.5.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -1
- data/lib/balotelli/base.rb +13 -13
- data/lib/balotelli/core/irc_logger.rb +22 -22
- data/lib/balotelli/core/router/action.rb +13 -0
- data/lib/balotelli/core/router/match.rb +24 -0
- data/lib/balotelli/core/router/router.rb +51 -0
- data/lib/balotelli/module/base.rb +1 -1
- data/lib/balotelli/thread/pool.rb +470 -0
- data/lib/balotelli/version.rb +1 -1
- data/test/lib/balotelli/base_test.rb +2 -2
- data/test/lib/balotelli/core/router_test.rb +3 -1
- metadata +17 -15
- data/lib/balotelli/core/router.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4af3a056d835bb509c9a8eeae2591a21c9f51f4a
|
4
|
+
data.tar.gz: fcb7b59a3e774c88e92318b375acdffe4ae3bde5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cda3aa74be90d37dac1f12f4dabd1b34c9336c4972b941d3e1b65691421e00d95d7f52e904be66f82564859a5ec616294beba6eb4d65e629597b70251924083
|
7
|
+
data.tar.gz: 8dfff8deb52f2b9cfcff2bce18308b28b5d14f777d2edf34b1aa7e479fbf0d03b591da357f9a35a14fa37c70747eb2dedfc89c20481d9ebaa654ffee51b3fbd2
|
data/Gemfile.lock
CHANGED
data/lib/balotelli/base.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
require 'balotelli/core/irc'
|
2
|
-
require 'balotelli/core/router'
|
2
|
+
require 'balotelli/core/router/router'
|
3
|
+
require 'balotelli/core/router/match'
|
4
|
+
require 'balotelli/core/router/action'
|
3
5
|
require 'balotelli/core/priv_msg'
|
4
6
|
require 'balotelli/core/join'
|
5
7
|
require 'balotelli/core/kick'
|
6
8
|
require 'balotelli/core/irc_logger'
|
7
9
|
require 'balotelli/core/utils'
|
8
10
|
require 'balotelli/config'
|
11
|
+
require 'balotelli/thread/pool'
|
9
12
|
|
10
13
|
module Balotelli
|
11
14
|
class Base
|
@@ -66,6 +69,7 @@ module Balotelli
|
|
66
69
|
def setup
|
67
70
|
setup_router
|
68
71
|
|
72
|
+
@pool = Thread::Pool.new(Thread::Pool.cpu_count)
|
69
73
|
@modules = {}
|
70
74
|
@cache = Hash.new { |hash, key| hash[key] = {} }
|
71
75
|
@cvs = {}
|
@@ -95,8 +99,6 @@ module Balotelli
|
|
95
99
|
def mod_match(str, priv = false)
|
96
100
|
if str =~ /\A[^a-zA-Z0-9\s]?(\S+) ?(.*)/
|
97
101
|
if (mod = @cache[:modules][Regexp.last_match(1).downcase])
|
98
|
-
str.slice!(0..Regexp.last_match(1).length)
|
99
|
-
str.lstrip!
|
100
102
|
mod.match(Regexp.last_match(2), priv)
|
101
103
|
end
|
102
104
|
elsif str == :join
|
@@ -126,7 +128,7 @@ module Balotelli
|
|
126
128
|
elsif !(str =~ /(JOIN\s|PRIVMSG\s)/)
|
127
129
|
process_table(str)
|
128
130
|
else
|
129
|
-
|
131
|
+
@pool.process { process_message(str) }
|
130
132
|
end
|
131
133
|
end
|
132
134
|
|
@@ -140,25 +142,23 @@ module Balotelli
|
|
140
142
|
|
141
143
|
def process_private_message(str, user, channel, message)
|
142
144
|
privacy = !(channel =~ /#.+/)
|
143
|
-
if (
|
144
|
-
(
|
145
|
-
new_response(user, channel, message,
|
145
|
+
if (matched = match(message, privacy)) ||
|
146
|
+
(matched = mod_match(message, privacy))
|
147
|
+
new_response(user, channel, message, matched, Core::PrivMsg, privacy)
|
146
148
|
end
|
147
149
|
end
|
148
150
|
|
149
151
|
def process_join(str, user, channel)
|
150
|
-
if (
|
151
|
-
new_response(user, channel, nil,
|
152
|
+
if (matched = match(:join)) || (matched = mod_match(:join))
|
153
|
+
new_response(user, channel, nil, matched, Core::Join)
|
152
154
|
end
|
153
155
|
end
|
154
156
|
|
155
157
|
|
156
158
|
def new_response(user, channel, message, route_match, klass, priv = nil)
|
157
|
-
route_pattern, block, module_name = route_match.flatten
|
158
159
|
message = klass.new(*[user, channel, message, priv].compact)
|
159
|
-
|
160
|
-
|
161
|
-
response.extend(module_name) if module_name.class.to_s == 'Module'
|
160
|
+
new(route_match.block, message, route_match.match_data).tap do |response|
|
161
|
+
response.extend(route_match.mod_name) if route_match.mod_name.class == ::Module
|
162
162
|
end.execute!
|
163
163
|
end
|
164
164
|
|
@@ -8,6 +8,28 @@ module Balotelli
|
|
8
8
|
"#{datetime.utc}: #{msg}\n"
|
9
9
|
end
|
10
10
|
|
11
|
+
module Methods
|
12
|
+
def sgets
|
13
|
+
str = orig_sgets
|
14
|
+
to_log = "<<#{str.inspect}\n"
|
15
|
+
@log_file.info(to_log)
|
16
|
+
if str =~ /\A:?\S+ \S+ (#\S+) .*/
|
17
|
+
@logger.channel_log(Regexp.last_match(1).downcase, to_log)
|
18
|
+
end
|
19
|
+
str
|
20
|
+
end
|
21
|
+
|
22
|
+
def sputs(str)
|
23
|
+
orig_sputs(str)
|
24
|
+
to_log = ">>#{str.inspect}\n"
|
25
|
+
@log_file.info(to_log)
|
26
|
+
if str =~ /\A:?\S+ (#\S+) .*/
|
27
|
+
@logger.channel_log(Regexp.last_match(1).downcase, to_log)
|
28
|
+
end
|
29
|
+
str
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
11
33
|
attr_reader :log_file, :dir, :channels, :channels_logs
|
12
34
|
|
13
35
|
def initialize(dir = '', log_name = 'logs.log', channels = [])
|
@@ -44,28 +66,6 @@ module Balotelli
|
|
44
66
|
end
|
45
67
|
end
|
46
68
|
|
47
|
-
module Methods
|
48
|
-
def sgets
|
49
|
-
str = orig_sgets
|
50
|
-
to_log = "<<#{str.inspect}\n"
|
51
|
-
@log_file.info(to_log)
|
52
|
-
if str =~ /\A:?\S+ \S+ (#\S+) .*/
|
53
|
-
@logger.channel_log(Regexp.last_match(1).downcase, to_log)
|
54
|
-
end
|
55
|
-
str
|
56
|
-
end
|
57
|
-
|
58
|
-
def sputs(str)
|
59
|
-
orig_sputs(str)
|
60
|
-
to_log = ">>#{str.inspect}\n"
|
61
|
-
@log_file.info(to_log)
|
62
|
-
if str =~ /\A:?\S+ (#\S+) .*/
|
63
|
-
@logger.channel_log(Regexp.last_match(1).downcase, to_log)
|
64
|
-
end
|
65
|
-
str
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
69
|
def define_log_method
|
70
70
|
define_method :log_file do |str|
|
71
71
|
class_variable_get(:@log_file)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Balotelli
|
2
|
+
module Core
|
3
|
+
module Router
|
4
|
+
class Match
|
5
|
+
attr_reader :data, :pattern, :block, :mod_name
|
6
|
+
def initialize(data, pattern, action, privacy)
|
7
|
+
@data = data
|
8
|
+
@pattern = pattern
|
9
|
+
@block = action.block
|
10
|
+
@mod_name = action.mod_name
|
11
|
+
@private = privacy
|
12
|
+
end
|
13
|
+
|
14
|
+
def is_private?
|
15
|
+
@private
|
16
|
+
end
|
17
|
+
|
18
|
+
def match_data
|
19
|
+
@match_data ||= data.match(pattern) if pattern.is_a?(Regexp)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Balotelli
|
2
|
+
module Core
|
3
|
+
module Router
|
4
|
+
RouteCollisionError = Class.new(StandardError)
|
5
|
+
|
6
|
+
def setup_router
|
7
|
+
@routes = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def on(route, options = {}, &block)
|
11
|
+
raise 'no block given' if block.nil?
|
12
|
+
|
13
|
+
privacy = options.fetch(:private, false)
|
14
|
+
|
15
|
+
if privacy == :both
|
16
|
+
on(route, options.clone.tap { |o| o[:private] = true }, &block)
|
17
|
+
on(route, options.clone.tap { |o| o[:private] = false }, &block)
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
@routes[route] ||= {}
|
22
|
+
|
23
|
+
if @routes[route][privacy] && !options[:force]
|
24
|
+
raise RouteCollisionError,
|
25
|
+
"Route #{route.inspect}, private: #{options[:private]} already exists"
|
26
|
+
end
|
27
|
+
|
28
|
+
@routes[route][privacy] = Action.new(block, self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def match?(str, route)
|
32
|
+
case route
|
33
|
+
when Regexp
|
34
|
+
str =~ route
|
35
|
+
when String
|
36
|
+
str == route
|
37
|
+
when Symbol
|
38
|
+
str == route
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def match(str, priv = false)
|
43
|
+
if (route = @routes.detect { |k, v| match?(str, k) && v[priv] })
|
44
|
+
pattern = route[0]
|
45
|
+
action = route[1][priv]
|
46
|
+
Match.new(str, pattern, action, priv)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,470 @@
|
|
1
|
+
# https://github.com/meh/ruby-thread/blob/master/lib/thread/pool.rb
|
2
|
+
require 'thread'
|
3
|
+
require 'etc'
|
4
|
+
|
5
|
+
# A pool is a container of a limited amount of threads to which you can add
|
6
|
+
# tasks to run.
|
7
|
+
#
|
8
|
+
# This is usually more performant and less memory intensive than creating a
|
9
|
+
# new thread for every task.
|
10
|
+
class Thread::Pool
|
11
|
+
# A task incapsulates a block being ran by the pool and the arguments to pass
|
12
|
+
# to it.
|
13
|
+
class Task
|
14
|
+
Timeout = Class.new(Exception)
|
15
|
+
Asked = Class.new(Exception)
|
16
|
+
|
17
|
+
attr_reader :pool, :timeout, :exception, :thread, :started_at, :result
|
18
|
+
|
19
|
+
# Create a task in the given pool which will pass the arguments to the
|
20
|
+
# block.
|
21
|
+
def initialize(pool, *args, &block)
|
22
|
+
@pool = pool
|
23
|
+
@arguments = args
|
24
|
+
@block = block
|
25
|
+
|
26
|
+
@running = false
|
27
|
+
@finished = false
|
28
|
+
@timedout = false
|
29
|
+
@terminated = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def running?
|
33
|
+
@running
|
34
|
+
end
|
35
|
+
|
36
|
+
def finished?
|
37
|
+
@finished
|
38
|
+
end
|
39
|
+
|
40
|
+
def timeout?
|
41
|
+
@timedout
|
42
|
+
end
|
43
|
+
|
44
|
+
def terminated?
|
45
|
+
@terminated
|
46
|
+
end
|
47
|
+
|
48
|
+
# Execute the task.
|
49
|
+
def execute
|
50
|
+
return if terminated? || running? || finished?
|
51
|
+
|
52
|
+
@thread = Thread.current
|
53
|
+
@running = true
|
54
|
+
@started_at = Time.now
|
55
|
+
|
56
|
+
pool.__send__ :wake_up_timeout
|
57
|
+
|
58
|
+
begin
|
59
|
+
@result = @block.call(*@arguments)
|
60
|
+
rescue Exception => reason
|
61
|
+
if reason.is_a? Timeout
|
62
|
+
@timedout = true
|
63
|
+
elsif reason.is_a? Asked
|
64
|
+
return
|
65
|
+
else
|
66
|
+
@exception = reason
|
67
|
+
raise @exception if Thread::Pool.abort_on_exception
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@running = false
|
72
|
+
@finished = true
|
73
|
+
@thread = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# Raise an exception in the thread used by the task.
|
77
|
+
def raise(exception)
|
78
|
+
@thread.raise(exception) if @thread
|
79
|
+
end
|
80
|
+
|
81
|
+
# Terminate the exception with an optionally given exception.
|
82
|
+
def terminate!(exception = Asked)
|
83
|
+
return if terminated? || finished? || timeout?
|
84
|
+
|
85
|
+
@terminated = true
|
86
|
+
|
87
|
+
return unless running?
|
88
|
+
|
89
|
+
self.raise exception
|
90
|
+
end
|
91
|
+
|
92
|
+
# Force the task to timeout.
|
93
|
+
def timeout!
|
94
|
+
terminate! Timeout
|
95
|
+
end
|
96
|
+
|
97
|
+
# Timeout the task after the given time.
|
98
|
+
def timeout_after(time)
|
99
|
+
@timeout = time
|
100
|
+
|
101
|
+
pool.__send__ :timeout_for, self, time
|
102
|
+
|
103
|
+
self
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
attr_reader :min, :max, :spawned, :waiting
|
108
|
+
|
109
|
+
# Create the pool with minimum and maximum threads.
|
110
|
+
#
|
111
|
+
# The pool will start with the minimum amount of threads created and will
|
112
|
+
# spawn new threads until the max is reached in case of need.
|
113
|
+
#
|
114
|
+
# A default block can be passed, which will be used to {#process} the passed
|
115
|
+
# data.
|
116
|
+
def initialize(min, max = nil, &block)
|
117
|
+
@min = min
|
118
|
+
@max = max || min
|
119
|
+
@block = block
|
120
|
+
|
121
|
+
@cond = ConditionVariable.new
|
122
|
+
@mutex = Mutex.new
|
123
|
+
|
124
|
+
@done = ConditionVariable.new
|
125
|
+
@done_mutex = Mutex.new
|
126
|
+
|
127
|
+
@todo = []
|
128
|
+
@workers = []
|
129
|
+
@timeouts = {}
|
130
|
+
|
131
|
+
@spawned = 0
|
132
|
+
@waiting = 0
|
133
|
+
@shutdown = false
|
134
|
+
@trim_requests = 0
|
135
|
+
@auto_trim = false
|
136
|
+
@idle_trim = nil
|
137
|
+
@timeout = nil
|
138
|
+
|
139
|
+
@mutex.synchronize {
|
140
|
+
min.times {
|
141
|
+
spawn_thread
|
142
|
+
}
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
# Check if the pool has been shut down.
|
147
|
+
def shutdown?
|
148
|
+
!!@shutdown
|
149
|
+
end
|
150
|
+
|
151
|
+
# Check if auto trimming is enabled.
|
152
|
+
def auto_trim?
|
153
|
+
@auto_trim
|
154
|
+
end
|
155
|
+
|
156
|
+
# Enable auto trimming, unneeded threads will be deleted until the minimum
|
157
|
+
# is reached.
|
158
|
+
def auto_trim!
|
159
|
+
@auto_trim = true
|
160
|
+
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
# Disable auto trimming.
|
165
|
+
def no_auto_trim!
|
166
|
+
@auto_trim = false
|
167
|
+
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
# Check if idle trimming is enabled.
|
172
|
+
def idle_trim?
|
173
|
+
!@idle_trim.nil?
|
174
|
+
end
|
175
|
+
|
176
|
+
# Enable idle trimming. Unneeded threads will be deleted after the given number of seconds of inactivity.
|
177
|
+
# The minimum number of threads is respeced.
|
178
|
+
def idle_trim!(timeout)
|
179
|
+
@idle_trim = timeout
|
180
|
+
|
181
|
+
self
|
182
|
+
end
|
183
|
+
|
184
|
+
# Turn of idle trimming.
|
185
|
+
def no_idle_trim!
|
186
|
+
@idle_trim = nil
|
187
|
+
|
188
|
+
self
|
189
|
+
end
|
190
|
+
|
191
|
+
# Resize the pool with the passed arguments.
|
192
|
+
def resize(min, max = nil)
|
193
|
+
@min = min
|
194
|
+
@max = max || min
|
195
|
+
|
196
|
+
trim!
|
197
|
+
end
|
198
|
+
|
199
|
+
# Get the amount of tasks that still have to be run.
|
200
|
+
def backlog
|
201
|
+
@mutex.synchronize {
|
202
|
+
@todo.length
|
203
|
+
}
|
204
|
+
end
|
205
|
+
|
206
|
+
# Are all tasks consumed?
|
207
|
+
def done?
|
208
|
+
@mutex.synchronize {
|
209
|
+
_done?
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
# Wait until all tasks are consumed. The caller will be blocked until then.
|
214
|
+
def wait(what = :idle)
|
215
|
+
case what
|
216
|
+
when :done
|
217
|
+
until done?
|
218
|
+
@done_mutex.synchronize {
|
219
|
+
break if _done?
|
220
|
+
|
221
|
+
@done.wait @done_mutex
|
222
|
+
}
|
223
|
+
end
|
224
|
+
|
225
|
+
when :idle
|
226
|
+
until idle?
|
227
|
+
@done_mutex.synchronize {
|
228
|
+
break if _idle?
|
229
|
+
|
230
|
+
@done.wait @done_mutex
|
231
|
+
}
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
self
|
236
|
+
end
|
237
|
+
|
238
|
+
# Check if there are idle workers.
|
239
|
+
def idle?
|
240
|
+
@mutex.synchronize {
|
241
|
+
_idle?
|
242
|
+
}
|
243
|
+
end
|
244
|
+
|
245
|
+
# Add a task to the pool which will execute the block with the given
|
246
|
+
# argument.
|
247
|
+
#
|
248
|
+
# If no block is passed the default block will be used if present, an
|
249
|
+
# ArgumentError will be raised otherwise.
|
250
|
+
def process(*args, &block)
|
251
|
+
unless block || @block
|
252
|
+
raise ArgumentError, 'you must pass a block'
|
253
|
+
end
|
254
|
+
|
255
|
+
task = Task.new(self, *args, &(block || @block))
|
256
|
+
|
257
|
+
@mutex.synchronize {
|
258
|
+
raise 'unable to add work while shutting down' if shutdown?
|
259
|
+
|
260
|
+
@todo << task
|
261
|
+
|
262
|
+
if @waiting == 0 && @spawned < @max
|
263
|
+
spawn_thread
|
264
|
+
end
|
265
|
+
|
266
|
+
@cond.signal
|
267
|
+
}
|
268
|
+
|
269
|
+
task
|
270
|
+
end
|
271
|
+
|
272
|
+
alias << process
|
273
|
+
|
274
|
+
# Trim the unused threads, if forced threads will be trimmed even if there
|
275
|
+
# are tasks waiting.
|
276
|
+
def trim(force = false)
|
277
|
+
@mutex.synchronize {
|
278
|
+
if (force || @waiting > 0) && @spawned - @trim_requests > @min
|
279
|
+
@trim_requests += 1
|
280
|
+
@cond.signal
|
281
|
+
end
|
282
|
+
}
|
283
|
+
|
284
|
+
self
|
285
|
+
end
|
286
|
+
|
287
|
+
# Force #{trim}.
|
288
|
+
def trim!
|
289
|
+
trim true
|
290
|
+
end
|
291
|
+
|
292
|
+
# Shut down the pool instantly without finishing to execute tasks.
|
293
|
+
def shutdown!
|
294
|
+
@mutex.synchronize {
|
295
|
+
@shutdown = :now
|
296
|
+
@cond.broadcast
|
297
|
+
}
|
298
|
+
|
299
|
+
wake_up_timeout
|
300
|
+
|
301
|
+
self
|
302
|
+
end
|
303
|
+
|
304
|
+
# Shut down the pool, it will block until all tasks have finished running.
|
305
|
+
def shutdown
|
306
|
+
@mutex.synchronize {
|
307
|
+
@shutdown = :nicely
|
308
|
+
@cond.broadcast
|
309
|
+
}
|
310
|
+
|
311
|
+
until @workers.empty?
|
312
|
+
if worker = @workers.first
|
313
|
+
worker.join
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
if @timeout
|
318
|
+
@shutdown = :now
|
319
|
+
|
320
|
+
wake_up_timeout
|
321
|
+
|
322
|
+
@timeout.join
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Shutdown the pool after a given amount of time.
|
327
|
+
def shutdown_after(timeout)
|
328
|
+
Thread.new {
|
329
|
+
sleep timeout
|
330
|
+
|
331
|
+
shutdown
|
332
|
+
}
|
333
|
+
end
|
334
|
+
|
335
|
+
class << self
|
336
|
+
# If true, tasks will allow raised exceptions to pass through.
|
337
|
+
#
|
338
|
+
# Similar to Thread.abort_on_exception
|
339
|
+
attr_accessor :abort_on_exception
|
340
|
+
|
341
|
+
def cpu_count
|
342
|
+
Etc.nprocessors * 2
|
343
|
+
rescue
|
344
|
+
16
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
private
|
349
|
+
def timeout_for(task, timeout)
|
350
|
+
unless @timeout
|
351
|
+
spawn_timeout_thread
|
352
|
+
end
|
353
|
+
|
354
|
+
@mutex.synchronize {
|
355
|
+
@timeouts[task] = timeout
|
356
|
+
|
357
|
+
wake_up_timeout
|
358
|
+
}
|
359
|
+
end
|
360
|
+
|
361
|
+
def wake_up_timeout
|
362
|
+
if defined? @pipes
|
363
|
+
@pipes.last.write_nonblock 'x' rescue nil
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def spawn_thread
|
368
|
+
@spawned += 1
|
369
|
+
|
370
|
+
thread = Thread.new {
|
371
|
+
loop do
|
372
|
+
task = @mutex.synchronize {
|
373
|
+
if @todo.empty?
|
374
|
+
while @todo.empty?
|
375
|
+
if @trim_requests > 0
|
376
|
+
@trim_requests -= 1
|
377
|
+
|
378
|
+
break
|
379
|
+
end
|
380
|
+
|
381
|
+
break if shutdown?
|
382
|
+
|
383
|
+
@waiting += 1
|
384
|
+
|
385
|
+
done!
|
386
|
+
|
387
|
+
if @idle_trim and @spawned > @min
|
388
|
+
check_time = Time.now + @idle_trim
|
389
|
+
@cond.wait @mutex, @idle_trim
|
390
|
+
@trim_requests += 1 if Time.now >= check_time && @spawned - @trim_requests > @min
|
391
|
+
else
|
392
|
+
@cond.wait @mutex
|
393
|
+
end
|
394
|
+
|
395
|
+
@waiting -= 1
|
396
|
+
end
|
397
|
+
|
398
|
+
break if @todo.empty? && shutdown?
|
399
|
+
end
|
400
|
+
|
401
|
+
@todo.shift
|
402
|
+
} or break
|
403
|
+
|
404
|
+
task.execute
|
405
|
+
|
406
|
+
break if @shutdown == :now
|
407
|
+
|
408
|
+
trim if auto_trim? && @spawned > @min
|
409
|
+
end
|
410
|
+
|
411
|
+
@mutex.synchronize {
|
412
|
+
@spawned -= 1
|
413
|
+
@workers.delete thread
|
414
|
+
}
|
415
|
+
}
|
416
|
+
|
417
|
+
@workers << thread
|
418
|
+
|
419
|
+
thread
|
420
|
+
end
|
421
|
+
|
422
|
+
def spawn_timeout_thread
|
423
|
+
@pipes = IO.pipe
|
424
|
+
@timeout = Thread.new {
|
425
|
+
loop do
|
426
|
+
now = Time.now
|
427
|
+
timeout = @timeouts.map {|task, time|
|
428
|
+
next unless task.started_at
|
429
|
+
|
430
|
+
now - task.started_at + task.timeout
|
431
|
+
}.compact.min unless @timeouts.empty?
|
432
|
+
|
433
|
+
readable, = IO.select([@pipes.first], nil, nil, timeout)
|
434
|
+
|
435
|
+
break if @shutdown == :now
|
436
|
+
|
437
|
+
if readable && !readable.empty?
|
438
|
+
readable.first.read_nonblock 1024
|
439
|
+
end
|
440
|
+
|
441
|
+
now = Time.now
|
442
|
+
@timeouts.each {|task, time|
|
443
|
+
next if !task.started_at || task.terminated? || task.finished?
|
444
|
+
|
445
|
+
if now > task.started_at + task.timeout
|
446
|
+
task.timeout!
|
447
|
+
end
|
448
|
+
}
|
449
|
+
|
450
|
+
@timeouts.reject! { |task, _| task.terminated? || task.finished? }
|
451
|
+
|
452
|
+
break if @shutdown == :now
|
453
|
+
end
|
454
|
+
}
|
455
|
+
end
|
456
|
+
|
457
|
+
def _done?
|
458
|
+
@todo.empty? and @waiting == @spawned
|
459
|
+
end
|
460
|
+
|
461
|
+
def _idle?
|
462
|
+
@todo.length < @waiting
|
463
|
+
end
|
464
|
+
|
465
|
+
def done!
|
466
|
+
@done_mutex.synchronize {
|
467
|
+
@done.broadcast if _done? or _idle?
|
468
|
+
}
|
469
|
+
end
|
470
|
+
end
|
data/lib/balotelli/version.rb
CHANGED
@@ -37,8 +37,8 @@ class BaseTest < Minitest::Test
|
|
37
37
|
|
38
38
|
def test_it_matches_module_routes
|
39
39
|
TestBot.register(Balotelli::Module::Asdf)
|
40
|
-
assert_equal TestBot.mod_match("asdf Yes, sir")
|
40
|
+
assert_equal TestBot.mod_match("asdf Yes, sir").pattern, /yes/i
|
41
41
|
TestBot.register(Balotelli::Module::Xyz, "hoh")
|
42
|
-
assert_equal TestBot.mod_match("!hoh Si si")
|
42
|
+
assert_equal TestBot.mod_match("!hoh Si si").pattern, /si/i
|
43
43
|
end
|
44
44
|
end
|
@@ -20,7 +20,9 @@ class RouterTest < Minitest::Test
|
|
20
20
|
def test_it_mathes_routes
|
21
21
|
lam = -> { puts 'kk' }
|
22
22
|
@route.on %r{ono}i, private: true, &lam
|
23
|
-
|
23
|
+
first_match = @route.match('onOmatopeja', true)
|
24
|
+
assert first_match.pattern == %r{ono}i
|
25
|
+
assert first_match.block == lam
|
24
26
|
refute @route.match('onOmatopeja', false)
|
25
27
|
@route.on 'asdf', &lam
|
26
28
|
assert @route.match('asdf')
|
metadata
CHANGED
@@ -1,52 +1,52 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: balotelli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcin Henryk Bartkowiak
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
16
15
|
requirements:
|
17
16
|
- - ">="
|
18
17
|
- !ruby/object:Gem::Version
|
19
18
|
version: '0'
|
20
|
-
|
19
|
+
name: bundler
|
21
20
|
prerelease: false
|
21
|
+
type: :development
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
30
|
- - "~>"
|
32
31
|
- !ruby/object:Gem::Version
|
33
32
|
version: 10.4.2
|
34
|
-
|
33
|
+
name: rake
|
35
34
|
prerelease: false
|
35
|
+
type: :development
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 10.4.2
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name: minitest
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
44
43
|
requirements:
|
45
44
|
- - "~>"
|
46
45
|
- !ruby/object:Gem::Version
|
47
46
|
version: 5.8.0
|
48
|
-
|
47
|
+
name: minitest
|
49
48
|
prerelease: false
|
49
|
+
type: :development
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
@@ -76,9 +76,12 @@ files:
|
|
76
76
|
- lib/balotelli/core/join.rb
|
77
77
|
- lib/balotelli/core/kick.rb
|
78
78
|
- lib/balotelli/core/priv_msg.rb
|
79
|
-
- lib/balotelli/core/router.rb
|
79
|
+
- lib/balotelli/core/router/action.rb
|
80
|
+
- lib/balotelli/core/router/match.rb
|
81
|
+
- lib/balotelli/core/router/router.rb
|
80
82
|
- lib/balotelli/core/utils.rb
|
81
83
|
- lib/balotelli/module/base.rb
|
84
|
+
- lib/balotelli/thread/pool.rb
|
82
85
|
- lib/balotelli/version.rb
|
83
86
|
- test/lib/balotelli/asdf.rb
|
84
87
|
- test/lib/balotelli/balotelli_helper_test.rb
|
@@ -94,7 +97,7 @@ homepage: https://github.com/mhib/balotelli
|
|
94
97
|
licenses:
|
95
98
|
- MIT
|
96
99
|
metadata: {}
|
97
|
-
post_install_message:
|
100
|
+
post_install_message:
|
98
101
|
rdoc_options: []
|
99
102
|
require_paths:
|
100
103
|
- lib
|
@@ -109,9 +112,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
112
|
- !ruby/object:Gem::Version
|
110
113
|
version: '0'
|
111
114
|
requirements: []
|
112
|
-
rubyforge_project:
|
113
|
-
rubygems_version: 2.6.
|
114
|
-
signing_key:
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.6.6
|
117
|
+
signing_key:
|
115
118
|
specification_version: 4
|
116
119
|
summary: Simple IRC bot framework
|
117
120
|
test_files:
|
@@ -125,4 +128,3 @@ test_files:
|
|
125
128
|
- test/lib/balotelli/special_one.rb
|
126
129
|
- test/lib/balotelli/xyz.rb
|
127
130
|
- test/test_helper.rb
|
128
|
-
has_rdoc:
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Balotelli
|
2
|
-
module Core
|
3
|
-
module Router
|
4
|
-
def setup_router
|
5
|
-
@routes = {}
|
6
|
-
end
|
7
|
-
|
8
|
-
def on(route, option = {}, &block)
|
9
|
-
raise 'no block given' if block.nil?
|
10
|
-
|
11
|
-
option[:private] = false if !option.key?(:private) || route == :join
|
12
|
-
|
13
|
-
if option[:private] == :both
|
14
|
-
on(route, option.clone.tap { |o| o[:private] = true }, &block)
|
15
|
-
on(route, option.clone.tap { |o| o[:private] = false }, &block)
|
16
|
-
return
|
17
|
-
end
|
18
|
-
|
19
|
-
@routes[route] ||= {}
|
20
|
-
|
21
|
-
if @routes[route][option[:private]] && !option[:force]
|
22
|
-
raise "Route #{route.inspect}, private: #{option[:private]} already exists"
|
23
|
-
end
|
24
|
-
|
25
|
-
@routes[route][option[:private]] = [block, self]
|
26
|
-
end
|
27
|
-
|
28
|
-
def match?(str, route)
|
29
|
-
case route
|
30
|
-
when Regexp
|
31
|
-
str =~ route
|
32
|
-
when String
|
33
|
-
str == route
|
34
|
-
when Symbol
|
35
|
-
str == route
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def match(str, priv = false)
|
40
|
-
if (m = @routes.detect { |k, v| match?(str, k) && v[priv] })
|
41
|
-
[m[0], m[1][priv]]
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|