herpes 0.0.1a1 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/herpes +17 -0
- data/examples/generator.rb +11 -0
- data/examples/rss.rb +55 -13
- data/herpes.gemspec +2 -0
- data/lib/herpes/email.rb +55 -0
- data/lib/herpes/event.rb +45 -0
- data/lib/herpes/extensions.rb +95 -0
- data/lib/herpes/module.rb +129 -0
- data/lib/herpes/rss.rb +88 -0
- data/lib/herpes/version.rb +2 -2
- data/lib/herpes/workers.rb +26 -0
- data/lib/herpes.rb +247 -0
- metadata +28 -7
data/bin/herpes
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
require 'herpes'
|
3
|
+
|
4
|
+
if ARGV.first == '-v' or ARGV.first == '--version'
|
5
|
+
puts Herpes.version
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
|
9
|
+
fail 'no configuration file passed' if ARGV.empty?
|
10
|
+
|
11
|
+
herpes = Herpes.load(*ARGV)
|
12
|
+
|
13
|
+
trap 'INT' do
|
14
|
+
herpes.stop!
|
15
|
+
end
|
16
|
+
|
17
|
+
herpes.start!
|
data/examples/rss.rb
CHANGED
@@ -1,26 +1,68 @@
|
|
1
|
+
require 'herpes/rss'
|
2
|
+
require 'herpes/email'
|
3
|
+
|
4
|
+
state '~/.herpes'
|
5
|
+
|
1
6
|
# load the RSS module
|
2
7
|
use :rss do
|
8
|
+
check_every 2.minutes
|
9
|
+
|
3
10
|
# register sankaku with the following tags for the generated events
|
4
11
|
tag :anime, :manga, :japan, :nsfw do
|
5
12
|
register 'http://www.sankakucomplex.com/feed/'
|
6
13
|
end
|
7
14
|
|
8
|
-
# register incomaemeglio in the blog group
|
15
|
+
# register incomaemeglio in the blog group and give it a name
|
16
|
+
# also register github's blog in the blog group
|
9
17
|
group :blog do
|
10
|
-
register 'http://feeds.feedburner.com/incomaemeglio'
|
18
|
+
register 'http://feeds.feedburner.com/incomaemeglio', :smeriglia
|
19
|
+
register 'https://github.com/blog.atom'
|
11
20
|
end
|
12
21
|
end
|
13
22
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
# only events coming from the RSS module
|
24
|
+
from :rss do
|
25
|
+
# define some common helper methods on every event
|
26
|
+
before do |event|
|
27
|
+
require 'nokogiri'
|
28
|
+
|
29
|
+
[event.channel.title, event.title, event.description].each {|obj|
|
30
|
+
class << obj
|
31
|
+
def strip_html
|
32
|
+
Nokogiri::HTML(self).search('//text()').text
|
33
|
+
end
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
class << event
|
38
|
+
def render
|
39
|
+
"#{title.strip_html} (#{link})\n\n#{description.strip_html}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
20
43
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
44
|
+
# for events that have the nsfw tag
|
45
|
+
on -> e { e.tags.include?(:nsfw) } do |event|
|
46
|
+
with :email do
|
47
|
+
from "#{event.channel.title.strip_html} <rss-nsfw@herpes>"
|
48
|
+
to 'herpes'
|
49
|
+
subject event.title.strip_html
|
50
|
+
|
51
|
+
via :procmail
|
52
|
+
|
53
|
+
send event.render
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
on :anything_else do |event|
|
58
|
+
with :email do
|
59
|
+
from "#{event.channel.title.strip_html} <rss@herpes>"
|
60
|
+
to 'herpes'
|
61
|
+
subject event.title.strip_html
|
62
|
+
|
63
|
+
via :procmail
|
64
|
+
|
65
|
+
send event.render
|
66
|
+
end
|
67
|
+
end
|
26
68
|
end
|
data/herpes.gemspec
CHANGED
data/lib/herpes/email.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'pony'
|
12
|
+
|
13
|
+
Herpes::Notifier.define :email, :mail do
|
14
|
+
@options = %w(from to cc bcc sender subject headers charset text_part_charset message_id via via_options attachments)
|
15
|
+
|
16
|
+
plain_accessor *@options
|
17
|
+
|
18
|
+
from 'herpes'
|
19
|
+
|
20
|
+
@attachments = {}
|
21
|
+
|
22
|
+
def via (name, options = {})
|
23
|
+
if name == :procmail
|
24
|
+
procmail = `which procmail`.chomp
|
25
|
+
procmail = procmail.empty? ? '/usr/bin/procmail' : procmail
|
26
|
+
|
27
|
+
via :sendmail, location: "#{options[:location] || procmail} -f - #"
|
28
|
+
else
|
29
|
+
@via = name
|
30
|
+
@via_options = options
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def attachment (name, path)
|
35
|
+
@attachments[name] = File.read(File.expand_path(path))
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_hash
|
39
|
+
result = {}
|
40
|
+
|
41
|
+
@options.each {|name|
|
42
|
+
result[name.to_sym] = instance_variable_get("@#{name}") if instance_variable_get("@#{name}")
|
43
|
+
}
|
44
|
+
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
def send (data)
|
49
|
+
if !data.is_a?(Hash)
|
50
|
+
data = { :text => data.to_s }
|
51
|
+
end
|
52
|
+
|
53
|
+
Pony.mail(to_hash.merge(body: data[:text], html_body: data[:html]))
|
54
|
+
end
|
55
|
+
end
|
data/lib/herpes/event.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
class Herpes
|
12
|
+
|
13
|
+
class Event
|
14
|
+
def initialize (&block)
|
15
|
+
@data = {}
|
16
|
+
|
17
|
+
instance_eval &block
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing (id, *args)
|
21
|
+
id = id.to_s.sub(/[=?]$/, '').to_sym
|
22
|
+
|
23
|
+
if args.length == 0
|
24
|
+
@data[id]
|
25
|
+
else
|
26
|
+
if respond_to? "#{id}="
|
27
|
+
send "#{id}=", *args
|
28
|
+
else
|
29
|
+
value = (args.length > 1) ? args : args.first
|
30
|
+
|
31
|
+
if value.nil?
|
32
|
+
@data.delete(id)
|
33
|
+
else
|
34
|
+
@data[id] = value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_hash
|
41
|
+
@data.dup
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'forwardable'
|
12
|
+
|
13
|
+
class Numeric
|
14
|
+
def seconds
|
15
|
+
self
|
16
|
+
end; alias second seconds
|
17
|
+
|
18
|
+
def minutes
|
19
|
+
self * 60
|
20
|
+
end; alias minute minutes
|
21
|
+
|
22
|
+
def hours
|
23
|
+
self * 60.minutes
|
24
|
+
end; alias hour hours
|
25
|
+
|
26
|
+
def days
|
27
|
+
self * 24.hours
|
28
|
+
end; alias day days
|
29
|
+
end
|
30
|
+
|
31
|
+
class Object
|
32
|
+
def plain_accessor (*names)
|
33
|
+
names.each {|name|
|
34
|
+
define_singleton_method name do |*args|
|
35
|
+
if args.empty?
|
36
|
+
instance_variable_get "@#{name}"
|
37
|
+
else
|
38
|
+
value = (args.length > 1) ? args : args.first
|
39
|
+
|
40
|
+
if value.nil?
|
41
|
+
remove_instance_variable "@#{name}"
|
42
|
+
else
|
43
|
+
instance_variable_set "@#{name}", value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
define_singleton_method "#{name}?" do
|
49
|
+
instance_variable_get "@#{name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
define_singleton_method "#{name}!" do
|
53
|
+
instance_variable_set "@#{name}", true
|
54
|
+
end
|
55
|
+
|
56
|
+
define_singleton_method "no_#{name}!" do
|
57
|
+
instance_variable_set "@#{name}", false
|
58
|
+
end
|
59
|
+
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Module
|
65
|
+
def plain_accessor (*names)
|
66
|
+
names.each {|name|
|
67
|
+
define_method name do |*args|
|
68
|
+
if args.empty?
|
69
|
+
instance_variable_get "@#{name}"
|
70
|
+
else
|
71
|
+
value = (args.length > 1) ? args : args.first
|
72
|
+
|
73
|
+
if value.nil?
|
74
|
+
remove_instance_variable "@#{name}"
|
75
|
+
else
|
76
|
+
instance_variable_set "@#{name}", value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
define_method "#{name}?" do
|
82
|
+
instance_variable_get "@#{name}"
|
83
|
+
end
|
84
|
+
|
85
|
+
define_method "#{name}!" do
|
86
|
+
instance_variable_set "@#{name}", true
|
87
|
+
end
|
88
|
+
|
89
|
+
define_method "no_#{name}!" do
|
90
|
+
instance_variable_set "@#{name}", false
|
91
|
+
end
|
92
|
+
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
class Herpes
|
12
|
+
|
13
|
+
class Module
|
14
|
+
def self.all
|
15
|
+
@modules ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.[] (name)
|
19
|
+
return name if name.is_a?(Module)
|
20
|
+
|
21
|
+
Module.all.find { |mod| mod =~ name }
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.define (name, *aliases, &block)
|
25
|
+
Module.all << new(name, *aliases, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
extend Forwardable
|
29
|
+
|
30
|
+
attr_reader :name, :aliases, :owner
|
31
|
+
def_delegators :owner, :state, :workers
|
32
|
+
|
33
|
+
def initialize (name, *aliases, &block)
|
34
|
+
@name = name
|
35
|
+
@aliases = aliases
|
36
|
+
|
37
|
+
instance_eval &block
|
38
|
+
end
|
39
|
+
|
40
|
+
def =~ (other)
|
41
|
+
return true if self == other
|
42
|
+
|
43
|
+
name.to_s.downcase == other.to_s.downcase || aliases.any? { |a| a.to_s.downcase == other.to_s.downcase }
|
44
|
+
end
|
45
|
+
|
46
|
+
def default (&block)
|
47
|
+
block ? @default : @default = block
|
48
|
+
end
|
49
|
+
|
50
|
+
def with (&block)
|
51
|
+
block = default unless block
|
52
|
+
|
53
|
+
if !block
|
54
|
+
raise ArgumentError, 'no block passed and a default is not present'
|
55
|
+
end
|
56
|
+
|
57
|
+
clone.tap { |o| o.instance_eval &block }
|
58
|
+
end
|
59
|
+
|
60
|
+
def use (*)
|
61
|
+
raise NotImplementedError, 'you have to use a specialized module'
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
"#<#{self.class.name}(#{name}#{" [#{aliases.join ', '}]" unless aliases.empty?})>"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Generator < Module
|
70
|
+
plain_accessor :check_every
|
71
|
+
|
72
|
+
def initialize (*)
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
def use (owner, &block)
|
77
|
+
with(&block).tap {|o|
|
78
|
+
o.instance_eval {
|
79
|
+
@owner = owner
|
80
|
+
|
81
|
+
owned if respond_to? :owned
|
82
|
+
|
83
|
+
if respond_to? :check
|
84
|
+
@owner.every(check_every, &method(:check))
|
85
|
+
end
|
86
|
+
}
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def dispatch (event = nil, &block)
|
91
|
+
if block && !event
|
92
|
+
event = Event.new(&block)
|
93
|
+
end
|
94
|
+
|
95
|
+
raise ArgumentError, 'you did not pass an Event' unless event.is_a?(Event)
|
96
|
+
|
97
|
+
event.generated_by self
|
98
|
+
|
99
|
+
owner.dispatch(event)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Notifier < Module
|
104
|
+
def initialize (*)
|
105
|
+
@matchers = []
|
106
|
+
|
107
|
+
super
|
108
|
+
end
|
109
|
+
|
110
|
+
def on (*args, &block)
|
111
|
+
@matchers << Struct.new(:arguments, :block).new(args, block)
|
112
|
+
end
|
113
|
+
|
114
|
+
def use (owner, &block)
|
115
|
+
with(&block).tap {|o|
|
116
|
+
o.instance_eval {
|
117
|
+
@owner = owner
|
118
|
+
|
119
|
+
owned if respond_to? :owned
|
120
|
+
|
121
|
+
@matchers.each {|matcher|
|
122
|
+
@owner.on *matcher.arguments, &matcher.block
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
data/lib/herpes/rss.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'rss'
|
12
|
+
require 'open-uri'
|
13
|
+
|
14
|
+
Herpes::Generator.define :rss do
|
15
|
+
plain_accessor :digest
|
16
|
+
|
17
|
+
check_every 5.minutes
|
18
|
+
|
19
|
+
@tags = []
|
20
|
+
@rss = []
|
21
|
+
|
22
|
+
def tag (*tags, &block)
|
23
|
+
@tags.push tags
|
24
|
+
instance_eval &block
|
25
|
+
@tags.pop
|
26
|
+
end
|
27
|
+
|
28
|
+
def group (group, &block)
|
29
|
+
@group, tmp = group, @group
|
30
|
+
instance_eval &block
|
31
|
+
@group = tmp
|
32
|
+
end
|
33
|
+
|
34
|
+
def register (url, name = nil)
|
35
|
+
@rss << Struct.new(:url, :name, :tags, :group).new(url, name, @tags.flatten, @group)
|
36
|
+
end
|
37
|
+
|
38
|
+
def check
|
39
|
+
digest = [] if digest?
|
40
|
+
|
41
|
+
@rss.each {|r|
|
42
|
+
(state[:rss] ||= {})[r.url] ||= []
|
43
|
+
|
44
|
+
RSS::Parser.parse(open(r.url).read, false).tap {|p|
|
45
|
+
p.items.each {|item|
|
46
|
+
next if state[:rss][r.url].member? [item.date, item.title]
|
47
|
+
|
48
|
+
event = Herpes::Event.new {
|
49
|
+
tags r.tags
|
50
|
+
group r.group
|
51
|
+
name r.name
|
52
|
+
|
53
|
+
channel p.channel.dup
|
54
|
+
|
55
|
+
title item.title.dup
|
56
|
+
link item.link.dup
|
57
|
+
description item.description.dup
|
58
|
+
date item.date
|
59
|
+
}
|
60
|
+
|
61
|
+
if digest
|
62
|
+
digest.push(event)
|
63
|
+
else
|
64
|
+
dispatch(event)
|
65
|
+
end
|
66
|
+
|
67
|
+
state[:rss][r.url].push [item.date, item.title]
|
68
|
+
}
|
69
|
+
|
70
|
+
state[:rss][r.url].reject! {|(date, title)|
|
71
|
+
p.items.none? {|item|
|
72
|
+
item.date == date && item.title == title
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
if digest
|
79
|
+
dispatch Herpes::Event.new {
|
80
|
+
events digest
|
81
|
+
|
82
|
+
def to_a
|
83
|
+
events
|
84
|
+
end
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/herpes/version.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'actionpool'
|
12
|
+
|
13
|
+
class Workers
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def_delegators :@pool, :max, :max=, :min, :min=
|
17
|
+
|
18
|
+
def initialize (range = 2 .. 4)
|
19
|
+
@pool = ActionPool::Pool.new(:min_threads => range.begin, :max_threads => range.end)
|
20
|
+
end
|
21
|
+
|
22
|
+
def do (*args, &block)
|
23
|
+
@pool.process(*args, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/lib/herpes.rb
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'herpes/version'
|
12
|
+
require 'herpes/extensions'
|
13
|
+
require 'herpes/workers'
|
14
|
+
require 'herpes/event'
|
15
|
+
require 'herpes/module'
|
16
|
+
|
17
|
+
class Herpes
|
18
|
+
class Callback
|
19
|
+
attr_reader :time, :block, :last
|
20
|
+
|
21
|
+
def initialize (time, one_shot = false, &block)
|
22
|
+
raise ArgumentError, 'no block has been passed' unless block
|
23
|
+
|
24
|
+
@time = time
|
25
|
+
@one_shot = one_shot
|
26
|
+
@block = block
|
27
|
+
|
28
|
+
if !one_shot?
|
29
|
+
@last = Time.now - time
|
30
|
+
else
|
31
|
+
called!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def one_shot?
|
36
|
+
@one_shot
|
37
|
+
end
|
38
|
+
|
39
|
+
def next_in
|
40
|
+
time - (Time.now - last)
|
41
|
+
end
|
42
|
+
|
43
|
+
def gonna_call!
|
44
|
+
@calling = :gonna
|
45
|
+
end
|
46
|
+
|
47
|
+
def gonna_call?
|
48
|
+
@calling == :gonna
|
49
|
+
end
|
50
|
+
|
51
|
+
def calling!
|
52
|
+
@calling = :gonna
|
53
|
+
end
|
54
|
+
|
55
|
+
def called!
|
56
|
+
@last = Time.now
|
57
|
+
@calling = false
|
58
|
+
end
|
59
|
+
|
60
|
+
def calling?
|
61
|
+
@calling == true
|
62
|
+
end
|
63
|
+
|
64
|
+
def call (*args, &block)
|
65
|
+
return if calling?
|
66
|
+
|
67
|
+
calling!
|
68
|
+
@block.call(*args, &block)
|
69
|
+
called!
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.load (*path)
|
74
|
+
new.load(*path)
|
75
|
+
end
|
76
|
+
|
77
|
+
attr_reader :workers, :modules
|
78
|
+
|
79
|
+
def initialize
|
80
|
+
@modules = []
|
81
|
+
@workers = Workers.new
|
82
|
+
@pipes = IO.pipe
|
83
|
+
|
84
|
+
@before = Hash.new { |h, k| h[k] = [] }
|
85
|
+
@matchers = Hash.new { |h, k| h[k] = [] }
|
86
|
+
@after = Hash.new { |h, k| h[k] = [] }
|
87
|
+
|
88
|
+
@callbacks = []
|
89
|
+
end
|
90
|
+
|
91
|
+
def state (path = nil)
|
92
|
+
if path && path != @path
|
93
|
+
@path = File.expand_path(path)
|
94
|
+
@state = Marshal.load(File.read(@path)) rescue nil
|
95
|
+
else
|
96
|
+
@state ||= {}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def save
|
101
|
+
return unless @state && @path
|
102
|
+
|
103
|
+
dump = Marshal.dump(@state)
|
104
|
+
|
105
|
+
File.open(@path, 'wb') { |f| f.write(dump) }
|
106
|
+
end
|
107
|
+
|
108
|
+
def load (*paths)
|
109
|
+
paths.each {|path|
|
110
|
+
instance_eval File.read(path), path, 1
|
111
|
+
}
|
112
|
+
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
def with (name, &block)
|
117
|
+
raise ArgumentError, "#{name} not found" unless Module[name]
|
118
|
+
|
119
|
+
Module[name].with(&block)
|
120
|
+
end
|
121
|
+
|
122
|
+
def use (name, &block)
|
123
|
+
raise ArgumentError, "#{name} not found" unless Module[name]
|
124
|
+
|
125
|
+
@modules << Module[name].use(self, &block)
|
126
|
+
end
|
127
|
+
|
128
|
+
def from (name, &block)
|
129
|
+
return unless block
|
130
|
+
|
131
|
+
@current, tmp = name, @current
|
132
|
+
result = instance_eval &block
|
133
|
+
@current = tmp
|
134
|
+
result
|
135
|
+
end
|
136
|
+
|
137
|
+
def before (&block)
|
138
|
+
@before[@current] = block
|
139
|
+
end
|
140
|
+
|
141
|
+
def on (matcher, &block)
|
142
|
+
return unless block
|
143
|
+
|
144
|
+
@matchers[@current] << Struct.new(:matcher, :block).new(matcher, block)
|
145
|
+
end
|
146
|
+
|
147
|
+
def after (&block)
|
148
|
+
@after[@current] = block
|
149
|
+
end
|
150
|
+
|
151
|
+
def dispatch (event = nil, &block)
|
152
|
+
if block && !event
|
153
|
+
event = Event.new(&block)
|
154
|
+
end
|
155
|
+
|
156
|
+
raise ArgumentError, 'you did not pass an Event' unless event.is_a?(Event)
|
157
|
+
|
158
|
+
@before.each {|name, block|
|
159
|
+
next unless name.nil? || (event.generated_by && event.generated_by =~ name)
|
160
|
+
|
161
|
+
block.call(event)
|
162
|
+
}
|
163
|
+
|
164
|
+
@matchers.each {|name, matchers|
|
165
|
+
next unless name.nil? || (event.generated_by && event.generated_by =~ name)
|
166
|
+
|
167
|
+
dispatched = false
|
168
|
+
|
169
|
+
matchers.each {|m|
|
170
|
+
begin
|
171
|
+
dispatched = true
|
172
|
+
|
173
|
+
m.block.call(event)
|
174
|
+
end if
|
175
|
+
(m.matcher == :anything) ||
|
176
|
+
(m.matcher == :anything_else && !dispatched) ||
|
177
|
+
(m.matcher.respond_to?(:call) && m.matcher.call(event))
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
@after.each {|name, block|
|
182
|
+
next unless name.nil? || (event.generated_by && event.generated_by =~ name)
|
183
|
+
|
184
|
+
block.call(event)
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
def every (time, &block)
|
189
|
+
@callbacks << Callback.new(time, &block)
|
190
|
+
wake_up
|
191
|
+
end
|
192
|
+
|
193
|
+
def after (time, &block)
|
194
|
+
@callbacks << Callback.new(time, true, &block)
|
195
|
+
wake_up
|
196
|
+
end
|
197
|
+
|
198
|
+
def until_next
|
199
|
+
next_in = @callbacks.min_by(&:next_in).next_in
|
200
|
+
|
201
|
+
next_in > 0 ? next_in : 0
|
202
|
+
rescue
|
203
|
+
10
|
204
|
+
end
|
205
|
+
|
206
|
+
def sleep (time)
|
207
|
+
@pipes.first.read_nonblock 1337 rescue nil
|
208
|
+
|
209
|
+
IO.select([@pipes.first], nil, nil, time)
|
210
|
+
end
|
211
|
+
|
212
|
+
def wake_up
|
213
|
+
@pipes.last.write 'x'
|
214
|
+
end
|
215
|
+
|
216
|
+
def running?; !!@running; end
|
217
|
+
def stopped?; !@running; end
|
218
|
+
|
219
|
+
def start!
|
220
|
+
@running = true
|
221
|
+
|
222
|
+
while running?
|
223
|
+
sleep until_next
|
224
|
+
|
225
|
+
@callbacks.select {|callback|
|
226
|
+
callback.next_in <= 0
|
227
|
+
}.uniq.each {|callback|
|
228
|
+
@callbacks.delete(callback) if callback.one_shot?
|
229
|
+
|
230
|
+
next if callback.gonna_call?
|
231
|
+
|
232
|
+
callback.gonna_call!
|
233
|
+
|
234
|
+
workers.do {
|
235
|
+
callback.call
|
236
|
+
}
|
237
|
+
}
|
238
|
+
end
|
239
|
+
|
240
|
+
save
|
241
|
+
end
|
242
|
+
|
243
|
+
def stop!
|
244
|
+
@running = false;
|
245
|
+
wake_up
|
246
|
+
end
|
247
|
+
end
|
metadata
CHANGED
@@ -1,26 +1,47 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: herpes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- meh.
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-11-
|
13
|
-
dependencies:
|
12
|
+
date: 2011-11-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: actionpool
|
16
|
+
requirement: &10434500 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *10434500
|
14
25
|
description:
|
15
26
|
email: meh@paranoici.org
|
16
|
-
executables:
|
27
|
+
executables:
|
28
|
+
- herpes
|
17
29
|
extensions: []
|
18
30
|
extra_rdoc_files: []
|
19
31
|
files:
|
20
32
|
- README.md
|
33
|
+
- bin/herpes
|
34
|
+
- examples/generator.rb
|
21
35
|
- examples/rss.rb
|
22
36
|
- herpes.gemspec
|
37
|
+
- lib/herpes.rb
|
38
|
+
- lib/herpes/email.rb
|
39
|
+
- lib/herpes/event.rb
|
40
|
+
- lib/herpes/extensions.rb
|
41
|
+
- lib/herpes/module.rb
|
42
|
+
- lib/herpes/rss.rb
|
23
43
|
- lib/herpes/version.rb
|
44
|
+
- lib/herpes/workers.rb
|
24
45
|
homepage: http://github.com/meh/herpes
|
25
46
|
licenses: []
|
26
47
|
post_install_message:
|
@@ -36,9 +57,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
57
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
58
|
none: false
|
38
59
|
requirements:
|
39
|
-
- - ! '
|
60
|
+
- - ! '>='
|
40
61
|
- !ruby/object:Gem::Version
|
41
|
-
version:
|
62
|
+
version: '0'
|
42
63
|
requirements: []
|
43
64
|
rubyforge_project:
|
44
65
|
rubygems_version: 1.8.10
|