flame 4.18.1 → 5.0.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) 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 -4
  6. data/lib/flame/application.rb +93 -40
  7. data/lib/flame/config.rb +73 -0
  8. data/lib/flame/controller.rb +62 -98
  9. data/lib/flame/controller/actions.rb +122 -0
  10. data/lib/flame/controller/cookies.rb +44 -0
  11. data/lib/flame/controller/path_to.rb +63 -0
  12. data/lib/flame/dispatcher.rb +44 -73
  13. data/lib/flame/dispatcher/request.rb +33 -4
  14. data/lib/flame/dispatcher/routes.rb +66 -0
  15. data/lib/flame/dispatcher/static.rb +26 -15
  16. data/lib/flame/errors/argument_not_assigned_error.rb +7 -6
  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 +9 -8
  20. data/lib/flame/errors/route_extra_arguments_error.rb +18 -18
  21. data/lib/flame/errors/route_not_found_error.rb +8 -7
  22. data/lib/flame/errors/template_not_found_error.rb +6 -6
  23. data/lib/flame/path.rb +141 -55
  24. data/lib/flame/render.rb +46 -15
  25. data/lib/flame/router.rb +41 -127
  26. data/lib/flame/router/controller_finder.rb +56 -0
  27. data/lib/flame/router/route.rb +16 -54
  28. data/lib/flame/router/routes.rb +136 -0
  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 +21 -11
  32. data/lib/flame/version.rb +1 -1
  33. metadata +139 -84
  34. data/bin/flame +0 -71
  35. data/lib/flame/application/config.rb +0 -43
  36. data/lib/flame/dispatcher/cookies.rb +0 -31
  37. data/template/.gitignore +0 -11
  38. data/template/Gemfile +0 -15
  39. data/template/Rakefile.erb +0 -64
  40. data/template/app.rb.erb +0 -7
  41. data/template/config.ru.erb +0 -20
  42. data/template/config/config.rb.erb +0 -14
  43. data/template/config/database.example.yml +0 -5
  44. data/template/config/sequel.rb.erb +0 -15
  45. data/template/config/thin.example.yml +0 -18
  46. data/template/controllers/_base_controller.rb.erb +0 -13
  47. data/template/db/.keep +0 -0
  48. data/template/helpers/.keep +0 -0
  49. data/template/lib/.keep +0 -0
  50. data/template/locales/en.yml +0 -0
  51. data/template/models/.keep +0 -0
  52. data/template/public/.keep +0 -0
  53. data/template/server +0 -49
  54. data/template/views/.keep +0 -0
data/lib/flame/render.rb CHANGED
@@ -6,38 +6,54 @@ require 'tilt'
6
6
  require 'tilt/plain'
7
7
  require 'tilt/erb'
8
8
 
9
- require 'gorilla-patch/inflections'
9
+ require 'gorilla_patch/inflections'
10
10
 
11
11
  require_relative 'errors/template_not_found_error'
12
12
 
13
13
  module Flame
14
14
  ## Helper for render functionality
15
15
  class Render
16
+ ## Create a new instance from controller, by path and with options
17
+ ## @param controller [Flame::Controller]
18
+ ## controller for default scope, views directory and cache
19
+ ## @param path [Symbol, String] path (full or the last part) for view search
20
+ ## @param options [Hash] options for template
21
+ ## @option options [Object] :scope (controller)
22
+ ## scope of visibility in rendering
23
+ ## @option options [Symbol, String, false] :layout ('layout.*')
24
+ ## name of the layout file
25
+ ## @option options [Hash] :tilt options for Tilt
26
+ ## @option options [Hash] :locals ({}) local variables for rendering
16
27
  def initialize(controller, path, options = {})
17
28
  ## Take options for rendering
18
29
  @controller = controller
19
- @scope = options.delete(:scope) || @controller
20
- @layout = options.delete(:layout)
21
- @layout = 'layout.*' if @layout.nil?
30
+ @scope = options.delete(:scope) { @controller }
31
+ @layout = options.delete(:layout) { 'layout.*' }
32
+
33
+ ## Options for Tilt Template
34
+ @tilt_options = options.delete(:tilt)
35
+
22
36
  ## And get the rest variables to locals
23
- @locals = options.merge(options.delete(:locals) || {})
37
+ @locals = options.merge(options.delete(:locals) { {} })
38
+
24
39
  ## Find filename
25
40
  @filename = find_file(path)
