cramp 0.13 → 0.14

Sign up to get free protection for your applications and to get access to all the features.
data/bin/cramp ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cramp'
4
+ require 'cramp/generators/application'
5
+
6
+ if ['--version', '-v'].include?(ARGV.first)
7
+ puts "Cramp #{Cramp::VERSION}"
8
+ exit(0)
9
+ end
10
+
11
+ if ARGV.first != "new"
12
+ ARGV[0] = "--help"
13
+ else
14
+ ARGV.shift
15
+ end
16
+
17
+ Cramp::Generators::Application.start
data/lib/cramp.rb CHANGED
@@ -19,7 +19,7 @@ if RUBY_VERSION >= '1.9.1'
19
19
  end
20
20
 
21
21
  module Cramp
22
- VERSION = '0.13'
22
+ VERSION = '0.14'
23
23
 
24
24
  mattr_accessor :logger
25
25
 
@@ -10,12 +10,14 @@ module Cramp
10
10
 
11
11
  class << self
12
12
  def call(env)
13
- controller = new(env).process
13
+ new(env).process
14
14
  end
15
15
  end
16
16
 
17
17
  def initialize(env)
18
18
  @env = env
19
+ @env['websocket.receive_callback'] = method(:_on_data_receive)
20
+
19
21
  @finished = false
20
22
  end
21
23
 
data/lib/cramp/action.rb CHANGED
@@ -51,10 +51,19 @@ module Cramp
51
51
  @body.call(result)
52
52
  end
53
53
 
54
+ def render_websocket(body, *)
55
+ data = ["\x00", body, "\xFF"].map(&method(:encode)) * ''
56
+ @body.call(data)
57
+ end
58
+
54
59
  # Used by SSE
55
60
  def sse_event_id
56
61
  @sse_event_id ||= Time.now.to_i
57
62
  end
58
63
 
64
+ def encode(string, encoding = 'UTF-8')
65
+ string.respond_to?(:force_encoding) ? string.force_encoding(encoding) : string
66
+ end
67
+
59
68
  end
60
69
  end
@@ -4,10 +4,12 @@ module Cramp
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- class_inheritable_accessor :before_start_callbacks, :on_finish_callbacks, :on_start_callback, :instance_reader => false
7
+ class_inheritable_accessor :before_start_callbacks, :on_finish_callbacks, :on_start_callback, :on_data_callbacks, :instance_reader => false
8
+
8
9
  self.before_start_callbacks = []
9
10
  self.on_finish_callbacks = []
10
11
  self.on_start_callback = []
12
+ self.on_data_callbacks = []
11
13
  end
12
14
 
13
15
  module ClassMethods
@@ -22,6 +24,10 @@ module Cramp
22
24
  def on_start(*methods)
23
25
  self.on_start_callback += methods
24
26
  end
27
+
28
+ def on_data(*methods)
29
+ self.on_data_callbacks += methods
30
+ end
25
31
  end
26
32
 
27
33
  def before_start(n = 0)
@@ -50,5 +56,14 @@ module Cramp
50
56
  EM.next_tick { yield }
51
57
  end
52
58
 
59
+ def _on_data_receive(data)
60
+ data = data.split(/\000([^\377]*)\377/).select{|d| !d.empty? }.collect{|d| d.gsub(/^\x00|\xff$/, '') }
61
+ self.class.on_data_callbacks.each do |callback|
62
+ data.each do |message|
63
+ EM.next_tick { send(callback, message) }
64
+ end
65
+ end
66
+ end
67
+
53
68
  end
54
69
  end
