wase_endpoint 0.0.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.
@@ -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