herald 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +72 -59
- data/Rakefile +1 -1
- data/TODO.txt +5 -3
- data/herald.gemspec +15 -5
- data/lib/herald.rb +32 -12
- data/lib/herald/notifiers/callback.rb +5 -1
- data/lib/herald/notifiers/growl.rb +19 -15
- data/lib/herald/notifiers/ping.rb +14 -33
- data/lib/herald/notifiers/post.rb +31 -19
- data/lib/herald/untitled.txt +5 -0
- data/lib/herald/version.rb +1 -1
- data/lib/herald/watcher.rb +17 -15
- data/lib/herald/watchers/rss.rb +12 -5
- data/lib/herald/watchers/twitter.rb +6 -5
- data/lib/herald/watchers/website.rb +95 -0
- data/test/herald_test.rb +3 -9
- data/test/mocks/rss.xml +19 -0
- data/test/mocks/web.html +17 -0
- data/test/notifier_callback_test.rb +29 -0
- data/test/notifier_growl_test.rb +22 -0
- data/test/notifier_ping_test.rb +30 -0
- data/test/notifier_post_test.rb +30 -0
- data/test/notifier_stdout_test.rb +26 -0
- data/test/notifier_test.rb +38 -0
- data/test/watcher_rss_test.rb +59 -0
- data/test/watcher_test.rb +74 -0
- data/test/watcher_twitter_test.rb +17 -0
- data/test/watcher_website_test.rb +33 -0
- metadata +56 -25
- data/lib/herald/watchers/imap.rb +0 -55
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2011 Luke Duncalfe
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Herald
|
2
2
|
====
|
3
3
|
|
4
|
-
Herald is a simple notifier for Twitter
|
4
|
+
Herald is a simple notifier for Twitter and RSS.
|
5
5
|
|
6
|
-
Pass Herald some keywords and sources to watch, and Herald will notify you using Growl,
|
6
|
+
Pass Herald some keywords and sources to watch, and Herald will notify you using Growl, pinging or posting to a site, or running Ruby code as soon as those keywords appear.
|
7
7
|
|
8
8
|
Installation
|
9
9
|
------------
|
@@ -16,34 +16,34 @@ Installation
|
|
16
16
|
|
17
17
|
git clone git://github.com/lukes/herald.git
|
18
18
|
gem build herald.gemspec
|
19
|
-
gem install herald
|
19
|
+
gem install herald-0.2.gem
|
20
20
|
|
21
21
|
Usage
|
22
22
|
-----
|
23
23
|
|
24
|
-
First step is `require` Herald into your Ruby project
|
24
|
+
First step is to `require` Herald into your Ruby project
|
25
25
|
|
26
26
|
require 'rubygems'
|
27
27
|
require 'herald'
|
28
28
|
|
29
|
-
Then, to watch for tweets containing "soundofmusic"
|
29
|
+
Then, to watch for tweets containing "soundofmusic"
|
30
30
|
|
31
31
|
Herald.watch do
|
32
32
|
check :twitter
|
33
33
|
_for "soundofmusic"
|
34
34
|
end
|
35
35
|
|
36
|
-
Or an RSS feed
|
36
|
+
Or an RSS feed
|
37
37
|
|
38
38
|
Herald.watch do
|
39
39
|
check :rss, :from => "http://example.com/.rss"
|
40
40
|
_for "soundofmusic"
|
41
41
|
end
|
42
|
-
|
43
|
-
Or
|
42
|
+
|
43
|
+
Or text in a website
|
44
44
|
|
45
45
|
Herald.watch do
|
46
|
-
check :
|
46
|
+
check :website, :from => "http://example.com"
|
47
47
|
_for "soundofmusic"
|
48
48
|
end
|
49
49
|
|
@@ -78,7 +78,7 @@ Swap in another action by passing Herald one of the following `action` parameter
|
|
78
78
|
|
79
79
|
[Growl](http://growl.info/) is a notification system for Mac OS X.
|
80
80
|
|
81
|
-
To use Growl, enable "Listen for incoming notifications" and "Allow remote application registration" on the [Network tab](http://growl.info/documentation/exploring-preferences.php) of the Growl Preference Panel, and pass Herald `action :growl
|
81
|
+
To use Growl, enable "Listen for incoming notifications" and "Allow remote application registration" on the [Network tab](http://growl.info/documentation/exploring-preferences.php) of the Growl Preference Panel, and pass Herald `action :growl`
|
82
82
|
|
83
83
|
Herald.watch_twitter do
|
84
84
|
_for "nz", "#election"
|
@@ -87,25 +87,25 @@ To use Growl, enable "Listen for incoming notifications" and "Allow remote appli
|
|
87
87
|
|
88
88
|
#### Ping
|
89
89
|
|
90
|
-
To ping a URI, pass Herald `action :ping, :uri => "http://address.to.ping"
|
90
|
+
To ping a URI, pass Herald `action :ping, :uri => "http://address.to.ping"`
|
91
91
|
|
92
92
|
Herald.watch_twitter do
|
93
|
-
_for "#
|
94
|
-
action :ping, :uri => "http://
|
93
|
+
_for "#fundamentalists", "#in", "#space"
|
94
|
+
action :ping, :uri => "http://armageddon-clock.com/+1"
|
95
95
|
end
|
96
96
|
|
97
97
|
#### Post
|
98
98
|
|
99
|
-
To post information about what Herald finds to a URI, pass Herald `action :post, :uri => "http://address.to.post.to"
|
99
|
+
To post information about what Herald finds to a URI, pass Herald `action :post, :uri => "http://address.to.post.to"`
|
100
100
|
|
101
101
|
Herald.watch_twitter do
|
102
|
-
_for "
|
103
|
-
action :post, :uri => "http://
|
102
|
+
_for "vanity", "tweeting"
|
103
|
+
action :post, :uri => "http://twitter-loves-me.com/post"
|
104
104
|
end
|
105
105
|
|
106
106
|
#### Callbacks
|
107
107
|
|
108
|
-
If you'd like to do your own thing entirely each time a keyword appears, pass a callback in the form of a Ruby block
|
108
|
+
If you'd like to do your own thing entirely each time a keyword appears, pass a callback in the form of a Ruby block
|
109
109
|
|
110
110
|
Herald.watch_twitter do
|
111
111
|
_for "revolution"
|
@@ -113,55 +113,31 @@ If you'd like to do your own thing entirely each time a keyword appears, pass a
|
|
113
113
|
`say "Viva!"`
|
114
114
|
end
|
115
115
|
end
|
116
|
+
|
116
117
|
|
117
|
-
|
118
|
-
|
119
|
-
By default Herald will sleep for 1 minute after checking each of the sources independently.
|
120
|
-
To set a different sleep time:
|
118
|
+
Pass the callback a parameter to be given a `Herald::Item` object within your block. The object makes available everything Herald could find in the source.
|
121
119
|
|
122
120
|
Herald.watch_twitter do
|
123
|
-
_for "
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
### Look Once
|
128
|
-
|
129
|
-
Rather than watching, if you just want to get a single poll of keywords, use `once()`. All the same parameters as with `watch()` can be used (except `every`).
|
130
|
-
|
131
|
-
Herald.once do
|
132
|
-
check :twitter
|
133
|
-
_for "#herald"
|
121
|
+
_for "revolution"
|
122
|
+
action do |item|
|
123
|
+
puts item.data # full Hash of all data
|
124
|
+
end
|
134
125
|
end
|
135
|
-
|
136
|
-
### Callback Scope and Herald Metaprogramming
|
137
126
|
|
138
|
-
|
127
|
+
### Timer
|
139
128
|
|
140
|
-
|
129
|
+
By default Herald will sleep for 1 minute after checking each of the sources independently.
|
130
|
+
To set a different sleep time
|
141
131
|
|
142
132
|
Herald.watch_twitter do
|
143
|
-
_for "
|
144
|
-
|
145
|
-
puts instance_variables
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
If passed in within the scope of `Herald::Watcher`, it will have access to the particular `Watcher`'s methods and instance variables:
|
150
|
-
|
151
|
-
Herald.watch do
|
152
|
-
check :twitter do
|
153
|
-
_for "#breaking", "news"
|
154
|
-
action do
|
155
|
-
puts instance_variables
|
156
|
-
end
|
157
|
-
end
|
133
|
+
_for "soundofmusic"
|
134
|
+
every 30 => "seconds" # or "minutes", "hours", or "days"
|
158
135
|
end
|
159
136
|
|
160
137
|
### For inquisitive minds
|
161
138
|
|
162
139
|
Assign the herald watcher to a variable
|
163
140
|
|
164
|
-
require 'herald'
|
165
141
|
herald = Herald.watch_rss :from => "http://www.reddit.com/r/pics/new/.rss" do
|
166
142
|
_for "imgur"
|
167
143
|
end
|
@@ -173,7 +149,7 @@ Edit your herald instance while it's working
|
|
173
149
|
herald.watchers.first.keywords << "cats"
|
174
150
|
herald.watchers.first.action { puts "callback" }
|
175
151
|
|
176
|
-
Stop and start herald
|
152
|
+
Stop and start the herald
|
177
153
|
|
178
154
|
herald.stop
|
179
155
|
herald.alive? # => false
|
@@ -183,15 +159,52 @@ Start a second herald
|
|
183
159
|
|
184
160
|
herald_the_second = Herald.watch_twitter { _for "#herald" }
|
185
161
|
|
186
|
-
Use `Herald` class methods to inspect and edit heralds as a batch
|
162
|
+
Use the `Herald` class methods to inspect and edit your heralds as a batch
|
187
163
|
|
188
|
-
Herald.heralds # prints
|
189
|
-
Herald.
|
190
|
-
Herald.stop # both heralds stopped
|
164
|
+
Herald.heralds # prints all running heralds
|
165
|
+
Herald.stop # all heralds stopped
|
191
166
|
Herald.alive? # => false
|
192
|
-
Herald.start #
|
167
|
+
Herald.start # all heralds restarted
|
168
|
+
|
169
|
+
### Look once
|
170
|
+
|
171
|
+
Rather than watching, if you just want to get a single poll of keywords, use `once()`. All the same parameters as with `watch()` can be used (`every` will be ignored)
|
172
|
+
|
173
|
+
herald = Herald.once do
|
174
|
+
check :twitter
|
175
|
+
_for "#herald"
|
176
|
+
end
|
177
|
+
|
178
|
+
As with watching, Herald will fork a new process (or one for every source you're `check`ing), but unlike with watching, Herald will block and wait for the process to finish
|
179
|
+
|
180
|
+
herald.start # waiting ... process ends at the same time you receive your notifications
|
181
|
+
herald.alive? # => false
|
182
|
+
|
183
|
+
### A bit about callback scope and Herald metaprogramming
|
184
|
+
|
185
|
+
Callbacks allow a great deal of reflection into the internals of Herald.
|
186
|
+
|
187
|
+
If the callback is passed with the scope of `Herald`, it will have access to the `Herald` methods and instance variables
|
188
|
+
|
189
|
+
Herald.watch_twitter do
|
190
|
+
_for "#breaking", "news"
|
191
|
+
action do
|
192
|
+
puts instance_variables
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
If passed in within the scope of `Herald::Watcher` (`action` agents), it will have access to the particular `Watcher`'s methods and instance variables
|
197
|
+
|
198
|
+
Herald.watch do
|
199
|
+
check :twitter do
|
200
|
+
_for "#breaking", "news"
|
201
|
+
action do
|
202
|
+
puts instance_variables
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
193
206
|
|
194
|
-
### Herald
|
207
|
+
### Herald binary [Not Implemented]
|
195
208
|
|
196
209
|
herald -watch twitter -for #eqnz
|
197
210
|
herald -once twitter -for #herald
|
data/Rakefile
CHANGED
data/TODO.txt
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
- Best way to require gems? Can I vendorise crack gem?
|
4
4
|
|
5
|
-
- Suppress Growl text on test()
|
6
|
-
|
7
5
|
- Comment in RDOC
|
8
6
|
|
9
7
|
- Handle exceptions in processes, by reporting the error, then set the @subprocess to nil
|
10
8
|
|
11
9
|
- Look at Gemspec
|
12
|
-
- Show dependency on ruby-growl if installed for mac
|
10
|
+
- Show dependency on ruby-growl if installed for mac
|
11
|
+
|
12
|
+
- Growl icon
|
13
|
+
|
14
|
+
- Custom Error classes
|
data/herald.gemspec
CHANGED
@@ -10,15 +10,25 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.email = ["lduncalfe@eml.cc"]
|
11
11
|
s.homepage = "http://github.com/lukes/herald"
|
12
12
|
s.summary = %q{A simple notifier for Twitter, RSS, or email}
|
13
|
-
s.description = %q{
|
13
|
+
s.description = %q{A simple and flexible notifier for Twitter, RSS, or email}
|
14
14
|
|
15
|
-
s.required_rubygems_version = ">= 1.3.6" # TODO test earliest dependency
|
16
|
-
s.add_development_dependency
|
17
|
-
s.add_dependency
|
18
|
-
s.
|
15
|
+
# s.required_rubygems_version = ">= 1.3.6" # TODO test earliest dependency
|
16
|
+
s.add_development_dependency("minitest")
|
17
|
+
s.add_dependency("crack")
|
18
|
+
s.add_dependency("hpricot", "~> 0.8")
|
19
|
+
# if gem is being installed on a mac, add a dependency on ruby-growl.
|
20
|
+
# note, this is not a fool-proof method of detecting the OS,
|
21
|
+
# apparently if return "java" if platform is JRuby
|
22
|
+
if RUBY_PLATFORM.downcase.include?("darwin")
|
23
|
+
s.add_dependency("ruby-growl", "~> 3.0")
|
24
|
+
end
|
25
|
+
|
26
|
+
s.rubyforge_project = "herald"
|
27
|
+
s.extra_rdoc_files = ["README.md", "LICENSE"]
|
19
28
|
|
20
29
|
s.files = `git ls-files`.split("\n")
|
21
30
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
22
31
|
# s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
32
|
+
# s.default_executable = "herald"
|
23
33
|
s.require_paths = ["lib"]
|
24
34
|
end
|
data/lib/herald.rb
CHANGED
@@ -1,7 +1,4 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
#require 'json'
|
3
|
-
gem 'crack'
|
4
|
-
require 'crack'
|
5
2
|
|
6
3
|
require 'herald/watcher'
|
7
4
|
require 'herald/notifier'
|
@@ -11,6 +8,7 @@ require 'herald/item'
|
|
11
8
|
class Herald
|
12
9
|
|
13
10
|
@@heralds = []
|
11
|
+
|
14
12
|
attr_accessor :watchers, :keep_alive, :subprocess
|
15
13
|
|
16
14
|
def self.watch(&block)
|
@@ -31,6 +29,9 @@ class Herald
|
|
31
29
|
def self.watch_rss(options = {}, &block)
|
32
30
|
watch() { check(:rss, options, &block) }
|
33
31
|
end
|
32
|
+
def self.watch_website(options = {}, &block)
|
33
|
+
watch() { check(:website, options, &block) }
|
34
|
+
end
|
34
35
|
|
35
36
|
# batch methods
|
36
37
|
def self.start
|
@@ -88,7 +89,9 @@ class Herald
|
|
88
89
|
|
89
90
|
# create a new Watcher
|
90
91
|
def check(type, options = {}, &block)
|
91
|
-
|
92
|
+
options[:keep_alive] ||= @keep_alive
|
93
|
+
@watchers << Herald::Watcher.new(type, options, &block)
|
94
|
+
self
|
92
95
|
end
|
93
96
|
|
94
97
|
# send keywords to Watchers
|
@@ -96,6 +99,7 @@ class Herald
|
|
96
99
|
@watchers.each do |watcher|
|
97
100
|
watcher._for(*keywords)
|
98
101
|
end
|
102
|
+
self
|
99
103
|
end
|
100
104
|
|
101
105
|
# send instructions to Watchers
|
@@ -103,6 +107,7 @@ class Herald
|
|
103
107
|
@watchers.each do |watcher|
|
104
108
|
watcher.action(type, options, &block)
|
105
109
|
end
|
110
|
+
self
|
106
111
|
end
|
107
112
|
|
108
113
|
# send sleep time to Watchers
|
@@ -110,6 +115,7 @@ class Herald
|
|
110
115
|
@watchers.each do |watcher|
|
111
116
|
watcher.every(time)
|
112
117
|
end
|
118
|
+
self
|
113
119
|
end
|
114
120
|
|
115
121
|
# start Watchers
|
@@ -134,9 +140,18 @@ class Herald
|
|
134
140
|
if @keep_alive
|
135
141
|
Process.detach(@subprocess)
|
136
142
|
else
|
137
|
-
|
138
|
-
|
139
|
-
|
143
|
+
begin
|
144
|
+
# wait before the end of this script
|
145
|
+
# for all watchers to finish their jobs
|
146
|
+
Process.waitpid(@subprocess)
|
147
|
+
# if @subprocess PID does not exist, it will
|
148
|
+
# be due to an error in the subprocess
|
149
|
+
# which has terminated it and waitpid()
|
150
|
+
# will throw an exception
|
151
|
+
rescue Errno::ECHILD => e
|
152
|
+
# do nothing
|
153
|
+
end
|
154
|
+
@subprocess = nil # signal unalive state
|
140
155
|
end
|
141
156
|
self # return instance object
|
142
157
|
end
|
@@ -145,7 +160,16 @@ class Herald
|
|
145
160
|
# or have watchers do cleanup tasks on exit?
|
146
161
|
# look at GOD
|
147
162
|
def stop
|
148
|
-
|
163
|
+
if @subprocess
|
164
|
+
begin
|
165
|
+
Process.kill("TERM", @subprocess)
|
166
|
+
# if @subprocess PID does not exist,
|
167
|
+
# this will be due to an error in the subprocess
|
168
|
+
# which has terminated it
|
169
|
+
rescue Errno::ESRCH => e
|
170
|
+
# do nothing
|
171
|
+
end
|
172
|
+
end
|
149
173
|
@subprocess = nil
|
150
174
|
self
|
151
175
|
end
|
@@ -155,10 +179,6 @@ class Herald
|
|
155
179
|
def alive?
|
156
180
|
!!@subprocess
|
157
181
|
end
|
158
|
-
|
159
|
-
def to_s
|
160
|
-
"#{alive? ? 'Alive' : 'Stopped'}Alive"
|
161
|
-
end
|
162
182
|
|
163
183
|
private
|
164
184
|
|
@@ -4,27 +4,31 @@ class Herald
|
|
4
4
|
|
5
5
|
module Growl
|
6
6
|
|
7
|
-
|
8
|
-
def parse_options(options); end
|
7
|
+
@@notification_type = "Herald Notification"
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# TODO throw custom exception
|
15
|
-
raise "ruby-growl gem is not installed. Run:\n\tsudo gem install ruby-growl"
|
9
|
+
def self.extended(base)
|
10
|
+
Herald.lazy_load('ruby-growl')
|
11
|
+
class << base
|
12
|
+
attr_accessor :growl, :sticky
|
16
13
|
end
|
17
14
|
end
|
18
15
|
|
16
|
+
def parse_options(options)
|
17
|
+
@sticky = options.delete(:sticky) || false
|
18
|
+
@priority = options.delete(:priority) || 1
|
19
|
+
host = options.delete(:host) || "localhost"
|
20
|
+
pass = options.delete(:pass) || nil
|
21
|
+
@growl = ::Growl.new(host, "Herald", [@@notification_type], [@@notification_type], pass)
|
22
|
+
end
|
23
|
+
|
24
|
+
# no tests for Growl
|
25
|
+
def test; end
|
26
|
+
|
19
27
|
# send a Growl notification
|
20
28
|
def notify(item)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# TODO escape characters in title and message
|
25
|
-
item.title.gsub!("'", '')
|
26
|
-
item.message.gsub!("'", '')
|
27
|
-
if !system("growl -H localhost -t '#{item.title}' -m '#{item.message}'")
|
29
|
+
begin
|
30
|
+
@growl.notify(@@notification_type, item.title, item.message, @priority, @sticky)
|
31
|
+
rescue Errno::ECONNREFUSED => e
|
28
32
|
# TODO throw custom exception
|
29
33
|
raise "Growl settings not configured to allow remote application registration. See Growl website docs: http://growl.info/documentation/exploring-preferences.php"
|
30
34
|
end
|