flame 5.0.0.rc4 → 5.0.0.rc7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +929 -0
  3. data/LICENSE.txt +19 -0
  4. data/README.md +135 -0
  5. data/lib/flame/application.rb +47 -46
  6. data/lib/flame/config.rb +73 -0
  7. data/lib/flame/controller/actions.rb +122 -0
  8. data/lib/flame/{dispatcher → controller}/cookies.rb +8 -3
  9. data/lib/flame/controller/path_to.rb +34 -10
  10. data/lib/flame/controller.rb +45 -78
  11. data/lib/flame/dispatcher/request.rb +22 -6
  12. data/lib/flame/dispatcher/routes.rb +22 -14
  13. data/lib/flame/dispatcher/static.rb +13 -9
  14. data/lib/flame/dispatcher.rb +15 -18
  15. data/lib/flame/errors/argument_not_assigned_error.rb +3 -8
  16. data/lib/flame/errors/config_file_not_found_error.rb +17 -0
  17. data/lib/flame/errors/controller_not_found_error.rb +19 -0
  18. data/lib/flame/errors/route_arguments_order_error.rb +3 -10
  19. data/lib/flame/errors/route_extra_arguments_error.rb +7 -20
  20. data/lib/flame/errors/route_not_found_error.rb +4 -9
  21. data/lib/flame/errors/template_not_found_error.rb +2 -8
  22. data/lib/flame/path.rb +36 -18
  23. data/lib/flame/render.rb +13 -5
  24. data/lib/flame/router/controller_finder.rb +56 -0
  25. data/lib/flame/router/route.rb +9 -0
  26. data/lib/flame/router/routes.rb +56 -9
  27. data/lib/flame/router/routes_refine/mounting.rb +57 -0
  28. data/lib/flame/router/routes_refine.rb +144 -0
  29. data/lib/flame/router.rb +7 -157
  30. data/lib/flame/validators.rb +14 -10
  31. data/lib/flame/version.rb +1 -1
  32. data/lib/flame.rb +12 -5
  33. metadata +107 -96
  34. data/bin/flame +0 -16
  35. data/lib/flame/application/config.rb +0 -49
  36. data/template/.editorconfig +0 -15
  37. data/template/.gitignore +0 -28
  38. data/template/.rubocop.yml +0 -14
  39. data/template/Gemfile +0 -55
  40. data/template/Rakefile +0 -824
  41. data/template/application.rb.erb +0 -10
  42. data/template/config/config.rb.erb +0 -56
  43. data/template/config/database.example.yml +0 -5
  44. data/template/config/deploy.example.yml +0 -2
  45. data/template/config/puma.rb +0 -56
  46. data/template/config/sequel.rb.erb +0 -22
  47. data/template/config/server.example.yml +0 -32
  48. data/template/config/session.example.yml +0 -7
  49. data/template/config.ru.erb +0 -72
  50. data/template/controllers/_controller.rb.erb +0 -14
  51. data/template/controllers/site/_controller.rb.erb +0 -18
  52. data/template/controllers/site/index_controller.rb.erb +0 -12
  53. data/template/db/.keep +0 -0
  54. data/template/filewatchers.yml +0 -12
  55. data/template/helpers/.keep +0 -0
  56. data/template/lib/.keep +0 -0
  57. data/template/locales/en.yml +0 -0
  58. data/template/models/.keep +0 -0
  59. data/template/public/.keep +0 -0
  60. data/template/server +0 -200
  61. data/template/services/.keep +0 -0
  62. data/template/views/.keep +0 -0
  63. data/template/views/site/index.html.erb.erb +0 -1
  64. data/template/views/site/layout.html.erb.erb +0 -10
@@ -3,65 +3,48 @@
3
3
  require 'forwardable'
4
4
  require 'gorilla_patch/namespace'
5
5
 
6
+ require_relative 'router'
7
+
8
+ require_relative 'controller/actions'
9
+ require_relative 'controller/cookies'
6
10
  require_relative 'controller/path_to'
7
- require_relative 'render'
8
11
 
12
+ ## Just because of `autoload`
9
13
  module Flame
