akane 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/akane.gemspec +2 -1
- data/lib/akane/cli.rb +30 -0
- data/lib/akane/config.rb +8 -0
- data/lib/akane/manager.rb +2 -16
- data/lib/akane/receivers/stream.rb +28 -36
- data/lib/akane/recorder.rb +6 -5
- data/lib/akane/storages/file.rb +8 -8
- data/lib/akane/util.rb +18 -0
- data/lib/akane/version.rb +1 -1
- data/spec/manager_spec.rb +0 -5
- data/spec/receivers/stream_spec.rb +44 -16
- data/spec/recorder_spec.rb +17 -17
- data/spec/spec_helper.rb +1 -1
- data/spec/support/mock_twitter_streaming.rb +98 -0
- data/spec/util_spec.rb +36 -0
- metadata +27 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0bd862e8bde263f78e295317635f8bb4eb0256cd
|
4
|
+
data.tar.gz: 4b64ada85e74c5018bc7f013c3f37d850cccb7e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2015deae33ec6b5598a17743dc308f01ce8cf452819f1f2ca2334b1a37365897fb79c7f7d8676bb1618ebe5bc9ac98d3e224d39cd34738bad89cecb038029343
|
7
|
+
data.tar.gz: fe3250c3bc69de4bafcad0f68ea3d488efdf0ee75d7718e04eabe27f3ce2a400a200fab02bb1fe124858dab17d430fa2555277278b38cf161ae38696e4f681da
|
data/akane.gemspec
CHANGED
@@ -18,8 +18,9 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "tweetstream", "~> 2.6.0"
|
22
21
|
spec.add_dependency "elasticsearch", "~> 0.4.1"
|
22
|
+
spec.add_dependency "twitter", "~> 5.5.1"
|
23
|
+
spec.add_dependency "oauth", ">= 0.4.7"
|
23
24
|
|
24
25
|
spec.add_development_dependency "bundler"
|
25
26
|
spec.add_development_dependency "rake"
|
data/lib/akane/cli.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'akane/manager'
|
2
2
|
require 'akane/config'
|
3
|
+
require 'optparse'
|
3
4
|
|
4
5
|
module Akane
|
5
6
|
class CLI
|
@@ -20,6 +21,35 @@ module Akane
|
|
20
21
|
manager.run
|
21
22
|
end
|
22
23
|
|
24
|
+
def auth
|
25
|
+
require 'twitter'
|
26
|
+
|
27
|
+
consumer = config.consumer
|
28
|
+
|
29
|
+
request_token = consumer.get_request_token
|
30
|
+
puts "Allow me using this URL: #{request_token.authorize_url}"
|
31
|
+
print "Then type shown PIN: "
|
32
|
+
pin = $stdin.gets.chomp
|
33
|
+
print "Authorizing... "
|
34
|
+
|
35
|
+
access_token = request_token.get_access_token(oauth_verifier: pin)
|
36
|
+
twitter = Twitter::REST::Client.new(
|
37
|
+
consumer_key: config[:consumer]['token'],
|
38
|
+
consumer_secret: config[:consumer]['secret'],
|
39
|
+
access_token: access_token.token,
|
40
|
+
access_token_secret: access_token.secret,
|
41
|
+
)
|
42
|
+
puts "done.\n\n"
|
43
|
+
|
44
|
+
puts <<-EOY
|
45
|
+
---
|
46
|
+
accounts:
|
47
|
+
#{twitter.current_user.screen_name}:
|
48
|
+
token: #{access_token.token}
|
49
|
+
secret: #{access_token.secret}
|
50
|
+
EOY
|
51
|
+
end
|
52
|
+
|
23
53
|
def help
|
24
54
|
puts <<-EOH
|
25
55
|
Usage:
|
data/lib/akane/config.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'logger'
|
3
|
+
require 'oauth'
|
3
4
|
|
4
5
|
module Akane
|
5
6
|
class Config
|
@@ -18,6 +19,13 @@ module Akane
|
|
18
19
|
@hash[k.to_s]
|
19
20
|
end
|
20
21
|
|
22
|
+
def consumer
|
23
|
+
consumer = self[:consumer]
|
24
|
+
return nil unless consumer
|
25
|
+
OAuth::Consumer.new(consumer['token'], consumer['secret'],
|
26
|
+
site: 'https://api.twitter.com/')
|
27
|
+
end
|
28
|
+
|
21
29
|
def logger
|
22
30
|
Logger.new(@hash["log"] || $stdout)
|
23
31
|
end
|
data/lib/akane/manager.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'eventmachine'
|
2
1
|
require 'akane/config'
|
3
2
|
require 'akane/recorder'
|
4
3
|
require 'akane/receivers/stream'
|
@@ -53,27 +52,14 @@ module Akane
|
|
53
52
|
@logger.info "Starting receivers..."
|
54
53
|
@receivers.each(&:start)
|
55
54
|
@logger.info "Starting recorder..."
|
56
|
-
|
57
|
-
EM.defer { @recorder.run }
|
58
|
-
else
|
59
|
-
@recorder.run
|
60
|
-
end
|
55
|
+
@recorder.run
|
61
56
|
end
|
62
57
|
|
63
58
|
def run
|
64
59
|
@logger.info "Running..."
|
65
60
|
self.prepare()
|
66
61
|
|
67
|
-
|
68
|
-
start()
|
69
|
-
else
|
70
|
-
@logger.info "Diving into Eventmachine"
|
71
|
-
EM.epoll
|
72
|
-
EM.kqueue
|
73
|
-
EM.run do
|
74
|
-
start()
|
75
|
-
end
|
76
|
-
end
|
62
|
+
start()
|
77
63
|
end
|
78
64
|
|
79
65
|
private
|
@@ -1,61 +1,53 @@
|
|
1
1
|
require 'akane/receivers/abstract_receiver'
|
2
|
-
require '
|
2
|
+
require 'twitter'
|
3
3
|
|
4
4
|
module Akane
|
5
5
|
module Receivers
|
6
6
|
class Stream < AbstractReceiver
|
7
7
|
def initialize(*)
|
8
8
|
super
|
9
|
-
@
|
9
|
+
@thread = nil
|
10
10
|
end
|
11
11
|
|
12
|
-
def running?() @
|
12
|
+
def running?() !!(@thread && @thread.alive?) end
|
13
13
|
|
14
14
|
def stream
|
15
|
-
@stream ||=
|
16
|
-
auth_method: :oauth,
|
15
|
+
@stream ||= Twitter::Streaming::Client.new(
|
17
16
|
consumer_key: @consumer[:token],
|
18
17
|
consumer_secret: @consumer[:secret],
|
19
|
-
|
20
|
-
|
21
|
-
)
|
22
|
-
|
23
|
-
invoke(:event, hash) if hash["event"]
|
24
|
-
end
|
25
|
-
|
26
|
-
stream.on_timeline_status do |tweet|
|
27
|
-
invoke(:tweet, tweet)
|
28
|
-
end
|
29
|
-
|
30
|
-
stream.on_delete do |tweet_id, user_id|
|
31
|
-
invoke(:delete, user_id, tweet_id)
|
32
|
-
end
|
18
|
+
access_token: @account[:token],
|
19
|
+
access_token_secret: @account[:secret]
|
20
|
+
)
|
21
|
+
end
|
33
22
|
|
34
|
-
|
35
|
-
invoke(:message, message)
|
36
|
-
end
|
23
|
+
attr_reader :thread
|
37
24
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
25
|
+
def start
|
26
|
+
@logger.info "Stream : Starting"
|
41
27
|
|
42
|
-
|
43
|
-
|
28
|
+
@thread = Thread.new do
|
29
|
+
stream.user do |obj|
|
30
|
+
case obj
|
31
|
+
when Twitter::Tweet
|
32
|
+
invoke(:tweet, obj)
|
33
|
+
when Twitter::DirectMessage
|
34
|
+
invoke(:message, obj)
|
35
|
+
when Twitter::Streaming::DeletedTweet
|
36
|
+
invoke(:delete, obj.user_id, obj.id)
|
37
|
+
when Twitter::Streaming::Event
|
38
|
+
invoke(:event,
|
39
|
+
'event' => obj.name, 'source' => obj.source,
|
40
|
+
'target' => obj.target, 'target_object' => obj.target_object)
|
41
|
+
end
|
44
42
|
end
|
45
|
-
|
46
|
-
end
|
43
|
+
end
|
47
44
|
|
48
|
-
def start
|
49
|
-
@logger.info "Stream : Starting"
|
50
|
-
stream.userstream
|
51
|
-
@running = true
|
52
45
|
self
|
53
46
|
end
|
54
47
|
|
55
48
|
def stop
|
56
|
-
|
57
|
-
@
|
58
|
-
@running = false
|
49
|
+
@thread.tap(&:kill).join
|
50
|
+
@thread = nil
|
59
51
|
self
|
60
52
|
end
|
61
53
|
end
|
data/lib/akane/recorder.rb
CHANGED
@@ -39,11 +39,12 @@ module Akane
|
|
39
39
|
|
40
40
|
def perform(action, account, *payload, raise_errors: false)
|
41
41
|
if action == :record_tweet
|
42
|
-
return if @recently_performed[payload.last[
|
43
|
-
@recently_performed.flag!(payload.last[
|
42
|
+
return if @recently_performed[payload.last[:id]]
|
43
|
+
@recently_performed.flag!(payload.last[:id])
|
44
44
|
|
45
|
-
|
46
|
-
|
45
|
+
# WTF: Twitter::NullObject
|
46
|
+
unless payload.last[:retweeted_status].nil?
|
47
|
+
perform(:record_tweet, account, payload.last[:retweeted_status], raise_errors: raise_errors)
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
@@ -64,7 +65,7 @@ module Akane
|
|
64
65
|
begin
|
65
66
|
self.dequeue(raise_errors)
|
66
67
|
rescue Exception => e
|
67
|
-
raise e if
|
68
|
+
raise e if Interrupt === e
|
68
69
|
raise e if raise_errors
|
69
70
|
@logger.error "Error while recorder dequing: #{e.inspect}"
|
70
71
|
@logger.error e.backtrace
|
data/lib/akane/storages/file.rb
CHANGED
@@ -17,10 +17,10 @@ module Akane
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def record_tweet(account, tweet)
|
20
|
-
timeline_io.puts "[#{tweet[
|
21
|
-
"#{tweet[
|
20
|
+
timeline_io.puts "[#{tweet[:created_at].xmlschema}][#{account}] #{tweet[:user][:screen_name]}: " \
|
21
|
+
"#{tweet[:text].gsub(/\r?\n/,' ')} (#{tweet[:user][:id]},#{tweet[:id]})"
|
22
22
|
|
23
|
-
tweets_io_for_user(tweet[
|
23
|
+
tweets_io_for_user(tweet[:user][:id], tweet[:user][:screen_name]) do |io|
|
24
24
|
io.puts tweet.attrs.to_json
|
25
25
|
end
|
26
26
|
end
|
@@ -33,16 +33,16 @@ module Akane
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def record_event(account, event)
|
36
|
-
event_io.puts event.merge(
|
36
|
+
event_io.puts event.merge(:happened_on => account).to_json
|
37
37
|
end
|
38
38
|
|
39
39
|
def record_message(account, message)
|
40
|
-
messages_raw_io_for_user(message[
|
40
|
+
messages_raw_io_for_user(message[:sender][:id], message[:sender][:screen_name]) do |io|
|
41
41
|
io.puts message.attrs.to_json
|
42
42
|
end
|
43
|
-
messages_io_for_user(message[
|
44
|
-
io.puts "[#{message[
|
45
|
-
" #{message[
|
43
|
+
messages_io_for_user(message[:sender][:id], message[:sender][:screen_name]) do |io|
|
44
|
+
io.puts "[#{message[:created_at].xmlschema}] #{message[:sender][:screen_name]} -> #{message[:recipient][:screen_name]}:" \
|
45
|
+
" #{message[:text]} (#{message[:sender][:id]} -> #{message[:recipient][:id]},#{message[:id]})"
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
data/lib/akane/util.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Akane
|
2
|
+
module Util
|
3
|
+
class << self
|
4
|
+
def symbolish_hash(hash)
|
5
|
+
Hash[hash.map { |k, v|
|
6
|
+
[
|
7
|
+
k.is_a?(Symbol) ? k : k.to_s.to_sym,
|
8
|
+
case v
|
9
|
+
when Hash; symbolish_hash(v)
|
10
|
+
when Array; v.map{ |i| i.kind_of?(Hash) ? symbolish_hash(i) : i }
|
11
|
+
else; v
|
12
|
+
end
|
13
|
+
]
|
14
|
+
}]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/akane/version.rb
CHANGED
data/spec/manager_spec.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'eventmachine'
|
3
2
|
require 'akane/manager'
|
4
3
|
require 'akane/receivers/stream'
|
5
4
|
require 'akane/storages/mock'
|
@@ -29,10 +28,6 @@ describe Akane::Manager do
|
|
29
28
|
|
30
29
|
subject { Akane::Manager.new(config) }
|
31
30
|
|
32
|
-
before do
|
33
|
-
EM.stub(:run) { |&block| block.call }
|
34
|
-
end
|
35
|
-
|
36
31
|
# receivers -> manager -> recorder -> storage
|
37
32
|
|
38
33
|
describe "#prepare" do
|
@@ -7,13 +7,13 @@ describe Akane::Receivers::Stream do
|
|
7
7
|
subject { described_class.new(consumer: {token: 'consumer-token', secret: 'consumer-secret'}, account: {token: '42-access-token', secret: 'access-secret'}, config: config, logger: Logger.new(nil)) }
|
8
8
|
|
9
9
|
after(:each) do
|
10
|
-
|
10
|
+
Twitter::Streaming::MockClient.clients.clear
|
11
11
|
end
|
12
12
|
|
13
13
|
describe "#start" do
|
14
14
|
it "starts listening" do
|
15
|
-
expect { subject.start } \
|
16
|
-
.to change {
|
15
|
+
expect { subject.start; 100.times { break if subject.thread.status == 'sleep'; sleep 0.1 } } \
|
16
|
+
.to change { Twitter::Streaming::MockClient.clients.size } \
|
17
17
|
.by(1)
|
18
18
|
end
|
19
19
|
|
@@ -27,11 +27,12 @@ describe Akane::Receivers::Stream do
|
|
27
27
|
describe "#stop" do
|
28
28
|
before do
|
29
29
|
subject.start
|
30
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
30
31
|
end
|
31
32
|
|
32
33
|
it "stops listening" do
|
33
34
|
expect { subject.stop } \
|
34
|
-
.to change {
|
35
|
+
.to change { Twitter::Streaming::MockClient.clients.size } \
|
35
36
|
.by(-1)
|
36
37
|
end
|
37
38
|
|
@@ -45,36 +46,51 @@ describe Akane::Receivers::Stream do
|
|
45
46
|
describe "when received tweet" do
|
46
47
|
before do
|
47
48
|
subject.start
|
49
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
48
50
|
end
|
49
51
|
|
50
52
|
it "calls on_tweet hook" do
|
51
53
|
called = nil
|
52
|
-
subject.on_tweet do |
|
53
|
-
called =
|
54
|
+
subject.on_tweet do |t|
|
55
|
+
called = t
|
54
56
|
end
|
55
|
-
|
56
|
-
|
57
|
+
|
58
|
+
tweet = Twitter::Tweet.new(id: 1)
|
59
|
+
Twitter::Streaming::MockClient.invoke(tweet)
|
60
|
+
|
61
|
+
Twitter::Streaming::MockClient.invoke(:disconnect)
|
62
|
+
subject.thread.join
|
63
|
+
|
64
|
+
expect(called).to eq(tweet)
|
57
65
|
end
|
58
66
|
end
|
59
67
|
|
60
68
|
describe "when received message" do
|
61
69
|
before do
|
62
70
|
subject.start
|
71
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
63
72
|
end
|
64
73
|
|
65
74
|
it "calls on_message hook" do
|
66
75
|
called = nil
|
67
|
-
subject.on_message do |
|
68
|
-
called =
|
76
|
+
subject.on_message do |m|
|
77
|
+
called = m
|
69
78
|
end
|
70
|
-
|
71
|
-
|
79
|
+
|
80
|
+
message = Twitter::DirectMessage.new(id: 1)
|
81
|
+
Twitter::Streaming::MockClient.invoke(message)
|
82
|
+
|
83
|
+
Twitter::Streaming::MockClient.invoke(:disconnect)
|
84
|
+
subject.thread.join
|
85
|
+
|
86
|
+
expect(called).to eq(message)
|
72
87
|
end
|
73
88
|
end
|
74
89
|
|
75
90
|
describe "when received deletion" do
|
76
91
|
before do
|
77
92
|
subject.start
|
93
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
78
94
|
end
|
79
95
|
|
80
96
|
it "calls on_delete hook" do
|
@@ -82,7 +98,13 @@ describe Akane::Receivers::Stream do
|
|
82
98
|
subject.on_delete do |u,t|
|
83
99
|
called = [u,t]
|
84
100
|
end
|
85
|
-
|
101
|
+
|
102
|
+
deletion = Twitter::Streaming::DeletedTweet.new(user_id: 42, id: 424242)
|
103
|
+
Twitter::Streaming::MockClient.invoke(deletion)
|
104
|
+
|
105
|
+
Twitter::Streaming::MockClient.invoke(:disconnect)
|
106
|
+
subject.thread.join
|
107
|
+
|
86
108
|
expect(called).to eq([42,424242])
|
87
109
|
end
|
88
110
|
end
|
@@ -90,6 +112,7 @@ describe Akane::Receivers::Stream do
|
|
90
112
|
describe "when received event" do
|
91
113
|
before do
|
92
114
|
subject.start
|
115
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
93
116
|
end
|
94
117
|
|
95
118
|
it "calls on_event hook" do
|
@@ -97,9 +120,14 @@ describe Akane::Receivers::Stream do
|
|
97
120
|
subject.on_event do |h|
|
98
121
|
called = h
|
99
122
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
123
|
+
event = Twitter::Streaming::Event.new(event: 'favorite', source: {id: 1}, target: {id: 2}, target_object: {id: 3})
|
124
|
+
Twitter::Streaming::MockClient.invoke(event)
|
125
|
+
|
126
|
+
Twitter::Streaming::MockClient.invoke(:disconnect)
|
127
|
+
subject.thread.join
|
128
|
+
|
129
|
+
expect(called).to eq('event' => event.name, 'source' => event.source,
|
130
|
+
'target' => event.target, 'target_object' => event.target_object)
|
103
131
|
end
|
104
132
|
end
|
105
133
|
end
|
data/spec/recorder_spec.rb
CHANGED
@@ -11,26 +11,26 @@ describe Akane::Recorder do
|
|
11
11
|
|
12
12
|
describe "recording tweets" do
|
13
13
|
it "records tweet" do
|
14
|
-
storages[0].should_receive(:record_tweet).with('a', {
|
15
|
-
subject.record_tweet('a',
|
14
|
+
storages[0].should_receive(:record_tweet).with('a', {id: 42})
|
15
|
+
subject.record_tweet('a', id: 42)
|
16
16
|
subject.dequeue(true)
|
17
17
|
end
|
18
18
|
|
19
19
|
it "doesn't record tweets which already recorded recently" do
|
20
|
-
storages[0].should_receive(:record_tweet).with('a', {
|
21
|
-
storages[0].should_receive(:record_tweet).with('a', {
|
20
|
+
storages[0].should_receive(:record_tweet).with('a', {id: 40})
|
21
|
+
storages[0].should_receive(:record_tweet).with('a', {id: 42})
|
22
22
|
|
23
|
-
subject.record_tweet('a',
|
24
|
-
subject.record_tweet('a',
|
23
|
+
subject.record_tweet('a', id: 40)
|
24
|
+
subject.record_tweet('a', id: 42)
|
25
25
|
subject.dequeue(true)
|
26
26
|
subject.dequeue(true)
|
27
|
-
subject.record_tweet('a',
|
27
|
+
subject.record_tweet('a', id: 42)
|
28
28
|
subject.dequeue(true)
|
29
29
|
end
|
30
30
|
|
31
31
|
it "records retweeted tweet" do
|
32
|
-
tweet = {
|
33
|
-
retweet = {
|
32
|
+
tweet = {id: 42, text: "foo", user: {id: 1, screen_name: "a"}}
|
33
|
+
retweet = {id: 43, text: "RT @a: foo", user: {id: 2, screen_name: "b"}, retweeted_status: tweet}
|
34
34
|
storages[0].should_receive(:record_tweet).with('a', retweet)
|
35
35
|
storages[0].should_receive(:record_tweet).with('a', tweet)
|
36
36
|
subject.record_tweet('a', retweet)
|
@@ -49,18 +49,18 @@ describe Akane::Recorder do
|
|
49
49
|
|
50
50
|
describe "recording messages" do
|
51
51
|
it "records message" do
|
52
|
-
subject.record_message('foo', {
|
52
|
+
subject.record_message('foo', {id: 42})
|
53
53
|
|
54
|
-
storages[0].should_receive(:record_message).with('foo',
|
54
|
+
storages[0].should_receive(:record_message).with('foo', id: 42)
|
55
55
|
subject.dequeue(true)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
59
|
describe "recording event" do
|
60
60
|
it "records event" do
|
61
|
-
subject.record_event('foo', {
|
61
|
+
subject.record_event('foo', {event: "favorite"})
|
62
62
|
|
63
|
-
storages[0].should_receive(:record_event).with('foo',
|
63
|
+
storages[0].should_receive(:record_event).with('foo', event: "favorite")
|
64
64
|
subject.dequeue(true)
|
65
65
|
end
|
66
66
|
end
|
@@ -72,10 +72,10 @@ describe Akane::Recorder do
|
|
72
72
|
end
|
73
73
|
|
74
74
|
it "continues dequeuing the queue" do
|
75
|
-
storages[0].should_receive(:record_tweet).with('a', {
|
76
|
-
storages[0].should_receive(:record_tweet).with('b', {
|
77
|
-
subject.record_tweet('a',
|
78
|
-
subject.record_tweet('b',
|
75
|
+
storages[0].should_receive(:record_tweet).with('a', {:id => 42})
|
76
|
+
storages[0].should_receive(:record_tweet).with('b', {:id => 43})
|
77
|
+
subject.record_tweet('a', :id => 42)
|
78
|
+
subject.record_tweet('b', :id => 43)
|
79
79
|
10.times { break if subject.queue_length.zero?; sleep 0.1 }
|
80
80
|
end
|
81
81
|
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'twitter'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
class Twitter::Streaming::MockClient
|
5
|
+
Original = Twitter::Streaming::Client
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def clients
|
9
|
+
@clients ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def clients_mutex
|
13
|
+
@clients_mutex ||= Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_client(client)
|
17
|
+
clients_mutex.synchronize {
|
18
|
+
clients << client
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def remove_client(client)
|
23
|
+
clients_mutex.synchronize {
|
24
|
+
clients.delete client
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def invoke(*args)
|
29
|
+
clients.each do |client|
|
30
|
+
client.invoke(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def enable!
|
37
|
+
klass = self
|
38
|
+
Twitter::Streaming.instance_eval {
|
39
|
+
remove_const :Client
|
40
|
+
const_set :Client, klass
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def disable!
|
45
|
+
klass = Original
|
46
|
+
Twitter::Streaming.instance_eval {
|
47
|
+
remove_const :Client
|
48
|
+
const_set :Client, klass
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(options={})
|
54
|
+
@options = options
|
55
|
+
@queues = []
|
56
|
+
@mutex = Mutex.new
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_accessor :options, :hooks
|
60
|
+
|
61
|
+
def invoke(*args)
|
62
|
+
@queues.each do |queue|
|
63
|
+
queue << args
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def user(options = {}, &block)
|
68
|
+
connect(&block)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def connect
|
74
|
+
queue = Queue.new
|
75
|
+
was_empty = nil
|
76
|
+
@mutex.synchronize {
|
77
|
+
was_empty = @queues.empty?
|
78
|
+
@queues << queue
|
79
|
+
|
80
|
+
if was_empty
|
81
|
+
self.class.add_client self
|
82
|
+
end
|
83
|
+
}
|
84
|
+
while _ = queue.pop
|
85
|
+
break if _ == [:disconnect]
|
86
|
+
yield *_
|
87
|
+
end
|
88
|
+
ensure
|
89
|
+
@mutex.synchronize {
|
90
|
+
@queues.delete queue
|
91
|
+
if @queues.empty?
|
92
|
+
self.class.remove_client self
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Twitter::Streaming::MockClient.enable!
|
data/spec/util_spec.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'akane/util'
|
3
|
+
|
4
|
+
describe Akane::Util do
|
5
|
+
describe ".symbolish_hash" do
|
6
|
+
let(:target) { {'a' => 1, 'b' => 2} }
|
7
|
+
subject { described_class.symbolish_hash(target) }
|
8
|
+
|
9
|
+
it { should eq(a: 1, b: 2) }
|
10
|
+
|
11
|
+
context "with Hash" do
|
12
|
+
let(:target) { {'a' => {'b' => 2}} }
|
13
|
+
it { should eq(a: {b: 2}) }
|
14
|
+
|
15
|
+
context "with nested Hash" do
|
16
|
+
let(:target) { {'a' => {'b' => {'c' => 4}}} }
|
17
|
+
it { should eq(a: {b: {c: 4}}) }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with nested Array" do
|
21
|
+
let(:target) { {'a' => {'b' => [{'c' => 4}]}} }
|
22
|
+
it { should eq(a: {b: [{c: 4}]}) }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with nested Array" do
|
26
|
+
let(:target) { {'a' => {'b' => [{'c' => {'d' => 5}}]}} }
|
27
|
+
it { should eq(a: {b: [{c: {d: 5}}]}) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with Hash in Array" do
|
32
|
+
let(:target) { {'a' => [1,2,{'b' => 3}]} }
|
33
|
+
it { should eq(a: [1,2,{b: 3}]) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
CHANGED
@@ -1,43 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: akane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shota Fukumori (sora_h)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: elasticsearch
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.4.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.4.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: twitter
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 5.5.1
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 5.5.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: oauth
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.4.7
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.4.7
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +136,7 @@ files:
|
|
122
136
|
- lib/akane/storages/file.rb
|
123
137
|
- lib/akane/storages/mock.rb
|
124
138
|
- lib/akane/storages/stdout.rb
|
139
|
+
- lib/akane/util.rb
|
125
140
|
- lib/akane/version.rb
|
126
141
|
- spec/config_spec.rb
|
127
142
|
- spec/manager_spec.rb
|
@@ -132,6 +147,8 @@ files:
|
|
132
147
|
- spec/storages/abstract_storage_spec.rb
|
133
148
|
- spec/storages/mock_spec.rb
|
134
149
|
- spec/support/mock_tweetstream.rb
|
150
|
+
- spec/support/mock_twitter_streaming.rb
|
151
|
+
- spec/util_spec.rb
|
135
152
|
homepage: https://github.com/sorah/akane
|
136
153
|
licenses:
|
137
154
|
- MIT
|
@@ -166,4 +183,6 @@ test_files:
|
|
166
183
|
- spec/storages/abstract_storage_spec.rb
|
167
184
|
- spec/storages/mock_spec.rb
|
168
185
|
- spec/support/mock_tweetstream.rb
|
186
|
+
- spec/support/mock_twitter_streaming.rb
|
187
|
+
- spec/util_spec.rb
|
169
188
|
has_rdoc:
|