racket-mvc 0.4.0 → 0.5.0

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -5
  3. data/lib/racket.rb +25 -7
  4. data/lib/racket/application.rb +70 -136
  5. data/lib/racket/controller.rb +95 -38
  6. data/lib/racket/current.rb +1 -1
  7. data/lib/racket/helpers/file.rb +7 -5
  8. data/lib/racket/helpers/routing.rb +5 -5
  9. data/lib/racket/helpers/sass.rb +11 -11
  10. data/lib/racket/helpers/view.rb +8 -5
  11. data/lib/racket/plugins/base.rb +1 -1
  12. data/lib/racket/plugins/sass.rb +1 -1
  13. data/lib/racket/request.rb +3 -3
  14. data/lib/racket/response.rb +1 -1
  15. data/lib/racket/router.rb +31 -12
  16. data/lib/racket/session.rb +3 -3
  17. data/lib/racket/settings/application.rb +27 -33
  18. data/lib/racket/settings/base.rb +19 -8
  19. data/lib/racket/settings/controller.rb +9 -7
  20. data/lib/racket/settings/defaults.rb +81 -0
  21. data/lib/racket/utils.rb +19 -15
  22. data/lib/racket/utils/application.rb +4 -114
  23. data/lib/racket/utils/application/handler_stack.rb +163 -0
  24. data/lib/racket/utils/application/logger.rb +72 -0
  25. data/lib/racket/utils/application/registry_builder.rb +88 -0
  26. data/lib/racket/utils/application/stateless_services.rb +73 -0
  27. data/lib/racket/utils/exceptions.rb +3 -3
  28. data/lib/racket/utils/file_system.rb +75 -47
  29. data/lib/racket/utils/helpers.rb +35 -13
  30. data/lib/racket/utils/routing.rb +62 -46
  31. data/lib/racket/utils/views.rb +19 -187
  32. data/lib/racket/utils/views/renderer.rb +75 -0
  33. data/lib/racket/utils/views/template_cache.rb +126 -0
  34. data/lib/racket/utils/views/template_locator.rb +83 -0
  35. data/lib/racket/utils/views/template_resolver.rb +112 -0
  36. data/lib/racket/version.rb +2 -2
  37. data/lib/racket/view_manager.rb +12 -4
  38. data/rake/utils.rb +5 -5
  39. data/spec/_custom.rb +69 -19
  40. data/spec/_default.rb +60 -44
  41. data/spec/_plugin.rb +12 -14
  42. data/spec/_template_cache.rb +176 -0
  43. data/spec/racket.rb +10 -13
  44. data/spec/test_custom_app/controllers/sub1/custom_sub_controller_1.rb +1 -1
  45. data/spec/test_custom_app/controllers/sub3/custom_sub_controller_3.rb +19 -1
  46. data/spec/test_custom_app/controllers/sub5/custom_sub_controller_5.rb +8 -0
  47. data/spec/test_custom_app/files/stuff.rb +3 -0
  48. data/spec/test_custom_app/files/triplet.erb +5 -0
  49. data/spec/test_custom_app/templates/sub5/text.erb +3 -0
  50. data/spec/test_default_app/controllers/default_root_controller.rb +3 -0
  51. data/spec/test_default_app/controllers/sub1/default_sub_controller_1.rb +1 -1
  52. metadata +52 -11