14
+ autoload :Render, "#{__dir__}/render"
15
+
10
16
  ## Class initialize when Dispatcher found route with it
11
17
  ## For new request and response
12
18
  class Controller
13
- extend Forwardable
19
+ extend Actions
20
+ include Memery
14
21
 
15
- ## Shortcut for not-inherited public methods: actions
16
- ## @return [Array<Symbol>] array of actions (public instance methods)
17
- def self.actions
18
- public_instance_methods(false)
19
- end
22
+ class << self
23
+ attr_accessor :path_arguments
24
+
25
+ def path
26
+ return self::PATH if const_defined?(:PATH)
20
27
 
21
- ## Re-define public instance methods (actions) from parent
22
- ## @param actions [Array<Symbol>] Actions for inheritance
23
- ## @param exclude [Array<Symbol>] Actions for excluding from inheritance
24
- ## @example Inherit all parent actions
25
- ## class MyController < BaseController
26
- ## inherit_actions
27
- ## end
28
- ## @example Inherit certain parent actions
29
- ## class MyController < BaseController
30
- ## inherit_actions :index, :show
31
- ## end
32
- ## @example Inherit all parent actions exclude certain
33
- ## class MyController < BaseController
34
- ## inherit_actions exclude: %i[edit update]
35
- ## end
36
- def self.inherit_actions(actions = superclass.actions, exclude: [])
37
- (actions - exclude).each do |public_method|
38
- um = superclass.public_instance_method(public_method)
39
- define_method public_method, um
28
+ default_path
40
29
  end
41
- end
42
30
 
43
- ## Re-define public instance method from module
44
- ## @example Define actions from module in controller
45
- ## class MyController < BaseController
46
- ## include with_actions Module1
47
- ## include with_actions Module2
48
- ## ....
49
- ## end
50
- def self.with_actions(mod, exclude: [])
51
- Module.new do
52
- @mod = mod
53
- @exclude = exclude
54
-
55
- def self.included(ctrl)
56
- ctrl.include @mod
57
-
58
- (@mod.public_instance_methods - @exclude).each do |meth|
59
- ctrl.send :define_method, meth, @mod.public_instance_method(meth)
60
- end
61
- end
31
+ private
32
+
33
+ using GorillaPatch::Inflections
34
+
35
+ ## Default root path of the controller for requests
36
+ def default_path
37
+ modules = underscore.split('/')
38
+ parts = modules.pop.split('_')
39
+ parts.shift if parts.first == 'index'
40
+ parts.pop if %w[controller ctrl].include? parts.last
41
+ parts = [modules.last] if parts.empty?
42
+ Flame::Path.merge nil, parts.join('_')
62
43
  end
63
44
  end
64
45
 
