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