flame 5.0.0.rc5 → 5.0.0.rc6

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 +921 -0
  3. data/LICENSE.txt +19 -0
  4. data/README.md +135 -0
  5. data/lib/flame.rb +12 -5
  6. data/lib/flame/application.rb +47 -46
  7. data/lib/flame/config.rb +73 -0
  8. data/lib/flame/controller.rb +45 -78
  9. data/lib/flame/controller/actions.rb +122 -0
  10. data/lib/flame/{dispatcher → controller}/cookies.rb +8 -3
  11. data/lib/flame/controller/path_to.rb +34 -10
  12. data/lib/flame/dispatcher.rb +14 -17
  13. data/lib/flame/dispatcher/request.rb +25 -6
  14. data/lib/flame/dispatcher/routes.rb +22 -14
  15. data/lib/flame/dispatcher/static.rb +13 -9
  16. data/lib/flame/errors/argument_not_assigned_error.rb +3 -8
  17. data/lib/flame/errors/config_file_not_found_error.rb +17 -0
  18. data/lib/flame/errors/controller_not_found_error.rb +19 -0
  19. data/lib/flame/errors/route_arguments_order_error.rb +5 -10
  20. data/lib/flame/errors/route_extra_arguments_error.rb +10 -20
  21. data/lib/flame/errors/route_not_found_error.rb +3 -8
  22. data/lib/flame/errors/template_not_found_error.rb +2 -8
  23. data/lib/flame/path.rb +36 -18
  24. data/lib/flame/render.rb +13 -5
  25. data/lib/flame/router.rb +7 -157
  26. data/lib/flame/router/controller_finder.rb +56 -0
  27. data/lib/flame/router/route.rb +9 -0
  28. data/lib/flame/router/routes.rb +58 -8
  29. data/lib/flame/router/routes_refine.rb +144 -0
  30. data/lib/flame/router/routes_refine/mounting.rb +57 -0
  31. data/lib/flame/validators.rb +14 -10
  32. data/lib/flame/version.rb +1 -1
  33. metadata +91 -99
  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.ru.erb +0 -72
  43. data/template/config/config.rb.erb +0 -56
  44. data/template/config/database.example.yml +0 -5
  45. data/template/config/deploy.example.yml +0 -2
  46. data/template/config/puma.rb +0 -56
  47. data/template/config/sequel.rb.erb +0 -22
  48. data/template/config/server.example.yml +0 -32
  49. data/template/config/session.example.yml +0 -7
  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
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gorilla_patch/slice'
4
+
5
+ module Flame
6
+ class Controller
7
+ ## Module for work with actions
8
+ module Actions
9
+ include Memery
10
+ using GorillaPatch::Slice
11
+
12
+ ## Shortcut for not-inherited public methods: actions
13
+ ## @return [Array<Symbol>] array of actions (public instance methods)
14
+ def actions
15
+ public_instance_methods(false)
16
+ end
17
+
18
+ ## Re-define public instance methods (actions) from parent
19
+ ## @param actions [Array<Symbol>] Actions for inheritance
20
+ ## @param exclude [Array<Symbol>] Actions for excluding from inheritance
21
+ ## @param from [Module]
22
+ ## Module (or Class) from which actions will be inherited
23
+ ## @example Inherit all parent actions
24
+ ## class MyController < BaseController
25
+ ## inherit_actions
26
+ ## end
27
+ ## @example Inherit certain parent actions
28
+ ## class MyController < BaseController
29
+ ## inherit_actions %i[index show]
30
+ ## end
31
+ ## @example Inherit all parent actions exclude certain
32
+ ## class MyController < BaseController
33
+ ## inherit_actions exclude: %i[edit update]
34
+ ## end
35
+ ## @example Inherit certain actions from specific module
36
+ ## class MyController < BaseController
37
+ ## inherit_actions %i[index show], from: ModuleWithActions
38
+ ## end
39
+ def inherit_actions(actions = nil, exclude: [], from: superclass)
40
+ actions = from.actions if actions.nil?
41
+ actions -= exclude
42
+
43
+ actions.each do |action|
44
+ define_method action, from.public_instance_method(action)
45
+ end
46
+
47
+ return unless from.respond_to?(:refined_http_methods)
48
+
49
+ refined_http_methods.merge!(
50
+ from.refined_http_methods.slice(*actions)
51
+ )
52
+ end
53
+
54
+ ## Re-define public instance method from module
55
+ ## @param mod [Module] Module for including to controller
56
+ ## @param exclude [Array<Symbol>] Actions for excluding
57
+ ## from module public instance methods
58
+ ## @param only [Array<Symbol>] Actions for re-defining
59
+ ## from module public instance methods
60
+ ## @example Define actions from module in controller
61
+ ## class MyController < BaseController
62
+ ## include with_actions Module1
63
+ ## include with_actions Module2
64
+ ## ....
65
+ ## end
66
+ ## @example Define actions from module exclude some actions in controller
67
+ ## class MyController < BaseController
68
+ ## include with_actions Module1, exclude: %i[action1 action2 ...]
69
+ ## include with_actions Module2, exclude: %i[action1 action2 ...]
70
+ ## ....
71
+ ## end
72
+ ## @example Define actions from module according list in controller
73
+ ## class MyController < BaseController
74
+ ## include with_actions Module1, only: %i[action1 action2 ...]
75
+ ## include with_actions Module2, only: %i[action1 action2 ...]
76
+ ## ....
77
+ ## end
78
+ def with_actions(mod, exclude: [], only: nil)
79
+ Module.new do
80
+ @mod = mod
81
+ @actions = only || (@mod.public_instance_methods(false) - exclude)
82
+
83
+ extend ModuleWithActions
84
+ end
85
+ end
86
+
87
+ memoize def refined_http_methods
88
+ {}
89
+ end
90
+
91
+ private
92
+
93
+ Flame::Router::HTTP_METHODS.each do |http_method|
94
+ downcased_http_method = http_method.downcase
95
+ define_method(
96
+ downcased_http_method
97
+ ) do |action_or_action_path, action = nil|
98
+ action, action_path =
99
+ if action
100
+ [action, action_or_action_path]
101
+ else
102
+ [action_or_action_path, nil]
103
+ end
104
+ refined_http_methods[action] = [downcased_http_method, action_path]
105
+ end
106
+ end
107
+
108
+ ## Base module for module `with_actions`
109
+ module ModuleWithActions
110
+ using GorillaPatch::Slice
111
+
112
+ def included(ctrl)
113
+ ctrl.include @mod
114
+
115
+ ctrl.inherit_actions @actions, from: @mod
116
+ end
117
+ end
118
+
119
+ private_constant :ModuleWithActions
120
+ end
121
+ end
122
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flame
4
- class Dispatcher
4
+ class Controller
5
5
  ## Helper class for cookies
