eldr 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +28 -0
  4. data/.rubocop_todo.yml +15 -0
  5. data/.travis.yml +3 -0
  6. data/DUCK.md +15 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +89 -0
  9. data/LICENSE +20 -0
  10. data/README.md +876 -0
  11. data/Rakefile +9 -0
  12. data/TODOS +3 -0
  13. data/eldr.gemspec +36 -0
  14. data/examples/README.md +7 -0
  15. data/examples/action_objects.ru +28 -0
  16. data/examples/app.ru +75 -0
  17. data/examples/builder.ru +24 -0
  18. data/examples/custom_response.ru +23 -0
  19. data/examples/errors.ru +16 -0
  20. data/examples/hello_world.ru +8 -0
  21. data/examples/inheritance.ru +22 -0
  22. data/examples/multiple_apps.ru +30 -0
  23. data/examples/rails_style_routing.ru +14 -0
  24. data/examples/rendering_templates.ru +38 -0
  25. data/examples/views/cats.slim +1 -0
  26. data/lib/eldr/app.rb +146 -0
  27. data/lib/eldr/builder.rb +60 -0
  28. data/lib/eldr/configuration.rb +37 -0
  29. data/lib/eldr/matcher.rb +36 -0
  30. data/lib/eldr/recognizer.rb +35 -0
  31. data/lib/eldr/route.rb +92 -0
  32. data/lib/eldr/version.rb +3 -0
  33. data/lib/eldr.rb +1 -0
  34. data/spec/app_spec.rb +194 -0
  35. data/spec/builder_spec.rb +11 -0
  36. data/spec/configuration_spec.rb +29 -0
  37. data/spec/examples/action_objects_spec.rb +18 -0
  38. data/spec/examples/builder_spec.rb +16 -0
  39. data/spec/examples/custom_response_spec.rb +17 -0
  40. data/spec/examples/errors_spec.rb +18 -0
  41. data/spec/examples/example_app_spec.rb +98 -0
  42. data/spec/examples/hello_world_spec.rb +17 -0
  43. data/spec/examples/inheritance_spec.rb +23 -0
  44. data/spec/examples/multiple_apps_spec.rb +31 -0
  45. data/spec/examples/rails_style_routing_spec.rb +17 -0
  46. data/spec/examples/rendering_templates_spec.rb +26 -0
  47. data/spec/matcher_spec.rb +21 -0
  48. data/spec/readme_definitions.yml +28 -0
  49. data/spec/readme_spec.rb +117 -0
  50. data/spec/recognizer_spec.rb +32 -0
  51. data/spec/route_spec.rb +131 -0
  52. data/spec/spec_helper.rb +14 -0
  53. metadata +252 -0
