wase_endpoint 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ *.log
@@ -0,0 +1,53 @@
1
+ = WaseEndpoint
2
+
3
+ WaseEndpoint is a library for building daemons that act as WASE Endpoints for the EngineYard Wase competition: http://bit.ly/3qRMbv
4
+
5
+ == Install
6
+
7
+ WaseEndpoint is hosted by http://gemcutter.com. Please make sure you have added them to your gem sources.
8
+
9
+ $ sudo gem install WaseEndpoint
10
+
11
+ == Usage
12
+
13
+ The following are all in the example directory.
14
+
15
+ Your endpoint logic:
16
+
17
+ # example/my_endpoint.rb
18
+
19
+ require 'rubygems'
20
+ require 'wase_endpoint'
21
+
22
+ class MyEndpoint < WaseEndpoint
23
+
24
+ # Just return our json as it came in.
25
+ def secret_sauce(raw_json)
26
+ raw_json
27
+ end
28
+
29
+ end
30
+
31
+ The init file:
32
+
33
+ # example/init.rb
34
+
35
+ require 'my_endpoint'
36
+
37
+ MyEndpoint.new( :username => 'twitter_username',
38
+ :password => 'twitter_password',
39
+ :logfile => 'my_endpoint.log',
40
+ :sleep_period => 60 )
41
+
42
+ That's it! I also included a basic sinatra server in 'server.rb' that can be used as an input/output/program-listing node.
43
+
44
+ == Problems, Comments, Suggestions?
45
+
46
+ Issues can be tracked on github:
47
+
48
+ All of the above are most welcome. mailto:dougal.s@gmail.com
49
+
50
+
51
+ == Credits
52
+
53
+ Douglas F Shearer - http://douglasfshearer.com
@@ -0,0 +1,24 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ task :default => :spec
5
+
6
+ desc "Run all specs"
7
+ Spec::Rake::SpecTask.new('spec') do |t|
8
+ t.spec_files = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+ begin
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gemspec|
14
+ gemspec.name = "wase_endpoint"
15
+ gemspec.summary = "WaseEndpoint is a library for building daemons that act as WASE Endpoints for http://bit.ly/3qRMbv"
16
+ gemspec.description = gemspec.summary
17
+ gemspec.email = "dougal.s@gmail.com"
18
+ gemspec.homepage = "http://github.com/dougal/wase_endpoint"
19
+ gemspec.authors = ["Douglas F Shearer"]
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
24
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,6 @@
1
+ require 'my_endpoint'
2
+
3
+ MyEndpoint.new( :username => 'twitter_username',
4
+ :password => 'twitter_password',
5
+ :logfile => 'my_endpoint.log',
6
+ :sleep_period => 60 )
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'wase_endpoint'
3
+
4
+ class MyEndpoint < WaseEndpoint
5
+
6
+ # Just return our json as it came in.
7
+ def secret_sauce(raw_json)
8
+ raw_json
9
+ end
10
+
11
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'sinatra'
3
+
4
+ get '/' do
5
+ # Pass back some json data.
6
+ "[1,2,3]"
7
+ end
8
+
9
+ put '/' do
10
+ # Print out the data.
11
+ puts params.inspect
12
+ end
13
+
14
+ get '/listing' do
15
+ "[\"@twitter_id_1\",\"@twitter_id_2\"]"
16
+ end
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'twitter'
3
+ require 'json'
4
+ require 'rest_client'
5
+ require 'robustthread'
6
+
7
+ require 'wase_endpoint/message'
8
+ require 'wase_endpoint/twitterer'
9
+
10
+ class WaseEndpoint
11
+
12
+ def initialize(options={})
13
+ @logger = Logger.new(options[:logfile])
14
+ @twitterer = Twitterer.new(options[:username], options[:password])
15
+ @sleep_period = options[:sleep_period] || 60
16
+
17
+ start
18
+ end
19
+
20
+ # Override this.
21
+ # Argument is a json encoded string.
22
+ # Should return a json encoded string.
23
+ def secret_sauce(raw_json)
24
+ raise Exception, 'You need to override secret_sauce'
25
+ end
26
+
27
+ protected
28
+
29
+ def logger
30
+ @logger
31
+ end
32
+
33
+ def start
34
+ RobustThread.logger = @logger
35
+ pid = fork do
36
+ RobustThread.loop(:seconds => @sleep_period, :label => 'Checking for new messages and processing them') do
37
+
38
+ # Grab any new messages.
39
+ messages = @twitterer.fetch
40
+ @logger.info "Processing #{messages.size} messages"
41
+
42
+ messages.each do |message|
43
+ program_listing = message.fetch_program_listing
44
+ if message.program_counter >= program_listing.size
45
+ throw Exception 'Program counter has gone too far'
46
+ end
47
+
48
+ # Here's where your magic happens.
49
+ message.send_input(secret_sauce(message.fetch_output))
50
+
51
+ # Tell the next endpoint.
52
+ @twitterer.send(program_listing[message.program_counter + 1], message.program_counter + 1, message.program_listing_uri, message.output_uri)
53
+ end
54
+
55
+ end
56
+ sleep
57
+ end
58
+ puts pid
59
+ Process.detach pid
60
+ end
61
+
62
+ end
@@ -0,0 +1,43 @@
1
+ class WaseEndpoint
2
+
3
+ class Message
4
+
5
+ attr_reader :program_counter, :program_listing_uri, :timestamp, :output_uri, :input_uri, :input_uri_1, :id
6
+
7
+ def initialize(id, raw_message)
8
+ myname_and_hashtag, program_counter, program_listing_uri, unix_timestamp, output_uri, input_uri, input_uri_1 = raw_message.split(/,/)
9
+
10
+ @id = id
11
+ @program_counter = program_counter.to_i
12
+ @program_listing_uri = program_listing_uri.strip
13
+ @timestamp = unix_timestamp.to_i
14
+ @output_uri = output_uri.strip
15
+ @input_uri = input_uri.strip if input_uri
16
+ @input_uri_1 = input_uri_1.strip if input_uri_1
17
+ end
18
+
19
+ def ==(other)
20
+ @id == other.id
21
+ end
22
+
23
+ def fetch_program_listing
24
+ JSON.parse(RestClient.get('http://' + @program_listing_uri))
25
+ end
26
+
27
+ def fetch_output
28
+ RestClient.get('http://' + @output_uri)
29
+ end
30
+
31
+ def send_input(input)
32
+ input_uri = 'http://' + (@input_uri || @output_uri)
33
+
34
+ # RestClient can't follow a redirect for put, so we'll expand it.
35
+ input_uri[/bit\.ly(\/\w+)/]
36
+ expanded_input_uri = Net::HTTP.new('bit.ly').head($1)['Location']
37
+
38
+ RestClient.put(expanded_input_uri, input)
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,37 @@
1
+ class WaseEndpoint
2
+ class Twitterer
3
+
4
+ def initialize(username, password)
5
+ twitter_http_auth = Twitter::HTTPAuth.new(username, password)
6
+ @twitter_client = Twitter::Base.new(twitter_http_auth)
7
+
8
+ # The oldest message ID could be stored here, but since twitter IDs
9
+ # aren't always time-linear, comparing the messages is safer.
10
+ @all_messages = []
11
+ end
12
+
13
+ # Fetches the timeline and returns any new messages.
14
+ def fetch
15
+ new_messages = []
16
+
17
+ # Ignore any messages that don't have the hashtag.
18
+ # Use twitter search to do this instead?
19
+ @twitter_client.replies.reject{|m| !m.text[/#wase/]}.each do |reply|
20
+ message = Message.new(reply.id, reply.text)
21
+
22
+ # Skip if we've already processed this message.
23
+ unless @all_messages.include?(message)
24
+ @all_messages << message
25
+ new_messages << message
26
+ end
27
+ end
28
+ new_messages
29
+ end
30
+
31
+ def send(recipient, program_counter, program_list_uri, output_uri, input_uri=nil, input_uri_1=nil)
32
+ text = ["#{recipient} #wase", program_counter, program_list_uri, Time.now.utc.to_i, output_uri, input_uri, input_uri_1].compact.join(', ')
33
+ @twitter_client.update(text)
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'wase_endpoint.rb'
@@ -0,0 +1,120 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+ require 'spec_helper'
3
+
4
+ describe WaseEndpoint::Message do
5
+
6
+ describe "with no input URI" do
7
+
8
+ before(:all) do
9
+ raw_message = "@ey-sort #wase, 0, bit.ly/7yQK6, 1256850843, bit.ly/2uhGcl"
10
+ @message = WaseEndpoint::Message.new(123, raw_message)
11
+ end
12
+
13
+ it "should return the id" do
14
+ @message.id.should == 123
15
+ end
16
+
17
+ it "should return the program counter as an integer" do
18
+ @message.program_counter.should == 0
19
+ end
20
+
21
+ it "should return the program listing url without whitespace" do
22
+ @message.program_listing_uri.should == 'bit.ly/7yQK6'
23
+ end
24
+
25
+ it "should return the timestamp as an integer" do
26
+ @message.timestamp.should == 1256850843
27
+ end
28
+
29
+ it "should return the output uri without whitespace" do
30
+ @message.output_uri.should == 'bit.ly/2uhGcl'
31
+ end
32
+
33
+ it "should return the input uri as nil" do
34
+ @message.input_uri.should be_nil
35
+ end
36
+
37
+ it "should return the second input uri as nil" do
38
+ @message.input_uri_1.should be_nil
39
+ end
40
+
41
+ it "should fetch the program listing as an array" do
42
+ RestClient.should_receive(:get).with('http://bit.ly/7yQK6').and_return('["@ey-sort", "@ey-firsthalf", "@ey-firsthalf", "@engineyard"]')
43
+
44
+ @message.fetch_program_listing.should == ["@ey-sort", "@ey-firsthalf", "@ey-firsthalf", "@engineyard"]
45
+ end
46
+
47
+ it "should fetch the output data" do
48
+ RestClient.should_receive(:get).with('http://bit.ly/2uhGcl').and_return('[58, 92, 12, 18, 76]')
49
+
50
+ @message.fetch_output.should == '[58, 92, 12, 18, 76]'
51
+ end
52
+
53
+ it "should send the input data using the expanded output URI" do
54
+ mock_net_http = mock(Net::HTTP)
55
+ mock_net_http.should_receive(:head).with('/2uhGcl').and_return({'Location' => 'http://example.com/'})
56
+ Net::HTTP.should_receive(:new).with('bit.ly').and_return(mock_net_http)
57
+ RestClient.should_receive(:put).with('http://example.com/', '[1, 2, 3, 4]')
58
+
59
+ @message.send_input('[1, 2, 3, 4]')
60
+ end
61
+
62
+ end
63
+
64
+ describe "with one input URI" do
65
+
66
+ before(:all) do
67
+ raw_message = "@ey-sort #wase, 0, bit.ly/7yQK6, 1256850843, bit.ly/2uhGcl, bit.ly/3kl0xs"
68
+ @message = WaseEndpoint::Message.new(123, raw_message)
69
+ end
70
+
71
+ it "should return the input uri without whitespace" do
72
+ @message.input_uri.should == 'bit.ly/3kl0xs'
73
+ end
74
+
75
+ it "should return the second input uri as nil" do
76
+ @message.input_uri_1.should be_nil
77
+ end
78
+
79
+ it "should send the input data using the expanded output URI" do
80
+ mock_net_http = mock(Net::HTTP)
81
+ mock_net_http.should_receive(:head).with('/3kl0xs').and_return({'Location' => 'http://example.com/'})
82
+ Net::HTTP.should_receive(:new).with('bit.ly').and_return(mock_net_http)
83
+ RestClient.should_receive(:put).with('http://example.com/', '[1, 2, 3, 4]')
84
+
85
+ @message.send_input('[1, 2, 3, 4]')
86
+ end
87
+
88
+ end
89
+
90
+ describe "with two input URIs" do
91
+
92
+ before(:all) do
93
+ raw_message = "@ey-sort #wase, 0, bit.ly/7yQK6, 1256850843, bit.ly/2uhGcl, bit.ly/3kl0xs, bit.ly/3kl0xt"
94
+ @message = WaseEndpoint::Message.new(123, raw_message)
95
+ end
96
+
97
+ it "should return the second input uri without whitespace" do
98
+ @message.input_uri_1.should == 'bit.ly/3kl0xt'
99
+ end
100
+
101
+ end
102
+
103
+ describe "comparison via ID" do
104
+
105
+ before(:all) do
106
+ @raw_message = "@ey-sort #wase, 0, bit.ly/7yQK6, 1256850843, bit.ly/2uhGcl"
107
+ @message_1 = WaseEndpoint::Message.new(123, @raw_message)
108
+ end
109
+
110
+ it "should equal" do
111
+ @message_1.should == WaseEndpoint::Message.new(123, @raw_message)
112
+ end
113
+
114
+ it "should not be equal" do
115
+ @message_1.should_not == WaseEndpoint::Message.new(456, @raw_message)
116
+ end
117
+
118
+ end
119
+
120
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+ require 'spec_helper'
3
+
4
+ describe WaseEndpoint do
5
+
6
+ it "should be a valid class object" do
7
+ WaseEndpoint.should be_a(Class)
8
+ end
9
+
10
+ it "should have more specs"
11
+
12
+ end
@@ -0,0 +1,83 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+ require 'spec_helper'
3
+
4
+ describe WaseEndpoint::Twitterer do
5
+
6
+ it "should return a twitter client object" do
7
+ username ='foo'
8
+ password = 'bar'
9
+ Twitter::HTTPAuth.should_receive(:new).with(username, password).and_return(mock_twitter_http_auth)
10
+ Twitter::Base.should_receive(:new).with(mock_twitter_http_auth).and_return(mock_twitter_base)
11
+
12
+ WaseEndpoint::Twitterer.new(username, password)
13
+ end
14
+
15
+ describe "fetching messages" do
16
+
17
+ before(:all) do
18
+ username ='foo'
19
+ password = 'bar'
20
+ Twitter::HTTPAuth.stub(:new)
21
+ Twitter::Base.stub(:new).and_return(mock_twitter_base)
22
+
23
+ @twitterer = WaseEndpoint::Twitterer.new(username, password)
24
+ end
25
+
26
+ it "should return the latest message" do
27
+ mock_twitter_base.should_receive(:replies).and_return([mock_mash(123)])
28
+ messages = @twitterer.fetch
29
+ messages.size.should == 1
30
+ messages.first.class.should == WaseEndpoint::Message
31
+ end
32
+
33
+ it "should return an empty messages array" do
34
+ mock_twitter_base.should_receive(:replies).and_return([mock_mash(123)])
35
+ @twitterer.fetch.should == []
36
+ end
37
+
38
+ end
39
+
40
+ describe "sending messages" do
41
+
42
+ before(:all) do
43
+ username ='foo'
44
+ password = 'bar'
45
+ Twitter::HTTPAuth.stub(:new)
46
+ Twitter::Base.stub(:new).and_return(mock_twitter_base)
47
+
48
+ @time = Time.now
49
+ Time.stub(:now).and_return(@time)
50
+
51
+ @twitterer = WaseEndpoint::Twitterer.new(username, password)
52
+ end
53
+
54
+ it "should send a message with all inputs" do
55
+ mock_twitter_base.should_receive(:update).with("@a #wase, 10, pl_uri, #{@time.utc.to_i}, o_uri, i_uri, i_1_uri")
56
+ @twitterer.send('@a', 10, 'pl_uri', 'o_uri', 'i_uri', 'i_1_uri')
57
+ end
58
+
59
+ it "should send a message with only one input" do
60
+ mock_twitter_base.should_receive(:update).with("@a #wase, 10, pl_uri, #{@time.utc.to_i}, o_uri, i_uri")
61
+ @twitterer.send('@a', 10, 'pl_uri', 'o_uri', 'i_uri')
62
+ end
63
+
64
+ it "should send a message with no inputs" do
65
+ mock_twitter_base.should_receive(:update).with("@a #wase, 10, pl_uri, #{@time.utc.to_i}, o_uri")
66
+ @twitterer.send('@a', 10, 'pl_uri', 'o_uri')
67
+ end
68
+
69
+ end
70
+
71
+ def mock_twitter_http_auth
72
+ @auth_mock ||= mock(Twitter::HTTPAuth)
73
+ end
74
+
75
+ def mock_twitter_base
76
+ @base_mock ||= mock(Twitter::Base)
77
+ end
78
+
79
+ def mock_mash(message_id)
80
+ mock(Mash, :id => message_id, :text => '@ey-sort #wase, 0, bit.ly/7yQK6, 1256850843, bit.ly/2uhGcl, bit.ly/3kl0xs')
81
+ end
82
+
83
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wase_endpoint
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Douglas F Shearer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-05 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: WaseEndpoint is a library for building daemons that act as WASE Endpoints for http://bit.ly/3qRMbv
17
+ email: dougal.s@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .gitignore
26
+ - README.rdoc
27
+ - Rakefile
28
+ - VERSION
29
+ - example/init.rb
30
+ - example/my_endpoint.rb
31
+ - example/server.rb
32
+ - lib/wase_endpoint.rb
33
+ - lib/wase_endpoint/message.rb
34
+ - lib/wase_endpoint/twitterer.rb
35
+ - spec/spec_helper.rb
36
+ - spec/wase_endpoint_message_spec.rb
37
+ - spec/wase_endpoint_spec.rb
38
+ - spec/wase_endpoint_twitterer_spec.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/dougal/wase_endpoint
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: WaseEndpoint is a library for building daemons that act as WASE Endpoints for http://bit.ly/3qRMbv
67
+ test_files:
68
+ - spec/spec_helper.rb
69
+ - spec/wase_endpoint_message_spec.rb
70
+ - spec/wase_endpoint_spec.rb
71
+ - spec/wase_endpoint_twitterer_spec.rb