46
+ extend Forwardable
47
+
65
48
  def_delegators(
66
49
  :@dispatcher,
67
50
  :config, :request, :params, :halt, :session, :response, :status, :body,
@@ -74,6 +57,11 @@ module Flame
74
57
  @dispatcher = dispatcher
75
58
  end
76
59
 
60
+ ## Cookies object as Hash
61
+ memoize def cookies
62
+ Cookies.new(request.cookies, response)
63
+ end
64
+
77
65
  include Flame::Controller::PathTo
78
66
 
79
67
  ## Redirect for response
@@ -127,6 +115,7 @@ module Flame
127
115
  content_dis = 'Content-Disposition'
128
116
  response[content_dis] = disposition.to_s
129
117
  return unless filename
118
+
130
119
  response[content_dis] << "; filename=\"#{File.basename(filename)}\""
131
120
  ext = File.extname(filename)
132
121
  response.content_type = ext unless ext.empty?
@@ -157,13 +146,15 @@ module Flame
157
146
  end
158
147
 
159
148
  def not_found
160
- body default_body
149
+ default_body
161
150
  end
162
151
 
163
152
  ## Default method for Internal Server Error, can be inherited
164
153
  ## @param _exception [Exception] exception from code executing
165
154
  ## @return [String] content of exception page
166
- def server_error(_exception)
155
+ def server_error(exception)
156
+ raise exception if Object.const_defined?(:BetterErrors)
157
+
167
158
  body default_body
168
159
  end
169
160
 
@@ -184,6 +175,12 @@ module Flame
184
175
  body
185
176
  end
186
177
 
178
+ using GorillaPatch::Slice
179
+
180
+ def controller_arguments
181
+ params.slice(*self.class.path_arguments)
182
+ end
183
+
187
184
  def extract_params_for(action)
188
185
  ## Take parameters from action method
189
186
  parameters = method(action).parameters
@@ -198,35 +195,5 @@ module Flame
198
195
  ## Concat values
199
196
  req_values + opt_values
200
197
  end
201
-
202
- def add_controller_class(args)
203
- args.unshift(self.class) if args[0].is_a?(Symbol)
204
- args.insert(1, :index) if args[0].is_a?(Class) && !args[1].is_a?(Symbol)
205
- end
206
-
207
- class << self
208
- using GorillaPatch::Inflections
209
-
210
- ## Default root path of the controller for requests
211
- def default_path
212
- modules = underscore.split('/')
213
- parts = modules.pop.split('_')
214
- parts.shift if parts.first == 'index'
215
- parts.pop if %w[controller ctrl].include? parts.last
216
- parts = [modules.last] if parts.empty?
217
- Flame::Path.merge nil, parts.join('_')
218
- end
219
-
220
- def refined_http_methods
221
- @refined_http_methods ||= []
222
- end
223
-
224
- Flame::Router::HTTP_METHODS.each do |http_method|
225
- downcased_http_method = http_method.downcase
226
- define_method(downcased_http_method) do |action_path, action = nil|
227
- refined_http_methods << [downcased_http_method, action_path, action]
228
- end
229
- end
230
- end
231
198
  end
232
199
  end
@@ -4,15 +4,15 @@ module Flame
4
4
  class Dispatcher
5
5
  ## Class for requests
6
6
  class Request < Rack::Request
7
+ include Memery
8
+
7
9
  ## Initialize Flame::Path
8
- def path
9
- @path ||= Flame::Path.new path_info
10
+ memoize def path
11
+ Flame::Path.new path_info
10
12
  end
11
13
 
12
14
  ## Override HTTP-method of the request if the param '_method' found
13
- def http_method
14
- return @http_method if defined?(@http_method)
15
-
15
+ memoize def http_method
16
16
  method_from_method =
17
17
  begin
18
18
  params['_method']
@@ -21,7 +21,23 @@ module Flame
21
21
  raise unless e.message.include?('invalid %-encoding')
22
22
  end
23
23
 
24
- @http_method = (method_from_method || request_method).upcase.to_sym
24
+ (method_from_method || request_method).upcase.to_sym
25
+ end
26
+
27
+ using GorillaPatch::Inflections
28
+
29
+ HEADER_PREFIX = 'HTTP_'
30
+
31
+ ## Helper method for comfortable Camel-Cased Hash of headers
32
+ memoize def headers
33
+ env.each_with_object({}) do |(key, value), result|
34
+ next unless key.start_with?(HEADER_PREFIX)
35
+
36
+ camelized_key =
37
+ key.delete_prefix(HEADER_PREFIX).downcase.tr('_', '/').camelize.gsub('::', '-')
38
+
39
+ result[camelized_key] = value
40
+ end
25
41
  end
26
42
  end
27
43
  end
@@ -11,8 +11,10 @@ module Flame
11
11
  http_method = request.http_method
12
12
  http_method = :GET if http_method == :HEAD
13
13
  return unless available_endpoint
14
+
14
15
  route = available_endpoint[http_method]
15
16
  return unless route || available_endpoint.allow
17
+
16
18
  halt(405, nil, 'Allow' => available_endpoint.allow) unless route
17
19
  status 200
18
20
  execute_route route
@@ -26,32 +28,38 @@ module Flame
26
28
  router.path_of(route).extract_arguments(request.path)
27
29
  )
28
30
  # route.execute(self)
29
- controller = route.controller.new(self)
30
- controller.send(:execute, action)
31
- rescue StandardError => exception
31
+ @current_controller = route.controller.new(self)
32
+ @current_controller.send(:execute, action)
33
+ rescue StandardError, SyntaxError => e
32
34
  # p 'rescue from dispatcher'
