atmos-singem 0.0.5

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.
Files changed (60) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +36 -0
  3. data/Rakefile +63 -0
  4. data/TODO +1 -0
  5. data/bin/singem +26 -0
  6. data/lib/generators/cucumber/USAGE +5 -0
  7. data/lib/generators/cucumber/cucumber_generator.rb +82 -0
  8. data/lib/generators/cucumber/templates/LICENSE +20 -0
  9. data/lib/generators/cucumber/templates/README.md +4 -0
  10. data/lib/generators/cucumber/templates/Rakefile +73 -0
  11. data/lib/generators/cucumber/templates/TODO +4 -0
  12. data/lib/generators/cucumber/templates/config.ru.erb +11 -0
  13. data/lib/generators/cucumber/templates/features/basics.feature.erb +6 -0
  14. data/lib/generators/cucumber/templates/features/step_definitions/basics.rb.erb +8 -0
  15. data/lib/generators/cucumber/templates/features/support/env.rb.erb +32 -0
  16. data/lib/generators/cucumber/templates/lib/templates.rb.erb +9 -0
  17. data/lib/generators/cucumber/templates/lib/templates/app.rb.erb +7 -0
  18. data/lib/generators/cucumber/templates/spec/fixtures.rb.erb +0 -0
  19. data/lib/generators/cucumber/templates/spec/spec_helper.rb.erb +40 -0
  20. data/lib/generators/cucumber/templates/spec/templates_spec.rb.erb +8 -0
  21. data/lib/generators/singem/USAGE +5 -0
  22. data/lib/generators/singem/singem_generator.rb +77 -0
  23. data/lib/generators/singem/templates/LICENSE +20 -0
  24. data/lib/generators/singem/templates/README.md +4 -0
  25. data/lib/generators/singem/templates/Rakefile +66 -0
  26. data/lib/generators/singem/templates/TODO +4 -0
  27. data/lib/generators/singem/templates/config.ru.erb +11 -0
  28. data/lib/generators/singem/templates/lib/templates.rb.erb +9 -0
  29. data/lib/generators/singem/templates/lib/templates/app.rb.erb +7 -0
  30. data/lib/generators/singem/templates/spec/fixtures.rb.erb +0 -0
  31. data/lib/generators/singem/templates/spec/spec_helper.rb.erb +40 -0
  32. data/lib/generators/singem/templates/spec/templates_spec.rb.erb +8 -0
  33. data/lib/generators/twitter/USAGE +5 -0
  34. data/lib/generators/twitter/templates/LICENSE +20 -0
  35. data/lib/generators/twitter/templates/README.md +4 -0
  36. data/lib/generators/twitter/templates/Rakefile +72 -0
  37. data/lib/generators/twitter/templates/TODO +4 -0
  38. data/lib/generators/twitter/templates/config.ru.erb +14 -0
  39. data/lib/generators/twitter/templates/features/adding_items_from_the_bookmarklet.feature +16 -0
  40. data/lib/generators/twitter/templates/features/adding_items_to_my_wishlist.feature +19 -0
  41. data/lib/generators/twitter/templates/features/basics.feature.erb +11 -0
  42. data/lib/generators/twitter/templates/features/step_definitions/basics.rb.erb +15 -0
  43. data/lib/generators/twitter/templates/features/support/env.rb.erb +20 -0
  44. data/lib/generators/twitter/templates/features/viewing_my_wish_list.feature +14 -0
  45. data/lib/generators/twitter/templates/features/viewing_the_site_for_the_first_time.feature +6 -0
  46. data/lib/generators/twitter/templates/lib/templates.rb.erb +56 -0
  47. data/lib/generators/twitter/templates/lib/templates/models/user.rb.erb +38 -0
  48. data/lib/generators/twitter/templates/lib/templates/sinatra/app.rb.erb +66 -0
  49. data/lib/generators/twitter/templates/lib/templates/views/about.haml +5 -0
  50. data/lib/generators/twitter/templates/lib/templates/views/failed.haml +4 -0
  51. data/lib/generators/twitter/templates/lib/templates/views/home.haml +1 -0
  52. data/lib/generators/twitter/templates/spec/fixtures.rb.erb +0 -0
  53. data/lib/generators/twitter/templates/spec/helpers.rb.erb +43 -0
  54. data/lib/generators/twitter/templates/spec/spec_helper.rb.erb +38 -0
  55. data/lib/generators/twitter/templates/spec/templates_spec.rb.erb +59 -0
  56. data/lib/generators/twitter/twitter_generator.rb +90 -0
  57. data/lib/singen.rb +0 -0
  58. data/spec/singem_spec.rb +7 -0
  59. data/spec/spec_helper.rb +2 -0
  60. metadata +196 -0