6
6
  class Cookies
7
7
  ## Create an instance of Cookies
@@ -22,14 +22,19 @@ module Flame
22
22
 
23
23
  ## Set (or delete) cookies for response
24
24
  ## @param key [String, Symbol] name of cookie
25
- ## @param new_value [Object, nil] value of cookie
25
+ ## @param new_value [Object, Hash, nil] value of cookie or Hash with `:value` and options
26
26
  ## @example Set new value to `cat` cookie
27
27
  ## cookies['cat'] = 'nice cat'
28
+ ## @example Set new value to `cat` cookie with `Max-Age` 60 seconds
29
+ ## cookies['cat'] = { value: 'nice cat', max_age: 60 }
28
30
  ## @example Delete `cat` cookie
29
31
  ## cookies['cat'] = nil
30
32
  def []=(key, new_value)
31
- if new_value.nil?
33
+ case new_value
34
+ when NilClass
32
35
  @response.delete_cookie(key.to_s, path: '/')
36
+ when Hash
37
+ @response.set_cookie(key.to_s, new_value)
33
38
  else
34
39
  @response.set_cookie(key.to_s, value: new_value, path: '/')
35
40
  end
@@ -4,24 +4,19 @@ module Flame
4
4
  class Controller
5
5
  ## Module with methods for path or URL building
6
6
  module PathTo
7
+ include Memery
8
+
7
9
  ## Look documentation at {Flame::Dispatcher#path_to}
8
10
  def path_to(*args)
9
11
  add_controller_class(args)
12
+ add_controller_arguments(args)
10
13
  @dispatcher.path_to(*args)
11
14
  end
12
15
 
13
16
  ## Build a URI to the given controller and action, or path
14
17
  def url_to(*args, **options)
15
- first_arg = args.first
16
- path =
17
- if first_arg.is_a?(String) || first_arg.is_a?(Flame::Path)
18
- find_static(first_arg).path(with_version: options[:version])
19
- else
20
- path_to(*args, **options)
21
- end
22
- Addressable::URI.new(
23
- scheme: request.scheme, host: request.host_with_port, path: path
24
- ).to_s
18
+ path = build_path_for_url(*args, **options)
19
+ Addressable::URI.join(request.base_url, path).to_s
25
20
  end
26
21
 
27
22
  using GorillaPatch::Namespace
@@ -31,9 +26,38 @@ module Flame
31
26
  def path_to_back
32
27
  back_path = request.referer
33
28
  return back_path if back_path && back_path != request.url
29
+
34
30
  return path_to :index if self.class.actions.include?(:index)
31
+
35
32
  '/'
36
33
  end
34
+
35
+ private
36
+
37
+ def add_controller_class(args)
38
+ args.unshift(self.class) if args[0].is_a?(Symbol)
39
+ args.insert(1, :index) if args[0].is_a?(Class) && !args[1].is_a?(Symbol)
40
+ end
41
+
42
+ def add_controller_arguments(args)
43
+ if args[-1].is_a?(Hash)
44
+ args[-1] = controller_arguments.merge args[-1]
45
+ else
46
+ args.push(controller_arguments)
47
+ end
48
+ end
49
+
50
+ def build_path_for_url(*args, **options)
51
+ first_arg = args.first
52
+ if first_arg.is_a?(String) || first_arg.is_a?(Flame::Path)
53
+ find_static(first_arg).path(with_version: options[:version])
54
+ else
55
+ path_to(*args, **options)
56
+ end
57
+ end
58
+
59
+ memoize :build_path_for_url,
60
+ condition: -> { config[:environment] == 'production' }
37
61
  end
38
62
  end
39
63
  end
@@ -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
@@ -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
@@ -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,26 @@ 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
+ ## TODO: Replace `String#[]` with `#delete_prefix`
37
+ ## after Ruby < 2.5 dropping
38
+ camelized_key =
39
+ key[HEADER_PREFIX.size..-1].downcase.tr('_', '/')
40
+ .camelize.gsub('::', '-')
41
+
42
+ result[camelized_key] = value
43
+ end
25
44
  end
26
45
  end
27
46
  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