33
- dump_error(exception)
35
+ dump_error(e)
34
36
  status 500
35
- controller&.send(:server_error, exception)
36
- # p 're raise exception from dispatcher'
37
- # raise exception
37
+ @current_controller&.send(:server_error, e)
38
+ # p 're raise error from dispatcher'
39
+ # raise e
38
40
  end
39
41
 
40
42
  ## Generate a default body of nearest route
41
43
  def default_body_of_nearest_route
42
44
  ## Return nil if must be no body for current HTTP status
43
45
  return if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
46
+
44
47
  ## Find the nearest route by the parts of requested path
45
48
  route = router.find_nearest_route(request.path)
46
49
  ## Return standard `default_body` if the route not found
47
50
  return default_body unless route
48
- controller = route.controller.new(self)
49
- ## Execute `not_found` method for the founded route
50
- if response.not_found?
51
- controller.send(:not_found)
52
- else
53
- body controller.send(:default_body)
54
- end
51
+
52
+ return not_found_body(route) if response.not_found?
53
+
54
+ controller =
55
+ (@current_controller if defined?(@current_controller)) || route.controller.new(self)
56
+ controller.send :default_body
57
+ end
58
+
59
+ def not_found_body(route)
60
+ ## Execute `not_found` method as action for the founded route
61
+ execute_route(route, :not_found)
62
+ body
55
63
  end
56
64
  end
57
65
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cgi'
4
+
3
5
  module Flame
4
6
  class Dispatcher
5
7
  ## Module for working with static files
@@ -16,9 +18,11 @@ module Flame
16
18
  private
17
19
 
18
20
  ## Find static files and try return it
19
- def try_static(*args)
20
- file = find_static(*args)
21
+ def try_static(*args, **kwargs)
22
+ file = find_static(*args, **kwargs)
21
23
  return nil unless file.exist?
24
+
25
+ halt 400 unless file.within_directory
22
26
  return_static(file)
23
27
  end
24
28
 
@@ -32,9 +36,14 @@ module Flame
32
36
 
33
37
  ## Class for static files with helpers methods
34
38
  class StaticFile
39
+ attr_reader :extname, :within_directory
40
+
35
41
  def initialize(filename, dir)
36
42
  @filename = filename.to_s
37
- @file_path = File.join dir, CGI.unescape(@filename)
43
+ @directory = File.expand_path dir
44
+ @file_path = File.expand_path File.join dir, CGI.unescape(@filename)
45
+ @extname = File.extname(@file_path)
46
+ @within_directory = @file_path.start_with? @directory
38
47
  end
39
48
 
40
49
  def exist?
@@ -45,10 +54,6 @@ module Flame
45
54
  File.mtime(@file_path)
46
55
  end
47
56
 
48
- def extname
49
- File.extname(@file_path)
50
- end
51
-
52
57
  def newer?(http_since)
53
58
  !http_since || mtime.to_i > Time.httpdate(http_since).to_i
54
59
  end
@@ -58,8 +63,7 @@ module Flame
58
63
  end
59
64
 
60
65
  def path(with_version: false)
61
- path = @filename
62
- with_version ? "#{path}?v=#{mtime.to_i}" : path
66
+ with_version ? "#{@filename}?v=#{mtime.to_i}" : @filename
63
67
  end
64
68
  end
65
69
 
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'gorilla_patch/symbolize'
4
+ require 'rack'
4
5
 
5
6
  require_relative 'dispatcher/request'
6
7
  require_relative 'dispatcher/response'
7
- require_relative 'dispatcher/cookies'
8
8
 
9
9
  require_relative 'dispatcher/static'
10
10
  require_relative 'dispatcher/routes'
@@ -14,6 +14,8 @@ require_relative 'errors/route_not_found_error'
14
14
  module Flame
15
15
  ## Helpers for dispatch Flame::Application#call
16
16
  class Dispatcher
17
+ include Memery
18
+
17
19
  GEM_STATIC_FILES = File.join(__dir__, '../../public').freeze