@@ -0,0 +1,97 @@
1
+ require 'thor/group'
2
+ require 'active_support/core_ext/string/strip'
3
+ require 'active_support/core_ext/string/inflections'
4
+ require 'active_support/core_ext/object/blank'
5
+
6
+ module Cramp
7
+ module Generators
8
+
9
+ class Application < Thor::Group
10
+ include Thor::Actions
11
+
12
+ argument :application_path, :type => :string
13
+ class_option :with_active_record, :type => :boolean, :aliases => "-M", :default => false, :desc => "Configures Active Record"
14
+
15
+ def initialize(*args)
16
+ raise Thor::Error, "No application name supplied. Please run: cramp --help" if args[0].blank?
17
+
18
+ super
19
+ end
20
+
21
+ def self.source_root
22
+ @_source_root ||= File.join(File.dirname(__FILE__), "templates/application")
23
+ end
24
+
25
+ def self.banner
26
+ "cramp new #{self.arguments.map(&:usage).join(' ')} [options]"
27
+ end
28
+
29
+ def create_root
30
+ self.destination_root = File.expand_path(application_path, destination_root)
31
+ valid_const?
32
+
33
+ empty_directory '.'
34
+ FileUtils.cd(destination_root)
35
+ end
36
+
37
+ def create_root_files
38
+ template 'config.ru'
39
+ template 'Gemfile'
40
+ template 'application.rb'
41
+
42
+ empty_directory "public"
43
+ empty_directory "public/javascripts"
44
+ end
45
+
46
+ def create_config
47
+ empty_directory "config"
48
+
49
+ inside "config" do
50
+ template "routes.rb"
51
+ template 'database.yml' if active_record?
52
+ end
53
+ end
54
+
55
+ def create_home_action
56
+ empty_directory "app/actions"
57
+
58
+ inside "app/actions" do
59
+ template "home_action.rb"
60
+ end
61
+ end
62
+
63
+ def create_models
64
+ if active_record?
65
+ empty_directory "app/models"
66
+ end
67
+ end
68
+
69
+ protected
70
+
71
+ def active_record?
72
+ options[:with_active_record]
73
+ end
74
+
75
+ def app_name
76
+ @app_name ||= File.basename(destination_root)
77
+ end
78
+
79
+ def app_const
80
+ @app_const ||= "#{app_const_base}::Application"
81
+ end
82
+
83
+ def app_const_base
84
+ @app_const_base ||= app_name.gsub(/\W/, '_').squeeze('_').camelize
85
+ end
86
+
87
+ def valid_const?
88
+ if app_const =~ /^\d/
89
+ raise Thor::Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers."
90
+ elsif Object.const_defined?(app_const_base)
91
+ raise Thor::Error, "Invalid application name #{app_name}, constant #{app_const_base} is already in use. Please choose another application name."
92
+ end
93
+ end
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,32 @@
1
+ source :rubygems
2
+
3
+ gem 'cramp'
4
+
5
+ # Async webserver for running a cramp application
6
+ gem 'thin'
7
+
8
+ # Rack based routing
9
+ gem 'http_router'
10
+
11
+ # Collection of async-proof rack middlewares - https://github.com/rkh/async-rack.git
12
+ gem 'async-rack'
13
+
14
+ # For async Active Record models
15
+ <% if active_record? -%>
16
+ gem 'mysql2', '~> 0.2.11'
17
+ gem 'activerecord'
18
+ <% else -%>
19
+ # gem 'mysql2', '~> 0.2.11'
20
+ # gem 'activerecord'
21
+ <% end -%>
22
+
23
+ # Using Fibers + async callbacks to emulate synchronous programming
24
+ # gem 'em-synchrony'
25
+
26
+ # Generic interface to multiple Ruby template engines - https://github.com/rtomayko/tilt
27
+ # gem 'tilt'
28
+
29
+ group :development do
30
+ # Development gems
31
+ # gem 'ruby-debug19'
32
+ end
@@ -0,0 +1,11 @@
1
+ class HomeAction < Cramp::Action
2
+ <% if active_record? -%>use_fiber_pool do |pool|
3
+ # Checkin database connection after each callback
4
+ pool.generic_callbacks << proc { ActiveRecord::Base.clear_active_connections! }
5
+ end
6
+
7
+ <% end %>def start
8
+ render "Hello World!"
9
+ finish
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+
4
+ module <%= app_const_base %>
5
+ class Application
6
+
7
+ def self.root(path = nil)
8
+ @_root ||= File.expand_path(File.dirname(__FILE__))
9
+ path ? File.join(@_root, path.to_s) : @_root
10
+ end
11
+
12
+ def self.env
13
+ @_env ||= ENV['RACK_ENV'] || 'development'
14
+ end
15
+
16
+ def self.routes
17
+ @_routes ||= eval(File.read('./config/routes.rb'))
18
+ end
19
+
20
+ <% if active_record? %>def self.database_config
21
+ @_database_config ||= YAML.load(File.read('./config/database.yml')).with_indifferent_access
22
+ end
23
+
24
+ <% end %># Initialize the application
25
+ def self.initialize!<% if active_record? %>
26
+ ActiveRecord::Base.configurations = <%= app_const %>.database_config
27
+ ActiveRecord::Base.establish_connection(<%= app_const %>.env)<% end %>
28
+ end
29
+
30
+ end
31
+ end
32
+
33
+ Bundler.require(:default, <%= app_const %>.env)
34
+
35
+ # Preload application classes
36
+ Dir['./app/**/*.rb'].each {|f| require f}
@@ -0,0 +1,25 @@
1
+ require './application'
2
+ <%= app_const %>.initialize!
3
+
4
+ # Development middlewares
5
+ if <%= app_const %>.env == 'development'
6
+ use AsyncRack::CommonLogger
7
+
8
+ # Enable code reloading on every request
9
+ use Rack::Reloader, 0
10
+
11
+ # Serve assets from /public
12
+ use Rack::Static, :urls => ["/javascripts"], :root => <%= app_const %>.root(:public)
13
+ end
14
+
15
+ # Running thin :
16
+ #
17
+ # bundle exec thin --max-persistent-conns 1024 --timeout 0 -R config.ru start
18
+ #
19
+ # Vebose mode :
20
+ #
21
+ # Very useful when you want to view all the data being sent/received by thin
22
+ #
23
+ # bundle exec thin --max-persistent-conns 1024 --timeout 0 -V -R config.ru start
24
+ #
25
+ run <%= app_const %>.routes
@@ -0,0 +1,6 @@
1
+ development:
2
+ adapter: em_mysql2
3
+ database: <%= app_name %>_development
4
+ host: localhost
5
+ username: root
6
+ pool: 100
@@ -0,0 +1,4 @@
1
+ # Check out https://github.com/joshbuddy/http_router for more information on HttpRouter
2
+ HttpRouter.new do
3
+ add('/').to(HomeAction)
4
+ end
@@ -1,47 +1,12 @@
1
1
  module Cramp