@@ -0,0 +1,72 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015-2016 Lars Olsson <lasso@lassoweb.se>
3
+ #
4
+ # This file is part of Racket.
5
+ #
6
+ # Racket is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Racket is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ module Racket
20
+ module Utils
21
+ # Namespace for application utilities
22
+ module Application
23
+ # Class for logging messages in the application.
24
+ class Logger
25
+ # Returns a service proc that can be used by the registry.
26
+ #
27
+ # @param [Hash] _options (unused)
28
+ # @return [Proc]
29
+ def self.service(_options = {})
30
+ lambda do |reg|
31
+ settings = reg.application_settings
32
+ new(settings.logger, settings.mode)
33
+ end
34
+ end
35
+
36
+ def initialize(logger, mode)
37
+ @logger = logger
38
+ @in_dev_mode = (mode == :dev)
39
+ end
40
+
41
+ # Sends a message to the logger.
42
+ #
43
+ # @param [String] message
44
+ # @param [Symbol] level
45
+ # @return nil
46
+ def inform_all(message, level = :info)
47
+ inform(message, level)
48
+ end
49
+
50
+ # Sends a message to the logger, but only if we are running in dev mode.
51
+ #
52
+ # @param [String] message
53
+ # @param [Symbol] level
54
+ # @return nil
55
+ def inform_dev(message, level = :debug)
56
+ (inform(message, level) if @in_dev_mode) && nil
57
+ end
58
+
59
+ private
60
+
61
+ # Writes a message to the logger if there is one present.
62
+ #
63
+ # @param [String] message
64
+ # @param [Symbol] level
65
+ # @return nil
66
+ def inform(message, level)
67
+ (@logger.send(level, message) if @logger) && nil
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,88 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015-2016 Lars Olsson <lasso@lassoweb.se>
3
+ #
4
+ # This file is part of Racket.
5
+ #
6
+ # Racket is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Racket is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require 'racket/registry'
20
+
21
+ require_relative 'stateless_services.rb'
22
+
23
+ module Racket
24
+ module Utils
25
+ module Application
26
+ # Class for easily building a Racket::Registry.
27
+ class RegistryBuilder
28
+ attr_reader :registry
29
+
30
+ def initialize(settings = {})
31
+ @settings = settings
32
+ @registry = Racket::Registry.singleton_map(service_map)
33
+ end
34
+
35
+ # Sets up a static server with the specified directory and logger.
36
+ #
37
+ # @param [Pathname|nil] static_dir
38
+ # @param [Logger] logger
39
+ # @return [Proc|nil]
40
+ def self.static_server(static_dir, logger)
41
+ unless static_dir
42
+ logger.inform_dev('Static server disabled.')
43
+ return nil
44
+ end
45
+ logger.inform_dev("Setting up static server to serve files from #{static_dir}.")
46
+ handler = Rack::File.new(static_dir)
47
+ ->(env) { handler.call(env) }
48
+ end
49
+
50
+ private
51
+
52
+ def controller_context
53
+ lambda do |reg|
54
+ Module.new do
55
+ define_singleton_method(:application_settings) { reg.application_settings }
56
+ define_singleton_method(:helper_cache) { reg.helper_cache }
57
+ define_singleton_method(:logger) { reg.application_logger }
58
+ define_singleton_method(:get_route) do |klass, action, params|
59
+ reg.router.get_route(klass, action, params)
60
+ end
61
+ define_singleton_method(:utils) { reg.utils }
62
+ define_singleton_method(:view_manager) { reg.view_manager }
63
+ end
64
+ end
65
+ end
66
+
67
+ def service_map
68
+ {
69
+ application_settings: Racket::Settings::Application.service(@settings),
70
+ controller_context: controller_context,
71
+ static_server: static_server,
72
+ utils: Racket::Utils::ToolBelt.service(root_dir: @settings.fetch(:root_dir, Dir.pwd))
73
+ }.merge!(StatelessServices.services)
74
+ end
75
+
76
+ def static_server
77
+ klass = self.class
78
+ lambda do |reg|
79
+ klass.static_server(
80
+ Racket::Utils::FileSystem.dir_or_nil(reg.application_settings.public_dir),
81
+ reg.application_logger
82
+ )
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,73 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015-2016 Lars Olsson <lasso@lassoweb.se>
3
+ #
4
+ # This file is part of Racket.
5
+ #
6
+ # Racket is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Racket is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ module Racket
20
+ module Utils
21
+ module Application
22
+ # Collects stateless services across the Racket
23
+ module StatelessServices
24
+ # Returns a list of stateless services (in the form of procs)
25
+ # that Racket uses. This is merely as conveniance method for getting
26
+ # all services from one place, the actual services are spread out over
27
+ # a large number of classes.
28
+ #
29
+ # @return [Hash]
30
+ def self.services
31
+ basic_services
32
+ .merge!(layout_services)
33
+ .merge!(template_services)
34
+ .merge!(view_services)
35
+ end
36
+
37
+ def self.basic_services
38
+ {
39
+ action_cache: Racket::Utils::Routing::ActionCache.service,
40
+ application_logger: Racket::Utils::Application::Logger.service,
41
+ handler_stack: Racket::Utils::Application::HandlerStack.service,
42
+ helper_cache: Racket::Utils::Helpers::HelperCache.service,
43
+ router: Racket::Router.service
44
+ }
45
+ end
46
+
47
+ def self.layout_services
48
+ {
49
+ layout_cache: Racket::Utils::Views::TemplateCache.service,
50
+ layout_resolver: Racket::Utils::Views::TemplateResolver.service(type: :layout)
51
+ }
52
+ end
53
+
54
+ def self.template_services
55
+ {
56
+ template_locator: Racket::Utils::Views::TemplateLocator.service,
57
+ template_renderer: Racket::Utils::Views::Renderer.service
58
+ }
59
+ end
60
+
61
+ def self.view_services
62
+ {
63
+ view_cache: Racket::Utils::Views::TemplateCache.service,
64
+ view_manager: Racket::ViewManager.service,
65
+ view_resolver: Racket::Utils::Views::TemplateResolver.service(type: :view)
66
+ }
67
+ end
68
+
69
+ private_class_method :basic_services, :layout_services, :template_services, :view_services
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,5 +1,5 @@
1
1
  # Racket - The noisy Rack MVC framework
