flame 5.0.0.rc4 → 5.0.0.rc7

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