herpes 0.0.1a1 → 0.0.1
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.
- 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
|