26
- unless @filename
27
- raise Flame::Errors::TemplateNotFoundError.new(controller, path)
28
- end
41
+ raise Flame::Errors::TemplateNotFoundError.new(controller, path) unless @filename
42
+
29
43
  @layout = nil if File.basename(@filename)[0] == '_'
30
44
  end
31
45
 
32
- ## Render template
46
+ ## Render template with layout
33
47
  ## @param cache [Boolean] cache compiles or not
34
- def render(cache: true)
48
+ ## @return [String] compiled template
49
+ def render(cache: true, &block)
35
50
  @cache = cache
36
51
  ## Compile Tilt to instance hash
37
52
  return unless @filename
53
+
38
54
  tilt = compile_file
39
55
  ## Render Tilt from instance hash with new options
40
- layout_render tilt.render(@scope, @locals)
56
+ layout_render tilt.render(@scope, @locals, &block)
41
57
  end
42
58
 
43
59
  private
@@ -51,7 +67,8 @@ module Flame
51
67
  def compile_file(filename = @filename)
52
68
  cached = @controller.cached_tilts[filename]
53
69
  return cached if @cache && cached
54
- compiled = Tilt.new(filename)
70
+
71
+ compiled = Tilt.new(filename, nil, @tilt_options)
55
72
  @controller.cached_tilts[filename] ||= compiled if @cache
56
73
  compiled
57
74
  end
@@ -76,7 +93,17 @@ module Flame
76
93
 
77
94
  ## Find template-file by path
78
95
  def find_file(path)
79
- find_files(path, controller_dirs).find { |file| Tilt[file] }
96
+ caller_path = caller_locations(4..4).first.path
97
+
98
+ caller_dir =
99
+ begin
100
+ File.dirname(caller_path).sub(views_dir, '') if Tilt[caller_path]
101
+ rescue LoadError
102
+ nil
103
+ end
104
+
105
+ find_files(path, controller_dirs | Array(caller_dir))
106
+ .find { |file| Tilt[file] }
80
107
  end
81
108
 
82
109
  ## Find layout-files by path
@@ -88,11 +115,13 @@ module Flame
88
115
 
89
116
  using GorillaPatch::Inflections
90
117
 
118
+ CONTROLLER_SUFFIXES = %w[_controller _ctrl].freeze
119
+ private_constant :CONTROLLER_SUFFIXES
120
+
91
121
  ## Find possible directories for the controller
92
122
  def controller_dirs
93
123
  parts = @controller.class.underscore.split('/').map do |part|
94
- %w[_controller _ctrl]
95
- .find { |suffix| part.chomp! suffix }
124
+ CONTROLLER_SUFFIXES.find { |suffix| part.chomp! suffix }
96
125
  part
97
126
  ## Alternative, but slower by ~50%:
98
127
  # part.sub(/_(controller|ctrl)$/, '')
@@ -128,8 +157,10 @@ module Flame
128
157
  ## @param result [String] result of template rendering
129
158
  def layout_render(content)
130
159
  return content unless @layout
160
+
131
161
  layout_files = find_layouts(@layout)
132
162
  return content if layout_files.empty?
163
+
133
164
  layout_files.each_with_object(content.dup) do |layout_file, result|
134
165
  layout = compile_file(layout_file)
135
166
  result.replace layout.render(@scope, @locals) { result }
data/lib/flame/router.rb CHANGED
@@ -1,153 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'router/route'
3
+ require 'gorilla_patch/deep_merge'
4
+ require 'gorilla_patch/inflections'
5
+ require 'gorilla_patch/namespace'
6
+
7
+ require_relative 'router/controller_finder'
8
+ require_relative 'errors/controller_not_found_error'
4
9
 
5
10
  module Flame
6
11
  ## Router class for routing
7
12
  class Router
8
- attr_reader :app, :routes
13
+ HTTP_METHODS = %i[GET POST PUT PATCH DELETE].freeze
14
+
15
+ require_relative 'router/route'
16
+ require_relative 'router/routes'
17
+ require_relative 'router/routes_refine'
18
+
19
+ extend Forwardable
20
+ def_delegators :routes, :navigate
21
+
22
+ attr_reader :app, :routes, :reverse_routes
9
23
 
24
+ ## @param app [Flame::Application] host application
10
25
  def initialize(app)
11
26
  @app = app
12
- @routes = []
27
+ @routes = Flame::Router::Routes.new
28
+ @reverse_routes = {}
13
29
  end
14
30
 
