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