flame 4.14.1 → 4.15.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.
- checksums.yaml +4 -4
- data/lib/flame/application.rb +59 -94
- data/lib/flame/application/config.rb +43 -0
- data/lib/flame/controller.rb +27 -5
- data/lib/flame/dispatcher.rb +13 -10
- data/lib/flame/{cookies.rb → dispatcher/cookies.rb} +0 -0
- data/lib/flame/dispatcher/request.rb +18 -0
- data/lib/flame/dispatcher/response.rb +20 -0
- data/lib/flame/{static.rb → dispatcher/static.rb} +0 -0
- data/lib/flame/errors/argument_not_assigned_error.rb +17 -0
- data/lib/flame/{errors.rb → errors/route_arguments_error.rb} +1 -25
- data/lib/flame/errors/route_not_found_error.rb +18 -0
- data/lib/flame/path.rb +169 -0
- data/lib/flame/router.rb +11 -26
- data/lib/flame/router/route.rb +80 -0
- data/lib/flame/validators.rb +5 -9
- data/lib/flame/version.rb +1 -1
- metadata +12 -8
- data/lib/flame/request.rb +0 -16
- data/lib/flame/response.rb +0 -18
- data/lib/flame/route.rb +0 -154
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3c14c31d4f80cb54a03a36060c74c668b624b4a
|
4
|
+
data.tar.gz: 13d93b6e48507a6ad1b5bb0263ab368e2ab973da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cdcdfca044347e8abe725cca2260987468519a1d4a875cae66656f064ed5e2cadab4818698d50d3cb5360e0302c43117ff3c875ad3c440ec92c751847404fb06
|
7
|
+
data.tar.gz: c73d1862a9e5e4f3fe1f30429b304518ab9c7d772fda324f09795067bf064510ff8048ae9c699b4153796a1fcce5228a8ac4a94e821ba13b5ffe46c26f631863
|
data/lib/flame/application.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'application/config'
|
3
4
|
require_relative 'router'
|
4
5
|
require_relative 'dispatcher'
|
5
6
|
|
@@ -8,117 +9,81 @@ module Flame
|
|
8
9
|
class Application
|
9
10
|
class << self
|
10
11
|
attr_accessor :config
|
11
|
-
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
## Router for routing
|
14
|
+
def router
|
15
|
+
@router ||= Flame::Router.new(self)
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
def cached_tilts
|
19
|
+
@cached_tilts ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
## Generating application config when inherited
|
23
|
+
def inherited(app)
|
24
|
+
app.config = Config.new(
|
25
|
+
app,
|
26
|
+
default_config_dirs(
|
27
|
+
root_dir: File.dirname(caller[0].split(':')[0])
|
28
|
+
).merge(
|
29
|
+
environment: ENV['RACK_ENV'] || 'development'
|
30
|
+
)
|
26
31
|
)
|
27
|
-
|
28
|
-
end
|
32
|
+
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
## Make available `run Application` without `.new` for `rackup`
|
35
|
+
def call(env)
|
36
|
+
@app ||= new
|
37
|
+
@app.call env
|
38
|
+
end
|
33
39
|
|
34
|
-
|
35
|
-
def call(env)
|
36
|
-
@app.call(env) if @app.respond_to? :call
|
37
|
-
Flame::Dispatcher.new(self, env).run!
|
38
|
-
end
|
40
|
+
private
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
@
|
43
|
-
@
|
44
|
-
|
42
|
+
## Mount controller in application class
|
43
|
+
## @param ctrl [Flame::Controller] the mounted controller class
|
44
|
+
## @param path [String, nil] root path for the mounted controller
|
45
|
+
## @yield refine defaults pathes for a methods of the mounted controller
|
46
|
+
## @example Mount controller with defaults
|
47
|
+
## mount ArticlesController
|
48
|
+
## @example Mount controller with specific path
|
49
|
+
## mount HomeController, '/welcome'
|
50
|
+
## @example Mount controller with specific path of methods
|
51
|
+
## mount HomeController do
|
52
|
+
## get '/bye', :goodbye
|
53
|
+
## post '/greetings', :new
|
54
|
+
## defaults
|
55
|
+
## end
|
56
|
+
def mount(ctrl, path = nil, &block)
|
57
|
+
router.add_controller(ctrl, path, &block)
|
58
|
+
end
|
45
59
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
## @example Mount controller with specific path of methods
|
55
|
-
## mount HomeController do
|
56
|
-
## get '/bye', :goodbye
|
57
|
-
## post '/greetings', :new
|
58
|
-
## defaults
|
59
|
-
## end
|
60
|
-
def self.mount(ctrl, path = nil, &block)
|
61
|
-
router.add_controller(ctrl, path, &block)
|
60
|
+
## Initialize default for config directories
|
61
|
+
def default_config_dirs(root_dir:)
|
62
|
+
result = { root_dir: File.realpath(root_dir) }
|
63
|
+
%i[public views config tmp].each do |key|
|
64
|
+
result[:"#{key}_dir"] = proc { File.join(config[:root_dir], key.to_s) }
|
65
|
+
end
|
66
|
+
result
|
67
|
+
end
|
62
68
|
end
|
63
69
|
|
64
|
-
##
|
65
|
-
def
|
66
|
-
|
70
|
+
## Framework configuration
|
71
|
+
def config
|
72
|
+
self.class.config
|
67
73
|
end
|
68
74
|
|
69
75
|
def router
|
70
76
|
self.class.router
|
71
77
|
end
|
72
78
|
|
73
|
-
|
74
|
-
|
75
|
-
result = { root_dir: File.realpath(root_dir) }
|
76
|
-
%i[public views config tmp].each do |key|
|
77
|
-
result[:"#{key}_dir"] = proc { File.join(config[:root_dir], key.to_s) }
|
78
|
-
end
|
79
|
-
result
|
80
|
-
end
|
81
|
-
|
82
|
-
def self.cached_tilts
|
83
|
-
@cached_tilts ||= {}
|
79
|
+
def initialize(app = nil)
|
80
|
+
@app = app
|
84
81
|
end
|
85
82
|
|
86
|
-
##
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
replace(hash)
|
91
|
-
end
|
92
|
-
|
93
|
-
def [](key)
|
94
|
-
result = super(key)
|
95
|
-
if result.class <= Proc && result.parameters.empty?
|
96
|
-
result = @app.class_exec(&result)
|
97
|
-
end
|
98
|
-
result
|
99
|
-
end
|
100
|
-
|
101
|
-
## Method for loading YAML-files from config directory
|
102
|
-
## @param file [String, Symbol] file name (typecast to String with '.yml')
|
103
|
-
## @param key [Symbol, String, nil]
|
104
|
-
## key for allocating YAML in config Hash (typecast to Symbol)
|
105
|
-
## @param set [Boolean] allocating YAML in Config Hash
|
106
|
-
## @example Load SMTP file from `config/smtp.yml' to config[]
|
107
|
-
## config.load_yaml('smtp.yml')
|
108
|
-
## @example Load SMTP file without extension, by Symbol
|
109
|
-
## config.load_yaml(:smtp)
|
110
|
-
## @example Load SMTP file with other key to config[:mail]
|
111
|
-
## config.load_yaml('smtp.yml', :mail)
|
112
|
-
## @example Load SMTP file without allocating in config[]
|
113
|
-
## config.load_yaml('smtp.yml', set: false)
|
114
|
-
def load_yaml(file, key: nil, set: true)
|
115
|
-
file = "#{file}.yml" if file.is_a? Symbol
|
116
|
-
file_path = File.join(self[:config_dir], file)
|
117
|
-
yaml = YAML.load_file(file_path)
|
118
|
-
key ||= File.basename(file, '.*')
|
119
|
-
self[key.to_sym] = yaml if set
|
120
|
-
yaml
|
121
|
-
end
|
83
|
+
## Request recieving method
|
84
|
+
def call(env)
|
85
|
+
@app.call(env) if @app.respond_to? :call
|
86
|
+
Flame::Dispatcher.new(self, env).run!
|
122
87
|
end
|
123
88
|
end
|
124
89
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
class Application
|
5
|
+
## Class for Flame::Application.config
|
6
|
+
class Config < Hash
|
7
|
+
def initialize(app, hash = {})
|
8
|
+
@app = app
|
9
|
+
replace(hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
result = super(key)
|
14
|
+
if result.class <= Proc && result.parameters.empty?
|
15
|
+
result = @app.class_exec(&result)
|
16
|
+
end
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
## Method for loading YAML-files from config directory
|
21
|
+
## @param file [String, Symbol] file name (typecast to String with '.yml')
|
22
|
+
## @param key [Symbol, String, nil]
|
23
|
+
## key for allocating YAML in config Hash (typecast to Symbol)
|
24
|
+
## @param set [Boolean] allocating YAML in Config Hash
|
25
|
+
## @example Load SMTP file from `config/smtp.yml' to config[]
|
26
|
+
## config.load_yaml('smtp.yml')
|
27
|
+
## @example Load SMTP file without extension, by Symbol
|
28
|
+
## config.load_yaml(:smtp)
|
29
|
+
## @example Load SMTP file with other key to config[:mail]
|
30
|
+
## config.load_yaml('smtp.yml', key: :mail)
|
31
|
+
## @example Load SMTP file without allocating in config[]
|
32
|
+
## config.load_yaml('smtp.yml', set: false)
|
33
|
+
def load_yaml(file, key: nil, set: true)
|
34
|
+
file = "#{file}.yml" if file.is_a? Symbol
|
35
|
+
file_path = File.join(self[:config_dir], file)
|
36
|
+
yaml = YAML.load_file(file_path)
|
37
|
+
key ||= File.basename(file, '.*')
|
38
|
+
self[key.to_sym] = yaml if set
|
39
|
+
yaml
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/flame/controller.rb
CHANGED
@@ -34,7 +34,13 @@ module Flame
|
|
34
34
|
|
35
35
|
## Build a URI to the given controller and action, or path
|
36
36
|
def url_to(*args)
|
37
|
-
|
37
|
+
first_arg = args.first
|
38
|
+
path =
|
39
|
+
if first_arg.is_a?(String) || first_arg.is_a?(Flame::Path)
|
40
|
+
first_arg
|
41
|
+
else
|
42
|
+
path_to(*args)
|
43
|
+
end
|
38
44
|
"#{request.scheme}://#{request.host_with_port}#{path}"
|
39
45
|
end
|
40
46
|
|
@@ -126,6 +132,20 @@ module Flame
|
|
126
132
|
|
127
133
|
private
|
128
134
|
|
135
|
+
## Execute any action from any controller
|
136
|
+
## @example Execute `new` action of `ArticlesController`
|
137
|
+
## reroute ArticlesController, :new
|
138
|
+
## @example Execute `index` action of `ArticlesController`
|
139
|
+
## reroute ArticlesController
|
140
|
+
## @example Execute `foo` action of current controller
|
141
|
+
## reroute :foo
|
142
|
+
def reroute(*args)
|
143
|
+
add_controller_class(args)
|
144
|
+
ctrl, action = args[0..1]
|
145
|
+
ctrl_object = ctrl == self.class ? self : ctrl.new(@dispatcher)
|
146
|
+
ctrl_object.send :execute, action
|
147
|
+
end
|
148
|
+
|
129
149
|
def extract_params_for(action)
|
130
150
|
# p action, params, parameters
|
131
151
|
method(action).parameters.each_with_object({}) do |parameter, result|
|
@@ -145,10 +165,12 @@ module Flame
|
|
145
165
|
|
146
166
|
## Default root path of the controller for requests
|
147
167
|
def default_path
|
148
|
-
modules =
|
149
|
-
parts = modules
|
150
|
-
|
151
|
-
parts.
|
168
|
+
modules = underscore.split('/')
|
169
|
+
parts = modules.pop.split('_')
|
170
|
+
parts.shift if parts.first == 'index'
|
171
|
+
parts.pop if %w[controller ctrl].include? parts.last
|
172
|
+
parts = [modules.last] if parts.empty?
|
173
|
+
Flame::Path.merge nil, parts.join('_')
|
152
174
|
end
|
153
175
|
|
154
176
|
## Re-define public instance method from parent
|
data/lib/flame/dispatcher.rb
CHANGED
@@ -2,10 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'gorilla-patch/symbolize'
|
4
4
|
|
5
|
-
require_relative '
|
6
|
-
require_relative '
|
7
|
-
|
8
|
-
require_relative '
|
5
|
+
require_relative 'dispatcher/request'
|
6
|
+
require_relative 'dispatcher/response'
|
7
|
+
|
8
|
+
require_relative 'dispatcher/cookies'
|
9
|
+
require_relative 'dispatcher/static'
|
10
|
+
|
11
|
+
require_relative 'errors/route_not_found_error'
|
9
12
|
|
10
13
|
module Flame
|
11
14
|
## Helpers for dispatch Flame::Application#call
|
@@ -22,8 +25,8 @@ module Flame
|
|
22
25
|
def initialize(app, env)
|
23
26
|
@app = app
|
24
27
|
@env = env
|
25
|
-
@request = Flame::Request.new(env)
|
26
|
-
@response = Flame::Response.new
|
28
|
+
@request = Flame::Dispatcher::Request.new(env)
|
29
|
+
@response = Flame::Dispatcher::Response.new
|
27
30
|
end
|
28
31
|
|
29
32
|
## Start of execution the request
|
@@ -96,7 +99,7 @@ module Flame
|
|
96
99
|
raise Errors::RouteNotFoundError.new(ctrl, action) unless route
|
97
100
|
query = Rack::Utils.build_nested_query args.delete(:params)
|
98
101
|
query = nil if query&.empty?
|
99
|
-
path = route.assign_arguments(args)
|
102
|
+
path = route.path.assign_arguments(args)
|
100
103
|
path = '/' if path.empty?
|
101
104
|
URI::Generic.build(path: path, query: query).to_s
|
102
105
|
end
|
@@ -150,7 +153,7 @@ module Flame
|
|
150
153
|
def try_route
|
151
154
|
route = @app.class.router.find_route(
|
152
155
|
method: request.http_method,
|
153
|
-
|
156
|
+
path: request.path
|
154
157
|
)
|
155
158
|
return nil unless route
|
156
159
|
status 200
|
@@ -160,7 +163,7 @@ module Flame
|
|
160
163
|
## Execute route
|
161
164
|
## @param route [Flame::Route] route that must be executed
|
162
165
|
def execute_route(route, action = route.action)
|
163
|
-
params.merge!
|
166
|
+
params.merge! route.path.extract_arguments(request.path)
|
164
167
|
# route.execute(self)
|
165
168
|
controller = route.controller.new(self)
|
166
169
|
controller.send(:execute, action)
|
@@ -178,7 +181,7 @@ module Flame
|
|
178
181
|
## Return nil if must be no body for current HTTP status
|
179
182
|
return if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
180
183
|
## Find the nearest route by the parts of requested path
|
181
|
-
route = @app.router.find_nearest_route(request.
|
184
|
+
route = @app.router.find_nearest_route(request.path)
|
182
185
|
## Return nil if the route not found
|
183
186
|
## or it's `default_body` method not defined
|
184
187
|
return default_body unless route
|
File without changes
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
class Dispatcher
|
5
|
+
## Class for requests
|
6
|
+
class Request < Rack::Request
|
7
|
+
## Initialize Flame::Path
|
8
|
+
def path
|
9
|
+
@path ||= Flame::Path.new path_info
|
10
|
+
end
|
11
|
+
|
12
|
+
## Override HTTP-method of the request if the param '_method' found
|
13
|
+
def http_method
|
14
|
+
@http_method ||= (params['_method'] || request_method).upcase.to_sym
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
class Dispatcher
|
5
|
+
## Class for responses
|
6
|
+
class Response < Rack::Response
|
7
|
+
## Set Content-Type header directly or by extension
|
8
|
+
## @param value [String] value for header or extension of file
|
9
|
+
## @return [String] setted value
|
10
|
+
## @example Set value directly
|
11
|
+
## content_type = 'text/css'
|
12
|
+
## @example Set value by file extension
|
13
|
+
## content_type = '.css'
|
14
|
+
def content_type=(value)
|
15
|
+
value = Rack::Mime.mime_type(value) if value.start_with? '.'
|
16
|
+
set_header Rack::CONTENT_TYPE, value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
File without changes
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
module Errors
|
5
|
+
## Error for Flame::Dispatcher.path_to
|
6
|
+
class ArgumentNotAssignedError < StandardError
|
7
|
+
def initialize(path, argument)
|
8
|
+
@path = path
|
9
|
+
@argument = argument
|
10
|
+
end
|
11
|
+
|
12
|
+
def message
|
13
|
+
"Argument '#{@argument}' for path '#{@path}' is not assigned"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -19,6 +19,7 @@ module Flame
|
|
19
19
|
case @extra[:place]
|
20
20
|
when :ctrl
|
21
21
|
## Error if path has no arguments, that controller's method has
|
22
|
+
## NOTE: It isn't using because `Flame::Path#adopt`
|
22
23
|
"Path '#{@path}' has no #{@extra[:type_name]}" \
|
23
24
|
" arguments #{@extra[:args].inspect}"
|
24
25
|
when :path
|
@@ -28,30 +29,5 @@ module Flame
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
end
|
31
|
-
|
32
|
-
## Error for Flame::Dispatcher.path_to
|
33
|
-
class RouteNotFoundError < StandardError
|
34
|
-
def initialize(ctrl, method)
|
35
|
-
@ctrl = ctrl
|
36
|
-
@method = method
|
37
|
-
end
|
38
|
-
|
39
|
-
def message
|
40
|
-
"Route with controller '#{@ctrl}' and method '#{@method}'" \
|
41
|
-
' not found in application routes'
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
## Error for Flame::Dispatcher.path_to
|
46
|
-
class ArgumentNotAssignedError < StandardError
|
47
|
-
def initialize(path, argument)
|
48
|
-
@path = path
|
49
|
-
@argument = argument
|
50
|
-
end
|
51
|
-
|
52
|
-
def message
|
53
|
-
"Argument '#{@argument}' for path '#{@path}' is not assigned"
|
54
|
-
end
|
55
|
-
end
|
56
32
|
end
|
57
33
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
module Errors
|
5
|
+
## Error for Flame::Dispatcher.path_to
|
6
|
+
class RouteNotFoundError < StandardError
|
7
|
+
def initialize(ctrl, method)
|
8
|
+
@ctrl = ctrl
|
9
|
+
@method = method
|
10
|
+
end
|
11
|
+
|
12
|
+
def message
|
13
|
+
"Route with controller '#{@ctrl}' and method '#{@method}'" \
|
14
|
+
' not found in application routes'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/flame/path.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'errors/argument_not_assigned_error'
|
4
|
+
|
5
|
+
module Flame
|
6
|
+
## Class for working with paths
|
7
|
+
class Path
|
8
|
+
def self.merge(*parts)
|
9
|
+
parts.join('/').gsub(%r{\/{2,}}, '/')
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(*paths)
|
13
|
+
@path = self.class.merge(*paths)
|
14
|
+
freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
def parts
|
18
|
+
@parts ||= @path.to_s.split('/').reject(&:empty?)
|
19
|
+
.map! { |part| PathPart.new(part) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def freeze
|
23
|
+
@path.freeze
|
24
|
+
parts.each(&:freeze)
|
25
|
+
parts.freeze
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
## Compare by parts count and the first arg position
|
30
|
+
def <=>(other)
|
31
|
+
self_parts, other_parts = [self, other].map(&:parts)
|
32
|
+
parts_size = self_parts.size <=> other_parts.size
|
33
|
+
return parts_size unless parts_size.zero?
|
34
|
+
self_parts.zip(other_parts)
|
35
|
+
.reduce(0) do |result, (self_part, other_part)|
|
36
|
+
break -1 if self_part.arg? && !other_part.arg?
|
37
|
+
break 1 if other_part.arg? && !self_part.arg?
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ==(other)
|
43
|
+
other = self.class.new(other) if other.is_a? String
|
44
|
+
parts == other.parts
|
45
|
+
end
|
46
|
+
|
47
|
+
## Complete path for the action of controller
|
48
|
+
## @todo Add :arg:type support (:id:num, :name:str, etc.)
|
49
|
+
def adapt(ctrl, action)
|
50
|
+
parameters = ctrl.instance_method(action).parameters
|
51
|
+
parameters.map! do |parameter|
|
52
|
+
parameter_type, parameter_name = parameter
|
53
|
+
path_part = PathPart.new parameter_name, arg: parameter_type
|
54
|
+
path_part unless parts.include? path_part
|
55
|
+
end
|
56
|
+
self.class.new @path.empty? ? "/#{action}" : self, *parameters
|
57
|
+
end
|
58
|
+
|
59
|
+
## Can recieve other as String
|
60
|
+
def match?(other)
|
61
|
+
other = self.class.new(other) if other.is_a? String
|
62
|
+
return false unless other_contain_required_parts?(other)
|
63
|
+
result = [self, other].map { |path| path.parts.size }.max.times do |i|
|
64
|
+
break false unless compare_parts parts[i], other.parts[i]
|
65
|
+
end
|
66
|
+
result = true if result
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
## Extract arguments from other path with values at arguments
|
71
|
+
## @param other_path [Flame::Path] other path with values at arguments
|
72
|
+
def extract_arguments(other_path)
|
73
|
+
parts.each_with_index.with_object({}) do |(part, i), args|
|
74
|
+
other_part = other_path.parts[i].to_s
|
75
|
+
next args unless part.arg?
|
76
|
+
break args if part.opt_arg? && other_part.empty?
|
77
|
+
args[
|
78
|
+
part[(part.opt_arg? ? 2 : 1)..-1].to_sym
|
79
|
+
] = URI.decode(other_part)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
## Assign arguments to path for `Controller.path_to`
|
84
|
+
## @param args [Hash] arguments for assigning
|
85
|
+
def assign_arguments(args = {})
|
86
|
+
result_parts = parts.map { |part| assign_argument(part, args) }.compact
|
87
|
+
self.class.merge result_parts.unshift(nil)
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
@path
|
92
|
+
end
|
93
|
+
alias to_str to_s
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def other_contain_required_parts?(other)
|
98
|
+
other_parts = other.parts
|
99
|
+
req_path_parts = parts.reject(&:opt_arg?)
|
100
|
+
fixed_path_parts = parts.reject(&:arg?)
|
101
|
+
(fixed_path_parts - other_parts).empty? &&
|
102
|
+
other_parts.count >= req_path_parts.count
|
103
|
+
end
|
104
|
+
|
105
|
+
def compare_parts(part, other_part)
|
106
|
+
return unless part
|
107
|
+
return if other_part.nil? && !part.opt_arg?
|
108
|
+
# p other_part, part
|
109
|
+
return true if part.arg?
|
110
|
+
return true if other_part == part
|
111
|
+
end
|
112
|
+
|
113
|
+
## Helpers for `assign_arguments`
|
114
|
+
def assign_argument(part, args = {})
|
115
|
+
## Not argument
|
116
|
+
return part unless part.arg?
|
117
|
+
## Not required argument
|
118
|
+
return args[part[2..-1].to_sym] if part.opt_arg?
|
119
|
+
## Required argument
|
120
|
+
param = args[part[1..-1].to_sym]
|
121
|
+
## Required argument is nil
|
122
|
+
error = Errors::ArgumentNotAssignedError.new(@path, part)
|
123
|
+
raise error if param.nil?
|
124
|
+
## All is ok
|
125
|
+
param
|
126
|
+
end
|
127
|
+
|
128
|
+
## Class for one part of Path
|
129
|
+
class PathPart
|
130
|
+
extend Forwardable
|
131
|
+
|
132
|
+
def_delegators :@part, :[], :hash
|
133
|
+
|
134
|
+
ARG_CHAR = ':'
|
135
|
+
ARG_CHAR_OPT = '?'
|
136
|
+
|
137
|
+
def initialize(part, arg: false)
|
138
|
+
@part = "#{ARG_CHAR if arg}#{ARG_CHAR_OPT if arg == :opt}#{part}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def freeze
|
142
|
+
@part.freeze
|
143
|
+
super
|
144
|
+
end
|
145
|
+
|
146
|
+
def ==(other)
|
147
|
+
to_s == other.to_s
|
148
|
+
end
|
149
|
+
|
150
|
+
alias eql? ==
|
151
|
+
|
152
|
+
def to_s
|
153
|
+
@part
|
154
|
+
end
|
155
|
+
|
156
|
+
def arg?
|
157
|
+
@part.start_with? ARG_CHAR
|
158
|
+
end
|
159
|
+
|
160
|
+
def opt_arg?
|
161
|
+
@part[1] == ARG_CHAR_OPT
|
162
|
+
end
|
163
|
+
|
164
|
+
def clean
|
165
|
+
@part.delete ARG_CHAR + ARG_CHAR_OPT
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/lib/flame/router.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'route'
|
3
|
+
require_relative 'router/route'
|
4
4
|
|
5
5
|
module Flame
|
6
6
|
## Router class for routing
|
@@ -32,12 +32,14 @@ module Flame
|
|
32
32
|
route.dup if route
|
33
33
|
end
|
34
34
|
|
35
|
-
## Find the nearest route by path
|
36
|
-
## @param
|
37
|
-
## @return [Flame::Route, nil] return the found nearest route
|
38
|
-
def find_nearest_route(
|
35
|
+
## Find the nearest route by path
|
36
|
+
## @param path [Flame::Path] path for route finding
|
37
|
+
## @return [Flame::Route, nil] return the found nearest route or `nil`
|
38
|
+
def find_nearest_route(path)
|
39
|
+
path = Flame::Path.new(path) if path.is_a? String
|
40
|
+
path_parts = path.parts.dup
|
39
41
|
while path_parts.size >= 0
|
40
|
-
route = find_route
|
42
|
+
route = find_route path: Flame::Path.new(*path_parts)
|
41
43
|
break if route || path_parts.empty?
|
42
44
|
path_parts.pop
|
43
45
|
end
|
@@ -90,14 +92,12 @@ module Flame
|
|
90
92
|
## @example Set method to :POST for action `goodbye`
|
91
93
|
## post :goodbye
|
92
94
|
method = request_method.downcase
|
93
|
-
define_method(method) do |path, action = nil
|
95
|
+
define_method(method) do |path, action = nil|
|
94
96
|
## Swap arguments if action in path variable
|
95
97
|
unless action
|
96
98
|
action = path.to_sym
|
97
99
|
path = nil
|
98
100
|
end
|
99
|
-
## Make path by controller method with parameners
|
100
|
-
path = default_action_path(action, path) if prefix || path.nil?
|
101
101
|
## Init new Route
|
102
102
|
route = Route.new(@ctrl, action, method, @path, path)
|
103
103
|
## Try to find route with the same action
|
@@ -123,7 +123,7 @@ module Flame
|
|
123
123
|
action = rest_route[:action]
|
124
124
|
next if !@ctrl.actions.include?(action) ||
|
125
125
|
find_route_index(action: action)
|
126
|
-
send(*rest_route.values.map(&:downcase)
|
126
|
+
send(*rest_route.values.map(&:downcase))
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
@@ -132,7 +132,7 @@ module Flame
|
|
132
132
|
## @param path [String, nil] root path for mounting controller
|
133
133
|
## @yield Block of code for routes refine
|
134
134
|
def mount(ctrl, path = nil, &block)
|
135
|
-
path =
|
135
|
+
path = Flame::Path.merge(@path, path || ctrl.default_path)
|
136
136
|
@router.add_controller(ctrl, path, &block)
|
137
137
|
end
|
138
138
|
|
@@ -148,21 +148,6 @@ module Flame
|
|
148
148
|
def find_route_index(attrs)
|
149
149
|
@routes.find_index { |route| route.compare_attributes(attrs) }
|
150
150
|
end
|
151
|
-
|
152
|
-
## Build path for the action of controller
|
153
|
-
## @todo Add :arg:type support (:id:num, :name:str, etc.)
|
154
|
-
def default_action_path(action, prefix)
|
155
|
-
unshifted = prefix ? prefix : action_prefix(action)
|
156
|
-
parameters = @ctrl.instance_method(action).parameters
|
157
|
-
parameters.map! do |par|
|
158
|
-
":#{par[0] == :req ? '' : ARG_CHAR_OPT}#{par[1]}"
|
159
|
-
end
|
160
|
-
Route.path_merge(parameters.unshift(unshifted))
|
161
|
-
end
|
162
|
-
|
163
|
-
def action_prefix(action)
|
164
|
-
action == :index ? '/' : action
|
165
|
-
end
|
166
151
|
end
|
167
152
|
end
|
168
153
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../path'
|
4
|
+
require_relative '../validators'
|
5
|
+
|
6
|
+
module Flame
|
7
|
+
class Router
|
8
|
+
## Class for Route in Router.routes
|
9
|
+
class Route
|
10
|
+
attr_reader :method, :controller, :action, :path
|
11
|
+
|
12
|
+
def initialize(controller, action, method, ctrl_path, action_path)
|
13
|
+
@controller = controller
|
14
|
+
@action = action
|
15
|
+
@method = method.to_sym.upcase
|
16
|
+
## Make path by controller method with parameners
|
17
|
+
action_path = Flame::Path.new(action_path).adapt(controller, action)
|
18
|
+
## Merge action path with controller path
|
19
|
+
@path = Flame::Path.new(ctrl_path, action_path)
|
20
|
+
Validators::RouteArgumentsValidator.new(
|
21
|
+
@controller, action_path, @action
|
22
|
+
).valid?
|
23
|
+
freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def freeze
|
27
|
+
@path.freeze
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
## Compare attributes for `Router.find_route`
|
32
|
+
## @param attrs [Hash] Hash of attributes for comparing
|
33
|
+
def compare_attributes(attrs)
|
34
|
+
attrs.each do |name, value|
|
35
|
+
next true if compare_attribute(name, value)
|
36
|
+
break false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
## Method for Routes comparison
|
41
|
+
def ==(other)
|
42
|
+
%i[controller action method path].reduce(true) do |result, method|
|
43
|
+
result && (
|
44
|
+
public_send(method) == other.public_send(method)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
## Compare by:
|
50
|
+
## 1. path parts count (more is matter);
|
51
|
+
## 2. args position (father is matter);
|
52
|
+
## 3. HTTP-method (default).
|
53
|
+
def <=>(other)
|
54
|
+
path_result = other.path <=> path
|
55
|
+
return path_result unless path_result.zero?
|
56
|
+
method <=> other.method
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
## Helpers for `compare_attributes`
|
62
|
+
def compare_attribute(name, value)
|
63
|
+
case name
|
64
|
+
when :method
|
65
|
+
compare_method(value)
|
66
|
+
when :path
|
67
|
+
path.match? value
|
68
|
+
else
|
69
|
+
send(name) == value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def compare_method(request_method)
|
74
|
+
request_method = request_method.upcase.to_sym
|
75
|
+
request_method = :GET if request_method == :HEAD
|
76
|
+
method.upcase.to_sym == request_method
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/flame/validators.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'errors'
|
3
|
+
require_relative 'errors/route_arguments_error'
|
4
4
|
|
5
5
|
module Flame
|
6
6
|
module Validators
|
@@ -8,7 +8,7 @@ module Flame
|
|
8
8
|
class RouteArgumentsValidator
|
9
9
|
def initialize(ctrl, path, action)
|
10
10
|
@ctrl = ctrl
|
11
|
-
@path = path
|
11
|
+
@path = Flame::Path.new(path)
|
12
12
|
@action = action
|
13
13
|
end
|
14
14
|
|
@@ -30,16 +30,12 @@ module Flame
|
|
30
30
|
|
31
31
|
## Split path to args array
|
32
32
|
def path_arguments
|
33
|
-
@path_arguments ||= @path.
|
33
|
+
@path_arguments ||= @path.parts
|
34
34
|
.each_with_object(req: [], opt: []) do |part, hash|
|
35
35
|
## Take only argument parts
|
36
|
-
next
|
37
|
-
## Clean argument from special chars
|
38
|
-
clean_part = part.delete(
|
39
|
-
Router::ARG_CHAR + Router::ARG_CHAR_OPT
|
40
|
-
).to_sym
|
36
|
+
next unless part.arg?
|
41
37
|
## Memorize arguments
|
42
|
-
hash[part
|
38
|
+
hash[part.opt_arg? ? :opt : :req] << part.clean.to_sym
|
43
39
|
end
|
44
40
|
end
|
45
41
|
|
data/lib/flame/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flame
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.15.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Popov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -222,16 +222,20 @@ files:
|
|
222
222
|
- bin/flame
|
223
223
|
- lib/flame.rb
|
224
224
|
- lib/flame/application.rb
|
225
|
+
- lib/flame/application/config.rb
|
225
226
|
- lib/flame/controller.rb
|
226
|
-
- lib/flame/cookies.rb
|
227
227
|
- lib/flame/dispatcher.rb
|
228
|
-
- lib/flame/
|
228
|
+
- lib/flame/dispatcher/cookies.rb
|
229
|
+
- lib/flame/dispatcher/request.rb
|
230
|
+
- lib/flame/dispatcher/response.rb
|
231
|
+
- lib/flame/dispatcher/static.rb
|
232
|
+
- lib/flame/errors/argument_not_assigned_error.rb
|
233
|
+
- lib/flame/errors/route_arguments_error.rb
|
234
|
+
- lib/flame/errors/route_not_found_error.rb
|
235
|
+
- lib/flame/path.rb
|
229
236
|
- lib/flame/render.rb
|
230
|
-
- lib/flame/request.rb
|
231
|
-
- lib/flame/response.rb
|
232
|
-
- lib/flame/route.rb
|
233
237
|
- lib/flame/router.rb
|
234
|
-
- lib/flame/
|
238
|
+
- lib/flame/router/route.rb
|
235
239
|
- lib/flame/validators.rb
|
236
240
|
- lib/flame/version.rb
|
237
241
|
- public/favicon.ico
|
data/lib/flame/request.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Flame
|
4
|
-
## Class for requests
|
5
|
-
class Request < Rack::Request
|
6
|
-
## Split path of the request to parts (Array of String)
|
7
|
-
def path_parts
|
8
|
-
@path_parts ||= path_info.to_s.split('/').reject(&:empty?)
|
9
|
-
end
|
10
|
-
|
11
|
-
## Override HTTP-method of the request if the param '_method' found
|
12
|
-
def http_method
|
13
|
-
@http_method ||= (params['_method'] || request_method).upcase.to_sym
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
data/lib/flame/response.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Flame
|
4
|
-
## Class for responses
|
5
|
-
class Response < Rack::Response
|
6
|
-
## Set Content-Type header directly or by extension
|
7
|
-
## @param value [String] value for header or extension of file
|
8
|
-
## @return [String] setted value
|
9
|
-
## @example Set value directly
|
10
|
-
## content_type = 'text/css'
|
11
|
-
## @example Set value by file extension
|
12
|
-
## content_type = '.css'
|
13
|
-
def content_type=(value)
|
14
|
-
value = Rack::Mime.mime_type(value) if value.start_with? '.'
|
15
|
-
set_header Rack::CONTENT_TYPE, value
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/lib/flame/route.rb
DELETED
@@ -1,154 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'validators'
|
4
|
-
|
5
|
-
module Flame
|
6
|
-
class Router
|
7
|
-
ARG_CHAR = ':'
|
8
|
-
ARG_CHAR_OPT = '?'
|
9
|
-
|
10
|
-
## Class for Route in Router.routes
|
11
|
-
class Route
|
12
|
-
attr_reader :method, :controller, :action, :path, :path_parts
|
13
|
-
|
14
|
-
def initialize(controller, action, method, ctrl_path, action_path)
|
15
|
-
## Merge action path with controller path
|
16
|
-
path = self.class.path_merge(ctrl_path, action_path)
|
17
|
-
@controller = controller
|
18
|
-
@action = action
|
19
|
-
@method = method.to_sym.upcase
|
20
|
-
## MAKE PATH
|
21
|
-
@path = path
|
22
|
-
Validators::RouteArgumentsValidator.new(
|
23
|
-
@controller, action_path, @action
|
24
|
-
).valid?
|
25
|
-
@path_parts = @path.to_s.split('/').reject(&:empty?)
|
26
|
-
freeze
|
27
|
-
end
|
28
|
-
|
29
|
-
def freeze
|
30
|
-
@path.freeze
|
31
|
-
@path_parts.freeze
|
32
|
-
super
|
33
|
-
end
|
34
|
-
|
35
|
-
## Compare attributes for `Router.find_route`
|
36
|
-
## @param attrs [Hash] Hash of attributes for comparing
|
37
|
-
def compare_attributes(attrs)
|
38
|
-
attrs.each do |name, value|
|
39
|
-
next true if compare_attribute(name, value)
|
40
|
-
break false
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
## Assign arguments to path for `Controller.path_to`
|
45
|
-
## @param args [Hash] arguments for assigning
|
46
|
-
def assign_arguments(args = {})
|
47
|
-
parts = @path_parts.map { |part| assign_argument(part, args) }.compact
|
48
|
-
self.class.path_merge(parts.unshift(nil))
|
49
|
-
end
|
50
|
-
|
51
|
-
## Extract arguments from request_parts for `execute`
|
52
|
-
## @param request_parts [Array] parts of the request (Array of String)
|
53
|
-
def arguments(request_parts)
|
54
|
-
@path_parts.each_with_index.with_object({}) do |(path_part, i), args|
|
55
|
-
request_part = request_parts[i].to_s
|
56
|
-
path_part_opt = path_part[1] == ARG_CHAR_OPT
|
57
|
-
next args unless path_part[0] == ARG_CHAR
|
58
|
-
break args if path_part_opt && request_part.empty?
|
59
|
-
args[
|
60
|
-
path_part[(path_part_opt ? 2 : 1)..-1].to_sym
|
61
|
-
] = URI.decode(request_part)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
## Method for Routes comparison
|
66
|
-
def ==(other)
|
67
|
-
%i[controller action method path_parts].reduce(true) do |result, method|
|
68
|
-
result && (
|
69
|
-
public_send(method) == other.public_send(method)
|
70
|
-
)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
## Compare by path parts count (more is matter)
|
75
|
-
def <=>(other)
|
76
|
-
self_parts, other_parts = [self, other].map(&:path_parts)
|
77
|
-
path_parts_size = other_parts.size <=> self_parts.size
|
78
|
-
return path_parts_size unless path_parts_size.zero?
|
79
|
-
other_parts.zip(self_parts)
|
80
|
-
.reduce(0) do |result, (other_part, self_part)|
|
81
|
-
break 1 if part_arg?(self_part) && !part_arg?(other_part)
|
82
|
-
break -1 if part_arg?(other_part) && !part_arg?(self_part)
|
83
|
-
result
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def self.path_merge(*parts)
|
88
|
-
parts.join('/').gsub(%r{\/{2,}}, '/')
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
def part_arg?(part)
|
94
|
-
part.start_with? ARG_CHAR
|
95
|
-
end
|
96
|
-
|
97
|
-
## Helpers for `compare_attributes`
|
98
|
-
def compare_attribute(name, value)
|
99
|
-
case name
|
100
|
-
when :method
|
101
|
-
compare_method(value)
|
102
|
-
when :path_parts
|
103
|
-
compare_path_parts(value)
|
104
|
-
else
|
105
|
-
send(name) == value
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def compare_method(request_method)
|
110
|
-
request_method = request_method.upcase.to_sym
|
111
|
-
request_method = :GET if request_method == :HEAD
|
112
|
-
method.upcase.to_sym == request_method
|
113
|
-
end
|
114
|
-
|
115
|
-
def compare_path_parts(request_parts)
|
116
|
-
return false unless request_contain_required_path_parts?(request_parts)
|
117
|
-
[request_parts.size, @path_parts.size].max.times do |i|
|
118
|
-
break false unless compare_parts request_parts[i], @path_parts[i]
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def compare_parts(request_part, path_part)
|
123
|
-
# p request_part, path_part
|
124
|
-
return unless path_part
|
125
|
-
return if request_part.nil? && path_part[1] != ARG_CHAR_OPT
|
126
|
-
# p request_part, path_part
|
127
|
-
return true if path_part[0] == ARG_CHAR
|
128
|
-
return true if request_part == path_part
|
129
|
-
end
|
130
|
-
|
131
|
-
def request_contain_required_path_parts?(request_parts)
|
132
|
-
req_path_parts = @path_parts.reject { |part| part[1] == ARG_CHAR_OPT }
|
133
|
-
fixed_path_parts = @path_parts.reject { |part| part[0] == ARG_CHAR }
|
134
|
-
(fixed_path_parts - request_parts).empty? &&
|
135
|
-
request_parts.count >= req_path_parts.count
|
136
|
-
end
|
137
|
-
|
138
|
-
## Helpers for `assign_arguments`
|
139
|
-
def assign_argument(path_part, args = {})
|
140
|
-
## Not argument
|
141
|
-
return path_part unless path_part[0] == ARG_CHAR
|
142
|
-
## Not required argument
|
143
|
-
return args[path_part[2..-1].to_sym] if path_part[1] == ARG_CHAR_OPT
|
144
|
-
## Required argument
|
145
|
-
param = args[path_part[1..-1].to_sym]
|
146
|
-
## Required argument is nil
|
147
|
-
error = Errors::ArgumentNotAssignedError.new(path, path_part)
|
148
|
-
raise error if param.nil?
|
149
|
-
## All is ok
|
150
|
-
param
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|