2
- # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
2
+ # Copyright (C) 2015-2016 Lars Olsson <lasso@lassoweb.se>
3
3
  #
4
4
  # This file is part of Racket.
5
5
  #
@@ -31,7 +31,7 @@ module Racket
31
31
  # @param [Array] errors
32
32
  # @return [true|flase]
33
33
  def self.run_block(errors)
34
- fail 'Need a block' unless block_given?
34
+ raise 'Need a block' unless block_given?
35
35
  begin
36
36
  true.tap { yield }
37
37
  rescue boolean_module(errors)
@@ -60,7 +60,7 @@ module Racket
60
60
  #
61
61
  # @param [Array] errors
62
62
  # @return [true|flase]
63
- def self.run_block(*errors, &block)
63
+ def run_block(*errors, &block)
64
64
  ExceptionHandler.run_block(errors, &block)
65
65
  end
66
66
  end
@@ -1,5 +1,5 @@
1
1
  # Racket - The noisy Rack MVC framework
2
- # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
2
+ # Copyright (C) 2015-2016 Lars Olsson <lasso@lassoweb.se>
3
3
  #
4
4
  # This file is part of Racket.
5
5
  #
@@ -47,15 +47,16 @@ module Racket
47
47
  #
48
48
  # @param [Array] args
49
49
  # @return [Pathname]
50
- def self.to_pathname(*args)
51
- new(args).path
50
+ def self.to_pathname(root_dir, *args)
51
+ new(root_dir, args).path
52
52
  end
53
53
 
54
54
  attr_reader :path
55
55
 
56
56
  private
57
57
 
58
- def initialize(args)
58
+ def initialize(root_dir, args)
59
+ @root_dir = root_dir
59
60
  extract_base_path(args.dup)
60
61
  build_path
61
62
  clean_path
@@ -72,30 +73,19 @@ module Racket
72
73
  end
73
74
  @args.map!(&:to_s)
74
75
  @path = Pathname.new(@args.shift)
75
- @path = Pathname.new(::Racket::Application.settings.root_dir).join(@path) if
76
- @path.relative?
76
+ @path = Pathname.new(@root_dir).join(@path) if @path.relative?
77
77
  end
78
78
 
79
79
  def build_path
80
80
  @args.each do |arg|
81
81
  path_part = Pathname.new(arg)
82
- fail ArgumentError, arg unless path_part.relative?
82
+ raise ArgumentError, arg unless path_part.relative?
83
83
  @path = @path.join(path_part)
84
84
  end
85
85
  remove_instance_variable :@args
86
86
  end
87
87
  end
88
88
 
89
- # Builds and returns a path in the file system from the provided arguments. The first element
90
- # in the argument list can be either absolute or relative, all other arguments must be
91
- # relative, otherwise they will be removed from the final path.
92
- #
93
- # @param [Array] args
94
- # @return [Pathname]
95
- def self.build_path(*args)
96
- PathBuilder.to_pathname(*args)
97
- end
98
-
99
89
  # Returns whether a directory is readable or not. In order to be readable, the directory must
100
90
  # a) exist
101
91
  # b) be a directory
@@ -107,6 +97,16 @@ module Racket
107
97
  path.exist? && path.directory? && path.readable?
108
98
  end
109
99
 
100
+ # Return a Pathname for a directory if the directory is readable, otherwise returns nil.
101
+ #
102
+ # @param [String] path
103
+ # @return [Pathname|nil]
104
+ def self.dir_or_nil(path)
105
+ return nil unless path
106
+ path = Pathname.new(path)
107
+ dir_readable?(path) ? path : nil
108
+ end
109
+
110
110
  # Extracts the correct directory and glob for a given base path/path combination.
111
111
  #
112
112
  # @param [Pathname] path
@@ -119,17 +119,6 @@ module Racket
119
119
  ]
120
120
  end
121
121
 
122
- # Given a base pathname and a url path string, returns a pathname.
123
- #
124
- # @param [Pathname] base_pathname
125
- # @param [String] url_path
126
- # @return [Pathname]
127
- def self.fs_path(base_pathname, url_path)
128
- parts = url_path.split('/').reject(&:empty?)
129
- parts.each { |part| base_pathname = base_pathname.join(part) }
130
- base_pathname
131
- end
132
-
133
122
  # Returns whether a file is readable or not. In order to be readable, the file must
