nyny 1.0.0.pre1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +5 -0
- data/Performance.md +8 -6
- data/README.md +116 -55
- data/Rakefile +5 -0
- data/benchmarks/filters/{frankie.rb → nyny.rb} +2 -2
- data/benchmarks/helpers/{frankie.rb → nyny.rb} +2 -2
- data/benchmarks/simple/{frankie.rb → nyny.rb} +2 -2
- data/benchmarks/url_pattern/{frankie.rb → nyny.rb} +2 -2
- data/examples/active_record/server.rb +2 -2
- data/examples/data_mapper/Gemfile +7 -0
- data/examples/data_mapper/database.rb +7 -0
- data/examples/data_mapper/models/shout.rb +7 -0
- data/examples/data_mapper/server.rb +41 -0
- data/examples/json_api.rb +2 -2
- data/examples/templates/server.rb +5 -4
- data/examples/web_sockets/server.rb +4 -4
- data/lib/nyny.rb +4 -2
- data/lib/nyny/app.rb +46 -47
- data/lib/nyny/middleware_chain.rb +14 -0
- data/lib/nyny/request_scope.rb +3 -9
- data/lib/nyny/route_signature.rb +11 -11
- data/lib/nyny/router.rb +44 -0
- data/lib/nyny/runner.rb +18 -0
- data/lib/nyny/version.rb +1 -1
- data/nyny.gemspec +2 -2
- data/spec/app_spec.rb +34 -24
- data/spec/primitives_spec.rb +11 -4
- data/spec/request_scope_spec.rb +12 -2
- data/spec/route_signature_spec.rb +9 -0
- data/spec/runner_spec.rb +23 -0
- data/spec/spec_helper.rb +14 -3
- metadata +23 -15
- data/.ruby-gemset +0 -1
- data/lib/nyny/class_level_api.rb +0 -40
- data/spec/class_level_api_spec.rb +0 -39
@@ -0,0 +1,41 @@
|
|
1
|
+
#!ruby -I ../../lib -I lib
|
2
|
+
|
3
|
+
ENV['RACK_ENV'] ||= 'development'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'bundler'
|
7
|
+
Bundler.setup(:default, ENV['RACK_ENV'].to_sym)
|
8
|
+
|
9
|
+
require 'nyny'
|
10
|
+
require_relative 'database'
|
11
|
+
|
12
|
+
TEMPLATE = DATA.read.freeze
|
13
|
+
|
14
|
+
class App < NYNY::App
|
15
|
+
get '/' do
|
16
|
+
shouts = Shout.all.reverse
|
17
|
+
ERB.new(TEMPLATE).result(binding)
|
18
|
+
end
|
19
|
+
|
20
|
+
post '/shouts' do
|
21
|
+
Shout.create :body => params[:body]
|
22
|
+
redirect_to '/'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
App.run! 9000
|
27
|
+
|
28
|
+
__END__
|
29
|
+
<html>
|
30
|
+
<body>
|
31
|
+
<form action="/shouts" method="post">
|
32
|
+
<input type="text" name="body"></input>
|
33
|
+
<input type="submit" value="SHOUT"></input>
|
34
|
+
</form>
|
35
|
+
<ul>
|
36
|
+
<% shouts.each do |shout| %>
|
37
|
+
<li><%= shout.body %>
|
38
|
+
<% end %>
|
39
|
+
</ul>
|
40
|
+
</body>
|
41
|
+
</html>
|
data/examples/json_api.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
#!ruby -I ../lib -I lib
|
2
|
-
require '
|
2
|
+
require 'nyny'
|
3
3
|
require 'json'
|
4
4
|
|
5
5
|
#
|
6
6
|
# Every response of this app will be automatically converted to json
|
7
7
|
#
|
8
|
-
class App <
|
8
|
+
class App < NYNY::App
|
9
9
|
before { headers 'Content-Type' => 'application/json' }
|
10
10
|
|
11
11
|
after do
|
@@ -1,9 +1,10 @@
|
|
1
|
-
#!ruby -I
|
2
|
-
require '
|
1
|
+
#!ruby -I ../../lib -I lib
|
2
|
+
require 'nyny'
|
3
3
|
require 'sinatra'
|
4
4
|
require 'ostruct'
|
5
5
|
|
6
6
|
module Views
|
7
|
+
CACHE = Tilt::Cache.new
|
7
8
|
include ::Sinatra::Templates
|
8
9
|
|
9
10
|
def settings
|
@@ -11,12 +12,12 @@ module Views
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def template_cache
|
14
|
-
|
15
|
+
CACHE
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
19
|
|
19
|
-
class App <
|
20
|
+
class App < NYNY::App
|
20
21
|
helpers Views
|
21
22
|
|
22
23
|
get '/' do
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#!ruby -I ../../lib -I lib
|
2
|
-
require '
|
2
|
+
require 'nyny'
|
3
3
|
require 'faye/websocket'
|
4
4
|
|
5
5
|
#
|
@@ -37,7 +37,7 @@ class WebSockets
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
class App <
|
40
|
+
class App < NYNY::App
|
41
41
|
#Serve static assets from public folder
|
42
42
|
use Rack::Static, :urls => ["/public"]
|
43
43
|
|
@@ -52,8 +52,8 @@ class App < Frankie::App
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
get '/
|
56
|
-
'yep, you can still use
|
55
|
+
get '/nyny' do
|
56
|
+
'yep, you can still use nyny'
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
data/lib/nyny.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'rack'
|
3
3
|
|
4
|
-
require
|
4
|
+
require 'nyny/version'
|
5
5
|
require 'nyny/primitives'
|
6
6
|
require 'nyny/request_scope'
|
7
7
|
require 'nyny/route_signature'
|
8
|
-
require 'nyny/
|
8
|
+
require 'nyny/runner'
|
9
|
+
require 'nyny/middleware_chain'
|
10
|
+
require 'nyny/router'
|
9
11
|
require 'nyny/app'
|
data/lib/nyny/app.rb
CHANGED
@@ -1,66 +1,65 @@
|
|
1
1
|
module NYNY
|
2
2
|
class App
|
3
|
-
|
4
|
-
|
5
|
-
RouteNotFoundError = Class.new StandardError
|
3
|
+
HTTP_VERBS = [:delete, :get, :head, :options, :patch, :post, :put, :trace]
|
4
|
+
extend Runner
|
6
5
|
|
6
|
+
attr_reader :middleware_chain, :router
|
7
7
|
def initialize app=nil
|
8
|
-
@
|
9
|
-
|
8
|
+
@router = Router.new({
|
9
|
+
:routes => self.class.routes,
|
10
|
+
:fallback => (app || lambda {|env| Response.new '', 404 }),
|
11
|
+
:before_hooks => self.class.before_hooks,
|
12
|
+
:after_hooks => self.class.after_hooks
|
13
|
+
})
|
14
|
+
@middleware_chain = MiddlewareChain.new(self.class.middlewares,
|
15
|
+
lambda {|env| _call(env)})
|
10
16
|
end
|
11
17
|
|
12
|
-
def
|
13
|
-
|
14
|
-
klass, args, blk = entry
|
15
|
-
klass.new prev, *args, &blk
|
16
|
-
end
|
18
|
+
def _call env
|
19
|
+
router.call env
|
17
20
|
end
|
18
21
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
begin
|
23
|
-
Rack::Handler::Thin
|
24
|
-
rescue LoadError
|
25
|
-
Rack::Handler::WEBrick
|
26
|
-
end.run new, :Port => port
|
22
|
+
def call env
|
23
|
+
middleware_chain.call env
|
27
24
|
end
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
#class methods
|
27
|
+
class << self
|
28
|
+
HTTP_VERBS.each do |method|
|
29
|
+
define_method method do |str, &blk|
|
30
|
+
(routes[method] ||= {})[RouteSignature.new(str)] = Proc.new &blk
|
31
|
+
end
|
33
32
|
end
|
34
33
|
|
35
|
-
|
36
|
-
|
34
|
+
def middlewares; @middlewares ||= [] end
|
35
|
+
def routes; @routes ||= {} end
|
36
|
+
def before_hooks; @before_hooks ||= [] end
|
37
|
+
def after_hooks; @after_hooks ||= [] end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
def use_protection! args={}
|
40
|
+
begin
|
41
|
+
require 'rack/protection'
|
42
|
+
middlewares.unshift [Rack::Protection, args]
|
43
|
+
rescue LoadError
|
44
|
+
puts "WARN: to use protection, you must install 'rack-protection' gem"
|
45
|
+
end
|
45
46
|
end
|
46
|
-
end
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
def before &blk
|
49
|
+
before_hooks << Proc.new(&blk)
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
_call env
|
55
|
-
else
|
56
|
-
if not @initialized_chain
|
57
|
-
@initialized_chain = true
|
58
|
-
@top.call(env)
|
59
|
-
else
|
60
|
-
@initialized_chain = false
|
61
|
-
_call env
|
62
|
-
end
|
52
|
+
def after &blk
|
53
|
+
after_hooks << Proc.new(&blk)
|
63
54
|
end
|
64
|
-
|
55
|
+
|
56
|
+
def use middleware, *args, &block
|
57
|
+
middlewares << [middleware, args, block]
|
58
|
+
end
|
59
|
+
|
60
|
+
def helpers *args
|
61
|
+
args.each {|m| RequestScope.add_helper_module m }
|
62
|
+
end
|
63
|
+
end #class methods
|
65
64
|
end
|
66
65
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module NYNY
|
2
|
+
class MiddlewareChain
|
3
|
+
def initialize middlewares, proxy
|
4
|
+
@top = middlewares.reverse.reduce (proxy) do |prev, entry|
|
5
|
+
klass, args, blk = entry
|
6
|
+
klass.new prev, *args, &blk
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def call env
|
11
|
+
@top.call(env)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/nyny/request_scope.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
module NYNY
|
2
2
|
class RequestScope
|
3
|
-
attr_reader :request, :
|
3
|
+
attr_reader :request, :response
|
4
4
|
|
5
5
|
def self.add_helper_module m
|
6
6
|
include m
|
7
7
|
end
|
8
8
|
|
9
|
-
def initialize
|
10
|
-
@app = app
|
9
|
+
def initialize request
|
11
10
|
@headers = {'Content-Type' => 'text/html'}
|
12
11
|
@status = 200
|
13
|
-
@request =
|
12
|
+
@request = request
|
14
13
|
end
|
15
14
|
|
16
15
|
def params
|
@@ -42,17 +41,12 @@ module NYNY
|
|
42
41
|
end
|
43
42
|
|
44
43
|
def apply_to &handler
|
45
|
-
params.default_proc = proc {|h,k| h[k.to_s] || h[k.to_sym]}
|
46
|
-
app.class.before_hooks.each {|h| instance_eval &h }
|
47
|
-
|
48
44
|
@response = @halt_response || begin
|
49
45
|
Response.new instance_eval(&handler), @status, @headers
|
50
46
|
end
|
51
47
|
|
52
48
|
cookies.each {|k,v| @response.set_cookie k,v }
|
53
49
|
@response.redirect(@redirect) if @redirect
|
54
|
-
|
55
|
-
app.class.after_hooks.each {|h| instance_eval &h }
|
56
50
|
@response.finish
|
57
51
|
@response
|
58
52
|
end
|
data/lib/nyny/route_signature.rb
CHANGED
@@ -3,27 +3,27 @@ module NYNY
|
|
3
3
|
NAME_PATTERN = /:(\S+)/
|
4
4
|
|
5
5
|
attr_reader :pattern
|
6
|
-
def initialize
|
7
|
-
@pattern =
|
8
|
-
route
|
9
|
-
else
|
10
|
-
pattern_for route.dup
|
11
|
-
end
|
6
|
+
def initialize signature
|
7
|
+
@pattern = pattern_for signature
|
12
8
|
end
|
13
9
|
|
14
10
|
def pattern_for string
|
11
|
+
return string if string.is_a? Regexp
|
15
12
|
return string unless string.include? ':'
|
16
|
-
string = "/#{string}" unless string.start_with? '/'
|
17
|
-
parts = string.split '/'
|
18
13
|
|
19
|
-
|
14
|
+
signature = string.start_with?('/') ? string : "/#{string}"
|
15
|
+
build_regex signature
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_regex signature
|
19
|
+
groups = signature.split('/').map do |part|
|
20
20
|
next part if part.empty?
|
21
21
|
next part unless part.start_with? ':'
|
22
22
|
name = NAME_PATTERN.match(part)[1]
|
23
23
|
%Q{(?<#{name}>\\S+)}
|
24
|
-
end.select {|s| !s.empty? }
|
24
|
+
end.select {|s| !s.empty? }.join('\/')
|
25
25
|
|
26
|
-
%r(\/#{groups
|
26
|
+
%r(\/#{groups})
|
27
27
|
end
|
28
28
|
|
29
29
|
def match path
|
data/lib/nyny/router.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module NYNY
|
2
|
+
class Router
|
3
|
+
NullHandler = Class.new
|
4
|
+
|
5
|
+
attr_reader :fallback, :routes, :before_hooks, :after_hooks
|
6
|
+
def initialize options
|
7
|
+
@fallback = options[:fallback]
|
8
|
+
@routes = options[:routes]
|
9
|
+
@before_hooks = options[:before_hooks]
|
10
|
+
@after_hooks = options[:after_hooks]
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_handler request
|
14
|
+
routes.fetch(request.request_method.downcase.to_sym, []).each do |sig, h|
|
15
|
+
params = sig.match request.path
|
16
|
+
return [h, params] if params
|
17
|
+
end
|
18
|
+
|
19
|
+
[NullHandler, {}]
|
20
|
+
end
|
21
|
+
|
22
|
+
def call env
|
23
|
+
req = Request.new(env)
|
24
|
+
handler, params = find_handler req
|
25
|
+
|
26
|
+
if handler != NullHandler
|
27
|
+
process req, handler, params
|
28
|
+
else
|
29
|
+
fallback.call env
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def process request, handler, url_params
|
34
|
+
request.params.merge! url_params
|
35
|
+
request.params.default_proc = proc {|h,k| h[k.to_s] || h[k.to_sym]}
|
36
|
+
|
37
|
+
scope = RequestScope.new(request)
|
38
|
+
before_hooks.each {|h| scope.instance_eval &h }
|
39
|
+
response = scope.apply_to &handler
|
40
|
+
after_hooks.each {|h| scope.instance_eval &h }
|
41
|
+
response
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/nyny/runner.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module NYNY
|
2
|
+
module Runner
|
3
|
+
def optimal_runner
|
4
|
+
return Rack::Handler::WEBrick if RUBY_PLATFORM == 'java'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Rack::Handler::Thin
|
8
|
+
rescue LoadError
|
9
|
+
Rack::Handler::WEBrick
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def run! port=9292
|
14
|
+
middlewares.unshift Rack::ShowExceptions, Rack::CommonLogger
|
15
|
+
optimal_runner.run new, :Port => port
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/nyny/version.rb
CHANGED
data/nyny.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = NYNY::VERSION
|
9
9
|
spec.authors = ["Andrei Lisnic"]
|
10
10
|
spec.email = ["andrei.lisnic@gmail.com"]
|
11
|
-
spec.description = %q{New York, New York.}
|
12
|
-
spec.summary = %q{
|
11
|
+
spec.description = %q{New York, New York - (very) small Sinatra clone.}
|
12
|
+
spec.summary = %q{New York, New York.}
|
13
13
|
spec.homepage = "https://github.com/alisnic/nyny"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
data/spec/app_spec.rb
CHANGED
@@ -1,16 +1,8 @@
|
|
1
|
-
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe App do
|
4
4
|
let (:app) { mock_app {} }
|
5
5
|
|
6
|
-
describe '.run!' do
|
7
|
-
#
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'should have the class methods included' do
|
11
|
-
extended_modules_for(App).should include(ClassLevelApi)
|
12
|
-
end
|
13
|
-
|
14
6
|
it 'should return a rack response on call' do
|
15
7
|
response = app.get '/'
|
16
8
|
response.should be_a(Rack::Response)
|
@@ -21,9 +13,17 @@ describe App do
|
|
21
13
|
response.status.should == 404
|
22
14
|
end
|
23
15
|
|
16
|
+
it '.use_protection! should add protection middleware on top' do
|
17
|
+
app_class = mock_app_class do
|
18
|
+
use_protection!
|
19
|
+
end
|
20
|
+
|
21
|
+
app_class.middlewares.first.should == [Rack::Protection, {}]
|
22
|
+
end
|
23
|
+
|
24
24
|
it 'should match a route for any supported verbs' do
|
25
25
|
url = random_url
|
26
|
-
verb =
|
26
|
+
verb = App::HTTP_VERBS.sample
|
27
27
|
|
28
28
|
app = mock_app do
|
29
29
|
send verb, url do
|
@@ -82,7 +82,7 @@ describe App do
|
|
82
82
|
[210, {}, ['Hello from downstream']]
|
83
83
|
end
|
84
84
|
|
85
|
-
app_class =
|
85
|
+
app_class = mock_app_class do
|
86
86
|
get '/' do
|
87
87
|
'hello'
|
88
88
|
end
|
@@ -122,23 +122,33 @@ describe App do
|
|
122
122
|
res.headers['Set-Cookie'].should == 'foo=bar'
|
123
123
|
end
|
124
124
|
|
125
|
-
describe '
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
125
|
+
describe 'Class level api' do
|
126
|
+
let (:app_class) { Class.new(App) }
|
127
|
+
describe 'middlewares' do
|
128
|
+
let (:app_class) do
|
129
|
+
mock_app_class do
|
130
|
+
use NullMiddleware
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should allow to add a middleware' do
|
135
|
+
app_class.middlewares.last.first.should == NullMiddleware
|
136
|
+
end
|
133
137
|
end
|
134
138
|
|
135
|
-
|
136
|
-
|
139
|
+
describe 'helpers' do
|
140
|
+
it 'should allow to include a helper in request scope' do
|
141
|
+
app_class.helpers NullHelper
|
142
|
+
RequestScope.ancestors.should include(NullHelper)
|
137
143
|
end
|
138
144
|
|
139
|
-
|
140
|
-
|
141
|
-
|
145
|
+
it 'should allow to include multiple helpers modules' do
|
146
|
+
module NullHelper2
|
147
|
+
end
|
148
|
+
|
149
|
+
app_class.helpers NullHelper, NullHelper2
|
150
|
+
RequestScope.ancestors.should include(NullHelper, NullHelper2)
|
151
|
+
end
|
142
152
|
end
|
143
153
|
end
|
144
154
|
end
|