15
- ## Add the controller with it's methods to routes
16
- ## @param ctrl [Flame::Controller] class of the controller which will be added
17
- ## @param path [String, nil] root path for controller's methods
18
- ## @yield block for routes refine
19
- def add_controller(ctrl, path = nil, &block)
20
- ## @todo Add Regexp paths
31
+ using GorillaPatch::DeepMerge
21
32
 
22
- ## Add routes from controller to glob array
23
- route_refine = RouteRefine.new(self, ctrl, path, block)
24
- concat_routes(route_refine)
25
- end
26
-
27
- ## Find route by any attributes
28
- ## @param attrs [Hash] attributes for comparing
29
- ## @return [Flame::Route, nil] return the found route, otherwise `nil`
30
- def find_route(attrs)
31
- route = routes.find { |r| r.compare_attributes(attrs) }
32
- route.dup if route
33
+ ## Add RoutesRefine to Router
34
+ ## @param routes_refine [Flame::Router::RoutesRefine] refined routes
35
+ def add(routes_refine)
36
+ routes.deep_merge! routes_refine.routes
37
+ reverse_routes.merge! routes_refine.reverse_routes
33
38
  end
34
39
 
35
40
  ## Find the nearest route by path
36
41
  ## @param path [Flame::Path] path for route finding
37
42
  ## @return [Flame::Route, nil] return the found nearest route or `nil`
38
43
  def find_nearest_route(path)
39
- path = Flame::Path.new(path) if path.is_a? String
40
44
  path_parts = path.parts.dup
41
- while path_parts.size >= 0
42
- route = find_route path: Flame::Path.new(*path_parts)
43
- break if route || path_parts.empty?
44
- path_parts.pop
45
+ loop do
46
+ route = routes.navigate(*path_parts)&.first_route
47
+ break route if route || path_parts.pop.nil?
45
48
  end
46
- route
47
49
  end
48
50
 
49
- private
50
-
51
- ## Add `RouteRefine` routes to the routes of `Flame::Router`
52
- ## @param route_refine [Flame::Router::RouteRefine] `RouteRefine` with routes
53
- def concat_routes(route_refine)
54
- routes.concat(route_refine.routes)
55
- end
56
-
57
- ## Helper class for controller routing refine
58
- class RouteRefine
59
- attr_accessor :rest_routes
60
- attr_reader :ctrl, :routes
61
-
62
- ## Defaults REST routes (methods, pathes, controllers actions)
63
- def rest_routes
64
- @rest_routes ||= [
65
- { method: :GET, path: '/', action: :index },
66
- { method: :POST, path: '/', action: :create },
67
- { method: :GET, path: '/', action: :show },
68
- { method: :PUT, path: '/', action: :update },
69
- { method: :DELETE, path: '/', action: :delete }
70
- ]
71
- end
72
-
73
- def initialize(router, ctrl, path, block)
74
- @router = router
75
- @ctrl = ctrl
76
- @path = path || @ctrl.default_path
77
- @routes = []
78
- execute(&block)
79
- end
80
-
81
- %i[GET POST PUT PATCH DELETE].each do |request_method|
82
- ## Define refine methods for all HTTP methods
83
- ## @overload post(path, action)
84
- ## Execute action on requested path and HTTP method
85
- ## @param path [String] path of method for the request
86
- ## @param action [Symbol] name of method for the request
87
- ## @example Set path to '/bye' and method to :POST for action `goodbye`
88
- ## post '/bye', :goodbye
89
- ## @overload post(action)
90
- ## Execute action on requested HTTP method
91
- ## @param action [Symbol] name of method for the request
92
- ## @example Set method to :POST for action `goodbye`
93
- ## post :goodbye
94
- method = request_method.downcase
95
- define_method(method) do |path, action = nil|
96
- ## Swap arguments if action in path variable
97
- unless action
98
- action = path.to_sym
99
- path = nil
100
- end
101
- ## Init new Route
102
- route = Route.new(@ctrl, action, method, @path, path)
103
- ## Try to find route with the same action
104
- index = find_route_index(action: action)
105
- ## Overwrite route if needed
106
- index ? @routes[index] = route : @routes.push(route)
107
- end
108
- end
109
-
110
- ## Assign remaining methods of the controller
111
- ## to defaults pathes and HTTP methods
112
- def defaults
113
- rest
114
- @ctrl.actions.each do |action|
115
- next if find_route_index(action: action)
116
- send(:GET.downcase, action)
117
- end
118
- end
119
-
120
- ## Assign methods of the controller to REST architecture
121
- def rest
122
- rest_routes.each do |rest_route|
123
- action = rest_route[:action]
124
- next if !@ctrl.actions.include?(action) ||
125
- find_route_index(action: action)
126
- send(*rest_route.values.map(&:downcase))
127
- end
128
- end
129
-
130
- ## Mount controller inside other (parent) controller
131
- ## @param ctrl [Flame::Controller] class of mounting controller
132
- ## @param path [String, nil] root path for mounting controller
133
- ## @yield Block of code for routes refine
134
- def mount(ctrl, path = nil, &block)
135
- path = Flame::Path.merge(@path, path || ctrl.default_path)
136
- @router.add_controller(ctrl, path, &block)
137
- end
138
-
139
- private
140
-
141
- ## Execute block of refinings end sorting routes
142
- def execute(&block)
143
- instance_exec(&block) if block
144
- defaults
145
- @routes.sort!
146
- end
147
-
148
- def find_route_index(attrs)
149
- @routes.find_index { |route| route.compare_attributes(attrs) }
51
+ ## Find the path of route
52
+ ## @param route_or_controller [Flame::Router::Route, Flame::Controller]
53
+ ## route or controller
54
+ ## @param action [Symbol, nil] action (or not for route)
55
+ ## @return [Flame::Path] mounted path to action of controller
56
+ def path_of(route_or_controller, action = nil)
57
+ if route_or_controller.is_a?(Flame::Router::Route)
58
+ route = route_or_controller
59
+ controller = route.controller
60
+ action = route.action
61
+ else
62
+ controller = route_or_controller
150
63
  end
