bhauman-twroute 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.document +5 -0
  2. data/.gitignore +10 -0
  3. data/README.rdoc +13 -0
  4. data/Rakefile +91 -0
  5. data/VERSION +1 -0
  6. data/example_app/Rakefile +5 -0
  7. data/example_app/config/config.yml.sample +16 -0
  8. data/example_app/config/twroutes.rb +3 -0
  9. data/example_app/scripts/twrouter.rb +10 -0
  10. data/lib/twroute/application/config.rb +30 -0
  11. data/lib/twroute/application/twrouter.rb +27 -0
  12. data/lib/twroute/application.rb +22 -0
  13. data/lib/twroute/dispatcher.rb +48 -0
  14. data/lib/twroute/parser/regex.rb +42 -0
  15. data/lib/twroute/parser/tags.rb +26 -0
  16. data/lib/twroute/parser/users.rb +27 -0
  17. data/lib/twroute/requester/basic.rb +13 -0
  18. data/lib/twroute/requester/delayed/create_delayed_jobs.rb +21 -0
  19. data/lib/twroute/requester/delayed/db_connection.rb +8 -0
  20. data/lib/twroute/requester/delayed.rb +21 -0
  21. data/lib/twroute/routes.rb +22 -0
  22. data/lib/twroute/tasks.rb +60 -0
  23. data/lib/twroute/tweet_printer.rb +7 -0
  24. data/lib/twroute/tweet_route.rb +64 -0
  25. data/lib/twroute/tweeter.rb +35 -0
  26. data/lib/twroute.rb +12 -0
  27. data/test/config_test.rb +19 -0
  28. data/test/dispatcher_test.rb +62 -0
  29. data/test/fixtures/config.yml +16 -0
  30. data/test/reqex_parser_test.rb +27 -0
  31. data/test/routes_test.rb +25 -0
  32. data/test/tags_parser_test.rb +30 -0
  33. data/test/test_helper.rb +15 -0
  34. data/test/test_suite.rb +11 -0
  35. data/test/tweet_maker.rb +47 -0
  36. data/test/tweet_maker_test.rb +13 -0
  37. data/test/tweet_route_test.rb +30 -0
  38. data/test/tweeter_test.rb +33 -0
  39. data/test/users_parser_test.rb +30 -0
  40. data/twroute.gemspec +96 -0
  41. metadata +133 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ example_app/config/config.yml
