flame 4.14.1 → 4.15.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5bc0b79cf5230c68de69ef02ca56cbcb87129596
4
- data.tar.gz: 704eeb2d13348ff0a2550f2bec091b3ce31f0107
3
+ metadata.gz: b3c14c31d4f80cb54a03a36060c74c668b624b4a
4
+ data.tar.gz: 13d93b6e48507a6ad1b5bb0263ab368e2ab973da
5
5
  SHA512:
6
- metadata.gz: c7e1bab7ef3dea8e2b4ebd3c0a2794238363d2667b8231a693450c906dfb49013d5280893003e65484678be80ff130860b7e16fab477aa62484630026c0ac347
7
- data.tar.gz: 22a49850f69e4ca3dfa5ac4a58bb9fcaf639be004b6f0c2ec9ce9d45e64a4784840ad90b4f7aaf5c6057d0bce35a1736dd965f0a7096d2eff8b110781443270c
6
+ metadata.gz: cdcdfca044347e8abe725cca2260987468519a1d4a875cae66656f064ed5e2cadab4818698d50d3cb5360e0302c43117ff3c875ad3c440ec92c751847404fb06
7
+ data.tar.gz: c73d1862a9e5e4f3fe1f30429b304518ab9c7d772fda324f09795067bf064510ff8048ae9c699b4153796a1fcce5228a8ac4a94e821ba13b5ffe46c26f631863
@@ -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
- ## Framework configuration
14
- def config
15
- self.class.config
16
- end
13
+ ## Router for routing
14
+ def router
15
+ @router ||= Flame::Router.new(self)
16
+ end
17
17
 
18
- ## Generating application config when inherited
19
- def self.inherited(app)
20
- app.config = Config.new(
21
- app,
22
- default_config_dirs(
23
- root_dir: File.dirname(caller[0].split(':')[0])
24
- ).merge(
25
- environment: ENV['RACK_ENV'] || 'development'
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
- def initialize(app = nil)
31
- @app = app
32
- end
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
- ## Request recieving method
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
- ## Make available `run Application` without `.new` for `rackup`
41
- def self.call(env)
42
- @app ||= new
43
- @app.call env
44
- end
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
- ## Mount controller in application class
47
- ## @param ctrl [Flame::Controller] the mounted controller class
48
- ## @param path [String, nil] root path for the mounted controller
49
- ## @yield refine defaults pathes for a methods of the mounted controller
50
- ## @example Mount controller with defaults
51
- ## mount ArticlesController
52
- ## @example Mount controller with specific path
53
- ## mount HomeController, '/welcome'
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
- ## Router for routing
65
- def self.router
66
- @router ||= Flame::Router.new(self)
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
- ## Initialize default for config directories
74
- def self.default_config_dirs(root_dir:)
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
- ## Class for Flame::Application.config
87
- class Config < Hash
88
- def initialize(app, hash = {})
89
- @app = app
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
@@ -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
- path = args.first.is_a?(String) ? args.first : path_to(*args)
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 = name.underscore.split('/')
149
- parts = modules[-1].split('_') - %w[index controller ctrl]
150
- return modules[-2] if parts.empty?
151
- parts.join('_')
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
@@ -2,10 +2,13 @@
2
2
 
3
3
  require 'gorilla-patch/symbolize'
4
4
 
5
- require_relative 'cookies'
6
- require_relative 'request'
7
- require_relative 'response'
8
- require_relative 'static'
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
- path_parts: request.path_parts
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!(route.arguments(request.path_parts))
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.path_parts)
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
@@ -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
@@ -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
@@ -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
@@ -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 parts
36
- ## @param path_parts [Array] parts of path for route finding
37
- ## @return [Flame::Route, nil] return the found nearest route, otherwise `nil`
38
- def find_nearest_route(path_parts)
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(path_parts: path_parts)
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, prefix: false|
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), prefix: true)
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 = Route.path_merge(@path, path || ctrl.default_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
@@ -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.split('/')
33
+ @path_arguments ||= @path.parts
34
34
  .each_with_object(req: [], opt: []) do |part, hash|
35
35
  ## Take only argument parts
36
- next if part[0] != Router::ARG_CHAR
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[1] != Router::ARG_CHAR_OPT ? :req : :opt] << clean_part
38
+ hash[part.opt_arg? ? :opt : :req] << part.clean.to_sym
43
39
  end
44
40
  end
45
41
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flame
4
- VERSION = '4.14.1'
4
+ VERSION = '4.15.2'
5
5
  end
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.14.1
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-05-29 00:00:00.000000000 Z
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/errors.rb
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/static.rb
238
+ - lib/flame/router/route.rb
235
239
  - lib/flame/validators.rb
236
240
  - lib/flame/version.rb
237
241
  - public/favicon.ico
@@ -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
@@ -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
@@ -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