64
+ reverse_routes.dig(controller.to_s, action)
151
65
  end
152
66
  end
153
67
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flame
4
+ ## Comment due to `private_constant`
5
+ class Router
6
+ ## Class for controller constant finding in namespace by names
7
+ class ControllerFinder
8
+ attr_reader :controller
9
+
10
+ def initialize(namespace_name, controller_or_name)
11
+ @namespace =
12
+ namespace_name.empty? ? Object : Object.const_get(namespace_name)
13
+
14
+ if controller_or_name.is_a?(Class)
15
+ @controller = controller_or_name
16
+ @controller_name = controller_or_name.name
17
+ else
18
+ @controller_name = controller_or_name
19
+ @controller = find
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def find
26
+ found_controller_name = controller_name_variations.find do |variation|
27
+ @namespace.const_defined?(variation) unless variation.empty?
28
+ end
29
+
30
+ raise_controller_not_found_error unless found_controller_name
31
+
32
+ controller = @namespace.const_get(found_controller_name)
33
+ return controller if controller < Flame::Controller
34
+
35
+ controller::IndexController
36
+ end
37
+
38
+ using GorillaPatch::Inflections
39
+
40
+ TRASNFORMATION_METHODS = %i[camelize upcase].freeze
41
+
42
+ def controller_name_variations
43
+ TRASNFORMATION_METHODS.each_with_object([]) do |method, result|
44
+ transformed = @controller_name.to_s.send(method)
45
+ result.push transformed, "#{transformed}Controller"
46
+ end
47
+ end
48
+
49
+ def raise_controller_not_found_error
50
+ raise Errors::ControllerNotFoundError.new(@controller_name, @namespace)
51
+ end
52
+ end
53
+
54
+ private_constant :ControllerFinder
55
+ end
56
+ end
@@ -7,73 +7,35 @@ module Flame
7
7
  class Router
8
8
  ## Class for Route in Router.routes
9
9
  class Route
10
- attr_reader :method, :controller, :action, :path
10
+ extend Forwardable
11
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
12
+ def_delegators :to_s, :inspect
25
13
 
26
- def freeze
27
- @path.freeze
28
- super
29
- end
14
+ attr_reader :controller, :action
30
15
 
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
16
+ ## Create a new instance
17
+ ## @param controller [Flame::Controller] controller
18
+ ## @param action [Symbol] action
19
+ def initialize(controller, action)
20
+ @controller = controller
21
+ @action = action
38
22
  end
39
23
 
40
24
  ## Method for Routes comparison
25
+ ## @param other [Flame::Router::Route] other route
26
+ ## @return [true, false] equal or not
41
27
  def ==(other)
42
- %i[controller action method path].reduce(true) do |result, method|
28
+ return false unless other.is_a? self.class
29
+
30
+ %i[controller action].reduce(true) do |result, method|
43
31
  result && (
44
32
  public_send(method) == other.public_send(method)
45
33
  )
46
34
  end
47
35
  end
48
36
 
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
37
+ def to_s
38
+ "#{controller}##{action}"
77
39
  end
78
40
  end
79
41
  end