7
+ *~
8
+ example_app/log/*.log
9
+ example_app/db/twroute.sqlite3
10
+ #*
data/README.rdoc ADDED
@@ -0,0 +1,13 @@
1
+ = Twroute - Route Twitter status updates over http to your web app
2
+
3
+ As a Rails developer the motivation to write this gem is because
4
+ I wanted a super simple way to write a twitter app where my app
5
+ can recieve messages from twitter over http and respond to the sender
6
+ over http.
7
+
8
+ In other words a controller can recieve a tweet and then reply to the
9
+ sender by simply rendering a view.
10
+
11
+ Cool huh? Well I like it.
12
+
13
+ == more docs soon?
data/Rakefile ADDED
@@ -0,0 +1,91 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
5
+
6
+
7
+ namespace :jobs do
8
+ desc "Clear the delayed_job queue."
9
+ task :clear do
10
+
11
+ Delayed::Job.delete_all
12
+ end
13
+
14
+ desc "Start a delayed_job worker."
15
+ task :work do
16
+ #require 'twroute'
17
+ #require 'twroute/requester/delayed'
18
+ #require 'twroute/requester/delayed/create_delayed_jobs'
19
+ Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'], :max_priority => ENV['MAX_PRIORITY']).start
20
+ end
21
+
22
+ desc 'creates delayed job database: not implemented'
23
+ desc :create_database do
24
+
25
+ end
26
+
27
+ desc "Create delayed_job table"
28
+ task :create_table do
29
+ CreateDelayedJobs.migrate(:up)
30
+ end
31
+
32
+ desc "Create delayed_job table"
33
+ task :drop_table do
34
+ CreateDelayedJobs.migrate(:down)
35
+ end
36
+ end
37
+
38
+ begin
39
+ require 'jeweler'
40
+ Jeweler::Tasks.new do |gem|
41
+ gem.name = "twroute"
42
+ gem.summary = %Q{Twroute listens for Twitter updates and redirects them to HTTP post requests}
43
+ gem.description = %Q{Twroute listens for Twitter updates and redirects them to HTTP post requests}
44
+ gem.email = "bhauman@gmail.com"
45
+ gem.homepage = "http://github.com/bhauman/twroute"
46
+ gem.authors = ["bhauman"]
47
+ gem.add_dependency('activerecord', '>= 2.1.0')
48
+ gem.add_dependency('tobi-delayed_job', '>= 1.7.0')
49
+ gem.add_dependency('brianmario-yajl-ruby', '>= 0.5.12')
50
+ gem.add_dependency('deamons', '>= 1.0.10')
51
+ end
52
+
53
+ rescue LoadError
54
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
55
+ end
56
+
57
+ require 'rake/testtask'
58
+ Rake::TestTask.new(:test) do |test|
59
+ test.libs << 'lib' << 'test'
60
+ test.pattern = 'test/**/*_test.rb'
61
+ test.verbose = true
62
+ end
63
+
64
+ begin
65
+ require 'rcov/rcovtask'
66
+ Rcov::RcovTask.new do |test|
67
+ test.libs << 'test'
68
+ test.pattern = 'test/**/*_test.rb'
69
+ test.verbose = true
70
+ end
71
+ rescue LoadError
72
+ task :rcov do
73
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
74
+ end
75
+ end
76
+
77
+ task :default => :test
78
+
79
+ require 'rake/rdoctask'
80
+ Rake::RDocTask.new do |rdoc|
81
+ if File.exist?('VERSION')
82
+ version = File.read('VERSION')
83
+ else
84
+ version = ""
85
+ end
86
+
87
+ rdoc.rdoc_dir = 'rdoc'
88
+ rdoc.title = "twroute #{version}"
89
+ rdoc.rdoc_files.include('README*')
90
+ rdoc.rdoc_files.include('lib/**/*.rb')
91
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,5 @@
1
+ require 'twroute'
2
+ require 'twroute/tasks'
3
+
4
+ Twroute::Tasks.new
5
+
@@ -0,0 +1,16 @@
1
+ submit_to:
2
+ host: localhost
3
+ port: 3000
4
+ http_auth_user: ''
5
+ http_auth_password: ''
6
+ twitter:
7
+ user: ''
8
+ password: ''
9
+ stream_api: track
10
+ stream_api_args:
11
+ track: twitter
12
+ database:
13
+ adapter: sqlite3
14
+ database: db/twroute.sqlite3
15
+ pool: 5
16
+ timeout: 5000
@@ -0,0 +1,3 @@
1
+ Twroute::Routes.draw do |map|
2
+ map.regex( {:tweet_text => /.*/ }, '/tweets/create' )
3
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/ruby
2
+ require 'rubygems'
3
+ require 'daemons'
4
+ require 'twroute'
5
+ require 'twroute/application'
6
+
7
+ Daemons.run_proc('twrout_proc.rb') do
8
+ ActiveRecord::Base.logger = Logger.new(File.open(File.join(::Twroute::Application.root_dir, 'log/database.log'), 'a'))
9
+ Twroute::Application.run
10
+ end
@@ -0,0 +1,30 @@
1
+ require 'yaml'
2
+ require 'ostruct'
3
+
4
+ module Twroute
5
+ module Application
6
+ def self.config
7
+ @@config ||= { }
8
+ end
9
+ def self.config=(config)
10
+ @@config = config
11
+ end
12
+ def self.root_dir
13
+ @@root_dir ||= Dir.pwd
14
+ end
15
+ def self.root_dir=(root)
16
+ @@root_dir = root
17
+ end
18
+ class Config
19
+ def self.load_config(file)
20
+ Application.config = self.open_struct(YAML.load_file(file))
21
+ end
22
+ def self.open_struct(hash)
23
+ hash.inject(OpenStruct.new) do |result, (key, value)|
24
+ result.send(key + '=', value.is_a?(Hash) ? open_struct(value) : value)
25
+ result
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ require 'twroute/requester/delayed'
2
+
3
+ module Twroute
4
+ module Application
5
+ class Twrouter
6
+ attr_accessor :config
7
+ def initialize
8
+ self.config = ::Twroute::Application.config
9
+ setup
10
+ end
11
+ def setup
12
+ @tweeter = ::Twroute::Tweeter.new(config.twitter.user,
13
+ config.twitter.password,
14
+ config.twitter.stream_api,
15
+ config.twitter.stream_api_args.marshal_dump)
16
+ @dispatcher = ::Twroute::Dispatcher.new( config.submit_to.marshal_dump,
17
+ *::Twroute::Routes.routes )
18
+ @requester = ::Twroute::Requester::Delayed.new
19
+ @tweeter.add_observer @dispatcher
20
+ @dispatcher.add_observer @requester
21
+ end
22
+ def start
23
+ @tweeter.start
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ # load the routes
2
+ require File.join(Dir.pwd, 'config', 'twroutes.rb')
3
+
4
+ # load the config
5
+ require 'twroute/application/config'
6
+ ::Twroute::Application::Config.load_config(File.join(Dir.pwd, 'config', 'config.yml'))
7
+ ::Twroute::Application.root_dir = Dir.pwd
8
+
9
+ # this pulls in delayed job and is dependant on the config having been read
10
+ require 'twroute/application/twrouter'
11
+ module Twroute
12
+ module Application
13
+ def self.run
14
+ @@twrouter = ::Twroute::Application::Twrouter.new
15
+ @@twrouter.start
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+
22
+
@@ -0,0 +1,48 @@
1
+ require 'uri'
2
+
3
+ module Twroute
4
+ class Dispatcher
5
+ include Observable
6
+
7
+ attr_accessor :tweet_routes
8
+
9
+ def initialize(options, *args)
10
+ @options = {
11
+ :host => "",
12
+ :port => false,
13
+ :http_auth_user => false,
14
+ :http_auth_password => false
15
+ }.merge(options)
16
+ self.tweet_routes = [*args]
17
+ end
18
+
19
+ def update(tweet_hash)
20
+ route = find_route(tweet_hash)
21
+ dispatch_route(route) if route
22
+ end
23
+
24
+ def find_route(tweet_hash)
25
+ tweet_routes.find do |route|
26
+ route.tweet(tweet_hash)
27
+ return route if route.is_match?
28
+ end
29
+ end
30
+
31
+ def dispatch_route(route)
32
+ uri = get_uri(route)
33
+ post_args = route.get_post_args
34
+ changed
35
+ notify_observers(uri, post_args)
36
+ end
37
+
38
+ def get_uri(route)
39
+ url = "http://"
40
+ url += "#{@options[:http_auth_user]}:#{@options[:http_auth_password]}@" if @options[:http_auth_user] && @options[:http_auth_password]
41
+ url += "#{@options[:host]}"
42
+ url += ":#{@options[:port]}" if @options[:port]
43
+ url += route.path
44
+ URI.parse(url)
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ module Twroute
2
+ module Parser
3
+ class Regex
4
+ attr_accessor :regex_hash
5
+
6
+ def initialize(regex_hash)
7
+ self.regex_hash = regex_hash
8
+ end
9
+
10
+ def parse_tweet(tweet_text)
11
+ @parsed_hash = parse_to_hash(tweet_text)
12
+ is_match? ? @parsed_hash : false
13
+ end
14
+
15
+ def is_match?
16
+ @parsed_hash.values.inject(true) do |accum, val|
17
+ accum && !!val
18
+ end
19
+ end
20
+
21
+ def parse_to_hash(tweet_text)
22
+ regex_hash.inject({ }) do |accum, key_val|
23
+ match_data = tweet_text.match(key_val.last)
24
+ accum[key_val.first] = (process_data(match_data) ? match_data[0] : nil)
25
+ accum
26
+ end
27
+ end
28
+
29
+ # delayed job can't unmarshal things that have lines that end in :
30
+ def process_data(data)
31
+ if(data && data.is_a?(String))
32
+ data.gsub(/:$/m, '')
33
+ else
34
+ data
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+
42
+
@@ -0,0 +1,26 @@
1
+ module Twroute
2
+ module Parser
3
+ class Tags
4
+ TAG_REGEX = /\#([\w\d_]+)/
5
+
6
+ def parse_tweet(tweet_text)
7
+ @parsed_hash = parse_to_hash(tweet_text)
8
+ end
9
+
10
+ def is_match?
11
+ !@parsed_hash.empty?
12
+ end
13
+
14
+ def parse_to_hash(tweet_text)
15
+ tags = parse_tags_out(tweet_text)
16
+ return { } if tags.empty?
17
+ { :tags => tags.join('-') }
18
+ end
19
+
20
+ def parse_tags_out(tweet_text = "")
21
+ tweet_text.scan(TAG_REGEX).to_a.flatten
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,27 @@
1
+ module Twroute
2
+ module Parser
3
+ class Users
4
+ USERS_REGEX = /\@([\w\d_]+)/
5
+
6
+ def parse_tweet(tweet_text)
7
+ @parsed_hash = parse_to_hash(tweet_text)
8
+ end
9
+
10
+ def is_match?
11
+ !@parsed_hash.empty?
12
+ end
13
+
14
+ def parse_to_hash(tweet_text)
15
+ users = parse_users_out(tweet_text)
16
+ return { } if users.empty?
17
+ { :users => users.join('-') }
18
+ end
19
+
20
+ def parse_users_out(tweet_text = "")
21
+ tweet_text.scan(USERS_REGEX).to_a.flatten
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+
@@ -0,0 +1,13 @@
1
+ require 'net/http'
2
+
3
+ module Twroute
4
+ module Requester
5
+ class Basic
6
+ def update(uri, post_args)
7
+ Thread.new(uri, post_args) do |uri, post_args|
8
+ response = Net::HTTP.post_form(uri, post_args)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ class CreateDelayedJobs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :delayed_jobs, :force => true do |table|
4
+ table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
5
+ table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
6
+ table.text :handler # YAML-encoded string of the object that will do work
7
+ table.text :last_error # reason for last failure (See Note below)
8
+ table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future.
9
+ table.datetime :locked_at # Set when a client is working on this object
10
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11
+ table.string :locked_by # Who is working on this object (if locked)
12
+ table.timestamps
13
+ end
14
+ end
15
+
16
+ def self.down
17
+ drop_table :delayed_jobs
18
+ end
19
+ end
20
+
21
+
@@ -0,0 +1,8 @@
1
+ require 'activerecord'
2
+
3
+ ::Twroute::Application.config.database.database = File.join(::Twroute::Application.root_dir,
4
+ ::Twroute::Application.config.database.database).to_s
5
+
6
+ ActiveRecord::Base.establish_connection(::Twroute::Application.config.database.marshal_dump)
7
+
8
+
@@ -0,0 +1,21 @@
1
+ require 'twroute/requester/delayed/db_connection'
2
+ require 'delayed_job'
3
+
4
+ require 'net/http'
5
+ module Twroute
6
+ module Requester
7
+
8
+ class Delayed
9
+ def update(uri, post_args)
10
+ ::Delayed::Job.enqueue DelayedRequest.new(uri, post_args)
11
+ end
12
+ end
13
+
14
+ class DelayedRequest < Struct.new(:uri, :post_args)
15
+ def perform
16
+ response = Net::HTTP.post_form(uri, post_args)
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ module Twroute
2
+ class Routes
3
+ def self.routes
4
+ @@routes ||= []
5
+ @@routes
6
+ end
7
+ def self.append_route(x)
8
+ @@routes ||= []
9
+ @@routes << x
10
+ end
11
+ def self.draw(&block)
12
+ yield Collector
13
+ end
14
+ class Collector
15
+ def self.regex(regex_hash, url_pattern)
16
+ Routes.append_route(
17
+ TweetRoute.new(Parser::Regex.new(regex_hash),
18
+ url_pattern))
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,60 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ module Twroute
5
+ class Tasks < ::Rake::TaskLib
6
+ def initialize
7
+ namespace :twroute do
8
+ task :load_env do
9
+ require 'twroute'
10
+ require 'twroute/application'
11
+ end
12
+
13
+ desc "create database"
14
+ task :create_database => :load_env do
15
+ require 'sqlite3'
16
+ db = SQLite3::Database.new(Twroute::Application.config.database.database)
17
+ db.close
18
+ end
19
+
20
+ desc "Create delayed_job table"
21
+ task :create_table => :load_env do
22
+ require 'twroute/requester/delayed/create_delayed_jobs'
23
+ CreateDelayedJobs.migrate(:up)
24
+ end
25
+
26
+ desc "create required directories"
27
+ task :create_dirs do
28
+ require 'fileutils'
29
+ ['log', 'db'].each do |dir|
30
+ FileUtils.mkdir_p(dir) if !File.exists?(dir)
31
+ end
32
+ end
33
+
34
+ desc "initialize twroute app : creates database : creates table : creates directories"
35
+ task :init => [:create_dirs, :create_database, :create_table] do
36
+
37
+ end
38
+
39
+ desc "Delete delayed_job table"
40
+ task :drop_table => :load_env do
41
+ require 'twroute/requester/delayed/create_delayed_jobs'
42
+ CreateDelayedJobs.migrate(:down)
43
+ end
44
+
45
+ desc "Clear the delayed_job queue."
46
+ task :clear => :load_env do
47
+ ActiveRecord::Base.logger = Logger.new(File.open('log/delayed.log', 'a'))
48
+ ::Delayed::Job.delete_all
49
+ end
50
+
51
+ desc "Start a delayed_job worker."
52
+ task :work => :load_env do
53
+ ActiveRecord::Base.logger = Logger.new(File.open('log/delayed.log', 'a'))
54
+ ::Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'],
55
+ :max_priority => ENV['MAX_PRIORITY']).start
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ module Twroute
2
+ class TweetPrinter
3
+ def update(tweet_hash)
4
+ puts tweet_hash.inspect
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,64 @@
1
+ module Twroute
2
+ class TweetRoute
3
+ attr_accessor :tweet_parser, :url_pattern
4
+ def initialize(tweet_parser, url_pattern)
5
+ self.tweet_parser = tweet_parser
6
+ self.url_pattern = url_pattern
7
+ end
8
+
9
+ def tweet(tweet_hash)
10
+ reset
11
+ @tweet_hash = tweet_hash
12
+ @sender_hash = tweet_hash.delete(:user)
13
+ parse_tweet(tweet_hash[:text])
14
+ end
15
+
16
+ def reset
17
+ @tweet_hash = { }
18
+ @sender_hash = { }
19
+ @parsed_hash = { }
20
+ end
21
+
22
+ def parse_tweet(tweet_text)
23
+ @parsed_hash = tweet_parser.parse_tweet(tweet_text) || { }
24
+ end
25
+
26
+ def is_match?
27
+ tweet_parser.is_match?
28
+ end
29
+
30
+ def path
31
+ fill_in_url_pattern
32
+ end
33
+
34
+ def fill_in_url_pattern
35
+ pattern = self.url_pattern
36
+ @parsed_hash.keys.each do |key|
37
+ pattern.gsub!(':' + key.to_s, @parsed_hash[key])
38
+ end
39
+
40
+ tweet_h = @tweet_hash.merge({ })
41
+ tweet_h.delete(:user)
42
+ tweet_h.keys.each do |key|
43
+ pattern.gsub!(':tweet[' + key.to_s + ']', @tweet_hash[key].to_s)
44
+ end
45
+
46
+ pattern
47
+ end
48
+
49
+ def get_post_args
50
+ post_hash = { }
51
+ @parsed_hash.keys.each do |key|
52
+ post_hash[key.to_s] = @parsed_hash[key]
53
+ end
54
+ @sender_hash.keys.each do |key|
55
+ post_hash["sender[#{key.to_s}]"] = @sender_hash[key]
56
+ end
57
+ @tweet_hash.keys.each do |key|
58
+ post_hash["tweet[#{key.to_s}]"] = @tweet_hash[key]
59
+ end
60
+ post_hash
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,35 @@
1
+ require 'yajl'
2
+ require 'yajl/http_stream'
3
+ require 'observer'
4
+ require 'uri'
5
+
6
+ module Twroute
7
+ class Tweeter
8
+ include Observable
9
+
10
+ def initialize(user, password, twitter_stream, post_arg_hash = {})
11
+ @user = user
12
+ @password = password
13
+ @twitter_stream = twitter_stream
14
+ @post_arg_hash = post_arg_hash
15
+ end
16
+
17
+ def twitter_stream_uri
18
+ URI.parse("http://#{@user}:#{@password}@stream.twitter.com/#{@twitter_stream}.json?#{self.query_string}")
19
+ end
20
+
21
+ def query_string
22
+ @post_arg_hash.keys.collect do |key|
23
+ "#{key}=#{URI.escape(@post_arg_hash[key])}"
24
+ end.sort_by { |x| x }.join('&')
25
+ end
26
+
27
+ def start
28
+ puts twitter_stream_uri.to_s
29
+ Yajl::HttpStream.get(twitter_stream_uri, :symbolize_keys => true) do |hash|
30
+ changed
31
+ notify_observers( hash )
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/twroute.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'twroute/tweeter'
3
+ require 'twroute/routes'
4
+ require 'twroute/dispatcher'
5
+ require 'twroute/tweet_route'
6
+ require 'twroute/parser/regex'
7
+ require 'twroute/parser/tags'
8
+ require 'twroute/parser/users'
9
+
10
+
11
+
12
+
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+ require 'twroute/application/config'
3
+
4
+ class ConfigTest < Test::Unit::TestCase
5
+ include Twroute
6
+
7
+ context "with loaded config" do
8
+ setup do
9
+ file = File.join(File.dirname(__FILE__), 'fixtures', 'config.yml')
10
+ Application::Config.load_config(file)
11
+ end
12
+ should "have config loaded" do
13
+ Application.config.database.nil?.should == false
14
+ Application.config.database.adapter.should == 'sqlite3'
15
+ Application.config.twitter.stream_api_args.track.should == 'twitter'
16
+ Application.config.submit_to.host.should == 'localhost'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,62 @@
1
+ require 'test_helper'
2
+ require 'twroute/dispatcher'
3
+
4
+ class DispatcherTest < Test::Unit::TestCase
5
+ include Twroute
6
+
7
+ context "with dispatcher" do
8
+ setup do
9
+ @tweet_route_user = TweetRoute.new(Parser::Regex.new({:user => /\@\w+/}), '/my_test_path/:user')
10
+ @tweet_route_tag = TweetRoute.new(Parser::Regex.new({:tag => /\#\w+/}), '/my_test_path/:tag')
11
+ @tweet_route_user_and_tag = TweetRoute.new(Parser::Regex.new({:tag => /\#\w+/,
12
+ :user => /\@\w+/ }),
13
+ '/my_test_path/:tag/:user')
14
+ @dispatcher = Dispatcher.new( {:host => 'test-host.com'},
15
+ @tweet_route_user_and_tag,
16
+ @tweet_route_user,
17
+ @tweet_route_tag )
18
+
19
+ @match_user_tweet = TweetMaker.make_tweet :text => "@this is cool too here 3about_it"
20
+ @match_tag_tweet = TweetMaker.make_tweet :text => "#this is cool too here 3about_it"
21
+ @match_user_and_tag_tweet = TweetMaker.make_tweet :text => "@random #this is cool too here 3about_it"
22
+ end
23
+
24
+ should "match be able to get the first matched route" do
25
+ @dispatcher.tweet_routes.length.should == 3
26
+
27
+ @dispatcher.find_route(@match_user_and_tag_tweet).should == @tweet_route_user_and_tag
28
+ @dispatcher.find_route(@match_user_tweet).should == @tweet_route_user
29
+ @dispatcher.find_route(@match_tag_tweet).should == @tweet_route_tag
30
+
31
+ end
32
+
33
+ should "be able to handle one route" do
34
+ disp = Dispatcher.new( {:host => 'test-host.com'},
35
+ @tweet_route_user
36
+ )
37
+ disp.find_route(@match_user_tweet).should == @tweet_route_user
38
+ end
39
+
40
+ should "to create a uri" do
41
+ disp = Dispatcher.new({:host => 'test-host.com'},
42
+ @tweet_route_user)
43
+ route = stub(:path, :return => '/path')
44
+ disp.get_uri(route).to_s.should == 'http://test-host.com/path'
45
+ disp = Dispatcher.new({:host => 'test-host.com',
46
+ :http_auth_user => 'jay'},
47
+ @tweet_route_user)
48
+ disp.get_uri(route).to_s.should == 'http://test-host.com/path'
49
+ disp = Dispatcher.new({:host => 'test-host.com',
50
+ :http_auth_user => 'jay',
51
+ :http_auth_password => 'man'},
52
+ @tweet_route_user)
53
+ disp.get_uri(route).to_s.should == 'http://jay:man@test-host.com/path'
54
+ disp = Dispatcher.new({:host => 'test-host.com',
55
+ :port => '3000'},
56
+ @tweet_route_user)
57
+ disp.get_uri(route).to_s.should == 'http://test-host.com:3000/path'
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,16 @@
1
+ submit_to:
2
+ host: localhost
3
+ port: 3000
4
+ http_auth_user: auth_user
5
+ http_auth_password: auth_pass
6
+ twitter:
7
+ user: twitter_user
8
+ password: twitter_pass
9
+ stream_api: track
10
+ stream_api_args:
11
+ track: twitter
12
+ database:
13
+ adapter: sqlite3
14
+ database: db/twroute.sqlite3
15
+ pool: 5
16
+ timeout: 5000
@@ -0,0 +1,27 @@
1
+ require 'test_helper.rb'
2
+
3
+ class RegexParserTest < Test::Unit::TestCase
4
+ context "with regex parser" do
5
+ setup do
6
+ @regex_parser = Twroute::Parser::Regex.new(:whole => /\@\w+\s+(\#\w+\s)+for\s+(\d+)\s+(day|week|month|year)s{0,1}\s+.*/,
7
+ :user => /\@\w+/,
8
+ :tags => /(\#\w+\s)+/,
9
+ :time => /for\s+(\d+)\s+(day|week|month|year)s{0,1}/)
10
+ @tweet_str = "@johnny #this #is #cool for 17 days yeah buddy"
11
+ end
12
+
13
+ should "parse hash" do
14
+ hash = @regex_parser.parse_to_hash(@tweet_str)
15
+ assert hash
16
+ hash[:whole].should == "@johnny #this #is #cool for 17 days yeah buddy"
17
+ hash[:user].should == '@johnny'
18
+ hash[:tags].should == '#this #is #cool '
19
+ hash[:time].should == 'for 17 days'
20
+ end
21
+
22
+ should "return false if all regexes don't match" do
23
+ @regex_parser.parse_tweet("@johnny #this #is #cool 17 days yeah buddy").should == false
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ class RoutesTest < Test::Unit::TestCase
4
+ include Twroute
5
+ context "with routes" do
6
+ setup do
7
+ Routes.draw do |map|
8
+ map.regex({ :tweet_text => /.*/ },
9
+ '/tweet/create')
10
+ map.regex({ :tweet_text => /happy/ },
11
+ '/happy/create')
12
+ map.regex({ :tweet_text => /happy2/ },
13
+ '/happy2/create')
14
+ end
15
+ end
16
+
17
+ should "have created the tweet routes and agregated them" do
18
+ Routes.routes.length.should == 3
19
+ Routes.routes[0].is_a?(TweetRoute).should == true
20
+ Routes.routes[0].tweet_parser.is_a?(Parser::Regex).should == true
21
+ Routes.routes[0].url_pattern.should == '/tweet/create'
22
+ Routes.routes[2].url_pattern.should == '/happy2/create'
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+ class TagsParserTest < Test::Unit::TestCase
3
+ context "with tags parser" do
4
+ setup do
5
+ @tags_parser = Twroute::Parser::Tags.new
6
+ @tags_str = "#this #is cool #too here #3about_it"
7
+ end
8
+
9
+ should "be able to determine a match" do
10
+ @tags_parser.parse_tweet(@tags_str)
11
+ @tags_parser.is_match?.should == true
12
+ @tags_parser.parse_tweet("hello there")
13
+ @tags_parser.is_match?.should == false
14
+ end
15
+
16
+ should "parse twitter tags from a string" do
17
+ @tags_parser.parse_tags_out(@tags_str).should == ['this', 'is', 'too', '3about_it']
18
+ end
19
+
20
+ should "be able to parse nil string" do
21
+ @tags_parser.parse_tags_out().should == []
22
+ end
23
+
24
+ should "return hash of tags" do
25
+ @tags_parser.parse_to_hash(@tags_str).should == {:tags => 'this-is-too-3about_it'}
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'matchy'
5
+ require 'stump'
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
+
10
+ require 'twroute'
11
+
12
+ class Test::Unit::TestCase
13
+ end
14
+
15
+
@@ -0,0 +1,11 @@
1
+ require 'test/unit'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+
5
+ require 'users_parser_test'
6
+ require 'tags_parser_test'
7
+ require 'reqex_parser_test'
8
+ require 'tweet_route_test'
9
+ require 'tweeter_test'
10
+ require 'tweet_maker_test'
11
+ require 'dispatcher_test'
@@ -0,0 +1,47 @@
1
+ class TweetMaker
2
+
3
+ def self.make_tweet(tweet_hash = { })
4
+ tweet = { }.merge(BASE_TWEET)
5
+ user = tweet_hash.delete(:user)
6
+ tweet[:user].merge!(user || { })
7
+ tweet.merge!(tweet_hash)
8
+ tweet
9
+ end
10
+
11
+ BASE_TWEET = {
12
+ :source=>"web",
13
+ :user=>{:url=>"http://test_host.com/",
14
+ :friends_count=>275,
15
+ :time_zone=>"Central Time (US & Canada)",
16
+ :screen_name=>"test_screen_name",
17
+ :notifications=>nil,
18
+ :utc_offset=>-21600,
19
+ :profile_image_url=>"http://s3.amazonaws.com/twitter_production/profile_images/352932267/twitterProfilePhoto_normal.jpg",
20
+ :protected=>false,
21
+ :verified=>false,
22
+ :profile_text_color=>"000000",
23
+ :name=>"test_name",
24
+ :profile_sidebar_border_color=>"D5D39E",
25
+ :statuses_count=>625,
26
+ :profile_link_color=>"B4C67C",
27
+ :location=>"Asheville NC",
28
+ :following=>nil,
29
+ :profile_background_image_url=>"http://s3.amazonaws.com/twitter_production/profile_background_images/2475770/background.png",
30
+ :description=>"I beleive that life is pretty darn cool",
31
+ :followers_count=>211,
32
+ :created_at=>"Thu May 01 02:11:28 +0000 2008",
33
+ :profile_background_tile=>true,
34
+ :id=>14609143,
35
+ :profile_background_color=>"9ae4e8",
36
+ :profile_sidebar_fill_color=>"778F5F",
37
+ :favourites_count=>19},
38
+ :text=>"this is a #test #tweet",
39
+ :favorited=>false,
40
+ :in_reply_to_screen_name=>nil,
41
+ :in_reply_to_status_id=>nil,
42
+ :created_at=>"Sun Aug 09 00:25:46 +0000 2009",
43
+ :id=>3199928174,
44
+ :in_reply_to_user_id=>nil,
45
+ :truncated=>false}
46
+ end
47
+
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+ require 'tweet_maker'
3
+ class TweetMakerTest < Test::Unit::TestCase
4
+ context "tweet maker" do
5
+ should "be able to make a tweet hash" do
6
+ tweet = TweetMaker.make_tweet({ :text => 'test test test', :user => { :name => 'testy_name'}})
7
+ tweet[:source].should == 'web'
8
+ tweet[:text].should == 'test test test'
9
+ tweet[:user][:url].should == 'http://test_host.com/'
10
+ tweet[:user][:name].should == 'testy_name'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper.rb'
2
+ class TweetRouteTest < Test::Unit::TestCase
3
+ context "with users parser" do
4
+ setup do
5
+ @tweet_route = Twroute::TweetRoute.new(Twroute::Parser::Tags.new, '/user/:tweet[sender]/tags/:tags')
6
+ @tweet_str = "#this #is cool #too"
7
+ @tweet = {:source=>"<a href=\"http://twitterfox.net/\">TwitterFox</a>", :user=>{:url=>"http://rosed1st.multiply.com/", :friends_count=>275, :time_zone=>"Central Time (US & Canada)", :screen_name=>"RoseD1st", :notifications=>nil, :utc_offset=>-21600, :profile_image_url=>"http://s3.amazonaws.com/twitter_production/profile_images/352932267/twitterProfilePhoto_normal.jpg", :protected=>false, :verified=>false, :profile_text_color=>"000000", :name=>"RoseD1st", :profile_sidebar_border_color=>"D5D39E", :statuses_count=>625, :profile_link_color=>"B4C67C", :location=>"Mad as Hell!", :following=>nil, :profile_background_image_url=>"http://s3.amazonaws.com/twitter_production/profile_background_images/2475770/background.png", :description=>"A conservatve whos had it with Obamas lies!I'm a proud \342\200\234Birther\342\200\235 and only those who haven't looked at the facts think there's no problem", :followers_count=>211, :created_at=>"Thu May 01 02:11:28 +0000 2008", :profile_background_tile=>true, :id=>14609143, :profile_background_color=>"9ae4e8", :profile_sidebar_fill_color=>"778F5F", :favourites_count=>19}, :text=>"this is a #test #tweet", :favorited=>false, :in_reply_to_screen_name=>nil, :in_reply_to_status_id=>nil, :created_at=>"Sun Aug 09 00:25:46 +0000 2009", :id=>3199928174, :in_reply_to_user_id=>nil, :truncated=>false}
8
+ end
9
+
10
+ should "be able to determine if the required hash keys have been met" do
11
+ @tweet_route.parse_tweet(@tweet_str)
12
+ @tweet_route.is_match?.should == true
13
+ end
14
+
15
+ should "be able to fill in a url_pattern" do
16
+ @tweet_route.tweet({ :text => @tweet_str, :sender => 'charlie' })
17
+ @tweet_route.fill_in_url_pattern.should == '/user/charlie/tags/this-is-too'
18
+ end
19
+
20
+ should "be able to generate post args" do
21
+ @tweet_route.tweet(@tweet)
22
+ post_args = @tweet_route.get_post_args
23
+ post_args['sender[screen_name]'].should == 'RoseD1st'
24
+ post_args['tweet[text]'].should == 'this is a #test #tweet'
25
+ post_args['tags'].should == 'test-tweet'
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,33 @@
1
+ require 'test_helper.rb'
2
+ require 'tweeter'
3
+
4
+ class TweeterTest < Test::Unit::TestCase
5
+ def setup
6
+ end
7
+
8
+ def teardown
9
+ end
10
+
11
+ context "with tweeter" do
12
+ setup do
13
+ @tweeter = Twroute::Tweeter.new('bhauman',
14
+ 'watertank',
15
+ 'track',
16
+ :track => 'twitter' )
17
+ end
18
+
19
+ should "be able to create query string" do
20
+ @tweeter = Twroute::Tweeter.new('bhauman',
21
+ 'watertank',
22
+ 'track',
23
+ :track => 'twitter', :twank => 'twonkle' )
24
+ @tweeter.query_string.should == 'track=twitter&twank=twonkle'
25
+ end
26
+
27
+ should "be able to create a twitter search url" do
28
+ @tweeter.twitter_stream_uri.to_s.should == 'http://bhauman:watertank@stream.twitter.com/track.json?track=twitter'
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+ class UsersParserTest < Test::Unit::TestCase
3
+ context "with users parser" do
4
+ setup do
5
+ @users_parser = Twroute::Parser::Users.new
6
+ @users_str = "@this @is cool @too here @3about_it"
7
+ end
8
+
9
+ should "be able to determine a match" do
10
+ @users_parser.parse_tweet(@users_str)
11
+ @users_parser.is_match?.should == true
12
+ @users_parser.parse_tweet("hello there")
13
+ @users_parser.is_match?.should == false
14
+ end
15
+
16
+ should "parse twitter users from a string" do
17
+ @users_parser.parse_users_out(@users_str).should == ['this', 'is', 'too', '3about_it']
18
+ end
19
+
20
+ should "be able to parse nil string" do
21
+ @users_parser.parse_users_out().should == []
22
+ end
23
+
24
+ should "return hash of users" do
25
+ @users_parser.parse_to_hash(@users_str).should == {:users => 'this-is-too-3about_it'}
26
+ end
27
+
28
+ end
29
+
30
+ end
data/twroute.gemspec ADDED
@@ -0,0 +1,96 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{twroute}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["bhauman"]
9
+ s.date = %q{2009-08-12}
10
+ s.description = %q{Twroute listens for Twitter updates and redirects them to HTTP post requests}
11
+ s.email = %q{bhauman@gmail.com}
12
+ s.extra_rdoc_files = [
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".document",
17
+ ".gitignore",
18
+ "README.rdoc",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "example_app/Rakefile",
22
+ "example_app/config/config.yml.sample",
23
+ "example_app/config/twroutes.rb",
24
+ "example_app/scripts/twrouter.rb",
25
+ "lib/twroute.rb",
26
+ "lib/twroute/application.rb",
27
+ "lib/twroute/application/config.rb",
28
+ "lib/twroute/application/twrouter.rb",
29
+ "lib/twroute/dispatcher.rb",
30
+ "lib/twroute/parser/regex.rb",
31
+ "lib/twroute/parser/tags.rb",
32
+ "lib/twroute/parser/users.rb",
33
+ "lib/twroute/requester/basic.rb",
34
+ "lib/twroute/requester/delayed.rb",
35
+ "lib/twroute/requester/delayed/create_delayed_jobs.rb",
36
+ "lib/twroute/requester/delayed/db_connection.rb",
37
+ "lib/twroute/routes.rb",
38
+ "lib/twroute/tasks.rb",
39
+ "lib/twroute/tweet_printer.rb",
40
+ "lib/twroute/tweet_route.rb",
41
+ "lib/twroute/tweeter.rb",
42
+ "test/config_test.rb",
43
+ "test/dispatcher_test.rb",
44
+ "test/fixtures/config.yml",
45
+ "test/reqex_parser_test.rb",
46
+ "test/routes_test.rb",
47
+ "test/tags_parser_test.rb",
48
+ "test/test_helper.rb",
49
+ "test/test_suite.rb",
50
+ "test/tweet_maker.rb",
51
+ "test/tweet_maker_test.rb",
52
+ "test/tweet_route_test.rb",
53
+ "test/tweeter_test.rb",
54
+ "test/users_parser_test.rb",
55
+ "twroute.gemspec"
56
+ ]
57
+ s.has_rdoc = true
58
+ s.homepage = %q{http://github.com/bhauman/twroute}
59
+ s.rdoc_options = ["--charset=UTF-8"]
60
+ s.require_paths = ["lib"]
61
+ s.rubygems_version = %q{1.3.1}
62
+ s.summary = %q{Twroute listens for Twitter updates and redirects them to HTTP post requests}
63
+ s.test_files = [
64
+ "test/config_test.rb",
65
+ "test/dispatcher_test.rb",
66
+ "test/reqex_parser_test.rb",
67
+ "test/routes_test.rb",
68
+ "test/tags_parser_test.rb",
69
+ "test/test_helper.rb",
70
+ "test/test_suite.rb",
71
+ "test/tweet_maker.rb",
72
+ "test/tweet_maker_test.rb",
73
+ "test/tweet_route_test.rb",
74
+ "test/tweeter_test.rb",
75
+ "test/users_parser_test.rb"
76
+ ]
77
+
78
+ if s.respond_to? :specification_version then
79
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
80
+ s.specification_version = 2
81
+
82
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
83
+ s.add_runtime_dependency(%q<activerecord>, [">= 2.1.0"])
84
+ s.add_runtime_dependency(%q<tobi-delayed_job>, [">= 1.7.0"])
85
+ s.add_runtime_dependency(%q<brianmario-yajl-ruby>, [">= 0.5.12"])
86
+ else
87
+ s.add_dependency(%q<activerecord>, [">= 2.1.0"])
88
+ s.add_dependency(%q<tobi-delayed_job>, [">= 1.7.0"])
89
+ s.add_dependency(%q<brianmario-yajl-ruby>, [">= 0.5.12"])
90
+ end
91
+ else
92
+ s.add_dependency(%q<activerecord>, [">= 2.1.0"])
93
+ s.add_dependency(%q<tobi-delayed_job>, [">= 1.7.0"])
94
+ s.add_dependency(%q<brianmario-yajl-ruby>, [">= 0.5.12"])
95
+ end
96
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bhauman-twroute
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - bhauman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-12 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.1.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: tobi-delayed_job
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.7.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: brianmario-yajl-ruby
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.5.12
44
+ version:
45
+ description: Twroute listens for Twitter updates and redirects them to HTTP post requests
46
+ email: bhauman@gmail.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.rdoc
53
+ files:
54
+ - .document
55
+ - .gitignore
56
+ - README.rdoc
57
+ - Rakefile
58
+ - VERSION
59
+ - example_app/Rakefile
60
+ - example_app/config/config.yml.sample
61
+ - example_app/config/twroutes.rb
62
+ - example_app/scripts/twrouter.rb
63
+ - lib/twroute.rb
64
+ - lib/twroute/application.rb
65
+ - lib/twroute/application/config.rb
66
+ - lib/twroute/application/twrouter.rb
67
+ - lib/twroute/dispatcher.rb
68
+ - lib/twroute/parser/regex.rb
69
+ - lib/twroute/parser/tags.rb
70
+ - lib/twroute/parser/users.rb
71
+ - lib/twroute/requester/basic.rb
72
+ - lib/twroute/requester/delayed.rb
73
+ - lib/twroute/requester/delayed/create_delayed_jobs.rb
74
+ - lib/twroute/requester/delayed/db_connection.rb
75
+ - lib/twroute/routes.rb
76
+ - lib/twroute/tasks.rb
77
+ - lib/twroute/tweet_printer.rb
78
+ - lib/twroute/tweet_route.rb
79
+ - lib/twroute/tweeter.rb
80
+ - test/config_test.rb
81
+ - test/dispatcher_test.rb
82
+ - test/fixtures/config.yml
83
+ - test/reqex_parser_test.rb
84
+ - test/routes_test.rb
85
+ - test/tags_parser_test.rb
86
+ - test/test_helper.rb
87
+ - test/test_suite.rb
88
+ - test/tweet_maker.rb
89
+ - test/tweet_maker_test.rb
90
+ - test/tweet_route_test.rb
91
+ - test/tweeter_test.rb
92
+ - test/users_parser_test.rb
93
+ - twroute.gemspec
94
+ has_rdoc: true
95
+ homepage: http://github.com/bhauman/twroute
96
+ licenses:
97
+ post_install_message:
98
+ rdoc_options:
99
+ - --charset=UTF-8
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: "0"
107
+ version:
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: "0"
113
+ version:
114
+ requirements: []
115
+
116
+ rubyforge_project:
117
+ rubygems_version: 1.3.5
118
+ signing_key:
119
+ specification_version: 2
120
+ summary: Twroute listens for Twitter updates and redirects them to HTTP post requests
121
+ test_files:
122
+ - test/config_test.rb
123
+ - test/dispatcher_test.rb
124
+ - test/reqex_parser_test.rb
125
+ - test/routes_test.rb
126
+ - test/tags_parser_test.rb
127
+ - test/test_helper.rb
128
+ - test/test_suite.rb
129
+ - test/tweet_maker.rb
130
+ - test/tweet_maker_test.rb
131
+ - test/tweet_route_test.rb
132
+ - test/tweeter_test.rb
133
+ - test/users_parser_test.rb