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/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'rubocop/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ RuboCop::RakeTask.new(:rubocop)
6
+
7
+ task :default do
8
+ Rake::Task['spec'].execute
9
+ end
data/TODOS ADDED
@@ -0,0 +1,3 @@
1
+ - [ ] Rdocs
2
+ - [ ] performance improvements
3
+ - [ ] wiki pages
data/eldr.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/eldr/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'eldr'
6
+ s.version = Eldr::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ['K-2052']
9
+ s.email = ['k@2052.me']
10
+ s.homepage = 'https://github.com/eldr-rb/eldr'
11
+ s.summary = 'A minimal framework that lights a fire in your development'
12
+ s.description = 'A minimal framework that lights a fire in your development.
13
+ Built close to Rack and the metal.'
14
+ s.licenses = ['MIT']
15
+
16
+ s.required_rubygems_version = '~> 1.3.6'
17
+ s.required_ruby_version = '~> 2.0'
18
+ s.rubyforge_project = 'eldr'
19
+
20
+ s.add_dependency 'rack', '~> 1.5'
21
+ s.add_dependency 'mustermann', '0.4.0'
22
+ s.add_dependency 'fast_blank', '0.0.2'
23
+
24
+ s.add_development_dependency 'bundler', '~> 1.7'
25
+ s.add_development_dependency 'rake', '10.4.2'
26
+ s.add_development_dependency 'rspec', '3.1.0'
27
+ s.add_development_dependency 'rubocop', '0.28.0'
28
+ s.add_development_dependency 'rack-test', '0.6.2'
29
+ s.add_development_dependency 'tilt', '2.0.1'
30
+ s.add_development_dependency 'slim', '3.0.1'
31
+ s.add_development_dependency 'coveralls', '~> 0.7'
32
+
33
+ s.files = `git ls-files`.split("\n")
34
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
35
+ s.require_path = 'lib'
36
+ end
@@ -0,0 +1,7 @@
1
+ # Running these examples
2
+
3
+ To run these examples pass them into rackup:
4
+
5
+ ```sh
6
+ $ bundle exec rackup examples/builder.ru
7
+ ```
@@ -0,0 +1,28 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'eldr'
3
+
4
+ class Show
5
+ attr_accessor :env
6
+
7
+ def helper_logic
8
+ # do things here
9
+ end
10
+
11
+ def params
12
+ env['eldr.params']
13
+ end
14
+
15
+ def call(env)
16
+ @env = env
17
+
18
+ helper_logic
19
+ # @cat = Cat.find params[:id]
20
+ Rack::Response.new "Found cat named #{params['name'].capitalize}!"
21
+ end
22
+ end
23
+
24
+ class CatsController < Eldr::App
25
+ get '/cats/:name', Show.new
26
+ end
27
+
28
+ run CatsController.new
data/examples/app.ru ADDED
@@ -0,0 +1,75 @@
1
+ class ExampleApp < Eldr::App
2
+ class NotFound < StandardError
3
+ def call(env)
4
+ [404, {}, ['']]
5
+ end
6
+ end
7
+ attr_accessor :cats
8
+
9
+ before :before do
10
+ @thing1 = "Thing1"
11
+ end
12
+
13
+ before do
14
+ @thing2 = "Thing2"
15
+ end
16
+
17
+ after :cats do
18
+ @cats == 'Cats!'
19
+ end
20
+
21
+ get '/posts' do |params|
22
+ Rack::Response.new "posts", 200
23
+ end
24
+
25
+ post '/posts' do
26
+ Rack::Response.new "posts", 201
27
+ end
28
+
29
+ put '/posts' do
30
+ Rack::Response.new "posts", 200
31
+ end
32
+
33
+ delete '/posts' do
34
+ Rack::Response.new "posts", 201
35
+ end
36
+
37
+ get '/deferred' do
38
+ throw :pass
39
+ end
40
+
41
+ get '/deferred' do
42
+ Rack::Response.new "Deferred!", 200
43
+ end
44
+
45
+ get '/raises-an-error' do
46
+ raise NotFound, 'Not Found'
47
+ end
48
+
49
+ get '/state' do
50
+ @state = 1 + @state.to_i
51
+ [200, {}, [@state]]
52
+ end
53
+
54
+ get '/state-2' do
55
+ [200, {}, [@state ||= 0]]
56
+ end
57
+
58
+ get '/before', name: :before do
59
+ [200, {}, [@thing1]]
60
+ end
61
+
62
+ get '/before-all', name: :before do
63
+ [200, {}, [@thing2]]
64
+ end
65
+
66
+ get '/after' do
67
+ [200, {}, ["Cats!"]]
68
+ end
69
+
70
+ get '/posts/:id' do
71
+ [200, {}, [params['id']]]
72
+ end
73
+ end
74
+
75
+ run ExampleApp
@@ -0,0 +1,24 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'eldr'
3
+
4
+ class SimpleCounterMiddleware
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env['eldr.simple_counter'] ||= 0
11
+ env['eldr.simple_counter'] += 1
12
+ @app.call(env)
13
+ end
14
+ end
15
+
16
+ class BuilderExample < Eldr::App
17
+ use SimpleCounterMiddleware
18
+
19
+ get '/' do |env|
20
+ Rack::Response.new env['eldr.simple_counter'].to_s
21
+ end
22
+ end
23
+
24
+ run BuilderExample
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'eldr'
3
+
4
+ class Response
5
+ attr_accessor :status, :headers, :body
6
+
7
+ def initialize(body, status=200, headers={})
8
+ @body, @status, @headers = body, status, headers
9
+ end
10
+
11
+ def to_a
12
+ [@status, @headers, [@body]]
13
+ end
14
+ alias_method :to_ary, :to_a
15
+ end
16
+
17
+ class CustomResponseExample < Eldr::App
18
+ get '/' do
19
+ Response.new("Hello World Custom!")
20
+ end
21
+ end
22
+
23
+ run CustomResponseExample
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'eldr'
3
+
4
+ class ErrorResponse < StandardError
5
+ def call(env)
6
+ Rack::Response.new message, 500
7
+ end
8
+ end
9
+
10
+ class ErrorsExample < Eldr::App
11
+ get '/' do
12
+ raise ErrorResponse, "Bad Data"
13
+ end
14
+ end
15
+
16
+ run ErrorsExample
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'eldr'
3
+
4
+ class HelloWorld < Eldr::App
5
+ get '/', proc { [200, {'Content-Type' => 'txt'}, ['Hello World!']] }
6
+ end
7
+
8
+ run HelloWorld
@@ -0,0 +1,22 @@
1
+ class SimpleMiddleware
2
+ def initialize(app)
3
+ @app = app
4
+ end
5
+
6
+ def call(env)
7
+ env['eldr.simple_counter'] ||= 0
8
+ env['eldr.simple_counter'] += 1
9
+ @app.call(env)
10
+ end
11
+ end
12
+
13
+ class TestInheritanceApp < Eldr::App
14
+ use SimpleCounterMiddleware
15
+ set :bob, 'what about him?'
16
+ end
17
+
18
+ class InheritedApp < TestInheritanceApp
19
+ get '/', proc { [200, {}, [env['eldr.simple_counter']]]}
20
+ end
21
+
22
+ run InheritedApp
@@ -0,0 +1,30 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'eldr'
3
+
4
+ class HelloWorldExample < Eldr::App
5
+ get '/' do
6
+ Rack::Response.new "Hello World EXAMPLE!"
7
+ end
8
+ end
9
+
10
+ class CatsExample < Eldr::App
11
+ get '/' do
12
+ Rack::Response.new "Hello Cats!"
13
+ end
14
+ end
15
+
16
+ class DogsExample < Eldr::App
17
+ get '/', proc { Rack::Response.new "Hello Dogs!" }
18
+ end
19
+
20
+ map '/' do
21
+ run HelloWorldExample
22
+ end
23
+
24
+ map '/cats' do
25
+ run CatsExample
26
+ end
27
+
28
+ map '/dogs' do
29
+ run DogsExample
30
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'eldr'
3
+
4
+ class Cats
5
+ def index env
6
+ Rack::Response.new "Hello from all the Cats!"
7
+ end
8
+ end
9
+
10
+ class RailsStyle < Eldr::App
11
+ get '/cats', to: 'Cats#index'
12
+ end
13
+
14
+ run RailsStyle
@@ -0,0 +1,38 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'eldr'
3
+ require 'tilt'
4
+ require 'slim'
5
+
6
+ module RenderingResponses
7
+ class NotFound < StandardError
8
+ def call(env)
9
+ Rack::Response.new message, 404
10
+ end
11
+ end
12
+ end
13
+
14
+ module RenderingHelpers
15
+ def render(path, resp_code=200)
16
+ Rack::Response.new Tilt.new(find_template(path)).render(self), resp_code
17
+ end
18
+
19
+ def find_template(path)
20
+ template = File.join(__dir__, 'views', path)
21
+ raise RenderingResponses::NotFound, 'Template Not Found' unless File.exists? template
22
+ template
23
+ end
24
+ end
25
+
26
+ class RenderingTemplatesExample < Eldr::App
27
+ include RenderingHelpers
28
+
29
+ get '/cats' do
30
+ render 'cats.slim'
31
+ end
32
+
33
+ get '/no-template' do
34
+ render 'template.slim'
35
+ end
36
+ end
37
+
38
+ run RenderingTemplatesExample
@@ -0,0 +1 @@
1
+ h1 Cats!
data/lib/eldr/app.rb ADDED
@@ -0,0 +1,146 @@
1
+ require 'forwardable'
2
+ require 'rack/request'
3
+
4
+ require_relative 'route'
5
+ require_relative 'matcher'
6
+ require_relative 'recognizer'
7
+ require_relative 'configuration'
8
+ require_relative 'builder'
9
+
10
+ ###
11
+ # Eldr is a minimal Ruby framework
12
+ ###
13
+ module Eldr
14
+ ###
15
+ # The main class all Eldr apps extend
16
+ ###
17
+ class App # rubocop:disable Metrics/ClassLength
18
+ attr_accessor :env, :configuration
19
+ HTTP_VERBS = %w(DELETE GET HEAD OPTIONS PATCH POST PUT)
20
+
21
+ class << self
22
+ attr_accessor :routes, :recognizer, :builder, :before_filters, :after_filters, :configuration
23
+ extend Forwardable
24
+ def_delegators :builder, :map, :use
25
+
26
+ alias_method :_new, :new
27
+ def new(*args, &block)
28
+ builder.run _new(*args, &block)
29
+ builder
30
+ end
31
+
32
+ def call(env)
33
+ new.call env
34
+ end
35
+
36
+ def inherited(base)
37
+ base.builder.use builder.middleware
38
+ base.configuration.merge!(configuration)
39
+ end
40
+
41
+ def builder
42
+ @builder ||= Builder.new
43
+ end
44
+
45
+ def configuration
46
+ @configuration ||= Configuration.new
47
+ end
48
+ alias_method :config, :configuration
49
+
50
+ def set(key, value)
51
+ configuration.set(key, value)
52
+ end
53
+
54
+ def before_filters
55
+ @before_filters ||= { all: [] }
56
+ end
57
+
58
+ def after_filters
59
+ @after_filters ||= { all: [] }
60
+ end
61
+
62
+ def before(*keys, &block)
63
+ *keys = [:all] if keys.empty?
64
+ keys.each do |key|
65
+ before_filters[key] ||= []
66
+ before_filters[key] << block
67
+ end
68
+ end
69
+
70
+ def after(*keys, &block)
71
+ *keys = [:all] if keys.empty?
72
+ keys.each do |key|
73
+ after_filters[key] ||= []
74
+ after_filters[key] << block
75
+ end
76
+ end
77
+
78
+ def routes
79
+ @routes ||= { delete: [], get: [], head: [], options: [], patch: [], post: [], put: [] }
80
+ end
81
+
82
+ def recognizer
83
+ @recognizer ||= Recognizer.new(routes)
84
+ end
85
+
86
+ HTTP_VERBS.each do |verb|
87
+ define_method verb.downcase.to_sym do |path, *args, &block|
88
+ handler = Proc.new(&block) if block
89
+ handler ||= args.pop if args.last.respond_to?(:call)
90
+ options ||= args.pop if args.last.is_a?(Hash)
91
+ options ||= {}
92
+ add(verb: verb.downcase.to_sym, path: path, handler: handler, options: options)
93
+ end
94
+ end
95
+
96
+ def add(verb: :get, path: '/', options: {}, handler: nil)
97
+ handler = Proc.new if block_given?
98
+ route = Route.new(verb: verb, path: path, options: options, handler: handler)
99
+
100
+ route.before_filters.push(*before_filters[route.name]) if before_filters.include? route.name
101
+ route.after_filters.push(*after_filters[route.name]) if after_filters.include? route.name
102
+
103
+ routes[verb] << route
104
+ route
105
+ end
106
+ alias_method :<<, :add
107
+ end
108
+
109
+ def initialize(configuration = nil)
110
+ configuration ||= self.class.configuration
111
+ @configuration = configuration
112
+ end
113
+
114
+ def call(env)
115
+ dup.call!(env)
116
+ end
117
+
118
+ def call!(env)
119
+ @env = env
120
+
121
+ recognize(env).each do |route|
122
+ env['eldr.route'] = route
123
+ params.merge!(route.params(env['PATH_INFO']))
124
+ catch(:pass) { return route.call(env, app: self) }
125
+ end
126
+ rescue => error
127
+ if error.respond_to? :call
128
+ error.call(env)
129
+ else
130
+ raise error
131
+ end
132
+ end
133
+
134
+ def request
135
+ env['eldr.request'] ||= Rack::Request.new(env)
136
+ end
137
+
138
+ def params
139
+ env['eldr.params'] ||= request.params
140
+ end
141
+
142
+ def recognize(env)
143
+ self.class.recognizer.call(env)
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,60 @@
1
+ require 'rack/builder'
2
+
3
+ module Eldr
4
+ class Builder < Rack::Builder
5
+ attr_accessor :middleware
6
+ def initialize(default_app = nil, &block)
7
+ @middleware, @map, @run, @warmup = [], nil, default_app, nil
8
+ instance_eval(&block) if block_given?
9
+ end
10
+
11
+ def self.app(default_app = nil, &block)
12
+ new(default_app, &block).to_app
13
+ end
14
+
15
+ def use(middleware, *args, &block)
16
+ if middleware.is_a? Array
17
+ @middleware.push(*middleware)
18
+ else
19
+ if @map
20
+ mapping, @map = @map, nil
21
+ @middleware << proc { |app| generate_map app, mapping }
22
+ end
23
+ @middleware << proc { |app| middleware.new(app, *args, &block) }
24
+ end
25
+ end
26
+
27
+ def run(app) # rubocop:disable Style/TrivialAccessors
28
+ @run = app
29
+ end
30
+
31
+ def warmup(prc = nil, &block)
32
+ @warmup = prc || block
33
+ end
34
+
35
+ def map(path, &block)
36
+ @map ||= {}
37
+ @map[path] = block
38
+ end
39
+
40
+ def to_app
41
+ app = @map ? generate_map(@run, @map) : @run
42
+ fail 'missing run or map statement' unless app
43
+ app = @middleware.reverse.inject(app) { |a, e| e[a] }
44
+ @warmup.call(app) if @warmup
45
+ app
46
+ end
47
+
48
+ def call(env)
49
+ to_app.call(env)
50
+ end
51
+
52
+ private
53
+
54
+ def generate_map(default_app, mapping)
55
+ mapped = default_app ? { '/' => default_app } : {}
56
+ mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
57
+ URLMap.new(mapped)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ module Eldr
2
+ ###
3
+ # This a OpenStruct like class.
4
+ # It wraps a hash and when you hit a method that doesn't exist it tries to pull it from the hash
5
+ # Probably can break in 100 ways an is dumb, but it works.
6
+ # And it performs astromnomically better than stuff like Hashie::Mash
7
+ ###
8
+ class Configuration
9
+ attr_accessor :table
10
+
11
+ def initialize
12
+ defaults = { lock: false }
13
+ table.merge!(defaults)
14
+ end
15
+
16
+ def set(key, value)
17
+ @table[key] = value
18
+ end
19
+
20
+ def table
21
+ @table ||= {}
22
+ end
23
+
24
+ def merge!(hash)
25
+ hash = hash.table unless hash.is_a? Hash
26
+ @table.merge!(hash)
27
+ end
28
+
29
+ def method_missing(method, *args)
30
+ if !args.empty? # we assume we are setting something
31
+ set(method.to_s.gsub(/\=$/, '').to_sym, args.pop)
32
+ else
33
+ @table[method.to_s.gsub(/\?$/, '').to_sym]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ require 'mustermann'
2
+
3
+ module Eldr
4
+ class Matcher
5
+ PATH_DELIMITER = '/'.freeze
6
+ QUERY_PREFIX = '?'.freeze
7
+ QUERY_DELIMITER = '&'.freeze
8
+
9
+ def initialize(path, options = {})
10
+ @path = path.is_a?(String) && path.empty? ? PATH_DELIMITER : path
11
+ @capture = options.delete(:capture)
12
+ @default_values = options.delete(:default_values)
13
+ end
14
+
15
+ def match(pattern)
16
+ match_data = handler.match(pattern)
17
+ return match_data if match_data
18
+ pattern = pattern[0..-2] if mustermann? && pattern != PATH_DELIMITER && pattern.end_with?(PATH_DELIMITER)
19
+ handler.match(pattern)
20
+ end
21
+
22
+ def mustermann?
23
+ handler.instance_of?(Mustermann)
24
+ end
25
+
26
+ def handler
27
+ @handler ||=
28
+ case @path
29
+ when String
30
+ Mustermann.new(@path, capture: @capture)
31
+ when Regexp
32
+ /^(?:#{@path})$/
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ require 'fast_blank'
2
+
3
+ module Eldr
4
+ class Recognizer
5
+ class NotFound < StandardError
6
+ def call(_env)
7
+ [404, {}, ['']]
8
+ end
9
+ end
10
+
11
+ attr_accessor :app, :env, :routes, :pattern
12
+
13
+ def initialize(routes)
14
+ @routes = routes
15
+ end
16
+
17
+ def call(env)
18
+ @env = env
19
+
20
+ ret_routes = routes[verb].select { |route| !route.match(pattern).nil? }
21
+ raise NotFound if ret_routes.empty?
22
+ ret_routes
23
+ end
24
+
25
+ def pattern
26
+ @pattern = env['PATH_INFO']
27
+ @pattern = '/' if @pattern.blank?
28
+ @pattern
29
+ end
30
+
31
+ def verb
32
+ env['REQUEST_METHOD'].downcase.to_sym
33
+ end
34
+ end
35
+ end