@@ -0,0 +1,11 @@
1
+ Feature: Visiting the Home Page
2
+ In order to display a splash page
3
+
4
+ Scenario: Seeing the splash page unauthenticated
5
+ When I visit the home page
6
+ Then I should be prompted to try the app
7
+
8
+ Scenario: Seeing the splash page authenticated
9
+ Given I am logged in
10
+ When I visit the home page
11
+ Then I should be greeted
@@ -0,0 +1,15 @@
1
+ When /^I visit the home page$/ do
2
+ get '/'
3
+ end
4
+
5
+ Then /^I should be greeted$/ do
6
+ last_response.should have_selector("h2:contains('Hello There, you little twitterer!')")
7
+ end
8
+
9
+ Then /^I should be prompted to try the app$/ do
10
+ last_response.should have_selector("p:contains('Would you mind trying out my new app')")
11
+ end
12
+
13
+ Given /^I am logged in$/ do
14
+ login_quentin
15
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec'
2
+ require 'spec/mocks'
3
+ require 'webrat'
4
+ require 'rack/test'
5
+ require 'dm-sweatshop'
6
+ require 'pp'
7
+
8
+ require File.dirname(__FILE__)+'/../../lib/<%= name %>'
9
+ require File.dirname(__FILE__)+'/../../spec/helpers'
10
+
11
+ World(Rack::Test::Methods)
12
+ World(Spec::Mocks::ExampleMethods)
13
+ World(Webrat::Methods)
14
+ World(Webrat::Matchers)
15
+ World(<%= name.camelize %>::AppHelpers)
16
+
17
+ Before do
18
+ DataMapper.setup(:default, "sqlite3://:memory:")
19
+ DataMapper.auto_migrate!
20
+ end
@@ -0,0 +1,14 @@
1
+ Feature: Viewing my wish list
2
+ In order to display a users' preferences
3
+ As a twitter user
4
+
5
+ Background:
6
+ Given I am logged in
7
+ And I have created a "53cm Pistas" category
8
+
9
+ Scenario: After Authenticating
10
+ When I visit the home page
11
+ Then I should see my list of categories
12
+ When I enter "Prostitutes" for a new category
13
+ And I click submit
14
+ Then I should see the "Prostitutes" list
@@ -0,0 +1,6 @@
1
+ Feature: Visiting the Home Page
2
+ In order to display a splash page
3
+ As an anonymous user
4
+ Scenario: Seeing the splash page
5
+ When I visit the home page
6
+ Then I should be greeted
@@ -0,0 +1,56 @@
1
+ gem 'oauth'
2
+ require 'oauth'
3
+ gem 'json'
4
+ require 'json'
5
+ gem 'haml', '~>2.0.9'
6
+ require 'haml/util'
7
+ require 'haml/engine'
8
+ gem 'curb'
9
+ require 'curb'
10
+ require 'logger'
11
+
12
+ gem 'data_objects', '~>0.9.11'
13
+ gem 'dm-core', '~>0.9.10'
14
+ gem 'dm-types', '~>0.9.10'
15
+ gem 'dm-validations', '~>0.9.10'
16
+ gem 'dm-timestamps', '~>0.9.10'
17
+ require 'dm-core'
18
+ require 'dm-types'
19
+ require 'dm-validations'
20
+ require 'dm-timestamps'
21
+ require 'sinatra/base'
22
+
23
+ module <%= name.camelize %>
24
+ module Log
25
+ def self.logger
26
+ if @logger.nil?
27
+ @logger = Logger.new("<%= name %>.log")
28
+ @logger.level = Logger::INFO
29
+ end
30
+ @logger
31
+ end
32
+ end
33
+
34
+ module OAuth
35
+ def self.consumer
36
+ ::OAuth::Consumer.new(ENV['<%= name.upcase %>_READKEY'],
37
+ ENV['<%= name.upcase %>_READSECRET'],
38
+ {:site => 'http://twitter.com'})
39
+ end
40
+ end
41
+
42
+ def self.retryable(options = {}, &block)
43
+ opts = { :tries => 1, :on => StandardError }.merge(options)
44
+ retry_exception, retries = opts[:on], opts[:tries]
45
+
46
+ begin
47
+ return yield
48
+ rescue retry_exception
49
+ retry if (retries -= 1) > 0
50
+ end
51
+ yield
52
+ end
53
+ end
54
+
55
+ require File.dirname(__FILE__)+'/<%= name %>/models/user'
56
+ require File.dirname(__FILE__)+'/<%= name %>/sinatra/app'
@@ -0,0 +1,38 @@
1
+ module <%= name.camelize %>
2
+ class User
3
+ include DataMapper::Resource
4
+ class UserCreationError < StandardError; end
5
+ storage_names[:default] = '<%= name.camelize %>_users'
6
+
7
+ property :id, Serial
8
+ property :twitter_id, Integer, :nullable => false, :unique => true
9
+ property :name, String
10
+ property :token, String
11
+ property :secret, String
12
+
13
+ property :url, String, :length => 512
14
+ property :avatar, String, :length => 512, :default => 'http://static.twitter.com/images/default_profile_normal.png'
15
+
16
+ timestamps :at
17
+
18
+ def access_token
19
+ ::OAuth::AccessToken.new(::<%= name.camelize %>::OAuth.consumer, token, secret)
20
+ end
21
+
22
+ def self.create_twitter_user(twitter_id)
23
+ content = Curl::Easy.perform("http://twitter.com/users/show/#{twitter_id}.json") do |curl|
24
+ curl.timeout = 30
25
+ end
26
+ user_info = JSON.parse(content.body_str)
27
+ raise UserCreationError.new("Unable to find '#{twitter_id}'") if(user_info['error'] == 'Not found')
28
+ result = unless user_info['error']
29
+ self.first_or_create({:twitter_id => user_info['id']},{
30
+ :name => user_info['name'],
31
+ :avatar => user_info['profile_image_url'],
32
+ :url => 'http://twitter.com/'+user_info['screen_name']})
33
+ end
34
+ rescue JSON::ParserError
35
+ raise UserCreationError.new("Unable to find '#{twitter_id}'")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,66 @@
1
+ module <%= name.camelize %>
2
+ class App < Sinatra::Default
3
+ set :views, File.expand_path(File.dirname(__FILE__)+'/../views')
4
+ enable :sessions
5
+ enable :methodoverride
6
+
7
+ helpers do
8
+ def oauth_consumer
9
+ ::<%= name.camelize %>::OAuth.consumer
10
+ end
11
+
12
+ def current_user
13
+ session[:user_id].nil? ? nil : ::<%= name.camelize %>::User.get(session[:user_id])
14
+ end
15
+ end
16
+
17
+ error do
18
+ <%= name.camelize %>::Log.logger.info env['sinatra.error'].message
19
+ <%= name.camelize %>::Log.logger.info env['sinatra.error'].backtrace.join("\n")
20
+ haml :failed
21
+ end
22
+
23
+ get '/' do
24
+ haml current_user.nil? ? :about : :home
25
+ end
26
+
27
+ get '/authenticate' do
28
+ ::<%= name.camelize %>.retryable(:times => 2) do
29
+ request_token = oauth_consumer.get_request_token
30
+ session[:request_token] = request_token.token
31
+ session[:request_token_secret] = request_token.secret
32
+ redirect request_token.authorize_url
33
+ end
34
+ end
35
+
36
+ get '/peace' do
37
+ session.clear
38
+ redirect '/'
39
+ end
40
+
41
+ get '/callback' do
42
+ @request_token = ::OAuth::RequestToken.new(oauth_consumer,
43
+ session[:request_token],
44
+ session[:request_token_secret])
45
+
46
+ access_token = @request_token.get_access_token
47
+
48
+ oauth_response = oauth_consumer.request(:get, '/account/verify_credentials.json',
49
+ access_token, { :scheme => :query_string })
50
+ case oauth_response
51
+ when Net::HTTPSuccess
52
+ user_info = JSON.parse(oauth_response.body)
53
+ user = ::<%= name.camelize %>::User.first_or_create(:twitter_id => user_info['id']) # really wish first_or_create behaved sanely
54
+ user.name, user.avatar = user_info['name'], user_info['profile_image_url']
55
+ user.token, user.secret = access_token.token, access_token.secret
56
+ user.url = 'http://twitter.com/'+user_info['screen_name']
57
+ user.save
58
+
59
+ session[:user_id] = user.id
60
+ redirect '/'
61
+ else
62
+ raise ArgumentError.new('Unhandled HTTP Response')
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,5 @@
1
+ %h2 Do you use twitter?
2
+ %p
3
+ Would you mind trying out my new app?
4
+ %p
5
+ Enter your custom stuff here.
@@ -0,0 +1,4 @@
1
+ %p
2
+ Something went wonky.
3
+ a{:href => '/'} Try again
4
+ ?
@@ -0,0 +1 @@
1
+ %h2 Hello There, you little twitterer!
@@ -0,0 +1,43 @@
1
+ class Net::HTTPResponse
2
+ def body=(content)
3
+ @body = content
4
+ @read = true
5
+ end
6
+ end
7
+
8
+ Webrat.configure do |config|
9
+ config.mode = :rack_test
10
+ config.application_port = 4567
11
+ end
12
+
13
+ module <%= name.camelize %>::AppHelpers
14
+ def app
15
+ @app = Rack::Builder.new do
16
+ run <%= name.camelize %>::App
17
+ end
18
+ end
19
+
20
+ def login_quentin
21
+ response = Net::HTTPSuccess.new('1.0', 200, nil)
22
+ response.body = "{\"description\":\"lulz\",\"profile_background_image_url\":\"http:\\/\\/static.twitter.com\\/images\\/themes\\/theme3\\/bg.gif\",\"utc_offset\":-25200,\"friends_count\":157,\"profile_background_color\":\"EDECE9\",\"profile_text_color\":\"634047\",\"url\":\"http:\\/\\/example.org\",\"name\":\"Quentin Blake\",\"favourites_count\":6,\"profile_link_color\":\"088253\",\"protected\":false,\"status\":{\"truncated\":false,\"in_reply_to_status_id\":null,\"text\":\"stu stu studio\",\"in_reply_to_user_id\":null,\"favorited\":false,\"created_at\":\"Tue Mar 31 19:02:12 +0000 2009\",\"id\":1426242614,\"source\":\"<a href=\\\"http:\\/\\/iconfactory.com\\/software\\/twitterrific\\\">twitterrific<\\/a>\"},\"created_at\":\"Sun Mar 18 20:07:13 +0000 2007\",\"statuses_count\":2560,\"profile_background_tile\":false,\"time_zone\":\"Mountain Time (US & Canada)\",\"profile_sidebar_fill_color\":\"E3E2DE\",\"profile_image_url\":\"http:\\/\\/static.twitter.com\\/images\\/default_profile_normal.png\",\"notifications\":false,\"profile_sidebar_border_color\":\"D3D2CF\",\"location\":\"Boulder, Colorado\",\"id\":1484261,\"following\":false,\"followers_count\":368,\"screen_name\":\"caboose\"}"
23
+ login(response)
24
+ end
25
+
26
+ def unauthorized_quentin
27
+ response = Net::HTTPUnauthorized.new('1.0', 401, nil)
28
+ response.body = "Unauthorized"
29
+ lambda { login(response) }.should raise_error(ArgumentError)
30
+ end
31
+
32
+ def login(response)
33
+ token = 'oU5W1XD2TTZhWT6Snfii9JbVBUkJOurCKhWQHz98765'
34
+
35
+ consumer = mock('Consumer', {:request => response})
36
+ request_token = mock('RequestToken', {:get_access_token => mock('AccessToken', {:token => 'foo', :secret => 'bar'})})
37
+
38
+ OAuth::Consumer.stub!(:new).and_return(consumer)
39
+ OAuth::RequestToken.stub!(:new).and_return(request_token)
40
+
41
+ visit "/callback?oauth_token=#{token}"
42
+ end
43
+ end
@@ -0,0 +1,38 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'rubygems'
4
+ require 'randexp'
5
+ require '<%= name %>'
6
+ require 'rack/test'
7
+ require 'webrat'
8
+ require 'dm-sweatshop'
9
+ gem 'fakeweb', '~>1.2.4'
10
+ require 'fakeweb'
11
+ require 'pp'
12
+
13
+ FakeWeb.allow_net_connect = false
14
+ ENV['<%= name.upcase %>_READKEY'] = /\w{18}/.gen
15
+ ENV['<%= name.upcase %>_READSECRET'] = /\w{18}/.gen
16
+
17
+ require File.dirname(__FILE__)+'/fixtures'
18
+ require File.dirname(__FILE__)+'/helpers'
19
+
20
+ DataMapper.setup(:default, 'sqlite3::memory:')
21
+
22
+ Spec::Runner.configure do |config|
23
+ config.include(Rack::Test::Methods)
24
+ config.include(Webrat::Methods)
25
+ config.include(Webrat::Matchers)
26
+ config.include(<%= name.camelize %>::AppHelpers)
27
+
28
+ config.before(:each) do
29
+ ::<%= name.camelize %>::App.environment = :development
30
+ DataMapper.auto_migrate!
31
+ FakeWeb.clean_registry
32
+ FakeWeb.register_uri(:post, "http://twitter.com:80/oauth/request_token",
33
+ [{:body => "oauth_token=requestkey&oauth_token_secret=requestsecret", :status => ["200", "OK"]},
34
+ {:body => "", :status => ["401", "Unauthorized"]},
35
+ {:body => "", :status => ["403", "Forbidden"]},
36
+ {:body => "Bad Gateway", :status => ["502", "Bad Gateway"]} ])
37
+ end
38
+ end
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "visiting /" do
4
+ describe "when unauthenticated" do
5
+ it "should display the about page" do
6
+ get '/'
7
+ last_response.should have_selector("h2:contains('Do you use twitter?')")
8
+ end
9
+ end
10
+ describe "when unauthorized" do
11
+ it "should raise an error" do
12
+ lambda { unauthorized_quentin }.should raise_error
13
+ end
14
+ end
15
+ describe "when authenticated" do
16
+ it "should display a greeting" do
17
+ login_quentin
18
+ get '/'
19
+ last_response.should have_selector("h2:contains('Hello There, you little twitterer!')")
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "visiting /peace" do
25
+ describe "when authenticated" do
26
+ it "should clear the session and redirect to the home page" do
27
+ login_quentin
28
+ get '/peace'
29
+ last_response.headers['Location'].should eql('/')
30
+
31
+ get last_response.headers['Location']
32
+ last_response.should have_selector("h2:contains('Do you use twitter?')")
33
+ end
34
+ end
35
+ end
36
+
37
+ describe "triggering a 404" do
38
+ it "should display the failed page" do
39
+ pending
40
+ get '/lost'
41
+ last_response.should have_selector("p:contains('Something went wonky.')")
42
+ end
43
+ end
44
+
45
+ describe "visiting /authenticate" do
46
+ it "should redirect to twitter for authentication" do
47
+ get '/authenticate'
48
+ last_response.headers['Location'].should eql('http://twitter.com/oauth/authorize?oauth_token=requestkey')
49
+ end
50
+ end
51
+
52
+ describe "<%= name.camelize %>::User" do
53
+ it "can create users from twitter ids that exist" do
54
+ lambda { <%= name.camelize %>::User.create_twitter_user('atmos') }.should_not raise_error
55
+ end
56
+ it "can't create users from twitter ids that do not exist" do
57
+ lambda { <%= name.camelize %>::User.create_twitter_user(/\w{18}/.gen) }.should raise_error(<%= name.camelize %>::User::UserCreationError)
58
+ end
59
+ end
@@ -0,0 +1,90 @@
1
+ class TwitterGenerator < RubiGen::Base
2
+ DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'],
3
+ Config::CONFIG['ruby_install_name'])
4
+
5
+ default_options :author => nil
6
+ attr_reader :name
7
+
8
+ def initialize(runtime_args, runtime_options = {})
9
+ super
10
+ usage if args.empty?
11
+ @destination_root = File.expand_path(args.shift)
12
+ @name = base_name
13
+ extract_options
14
+ end
15
+
16
+ def manifest
17
+ record do |m|
18
+ # Ensure appropriate folder(s) exists
19
+ m.directory ''
20
+ BASEDIRS.each { |path| m.directory path }
21
+ m.directory "lib/#{name}"
22
+ m.directory "features/support"
23
+ m.directory "features/step_definitions"
24
+ m.directory "lib/#{name}/sinatra"
25
+ m.directory "lib/#{name}/models"
26
+ m.directory "lib/#{name}/views"
27
+
28
+ # Create stubs
29
+ m.template "config.ru.erb", "config.ru.example"
30
+ m.template "lib/templates.rb.erb", "lib/#{name}.rb"
31
+ m.template "lib/templates/sinatra/app.rb.erb", "lib/#{name}/sinatra/app.rb"
32
+ m.template "lib/templates/models/user.rb.erb", "lib/#{name}/models/user.rb"
33
+
34
+ # cucumber stubs
35
+ m.template "features/support/env.rb.erb", "features/support/env.rb"
36
+ m.template "features/basics.feature.erb", "features/#{name}.feature"
37
+ m.template "features/step_definitions/basics.rb.erb", "features/step_definitions/#{name}.rb"
38
+
39
+ # rspec stubs
40
+ m.template "spec/spec_helper.rb.erb", "spec/spec_helper.rb"
41
+ m.template "spec/helpers.rb.erb", "spec/helpers.rb"
42
+ m.template "spec/templates_spec.rb.erb", "spec/#{name}_spec.rb"
43
+ m.template "spec/fixtures.rb.erb", "spec/fixtures.rb"
44
+
45
+ %w(LICENSE Rakefile README.md).each do |file|
46
+ m.template file, file
47
+ end
48
+ %w(TODO).each do |file|
49
+ m.file file, file
50
+ end
51
+ %w(about failed home).each do |file|
52
+ m.file "lib/templates/views/#{file}.haml", "lib/#{name}/views/#{file}.haml"
53
+ end
54
+ end
55
+ end
56
+
57
+ protected
58
+ def banner
59
+ <<-EOS
60
+ Creates a simple RubyGems scaffold.
61
+
62
+ USAGE: #{spec.name} name --simple"
63
+ EOS
64
+ end
65
+
66
+ def add_options!(opts)
67
+ opts.separator ''
68
+ opts.separator 'Options:'
69
+ # For each option below, place the default
70
+ # at the top of the file next to "default_options"
71
+ # opts.on("-a", "--author=\"Your Name\"", String,
72
+ # "Some comment about this option",
73
+ # "Default: none") { |x| options[:author] = x }
74
+ opts.on("-v", "--version", "Show the #{File.basename($0)} version number and quit.")
75
+ end
76
+
77
+ def extract_options
78
+ # for each option, extract it into a local variable (and create an "attr_reader :author" at the top)
79
+ # Templates can access these value via the attr_reader-generated methods, but not the
80
+ # raw instance variable value.
81
+ # @author = options[:author]
82
+ end
83
+
84
+ # Installation skeleton. Intermediate directories are automatically
85
+ # created so don't sweat their absence here.
86
+ BASEDIRS = %w(
87
+ lib
88
+ spec
89
+ )
90
+ end