2
- class Websocket < Abstract
3
- include PeriodicTimer
4
-
5
- # TODO : Websockets shouldn't need this in an ideal world
6
- include KeepConnectionAlive
7
-
8
- class_inheritable_accessor :on_data_callbacks, :instance_reader => false
9
- self.on_data_callbacks = []
2
+ class Websocket < Action
3
+ self.transport = :websocket
10
4
 
11
5
  class << self
12
6
  def backend=(backend)
13
7
  raise "Websocket backend #{backend} is unknown" unless [:thin, :rainbows].include?(backend.to_sym)
14
8
  require "cramp/websocket/#{backend}_backend.rb"
15
9
  end
16
-
17
- def on_data(*methods)
18
- self.on_data_callbacks += methods
19
- end
20
- end
21
-
22
- def process
23
- @env['websocket.receive_callback'] = method(:_on_data_receive)
24
- super
25
- end
26
-
27
- def render(body)
28
- data = ["\x00", body, "\xFF"].map(&method(:encode)) * ''
29
- @body.call(data)
30
- end
31
-
32
- def _on_data_receive(data)
33
- data = data.split(/\000([^\377]*)\377/).select{|d| !d.empty? }.collect{|d| d.gsub(/^\x00|\xff$/, '') }
34
- self.class.on_data_callbacks.each do |callback|
35
- data.each do |message|
36
- EM.next_tick { send(callback, message) }
37
- end
38
- end
39
- end
40
-
41
- protected
42
-
43
- def encode(string, encoding = 'UTF-8')
44
- string.respond_to?(:force_encoding) ? string.force_encoding(encoding) : string
45
10
  end
46
11
 
47
12
  end