data/lib/eldr/route.rb ADDED
@@ -0,0 +1,92 @@
1
+ module Eldr
2
+ class Route
3
+ attr_accessor :name, :app, :capture, :order, :options, :handler
4
+ attr_accessor :before_filters, :after_filters, :to
5
+
6
+ def initialize(verb: :get, path: '/', name: nil, options: {}, handler: nil, app: nil)
7
+ @path, @verb = path, verb.to_s.upcase
8
+
9
+ @app = app
10
+ @capture = {}
11
+ @before_filters = []
12
+ @after_filters = []
13
+ @name = name
14
+
15
+ merge_with_options!(options)
16
+
17
+ @handler = handler
18
+ @handler ||= @to
19
+ end
20
+
21
+ def call(env, app: nil)
22
+ # TODO: Investigate
23
+ # maybe if we passed this around between methods it would be more perfomant
24
+ # than setting the accessor?
25
+ @app = app
26
+
27
+ app.class.before_filters[:all].each { |filter| app.instance_exec(env, &filter) } if app
28
+ @before_filters.each { |filter| app.instance_exec(env, &filter) }
29
+
30
+ resp = response(env)
31
+
32
+ app.class.after_filters[:all].each { |filter| app.instance_exec(env, &filter) } if app
33
+ @after_filters.each { |filter| app.instance_exec(env, &filter) }
34
+
35
+ resp
36
+ end
37
+
38
+ def response(env)
39
+ if @handler.is_a? Proc
40
+ app.instance_exec(env, &@handler)
41
+ elsif @handler.is_a? String
42
+ rails_style_response(env)
43
+ else
44
+ @handler.call(env)
45
+ end
46
+ end
47
+
48
+ def rails_style_response(env)
49
+ controller, method = @handler.split('#')
50
+
51
+ obj = Object.const_get(controller).new
52
+
53
+ if method
54
+ obj.send(method.to_sym, env)
55
+ else
56
+ obj.call(env)
57
+ end
58
+ end
59
+
60
+ def merge_with_options!(options)
61
+ @options = {} unless @options
62
+ options.each_pair do |key, value|
63
+ accessor?(key) ? __send__("#{key}=", value) : (@options[key] = value)
64
+ end
65
+ end
66
+
67
+ def accessor?(key)
68
+ respond_to?("#{key}=") && respond_to?(key)
69
+ end
70
+
71
+ def matcher
72
+ @matcher ||= Matcher.new(path, capture: @capture)
73
+ end
74
+
75
+ def match(pattern)
76
+ matcher.match(pattern)
77
+ end
78
+
79
+ def path(*args)
80
+ return @path if args.empty?
81
+ params = args[0]
82
+ params.delete(:captures)
83
+ matcher.expand(params)
84
+ end
85
+
86
+ def params(pattern)
87
+ params = matcher.handler.params(pattern)
88
+ params ||= {}
89
+ params
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,3 @@
1
+ module Eldr
2
+ VERSION = '0.0.2'
3
+ end
data/lib/eldr.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'eldr/app'
data/spec/app_spec.rb ADDED
@@ -0,0 +1,194 @@
1
+ describe Eldr::App do
2
+ it 'has methods for all the http verbs' do
3
+ %w(DELETE GET HEAD OPTIONS PATCH POST PUT).each do |verb|
4
+ expect(Eldr::App.respond_to? verb.downcase.to_sym).to eq(true)
5
+ end
6
+ end
7
+
8
+ describe '.inherited' do
9
+ it 'inherits middleware' do
10
+ bob = Class.new(Eldr::App)
11
+ middleware = Class.new
12
+ expect(bob.builder.middleware.length).to eq(0)
13
+
14
+ bob.use middleware
15
+ expect(bob.builder.middleware.length).to eq(1)
16
+
17
+ # TODO Check that is is the actual class we wanted
18
+ inherited = Class.new(bob)
19
+ expect(inherited.builder.middleware.length).to eq(1)
20
+ end
21
+
22
+ it 'inherits configuration' do
23
+ bob = Class.new(Eldr::App)
24
+ bob.set(:bob, 'what about him?')
25
+
26
+ inherited = Class.new(bob)
27
+ expect(inherited.configuration.bob).to eq('what about him?')
28
+ end
29
+ end
30
+
31
+ describe '.new' do
32
+ it 'returns a new instance of Eldr::Builder' do
33
+ expect(Eldr::App.new).to be_instance_of Eldr::Builder
34
+ end
35
+ end
36
+
37
+ describe '.set' do
38
+ it 'sets configurations values' do
39
+ bob = Class.new(Eldr::App)
40
+ bob.set(:bob, 'what about him?')
41
+
42
+ expect(bob.config.bob).to eq('what about him?')
43
+ end
44
+ end
45
+
46
+ describe '.before' do
47
+ it 'adds a before filter' do
48
+ bob = Class.new(Eldr::App)
49
+ bob.before {}
50
+ bob.before(:cats) { }
51
+ expect(bob.before_filters[:all].length).to eq(1)
52
+ expect(bob.before_filters[:cats].length).to eq(1)
53
+ end
54
+ end
55
+
56
+ describe '.after' do
57
+ it 'adds an before filter' do
58
+ bob = Class.new(Eldr::App)
59
+ bob.after {}
60
+ bob.after(:cats) { }
61
+ expect(bob.after_filters[:all].length).to eq(1)
62
+ expect(bob.after_filters[:cats].length).to eq(1)
63
+ end
64
+ end
65
+
66
+ describe '.add' do
67
+ it 'adds a route to self.routes' do
68
+ bob = Class.new(Eldr::App)
69
+ expect(bob.routes[:get].length).to eq(0)
70
+ bob.add(verb: :get, path: '/', handler: proc {})
71
+ expect(bob.routes[:get].length).to eq(1)
72
+ end
73
+ end
74
+
75
+ describe '.call' do
76
+ let(:env) do
77
+ Rack::MockRequest.env_for('/', {:method => :get})
78
+ end
79
+
80
+ it 'creates a new builder instance runs in and returns a response' do
81
+ bob = Class.new(Eldr::App)
82
+ expect(bob.call(env)).to eq([404, {}, [""]])
83
+ end
84
+ end
85
+
86
+ describe '#recognize' do
87
+ let(:resp_proc) { proc { [200, {}, ['Hello Bob!']] } }
88
+ subject(:app) do
89
+ bob = Class.new(Eldr::App)
90
+ bob.get '/cats', resp_proc
91
+ bob._new
92
+ end
93
+
94
+ let(:env) do
95
+ Rack::MockRequest.env_for('/cats', {:method => :get})
96
+ end
97
+
98
+ it 'recognizes and returns a route for a given pattern' do
99
+ expect(app.recognize(env).first.handler).to eq(resp_proc)
100
+ end
101
+ end
102
+
103
+ describe '#call' do
104
+ subject(:app) do
105
+ Class.new(Eldr::App)
106
+ end
107
+
108
+ let(:env) do
109
+ Rack::MockRequest.env_for('/cats', {:method => :get})
110
+ end
111
+
112
+ it 'duplicates and runs call! on the duplicate' do
113
+ expect_any_instance_of(app).to receive(:call!)
114
+ app._new.call(env)
115
+ end
116
+ end
117
+
118
+ describe '#call!' do
119
+ subject(:app) do
120
+ bob = Class.new(Eldr::App)
121
+ bob.get '/cats/:id', name: :cats do
122
+ [200, {}, ['Hello Bob!']]
123
+ end
124
+
125
+ bob.get '/defer' do
126
+ throw :pass
127
+ end
128
+
129
+ bob.get '/defer' do
130
+ [200, {}, ['Hello From Deferred!']]
131
+ end
132
+
133
+ class Error < StandardError
134
+ def call(_env)
135
+ [500, {}, ['']]
136
+ end
137
+ end
138
+
139
+ bob.get '/error' do
140
+ raise Error
141
+ end
142
+
143
+ bob._new
144
+ end
145
+
146
+ let(:env) do
147
+ Rack::MockRequest.env_for('/cats/bob', {:method => :get})
148
+ end
149
+
150
+ let(:env_defer) do
151
+ Rack::MockRequest.env_for('/defer', {:method => :get})
152
+ end
153
+
154
+ let(:env_error) do
155
+ Rack::MockRequest.env_for('/error', {:method => :get})
156
+ end
157
+
158
+ let(:env_error) do
159
+ Rack::MockRequest.env_for('/error', {:method => :get})
160
+ end
161
+
162
+ let(:env_not_found) do
163
+ Rack::MockRequest.env_for('/god', {:method => :get})
164
+ end
165
+
166
+ it 'sets eldr.route' do
167
+ app.call!(env)
168
+ expect(app.env['eldr.route'].name).to eq(:cats)
169
+ end
170
+
171
+ it 'sets eldr.params' do
172
+ app.call!(env)
173
+ expect(app.env['eldr.params']['id']).to eq('bob')
174
+ end
175
+
176
+ context 'when passing/deferring' do
177
+ it 'calls the next matching route' do
178
+ expect(app.call!(env_defer).to_a[2].first).to eq('Hello From Deferred!')
179
+ end
180
+ end
181
+
182
+ context 'when raising an error' do
183
+ it 'catches the error and returns a response' do
184
+ expect(app.call!(env_error).first).to eq(500)
185
+ end
186
+ end
187
+
188
+ context 'when route does not exist' do
189
+ it 'returns a 404' do
190
+ expect(app.call!(env_not_found).first).to eq(404)
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,11 @@
1
+ describe Eldr::Builder do
2
+ describe '#use' do
3
+ it 'supports passing an array of middleware' do
4
+ builder = Eldr::Builder.new
5
+ builder.use Class.new
6
+ builder_2 = Eldr::Builder.new
7
+ builder_2.use builder.middleware
8
+ expect(builder_2.middleware).to eq(builder.middleware)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ describe Eldr::Configuration do
2
+ describe '#initialize' do
3
+ it 'sets defaults' do
4
+ expect(Eldr::Configuration.new().lock).to eq(false)
5
+ end
6
+ end
7
+
8
+ describe '#set' do
9
+ let(:config) { Eldr::Configuration.new }
10
+
11
+ it 'sets a configuration value in #table' do
12
+ config.set(:bob, 'what about him?')
13
+ expect(config.bob).to eq('what about him?')
14
+ end
15
+ end
16
+
17
+ describe '#method_missing' do
18
+ let(:config) { Eldr::Configuration.new }
19
+
20
+ it 'returns a value from #table' do
21
+ config.set(:bob, 'what about him?')
22
+ expect(config.bob).to eq('what about him?')
23
+ end
24
+
25
+ it 'returns nil as a default' do
26
+ expect(config.franklin).to eq(nil)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ describe "ActionObjectsExample" do
2
+ let(:app) do
3
+ path = File.expand_path("../../examples/action_objects.ru", File.dirname(__FILE__))
4
+ Rack::Builder.parse_file(path).first
5
+ end
6
+
7
+ let(:rt) do
8
+ Rack::Test::Session.new(app)
9
+ end
10
+
11
+ describe 'GET /cats/bob' do
12
+ it 'returns bob' do
13
+ response = rt.get '/cats/bob'
14
+ expect(response.body).to eq('Found cat named Bob!')
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,16 @@
1
+ describe 'BuilderExample' do
2
+ let(:app) do
3
+ path = File.expand_path("../../examples/builder.ru", File.dirname(__FILE__))
4
+ Rack::Builder.parse_file(path).first
5
+ end
6
+
7
+ let(:rt) do
8
+ Rack::Test::Session.new(app)
9
+ end
10
+
11
+ describe 'GET /' do
12
+ it 'does some counting using middleware' do
13
+ expect(rt.get('/').body).to eq('1')
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ describe 'CustomResponseExample' do
2
+ let(:app) do
3
+ path = File.expand_path("../../examples/custom_response.ru", File.dirname(__FILE__))
4
+ Rack::Builder.parse_file(path).first
5
+ end
6
+
7
+ let(:rt) do
8
+ Rack::Test::Session.new(app)
9
+ end
10
+
11
+ describe 'GET /' do
12
+ it 'returns hello world' do
13
+ response = rt.get '/'
14
+ expect(response.body).to eq('Hello World Custom!')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ describe 'ErrorsExample' do
2
+ let(:app) do
3
+ path = File.expand_path("../../examples/errors.ru", File.dirname(__FILE__))
4
+ Rack::Builder.parse_file(path).first
5
+ end
6
+
7
+ let(:rt) do
8
+ Rack::Test::Session.new(app)
9
+ end
10
+
11
+ describe 'GET /' do
12
+ it 'returns an error' do
13
+ response = rt.get '/'
14
+ expect(response.body).to eq('Bad Data')
15
+ expect(response.status).to eq(500)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,98 @@
1
+ describe "ExampleApp" do
2
+ it 'does not pollute the base class with routes' do
3
+ expect(Eldr::App.routes[:get].length).to eq(0)
4
+ end
5
+
6
+ let(:app) do
7
+ path = File.expand_path("../../examples/app.ru", File.dirname(__FILE__))
8
+ Rack::Builder.parse_file(path).first
9
+ end
10
+
11
+ let(:rt) do
12
+ Rack::Test::Session.new(app)
13
+ end
14
+
15
+ describe 'GET /posts' do
16
+ it 'returns a 200 status' do
17
+ response = rt.get '/posts'
18
+ expect(response.status).to eq(200)
19
+ end
20
+ end
21
+
22
+ describe 'POST /posts' do
23
+ it 'returns a 201 status' do
24
+ response = rt.post '/posts'
25
+ expect(response.status).to eq(201)
26
+ end
27
+ end
28
+
29
+ describe 'PUT /posts' do
30
+ it 'returns a 200 status' do
31
+ response = rt.put '/posts'
32
+ expect(response.status).to eq(200)
33
+ end
34
+ end
35
+
36
+ describe 'DELETE /posts' do
37
+ it 'returns a 201 status' do
38
+ response = rt.delete '/posts'
39
+ expect(response.status).to eq(201)
40
+ end
41
+ end
42
+
43
+ describe 'GET /state' do
44
+ it 'does not maintain state between requests' do
45
+ response = rt.get '/state'
46
+ expect(response.body).to eq('1')
47
+
48
+ response = rt.get '/state'
49
+ expect(response.body).to eq('1')
50
+ end
51
+ end
52
+
53
+ describe 'before and after filters GET /' do
54
+ it 'comes out in the correct order' do
55
+ order = []
56
+
57
+ cats = Class.new(Eldr::App)
58
+ cats.after { order << :after }
59
+ cats.before { order << :before }
60
+
61
+ cats.get'/' do
62
+ order << :action
63
+ Rack::Response.new("Cats!", 200)
64
+ end
65
+
66
+ app_a = cats.new
67
+
68
+ app_b = Rack::Builder.new do
69
+ run app_a
70
+ end
71
+
72
+ rt2 = Rack::Test::Session.new(app_b)
73
+ rt2.get '/'
74
+ expect(order).to eq([:before, :action, :after])
75
+ end
76
+ end
77
+
78
+ describe 'GET /raises-an-error' do
79
+ it 'returns a 404 status' do
80
+ response = rt.get '/raises-an-error'
81
+ expect(response.status).to eq(404)
82
+ end
83
+ end
84
+
85
+ describe 'GET /defer' do
86
+ it 'defers a route' do
87
+ response = rt.get '/deferred'
88
+ expect(response.body).to eq('Deferred!')
89
+ end
90
+ end
91
+
92
+ describe 'GET /posts/:id' do
93
+ it 'returns a posts id' do
94
+ response = rt.get '/posts/bob'
95
+ expect(response.body).to eq('bob')
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,17 @@
1
+ describe 'HelloWorldExample' do
2
+ let(:app) do
3
+ path = File.expand_path("../../examples/hello_world.ru", File.dirname(__FILE__))
4
+ Rack::Builder.parse_file(path).first
5
+ end
6
+
7
+ let(:rt) do
8
+ Rack::Test::Session.new(app)
9
+ end
10
+
11
+ describe 'GET /' do
12
+ it 'returns hello world' do
13
+ response = rt.get '/'
14
+ expect(response.body).to eq('Hello World!')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ describe 'InheritedApp' do
2
+ let(:app) do
3
+ path = File.expand_path("../../examples/inheritance.ru", File.dirname(__FILE__))
4
+ Rack::Builder.parse_file(path).first
5
+ end
6
+
7
+ let(:rt) do
8
+ Rack::Test::Session.new(app)
9
+ end
10
+
11
+ describe 'GET /' do
12
+ it 'counts using counter middleware' do
13
+ response = rt.get '/'
14
+ expect(response.body).to eq('1')
15
+ end
16
+ end
17
+
18
+ describe '.configuration#bob' do
19
+ it 'returns "what about him?"' do
20
+ expect(app.configuration.bob).to eq('what about him?')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ describe 'MultipleAppsExample' do
2
+ let(:app) do
3
+ path = File.expand_path("../../examples/multiple_apps.ru", File.dirname(__FILE__))
4
+ Rack::Builder.parse_file(path).first
5
+ end
6
+
7
+ let(:rt) do
8
+ Rack::Test::Session.new(app)
9
+ end
10
+
11
+ describe 'GET /' do
12
+ it 'returns hello world' do
13
+ response = rt.get '/'
14
+ expect(response.body).to eq('Hello World EXAMPLE!')
15
+ end
16
+ end
17
+
18
+ describe 'GET /cats' do
19
+ it 'returns cats' do
20
+ response = rt.get '/cats'
21
+ expect(response.body).to eq('Hello Cats!')
22
+ end
23
+ end
24
+
25
+ describe 'GET /dogs' do
26
+ it 'returns dogs' do
27
+ response = rt.get '/dogs'
28
+ expect(response.body).to eq('Hello Dogs!')
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ describe 'RailsStyleRoutingExample' do
2
+ let(:app) do
3
+ path = File.expand_path("../../examples/rails_style_routing.ru", File.dirname(__FILE__))
4
+ Rack::Builder.parse_file(path).first
5
+ end
6
+
7
+ let(:rt) do
8
+ Rack::Test::Session.new(app)
9
+ end
10
+
11
+ describe 'GET /cats' do
12
+ it 'returns a greeting from cats' do
13
+ response = rt.get '/cats'
14
+ expect(response.body).to eq('Hello from all the Cats!')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ describe 'RenderingTemplatesExample' do
2
+ let(:app) do
3
+ path = File.expand_path("../../examples/rendering_templates.ru", File.dirname(__FILE__))
4
+ Rack::Builder.parse_file(path).first
5
+ end
6
+
7
+ let(:rt) do
8
+ Rack::Test::Session.new(app)
9
+ end
10
+
11
+ describe 'GET /cats' do
12
+ it "should return a template" do
13
+ response = rt.get '/cats'
14
+ file_path = File.expand_path("../../examples/views/cats.slim", File.dirname(__FILE__))
15
+ expect(response.body).to eq Tilt.new(file_path).render()
16
+ end
17
+ end
18
+
19
+ describe 'GET /no-template' do
20
+ it 'returns 404' do
21
+ response = rt.get '/no-template'
22
+ expect(response.status).to eq(404)
23
+ expect(response.body).to eq('Template Not Found')
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ describe Eldr::Matcher do
2
+ describe '#match' do
3
+ let(:matcher) { Eldr::Matcher.new('/cats/:id') }
4
+
5
+ context 'when pattern matches' do
6
+ it 'returns MatchData' do
7
+ expect(matcher.match('/cats/bob')).to be_instance_of MatchData
8
+ end
9
+
10
+ it 'returns the splats' do
11
+ expect(matcher.match('/cats/bob')[:id]).to eq('bob')
12
+ end
13
+ end
14
+
15
+ context 'when pattern does not match' do
16
+ it 'returns nil' do
17
+ expect(matcher.match('/dogs/bob')).to be_nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ has_hook: true
2
+ links_to_badges: true
3
+ has_simple_example: true
4
+ are_rack_apps: true
5
+ installation_instructions: true
6
+ features_list: true
7
+ has_quickstart: true
8
+ quickstart:
9
+ has_rundown: true
10
+ templates: true
11
+ helpers: true
12
+ rails_style_routing: true
13
+ rails_style_responses: true
14
+ rails_style_requests: true
15
+ extensions: true
16
+ builder: true
17
+ redirects: true
18
+ handlers:
19
+ call: true
20
+ any_object: true
21
+ action_objects: true
22
+ action_objects_example: true
23
+ testing: true
24
+ has_rack_loop: true
25
+ mentions_examples_folder: true
26
+ has_help: true
27
+ has_license: true
28
+ has_contributing: true