18
20
 
19
21
  extend Forwardable
@@ -73,34 +75,27 @@ module Flame
73
75
 
74
76
  ## Parameters of the request
75
77
  def params
76
- @params ||=
77
- begin
78
- request.params.symbolize_keys(deep: true)
79
- rescue ArgumentError => e
80
- raise unless e.message.include?('invalid %-encoding')
81
- {}
82
- end
78
+ request.params.symbolize_keys(deep: true)
79
+ rescue ArgumentError => e
80
+ raise unless e.message.include?('invalid %-encoding')
81
+
82
+ {}
83
83
  end
84
+ memoize :params
84
85
 
85
86
  ## Session object as Hash
86
87
  def session
87
88
  request.session
88
89
  end
89
90
 
90
- ## Cookies object as Hash
91
- def cookies
92
- @cookies ||= Cookies.new(request.cookies, response)
93
- end
94
-
95
91
  ## Application-config object as Hash
96
92
  def config
97
93
  @app_class.config
98
94
  end
99
95
 
100
96
  ## Available routes endpoint
101
- def available_endpoint
102
- return @available_endpoint if defined? @available_endpoint
103
- @available_endpoint = router.navigate(*request.path.parts)
97
+ memoize def available_endpoint
98
+ router.navigate(*request.path.parts)
104
99
  end
105
100
 
106
101
  ## Interrupt the execution of route, and set new optional data
@@ -129,7 +124,7 @@ module Flame
129
124
  def dump_error(error)
130
125
  error_message = [
131
126
  "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - " \
132
- "#{error.class} - #{error.message}:",
127
+ "#{error.class} - #{error.message}:",
133
128
  *error.backtrace
134
129
  ].join("\n\t")
135
130
  @env[Rack::RACK_ERRORS].puts(error_message)
@@ -138,7 +133,7 @@ module Flame
138
133
  ## Generate default body of error page
139
134
  def default_body
140
135
  # response.headers[Rack::CONTENT_TYPE] = 'text/html'
141
- "<h1>#{Rack::Utils::HTTP_STATUS_CODES[status]}</h1>"
136
+ Rack::Utils::HTTP_STATUS_CODES[status]
142
137
  end
143
138
 
144
139
  ## All cached tilts (views) for application by Flame::Render
@@ -153,12 +148,14 @@ module Flame
153
148
  request.params
154
149
  rescue ArgumentError => e
155
150
  raise unless e.message.include?('invalid %-encoding')
151
+
156
152
  halt 400
157
153
  end
158
154
 
159
155
  ## Return response if HTTP-method is OPTIONS
160
156
  def try_options
161
157
  return unless request.http_method == :OPTIONS
158
+
162
159
  allow = available_endpoint&.allow
163
160
  halt 404 unless allow
164
161
  response.headers['Allow'] = allow
@@ -9,14 +9,9 @@ module Flame
9
9
  ## @param argument [Flame::Path::Part, String, Symbol]
10
10
  ## not assigned argument
11
11
  def initialize(path, argument)
12
- @path = path
13
- @argument = argument
14
- end
15
-
16
- ## Calculated message of the error
17
- ## @return [String] message of the error
18
- def message
19
- "Argument '#{@argument}' for path '#{@path}' is not assigned"
12
+ super(
13
+ "Argument '#{argument}' for path '#{path}' is not assigned"
14
+ )
20
15
  end
21
16
  end