134
123
  # a) exist
135
124
  # b) be a file
@@ -144,16 +133,6 @@ module Racket
144
133
  path.exist? && path.file? && path.readable?
145
134
  end
146
135
 
147
- # Returns all paths under +base_path+ that matches +glob+.
148
- #
149
- # @param [Pathname] base_path
150
- # @param [Pathname] glob
151
- # @return [Array]
152
- def self.matching_paths(base_path, glob)
153
- return [] unless Utils.dir_readable?(base_path)
154
- Dir.chdir(base_path) { Pathname.glob(glob) }.map { |path| base_path.join(path) }
155
- end
156
-
157
136
  # Returns the first matching path under +base_path+ matching +glob+. If no matching path can
158
137
  # be found, +nil+ is returned.
159
138
  #
@@ -165,14 +144,63 @@ module Racket
165
144
  paths.empty? ? nil : paths.first
166
145
  end
167
146
 
168
- # Returns a list of relative file paths, sorted by path (longest first).
147
+ # Given a base pathname and a url path string, returns a pathname.
169
148
  #
170
- # @param [String] base_dir
171
- # @param [String] glob
172
- # return [Array]
173
- def self.paths_by_longest_path(base_dir, glob)
174
- paths = matching_paths(base_dir, glob).map { |path| SizedPath.new(path) }.sort
175
- paths.map(&:path)
149
+ # @param [Pathname] base_pathname
150
+ # @param [String] url_path
151
+ # @return [Pathname]
152
+ def self.fs_path(base_pathname, url_path)
153
+ parts = url_path.split('/').reject(&:empty?)
154
+ parts.each { |part| base_pathname = base_pathname.join(part) }
155
+ base_pathname
156
+ end
157
+
158
+ # Locates a file in the filesystem matching an URL path. If there exists a matching file,
159
+ # the path to it is returned. If there is no matching file, +nil+ is returned.
160
+ # @param [Pathname] path
161
+ # @return [Pathname|nil]
162
+ def self.resolve_path(path)
163
+ first_matching_path(*extract_dir_and_glob(path))
164
+ end
165
+
166
+ # Locates a file in the filesystem matching an URL path. If there exists a matching file,
167
+ # the path to it is returned. If there is no matching file and +default+ is a
168
+ # String or a Symbol, another lookup will be performed using +default+. If
169
+ # +default+ is a Proc or nil, +default+ will be used as is instead.
170
+ #
171
+ # @param [Pathname] path
172
+ # @param [String|Symbol|Proc|nil] default
173
+ # @return [String|Proc|nil]
174
+ def self.resolve_path_with_default(path, default)
175
+ # Return template if it can be found in the file system
176
+ template = resolve_path(path)
177
+ return template if template
178
+ # No template found for path. Try the default template instead.
179
+ # If default template is a string or a symbol, look it up in the file system
180
+ return resolve_path(fs_path(path.dirname, default)) if
181
+ default.is_a?(String) || default.is_a?(Symbol)
182
+ # If default template is a proc or nil, just return it
183
+ default
184
+ end
185
+
186
+ # Returns all paths under +base_path+ that matches +glob+.
187
+ #
188
+ # @param [Pathname] base_path
189
+ # @param [Pathname] glob
190
+ # @return [Array]
191
+ def self.matching_paths(base_path, glob)
192
+ return [] unless dir_readable?(base_path)
193
+ Dir.chdir(base_path) { Pathname.glob(glob) }.map { |path| base_path.join(path) }
194
+ end
195
+
196
+ # Builds and returns a path in the file system from the provided arguments. The first element
197
+ # in the argument list can be either absolute or relative, all other arguments must be
198
+ # relative, otherwise they will be removed from the final path.
199
+ #
200
+ # @param [Array] args
201
+ # @return [Pathname]
202
+ def build_path(*args)
203
+ PathBuilder.to_pathname(@root_dir, *args)
176
204
  end
177
205
 
178
206
  # Safely requires a file. This method will catch load errors and return true (if the file
@@ -180,8 +208,8 @@ module Racket
180
208
  #
181
209
  # @param [String] resource
182
210
  # @return [true|false]
183
- def self.safe_require(resource)
184
- Utils.run_block(LoadError) { require resource }
211
+ def safe_require(resource)
212
+ run_block(LoadError) { require resource }
185
213
  end
186
214
  end
187
215
  end