@@ -0,0 +1,44 @@
1
+ class Cramp::Websocket::Rainbows < Rainbows::EventMachine::Client
2
+ include Cramp::WebsocketExtension
3
+
4
+ def receive_data(data)
5
+ case @state
6
+ when :websocket
7
+ callback = @env[WEBSOCKET_RECEIVE_CALLBACK]
8
+ callback.call(data) if callback
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ def on_read(data)
15
+ if @state == :headers
16
+ @hp.add_parse(data) or return want_more
17
+ @state = :body
18
+ if 0 == @hp.content_length && !websocket?
19
+ app_call NULL_IO # common case
20
+ else # nil or len > 0
21
+ prepare_request_body
22
+ end
23
+ elsif @state == :body && websocket? && @hp.body_eof?
24
+ @state = :websocket
25
+ @input.rewind
26
+
27
+ handler = @env['HTTP_SEC_WEBSOCKET_KEY1'] &&
28
+ @env['HTTP_SEC_WEBSOCKET_KEY2'] ? Protocol76 : Protocol75
29
+ write(handler.new(@env, websocket_url, @buf).handshake)
30
+ app_call NULL_IO
31
+ else
32
+ super
33
+ end
34
+ rescue => e
35
+ handle_error(e)
36
+ end
37
+
38
+ def write_response(status, headers, body, alive)
39
+ write_headers(status, headers, alive) unless websocket?
40
+ write_body_each(body)
41
+ ensure
42
+ body.close if body.respond_to?(:close)
43
+ end
44
+ end
@@ -1,40 +1,8 @@
1
- require 'rainbows'
2
-
3
- class Rainbows::EventMachine::Client
4
- include Cramp::WebsocketExtension
5
-
6
- def websocket_handshake!
7
- @state = :websocket
8
- end
9
-
10
- def receive_data_with_websocket(data)
11
- case @state
12
- when :websocket
13
- callback = @env[WEBSOCKET_RECEIVE_CALLBACK]
14
- callback.call(data) if callback
15
- else
16
- receive_data_without_websocket(data)
17
- end
18
- end
19
-
20
- alias_method_chain :receive_data, :websocket
21
- end
22
-
23
- class Rainbows::HttpResponse
24
- class << self
25
-
26
- def write_with_magic(socket, rack_response, out = [])
27
- if socket.websocket?
28
- socket.write socket.websocket_upgrade_data
29
- socket.websocket_handshake!
30
-
31
- out = nil # To make sure Rainbows! doesn't send back regular HTTP headers
32
- end
33
-
34
- write_without_magic(socket, rack_response, out)
35
- end
36
-
37
- alias_method_chain :write, :magic
38
- end
39
-
1
+ # :enddoc:
2
+ require "rainbows"
3
+ class Cramp::Websocket
4
+ # we use autoload since Rainbows::EventMachine::Client should only be
5
+ # loaded in the worker proceses and we want to be preload_app-friendly
6
+ autoload :Rainbows, "cramp/websocket/rainbows"
40
7
  end
8
+ Rainbows::O[:em_client_class] = "Cramp::Websocket::Rainbows"
metadata CHANGED
@@ -1,85 +1,81 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: cramp
3
- version: !ruby/object:Gem::Version
4
- hash: 17
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.14'
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 13
9
- version: "0.13"
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Pratik Naik
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2011-07-31 00:00:00 +01:00
12
+ date: 2011-08-05 00:00:00.000000000 +01:00
18
13
  default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
21
16
  name: activesupport
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
17
+ requirement: &2156676820 !ruby/object:Gem::Requirement
24
18
  none: false
25
- requirements:
19
+ requirements:
26
20
  - - ~>
27
- - !ruby/object:Gem::Version
28
- hash: 21
29
- segments:
30
- - 3
31
- - 0
32
- - 9
21
+ - !ruby/object:Gem::Version
33
22
  version: 3.0.9
34
23
  type: :runtime
35
- version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
37
- name: rack
38
24
  prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
25
+ version_requirements: *2156676820
26
+ - !ruby/object:Gem::Dependency
27
+ name: rack
28
+ requirement: &2156676220 !ruby/object:Gem::Requirement
40
29
  none: false
41
- requirements:
30
+ requirements:
42
31
  - - ~>
