eldr 0.0.2

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 (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