jeanine 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/jeanine.rb +26 -0
- data/lib/jeanine/app.rb +68 -0
- data/lib/jeanine/callbacks.rb +28 -0
- data/lib/jeanine/core_ext.rb +4 -0
- data/lib/jeanine/core_ext/array.rb +5 -0
- data/lib/jeanine/core_ext/hash.rb +8 -0
- data/lib/jeanine/core_ext/nil_class.rb +5 -0
- data/lib/jeanine/core_ext/string.rb +6 -0
- data/lib/jeanine/environment.rb +18 -0
- data/lib/jeanine/headers.rb +76 -0
- data/lib/jeanine/mimes.rb +33 -0
- data/lib/jeanine/path_proxy.rb +73 -0
- data/lib/jeanine/renderer.rb +130 -0
- data/lib/jeanine/request.rb +64 -0
- data/lib/jeanine/response.rb +49 -0
- data/lib/jeanine/router.rb +38 -0
- data/lib/jeanine/routing.rb +2 -0
- data/lib/jeanine/routing/dsl.rb +60 -0
- data/lib/jeanine/routing/evaluation.rb +96 -0
- data/lib/jeanine/version.rb +3 -0
- data/lib/jeanine/view.rb +4 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8d88a21a82401395ddca515402a07e3155939eb0
|
4
|
+
data.tar.gz: e6edce8a4097864466149cf6769765332a868c76
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 17f7d684adeefc7ec56e7244c6c53fddeafb9d037e4a1ee9aab94d8925193a39a95d0a079ba2aad85ae3f5a64b972315a7893f6ced6d07ba7e693923be9cc67e
|
7
|
+
data.tar.gz: 82082d64a1620c947e51aa84d9ada5701825c0525061cd930022b8ef68611338570fc64f20038a4605e48bf1d27578c668ff2923a0ef7d88bc3b55435792877d
|
data/lib/jeanine.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require "jeanine/version"
|
5
|
+
require 'jeanine/core_ext'
|
6
|
+
require 'jeanine/router'
|
7
|
+
require 'jeanine/app'
|
8
|
+
require 'tilt'
|
9
|
+
|
10
|
+
module Jeanine
|
11
|
+
def self.tilt_cache
|
12
|
+
@title_cache ||= Tilt::Cache.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.logger
|
16
|
+
@logger ||= Logger.new(STDOUT)
|
17
|
+
end
|
18
|
+
|
19
|
+
def logger=(val)
|
20
|
+
@logger = val
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.router
|
24
|
+
@router ||= Router.new
|
25
|
+
end
|
26
|
+
end
|
data/lib/jeanine/app.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'jeanine/callbacks'
|
2
|
+
require 'jeanine/mimes'
|
3
|
+
require 'jeanine/request'
|
4
|
+
require 'jeanine/response'
|
5
|
+
require 'jeanine/renderer'
|
6
|
+
require 'jeanine/routing'
|
7
|
+
|
8
|
+
module Jeanine
|
9
|
+
class App
|
10
|
+
include Routing::Evaluation
|
11
|
+
attr_reader :request, :response
|
12
|
+
class << self
|
13
|
+
include Routing::DSL
|
14
|
+
include Callbacks
|
15
|
+
|
16
|
+
alias :_new :new
|
17
|
+
def new(*args, &block)
|
18
|
+
initialize!
|
19
|
+
stack.run _new(*args, &block)
|
20
|
+
stack
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize!
|
24
|
+
Mimes.load!
|
25
|
+
end
|
26
|
+
|
27
|
+
def stack
|
28
|
+
@stack ||= Rack::Builder.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def router
|
32
|
+
Jeanine.router
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(env)
|
36
|
+
new.call env
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(env)
|
41
|
+
@env = env
|
42
|
+
@request = Jeanine::Request.new(env)
|
43
|
+
@response = Jeanine::Response.new
|
44
|
+
catch(:halt) { route_eval }
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def params
|
50
|
+
@params ||= if @request.format
|
51
|
+
@request.params.merge({ format: @request.format })
|
52
|
+
else
|
53
|
+
@request.params
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def render(*args)
|
58
|
+
@response.action_variables = instance_variables_cache
|
59
|
+
Renderer.new(@response).render(*args)
|
60
|
+
end
|
61
|
+
|
62
|
+
def instance_variables_cache
|
63
|
+
instance_variables.each_with_object({}) do |var, obj|
|
64
|
+
obj[var] = instance_variable_get(var)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Jeanine
|
2
|
+
module Callbacks
|
3
|
+
def _callbacks
|
4
|
+
@_callbacks ||= {
|
5
|
+
before: [],
|
6
|
+
after: [],
|
7
|
+
before_all: [],
|
8
|
+
after_all: []
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def _register_callback(type, paths = [], &block)
|
13
|
+
if paths == []
|
14
|
+
_callbacks["#{type}_all".to_sym] << { block: block }
|
15
|
+
else
|
16
|
+
_callbacks[type] << { paths: paths, block: block }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def before(*paths, &block)
|
21
|
+
_register_callback(:before, paths, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def after(*paths, &block)
|
25
|
+
_register_callback(:after, paths, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Broding
|
2
|
+
module Environment
|
3
|
+
def env
|
4
|
+
@_env ||= (ENV["RACK_ENV"].presence || "development")
|
5
|
+
end
|
6
|
+
|
7
|
+
def groups(*groups)
|
8
|
+
hash = groups.extract_options!
|
9
|
+
env = Jeanine.env
|
10
|
+
groups.unshift(:default, env)
|
11
|
+
groups.concat ENV["BRODY_GROUPS"].to_s.split(",")
|
12
|
+
groups.concat hash.map { |k, v| k if v.map(&:to_s).include?(env) }
|
13
|
+
groups.compact!
|
14
|
+
groups.uniq!
|
15
|
+
groups
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jeanine
|
4
|
+
class Headers
|
5
|
+
CGI_VARIABLES = Set.new(%W[
|
6
|
+
AUTH_TYPE
|
7
|
+
CONTENT_LENGTH
|
8
|
+
CONTENT_TYPE
|
9
|
+
GATEWAY_INTERFACE
|
10
|
+
HTTPS
|
11
|
+
PATH_INFO
|
12
|
+
PATH_TRANSLATED
|
13
|
+
QUERY_STRING
|
14
|
+
REMOTE_ADDR
|
15
|
+
REMOTE_HOST
|
16
|
+
REMOTE_IDENT
|
17
|
+
REMOTE_USER
|
18
|
+
REQUEST_METHOD
|
19
|
+
SCRIPT_NAME
|
20
|
+
SERVER_NAME
|
21
|
+
SERVER_PORT
|
22
|
+
SERVER_PROTOCOL
|
23
|
+
SERVER_SOFTWARE
|
24
|
+
]).freeze
|
25
|
+
|
26
|
+
HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
|
27
|
+
|
28
|
+
include Enumerable
|
29
|
+
|
30
|
+
def initialize(request)
|
31
|
+
@req = request
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](key)
|
35
|
+
@req.get_header env_name(key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def key?(key)
|
39
|
+
@req.has_header? env_name(key)
|
40
|
+
end
|
41
|
+
alias :include? :key?
|
42
|
+
|
43
|
+
DEFAULT = Object.new
|
44
|
+
|
45
|
+
def fetch(key, default = DEFAULT)
|
46
|
+
@req.fetch_header(env_name(key)) do
|
47
|
+
return default unless default == DEFAULT
|
48
|
+
return yield if block_given?
|
49
|
+
raise KeyError, key
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def each(&block)
|
54
|
+
@req.each_header(&block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_h
|
58
|
+
obj = {}
|
59
|
+
each do |k,v|
|
60
|
+
obj[k] = v
|
61
|
+
end
|
62
|
+
obj
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def env_name(key)
|
68
|
+
key = key.to_s
|
69
|
+
if HTTP_HEADER.match?(key)
|
70
|
+
key = key.upcase.tr("-", "_")
|
71
|
+
key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
|
72
|
+
end
|
73
|
+
key
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Jeanine
|
2
|
+
class Mimes
|
3
|
+
MimeTypeNotFound = Class.new(StandardError)
|
4
|
+
MIME_TYPES_INVERTED = Rack::Mime::MIME_TYPES.invert
|
5
|
+
|
6
|
+
def self.load!
|
7
|
+
register(:json, Rack::Mime::MIME_TYPES[".json"])
|
8
|
+
register(:html, Rack::Mime::MIME_TYPES[".html"])
|
9
|
+
register(:text, Rack::Mime::MIME_TYPES[".text"])
|
10
|
+
register(:plain, self.for(:text))
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.register(type, header)
|
14
|
+
mime_types[type] = header
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.for(type)
|
18
|
+
mime_types.fetch(type) do
|
19
|
+
raise(MimeTypeNotFound, "Mime #{type} not registered")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.mime_types
|
24
|
+
@mime_types ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
private_class_method :mime_types
|
28
|
+
|
29
|
+
def initialize(*)
|
30
|
+
raise "Should not be initialiazed"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Jeanine
|
2
|
+
class PathProxy
|
3
|
+
def initialize(context, pathname, options = {})
|
4
|
+
@context = context
|
5
|
+
@pathname = pathname
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(path = nil, options = {}, &block)
|
10
|
+
@options.reverse_merge!(options)
|
11
|
+
@context.get(merged_path(path), options, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def post(path = nil, options = {}, &block)
|
15
|
+
@options.reverse_merge!(options)
|
16
|
+
@context.post(merged_path(path), options, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def patch(path = nil, options = {}, &block)
|
20
|
+
@options.reverse_merge!(options)
|
21
|
+
@context.patch(merged_path(path), options, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def put(path = nil, options = {}, &block)
|
25
|
+
@options.reverse_merge!(options)
|
26
|
+
@context.put(merged_path(path), options, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def options(path = nil, options = {}, &block)
|
30
|
+
@options.reverse_merge!(options)
|
31
|
+
@context.options(merged_path(path), options, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def head(path = nil, options = {}, &block)
|
35
|
+
@options.reverse_merge!(options)
|
36
|
+
@context.head(merged_path(path), options, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(path = nil, options = {}, &block)
|
40
|
+
@options.reverse_merge!(options)
|
41
|
+
@context.delete(merged_path(path), options, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def path(path = nil, options = {}, &block)
|
45
|
+
@options.reverse_merge!(options)
|
46
|
+
@context.path(merged_path(path), options, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def match(path = nil, options = {}, &block)
|
50
|
+
@options.reverse_merge!(options)
|
51
|
+
@context.match(merged_path(path), options, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def root(path = '/', options = {}, &block)
|
55
|
+
@options.reverse_merge!(options)
|
56
|
+
@context.root(merged_path(path), options, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def before(*paths, &block)
|
60
|
+
@context.before(*paths, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def after(*paths, &block)
|
64
|
+
@context.after(*paths, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def merged_path(path)
|
70
|
+
[@pathname, path].join
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'jeanine/view'
|
2
|
+
|
3
|
+
module Jeanine
|
4
|
+
class Renderer
|
5
|
+
def self._renderers
|
6
|
+
@_renderers ||= Set.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.add(key, &block)
|
10
|
+
define_method(_render_with_renderer_method_name(key), &block)
|
11
|
+
_renderers << key.to_sym
|
12
|
+
end
|
13
|
+
|
14
|
+
def _render_with_renderer_method_name(key)
|
15
|
+
self.class._render_with_renderer_method_name(key)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self._render_with_renderer_method_name(key)
|
19
|
+
"_render_with_renderer_#{key}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(response)
|
23
|
+
@response = response
|
24
|
+
end
|
25
|
+
|
26
|
+
def render(*args)
|
27
|
+
options = _normalize_render(*args)
|
28
|
+
_render_to_body_with_renderer(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def _normalize_render(*args, &block)
|
34
|
+
options = _normalize_args(*args, &block)
|
35
|
+
_normalize_options(options)
|
36
|
+
options
|
37
|
+
end
|
38
|
+
|
39
|
+
def _normalize_args(action = nil, options = {})
|
40
|
+
if action.is_a?(Hash)
|
41
|
+
action
|
42
|
+
else
|
43
|
+
options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def _normalize_options(options)
|
48
|
+
options
|
49
|
+
end
|
50
|
+
|
51
|
+
def _render_to_body_with_renderer(options)
|
52
|
+
self.class._renderers.each do |name|
|
53
|
+
if options.key?(name)
|
54
|
+
_process_options(options)
|
55
|
+
method_name = _render_with_renderer_method_name(name)
|
56
|
+
return send(method_name, options.delete(name), options)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def _process_options(options)
|
63
|
+
status, content_type, location = options.values_at(:status, :content_type, :location)
|
64
|
+
@response.status = status if status
|
65
|
+
@response.content_type = content_type if content_type
|
66
|
+
@response.headers["Location"] = location if location
|
67
|
+
end
|
68
|
+
|
69
|
+
add :json do |json, options|
|
70
|
+
json = json.to_json(options) unless json.is_a?(String)
|
71
|
+
|
72
|
+
if options[:callback]
|
73
|
+
"/**/#{options[:callback]}(#{json})"
|
74
|
+
else
|
75
|
+
@response.content_type = Mimes.for(:json)
|
76
|
+
json
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def cache
|
81
|
+
Thread.current[:tilt_cache] ||= Tilt::Cache.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def html(engine, template_name, options = {}, locals = {}, &block)
|
85
|
+
locals = options.delete(:locals) || locals || {}
|
86
|
+
layout = options[:layout]
|
87
|
+
scope = options.delete(:scope) || self
|
88
|
+
options.delete(:layout)
|
89
|
+
options[:outvar] ||= '@_out_buf'
|
90
|
+
options[:default_encoding] ||= "UTF-8"
|
91
|
+
template = compile_template(engine, template_name, options)
|
92
|
+
output = template.render(scope, locals, &block)
|
93
|
+
if layout
|
94
|
+
unless layout.include?("layouts/")
|
95
|
+
layout = "layouts/#{layout}"
|
96
|
+
end
|
97
|
+
options = options.merge(layout: false, scope: scope)
|
98
|
+
catch(:layout_missing) { return html(engine, layout, options, locals) { output } }
|
99
|
+
end
|
100
|
+
output
|
101
|
+
end
|
102
|
+
|
103
|
+
def compile_template(engine, template_name, options)
|
104
|
+
Tilt::Cache.new.fetch engine, template_name, options do
|
105
|
+
template = Tilt[engine]
|
106
|
+
raise "Template engine not found: #{engine}" if template.nil?
|
107
|
+
template.new("views/#{template_name}", options)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
add :template do |template, options|
|
112
|
+
view = Jeanine::View.new
|
113
|
+
@response.action_variables.each do |k,v|
|
114
|
+
view.instance_variable_set(k, v.dup)
|
115
|
+
end
|
116
|
+
options[:scope] = view
|
117
|
+
html(:erb, template, options)
|
118
|
+
end
|
119
|
+
|
120
|
+
add :text do |text, _options|
|
121
|
+
@response.content_type = Mimes.for(:text)
|
122
|
+
text
|
123
|
+
end
|
124
|
+
|
125
|
+
add :plain do |text, _options|
|
126
|
+
@response.content_type = Mimes.for(:plain)
|
127
|
+
text
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'jeanine/headers'
|
3
|
+
|
4
|
+
module Jeanine
|
5
|
+
class Request < Rack::Request
|
6
|
+
FORMAT_REGEX = %r{\.(\w+)\z}
|
7
|
+
|
8
|
+
def initialize(env)
|
9
|
+
env['PATH_INFO'] = '/' if env['PATH_INFO'].empty?
|
10
|
+
if env['PATH_INFO'].include?(".")
|
11
|
+
format = env['PATH_INFO'].match(FORMAT_REGEX)
|
12
|
+
if format
|
13
|
+
@format = format.captures[0]
|
14
|
+
env['PATH_INFO'].gsub!(FORMAT_REGEX, '')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def headers
|
21
|
+
@headers ||= Jeanine::Headers.new(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def post?
|
25
|
+
request_method == 'POST'
|
26
|
+
end
|
27
|
+
|
28
|
+
def get?
|
29
|
+
request_method == 'GET'
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete?
|
33
|
+
request_method == 'DELETE'
|
34
|
+
end
|
35
|
+
|
36
|
+
def put?
|
37
|
+
request_method == 'PUT'
|
38
|
+
end
|
39
|
+
|
40
|
+
def patch?
|
41
|
+
request_method == 'PATCH'
|
42
|
+
end
|
43
|
+
|
44
|
+
def options?
|
45
|
+
request_method == 'options'
|
46
|
+
end
|
47
|
+
|
48
|
+
def head?
|
49
|
+
request_method == 'head'
|
50
|
+
end
|
51
|
+
|
52
|
+
def json?
|
53
|
+
format == 'json'
|
54
|
+
end
|
55
|
+
|
56
|
+
def mime_type
|
57
|
+
@mime_type ||= Mimes::MIME_TYPES_INVERTED[content_type]
|
58
|
+
end
|
59
|
+
|
60
|
+
def format
|
61
|
+
@format || Rack::Mime::MIME_TYPES[content_type]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Jeanine
|
5
|
+
class Response
|
6
|
+
attr_accessor :action_variables
|
7
|
+
attr_reader :headers, :body
|
8
|
+
attr_accessor :status
|
9
|
+
|
10
|
+
def initialize(body = [], status = 200, headers = { 'Content-Type' => 'text/html; charset=utf-8' })
|
11
|
+
@body = body
|
12
|
+
@status = status
|
13
|
+
@headers = headers
|
14
|
+
@length = 0
|
15
|
+
|
16
|
+
return if body == []
|
17
|
+
if body.respond_to? :to_str
|
18
|
+
write body.to_str
|
19
|
+
elsif body.respond_to? :each
|
20
|
+
body.each { |i| write i.to_s }
|
21
|
+
else
|
22
|
+
raise TypeError, 'body must #respond_to? #to_str or #each'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def complete!
|
27
|
+
unless (100..199).include?(status) || status == 204
|
28
|
+
headers['Content-Length'] = @length.to_s
|
29
|
+
end
|
30
|
+
[status, headers, body]
|
31
|
+
end
|
32
|
+
|
33
|
+
def content_type=(type)
|
34
|
+
headers['Content-Type'] = type
|
35
|
+
end
|
36
|
+
|
37
|
+
def redirect_to(target, status = 302)
|
38
|
+
self.status = status
|
39
|
+
headers['Location'] = target
|
40
|
+
end
|
41
|
+
|
42
|
+
def write(string)
|
43
|
+
s = string.to_s
|
44
|
+
@length += s.bytesize
|
45
|
+
|
46
|
+
body << s
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Jeanine
|
4
|
+
class Router
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@routes, :[]
|
8
|
+
|
9
|
+
attr_reader :routes
|
10
|
+
def initialize
|
11
|
+
@routes = {
|
12
|
+
GET: [],
|
13
|
+
POST: [],
|
14
|
+
PATCH: [],
|
15
|
+
PUT: [],
|
16
|
+
DELETE: [],
|
17
|
+
OPTIONS: [],
|
18
|
+
HEAD: [],
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def add(verb, path, options = {}, &block)
|
23
|
+
routes[verb] << build_route("#{path}", options, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_route(path, options = {}, &block)
|
29
|
+
route = { path: "#{path}", compiled_path: nil, params: [], block: block }
|
30
|
+
compiled_path = route[:path].gsub(/:\w+/) do |match|
|
31
|
+
route[:params] << match.tr(':', '').to_sym
|
32
|
+
'([^/?#]+)'
|
33
|
+
end
|
34
|
+
route[:compiled_path] = /^#{compiled_path}?$/
|
35
|
+
route
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'jeanine/path_proxy'
|
2
|
+
|
3
|
+
module Jeanine
|
4
|
+
module Routing
|
5
|
+
module DSL
|
6
|
+
extend SingleForwardable
|
7
|
+
|
8
|
+
RouteError = Class.new(StandardError)
|
9
|
+
|
10
|
+
def_delegator :Jeanine, :router
|
11
|
+
|
12
|
+
def get(path = nil, options = {}, &block)
|
13
|
+
router.add(:GET, path, options, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def post(path = nil, options = {}, &block)
|
17
|
+
router.add(:POST, path, options, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def put(path = nil, options = {}, &block)
|
21
|
+
router.add(:PUT, path, options, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def patch(path = nil, options = {}, &block)
|
25
|
+
router.add(:PATCH, path, options, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def head(path = nil, options = {}, &block)
|
29
|
+
router.add(:HEAD, path, options, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def options(path = nil, options = {}, &block)
|
33
|
+
router.add(:OPTIONS, path, options, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete(path = nil, options = {}, &block)
|
37
|
+
router.add(:DELETE, path, options, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def path(pathname, options = {}, &block)
|
41
|
+
option_merger = Jeanine::PathProxy.new(self, pathname, options)
|
42
|
+
option_merger.instance_eval(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def root(path = "/", options = {}, &block)
|
46
|
+
router.add(:GET, path, options, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def match(path = nil, options = {}, &block)
|
50
|
+
via = options.delete(:via)
|
51
|
+
unless via.is_a?(Array)
|
52
|
+
raise RouteError, "options[:via] must be an array of HTTP verbs"
|
53
|
+
end
|
54
|
+
via.each do |verb|
|
55
|
+
router.add(verb.upcase, path, options, &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Jeanine
|
2
|
+
module Routing
|
3
|
+
module Evaluation
|
4
|
+
IGNORED_INSTANCE_VARIABLES = [:@env]
|
5
|
+
def route_eval
|
6
|
+
before_find_route!
|
7
|
+
route = find_route
|
8
|
+
|
9
|
+
if route
|
10
|
+
before_evaluate_route!
|
11
|
+
result = instance_eval(&route[:block])
|
12
|
+
@response.write(result)
|
13
|
+
after_evaluate_route!
|
14
|
+
else
|
15
|
+
@response.status = 404
|
16
|
+
end
|
17
|
+
after_response!
|
18
|
+
@response.complete!
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def find_route
|
24
|
+
matches = nil
|
25
|
+
route = Jeanine.router[@request.request_method.to_sym].detect do |r|
|
26
|
+
matches = r[:compiled_path].match(@request.path_info)
|
27
|
+
!matches.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
return nil if route.nil?
|
31
|
+
return route if route && route[:params].empty?
|
32
|
+
|
33
|
+
index = 0
|
34
|
+
while index < matches.captures.size
|
35
|
+
param = route[:params][index]
|
36
|
+
@request.params[param] = matches.captures[index]
|
37
|
+
if index == match.captures.size
|
38
|
+
@request.params[param].gsub!(@request.format)
|
39
|
+
end
|
40
|
+
index += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
route
|
44
|
+
end
|
45
|
+
|
46
|
+
def before_find_route!
|
47
|
+
run_before_callbacks!(:before_all)
|
48
|
+
end
|
49
|
+
|
50
|
+
def before_evaluate_route!
|
51
|
+
run_before_callbacks!(:before)
|
52
|
+
end
|
53
|
+
|
54
|
+
def after_evaluate_route!
|
55
|
+
run_after_callbacks!(:after)
|
56
|
+
end
|
57
|
+
|
58
|
+
def after_response!
|
59
|
+
run_after_callbacks!(:after_all)
|
60
|
+
end
|
61
|
+
|
62
|
+
def run_before_callbacks!(type)
|
63
|
+
if type == :before_all
|
64
|
+
self.class._callbacks[type].each { |callback| eval_callback(&callback[:block]) }
|
65
|
+
else
|
66
|
+
matching_callbacks(type) do |callback|
|
67
|
+
eval_callback(&callback[:block])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def run_after_callbacks!(type)
|
73
|
+
if type == :after_all
|
74
|
+
self.class._callbacks[type].each { |callback| eval_callback(&callback[:block]) }
|
75
|
+
else
|
76
|
+
matching_callbacks(type) do |callback|
|
77
|
+
eval_callback(&callback[:block])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def matching_callbacks(type)
|
83
|
+
self.class._callbacks[type].select do |callback|
|
84
|
+
paths = callback[:paths]
|
85
|
+
if paths.detect { |path| path.match?(@request.path_info) }
|
86
|
+
yield callback
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def eval_callback(*args, &callback)
|
92
|
+
instance_exec(*args, &callback)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/jeanine/view.rb
ADDED
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jeanine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Josh Brody
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tilt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A framework.
|
70
|
+
email:
|
71
|
+
- git@josh.mn
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- lib/jeanine.rb
|
77
|
+
- lib/jeanine/app.rb
|
78
|
+
- lib/jeanine/callbacks.rb
|
79
|
+
- lib/jeanine/core_ext.rb
|
80
|
+
- lib/jeanine/core_ext/array.rb
|
81
|
+
- lib/jeanine/core_ext/hash.rb
|
82
|
+
- lib/jeanine/core_ext/nil_class.rb
|
83
|
+
- lib/jeanine/core_ext/string.rb
|
84
|
+
- lib/jeanine/environment.rb
|
85
|
+
- lib/jeanine/headers.rb
|
86
|
+
- lib/jeanine/mimes.rb
|
87
|
+
- lib/jeanine/path_proxy.rb
|
88
|
+
- lib/jeanine/renderer.rb
|
89
|
+
- lib/jeanine/request.rb
|
90
|
+
- lib/jeanine/response.rb
|
91
|
+
- lib/jeanine/router.rb
|
92
|
+
- lib/jeanine/routing.rb
|
93
|
+
- lib/jeanine/routing/dsl.rb
|
94
|
+
- lib/jeanine/routing/evaluation.rb
|
95
|
+
- lib/jeanine/version.rb
|
96
|
+
- lib/jeanine/view.rb
|
97
|
+
homepage: https://github.com/joshmn/jeanine
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
metadata:
|
101
|
+
homepage_uri: https://github.com/joshmn/jeanine
|
102
|
+
source_code_uri: https://github.com/joshmn/jeanine
|
103
|
+
changelog_uri: https://github.com/joshmn/jeanine
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 2.3.0
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 2.6.14.3
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: A framework.
|
124
|
+
test_files: []
|