22
17
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flame
4
+ module Errors
5
+ ## Error for not found config file in Config
6
+ class ConfigFileNotFoundError < StandardError
7
+ ## Create a new instance of error
8
+ ## @param file_name [String]
9
+ ## file name mask by which file was not found
10
+ ## @param directory [String] directory in which file was not found
11
+ def initialize(file_name, directory)
12
+ directory = directory.sub(%r{^/+}, '').sub(%r{/+$}, '')
13
+ super "Config file '#{file_name}' not found in '#{directory}/'"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flame
4
+ module Errors
5
+ ## Error for not found controller by name in namespace
6
+ class ControllerNotFoundError < StandardError
7
+ ## Create a new instance of error
8
+ ## @param controller_name [Symbol, String]
9
+ ## name of controller which not found
10
+ ## @param namespace [Module]
11
+ ## namespace for which controller not found
12
+ def initialize(controller_name, namespace)
13
+ super(
14
+ "Controller '#{controller_name}' not found for '#{namespace}'"
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -9,16 +9,9 @@ module Flame
9
9
  ## @param wrong_ordered_arguments [Array<Symbol>]
10
10
  ## two wrong ordered arguments
11
11
  def initialize(path, wrong_ordered_arguments)
12
- @path = path
13
- @wrong_ordered_arguments = wrong_ordered_arguments
14
- end
15
-
16
- ## Calculated message of the error
17
- ## @return [String] message of the error
18
- def message
19
- "Path '#{@path}' should have" \
20
- " '#{@wrong_ordered_arguments.first}' argument before" \
21
- " '#{@wrong_ordered_arguments.last}'"
12
+ super(<<~MESSAGE.chomp)
13
+ Path '#{path}' should have '#{wrong_ordered_arguments.first}' argument before '#{wrong_ordered_arguments.last}'
14
+ MESSAGE
22
15
  end
23
16
  end
24
17
  end
@@ -13,30 +13,17 @@ module Flame
13
13
  ## @option extra [Symbol] :place extra arguments in controller or path
14
14
  ## @option extra [Array<Symbol>] :args extra arguments
15
15
  def initialize(ctrl, action, path, extra)
16
- @ctrl = ctrl
17
- @action = action
18
- @path = path
19
- @extra = extra
20
- @extra[:type_name] = {
21
- req: 'required',
22
- opt: 'optional'
23
- }[@extra[:type]]
24
- end
16
+ extra[:type_name] = { req: 'required', opt: 'optional' }[extra[:type]]
25
17
 
26
- ## Calculated message of the error
27
- ## @return [String] message of the error
28
- def message
29
- case @extra[:place]
30
- when :ctrl
18
+ entity = {
31
19
  ## Error if path has no arguments, that controller's method has
32
20
  ## NOTE: It isn't using because `Flame::Path#adopt`
33
- "Path '#{@path}' has no #{@extra[:type_name]}" \
34
- " arguments #{@extra[:args].inspect}"
35
- when :path
21
+ ctrl: "Path '#{path}'",
36
22
  ## Error if path has more arguments, than controller's method
37
- "Action '#{@ctrl}##{@action}' has no #{@extra[:type_name]}" \
38
- " arguments #{@extra[:args].inspect}"
39
- end
23
+ path: "Action '#{ctrl}##{action}'"
24
+ }[extra[:place]]
25
+
26
+ super "#{entity} has no #{extra[:type_name]} arguments #{extra[:args].inspect}"
40
27
  end
41
28
  end
42
29
  end
@@ -9,15 +9,10 @@ module Flame
9
9
  ## controller with which route not found
10
10
  ## @param action [Symbol] action with which route not found
11
11
  def initialize(controller, action)
12
- @controller = controller
13
- @action = action
14
- end
15
-
16
- ## Calculated message of the error
17
- ## @return [String] message of the error
18
- def message
19
- "Route with controller '#{@controller}' and action '#{@action}'" \
20
- ' not found in application routes'
12
+ super(
13
+ "Route with controller '#{controller}' and action '#{action}' " \
14
+ 'not found in application routes'
15
+ )
21
16
  end
22
17
  end
23
18
  end
@@ -9,15 +9,9 @@ module Flame
9
9
  ## controller from which template not found
10
10
  ## @param path [String, Symbol] path of not founded template
11
11
  def initialize(controller, path)
12
- @controller = controller
13
- @controller = @controller.class unless @controller.is_a? Class
14
- @path = path
15
- end
12
+ controller = controller.class unless controller.is_a? Class
16
13
 
17
- ## Calculated message of the error
18
- ## @return [String] message of the error
19
- def message
20
- "Template '#{@path}' not found for '#{@controller}'"
14
+ super "Template '#{path}' not found for '#{controller}'"
21
15
  end
22
16
  end
23
17
  end