43
- - !ruby/object:Gem::Version
44
- hash: 29
45
- segments:
46
- - 1
47
- - 2
48
- - 1
49
- version: 1.2.1
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.2
50
34
  type: :runtime
51
- version_requirements: *id002
52
- - !ruby/object:Gem::Dependency
35
+ prerelease: false
36
+ version_requirements: *2156676220
37
+ - !ruby/object:Gem::Dependency
53
38
  name: eventmachine
39
+ requirement: &2156675640 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 1.0.0.beta.3
45
+ type: :runtime
54
46
  prerelease: false
55
- requirement: &id003 !ruby/object:Gem::Requirement
47
+ version_requirements: *2156675640
48
+ - !ruby/object:Gem::Dependency
49
+ name: thor
50
+ requirement: &2156674960 !ruby/object:Gem::Requirement
56
51
  none: false
57
- requirements:
52
+ requirements:
58
53
  - - ~>
59
- - !ruby/object:Gem::Version
60
- hash: 59
61
- segments:
62
- - 0
63
- - 12
64
- - 10
65
- version: 0.12.10
54
+ - !ruby/object:Gem::Version
55
+ version: 0.14.6
66
56
  type: :runtime
67
- version_requirements: *id003
57
+ prerelease: false
58
+ version_requirements: *2156674960
68
59
  description: Cramp is a framework for developing asynchronous web applications.
69
60
  email: pratiknaik@gmail.com
70
- executables: []
71
-
61
+ executables:
62
+ - cramp
72
63
  extensions: []
73
-
74
64
  extra_rdoc_files: []
75
-
76
- files:
65
+ files:
77
66
  - MIT-LICENSE
78
67
  - lib/cramp/abstract.rb
79
68
  - lib/cramp/action.rb
80
69
  - lib/cramp/body.rb
81
70
  - lib/cramp/callbacks.rb
82
71
  - lib/cramp/fiber_pool.rb
72
+ - lib/cramp/generators/application.rb
73
+ - lib/cramp/generators/templates/application/app/actions/home_action.rb
74
+ - lib/cramp/generators/templates/application/application.rb
75
+ - lib/cramp/generators/templates/application/config/database.yml
76
+ - lib/cramp/generators/templates/application/config/routes.rb
77
+ - lib/cramp/generators/templates/application/config.ru
78
+ - lib/cramp/generators/templates/application/Gemfile
83
79
  - lib/cramp/keep_connection_alive.rb
84
80
  - lib/cramp/long_polling.rb
85
81
  - lib/cramp/periodic_timer.rb
@@ -87,44 +83,36 @@ files:
87
83
  - lib/cramp/sse.rb
88
84
  - lib/cramp/test_case.rb
89
85
  - lib/cramp/websocket/extension.rb
86
+ - lib/cramp/websocket/rainbows.rb
90
87
  - lib/cramp/websocket/rainbows_backend.rb
91
88
  - lib/cramp/websocket/thin_backend.rb
92
89
  - lib/cramp/websocket.rb
93
90
  - lib/cramp.rb
94
91
  - lib/vendor/fiber_pool.rb
95
- has_rdoc: true
92
+ - bin/cramp
93
+ has_rdoc: false
96
94
  homepage: http://m.onkey.org
97
95
  licenses: []
98
-
99
96
  post_install_message:
100
97
  rdoc_options: []
101
-
102
- require_paths:
98
+ require_paths:
103
99
  - lib
104
- required_ruby_version: !ruby/object:Gem::Requirement
100
+ required_ruby_version: !ruby/object:Gem::Requirement
105
101
  none: false
106
- requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- hash: 3
110
- segments:
111
- - 0
112
- version: "0"
113
- required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
107
  none: false
115
- requirements:
116
- - - ">="
117
- - !ruby/object:Gem::Version
118
- hash: 3
119
- segments:
120
- - 0
121
- version: "0"
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
122
112
  requirements: []
123
-
124
113
  rubyforge_project:
125
114
  rubygems_version: 1.6.2
126
115
  signing_key:
127
116
  specification_version: 3
128
117
  summary: Asynchronous web framework.
129
118
  test_files: []
130
-