racket-mvc 0.3.0 → 0.3.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 02938217c8513aa37d2e87dbc1232d85be25b94a
4
- data.tar.gz: e2686cd3a87d07793b886e48428b277bd3ae1639
3
+ metadata.gz: 395ab3d4006c31ae432dfa280c626166fc41a061
4
+ data.tar.gz: 0e22dba439da9506e191c0a253f49b36e6f0d050
5
5
  SHA512:
6
- metadata.gz: a76232aecaf2a67c3062713204dca768b1cbc6307e70cb6eaa3fa90d56e0c814c1cace2f756071294b09f1f28375c46e81e93db5842151e9d0e44a5489b74d61
7
- data.tar.gz: 509beaf22372ace6bd8cc85739c8524006cc03790bf9a49279991c89d4991514a7e176626904ce9367beba87c72f72ed51702026f99357d0e042b3747804d9ad
6
+ metadata.gz: c8cf2e4fdcb7814d204a72ff06f6774088f7903afce071039e75b69c83340767f2f6dd4b91c60bd0a197e89cf78b85dc95b5a33784423a8cfc5a257fdb3d46ac
7
+ data.tar.gz: 02af1c5652968eb149b6945d81232eda5b5291e9e79eae9aa1f4f0bb5ef649a22bfbe5c618e8d0d2112731b361229ee3fcba2853bbedad26e4486ef1d4a0f418
@@ -48,7 +48,7 @@ module Racket
48
48
  end
49
49
  run lambda { |env|
50
50
  static_result = instance.serve_static_file(env)
51
- return static_result unless static_result.nil? || static_result.first >= 400
51
+ return static_result if static_result && static_result.first < 400
52
52
  instance.router.route(env)
53
53
  }
54
54
  end
@@ -134,32 +134,34 @@ module Racket
134
134
  def self.load_controllers
135
135
  inform_dev('Loading controllers.')
136
136
  @settings.store(:last_added_controller, [])
137
- @controller = nil
138
- Dir.chdir(@settings.controller_dir) do
139
- files = Pathname.glob(File.join('**', '*.rb')).map!(&:to_s)
140
- # Sort by longest path so that the longer paths gets matched first
141
- # HttpRouter claims to be doing this already, but this "hack" is needed in order
142
- # for the router to work.
143
- files.sort! { |a, b| b.split('/').length <=> a.split('/').length }
144
- files.each do |file|
145
- ::Kernel.require File.expand_path(file)
146
- path = "/#{File.dirname(file)}"
147
- path = '' if path == '/.'
148
- @router.map(path, @settings.fetch(:last_added_controller).pop)
149
- end
137
+ Utils.files_by_longest_path(@settings.controller_dir, File.join('**', '*.rb')).each do |file|
138
+ load_controller_file(file)
150
139
  end
151
140
  @settings.delete(:last_added_controller)
152
141
  inform_dev('Done loading controllers.') && nil
153
142
  end
154
143
 
144
+ # Loads a controller file.
145
+ #
146
+ # @param [String] file Relative path from controller dir
147
+ # @return nil
148
+ def self.load_controller_file(file)
149
+ ::Kernel.require Utils.build_path(@settings.controller_dir, file)
150
+ path = "/#{File.dirname(file)}"
151
+ path = '' if path == '/.'
152
+ @router.map(path, @settings.fetch(:last_added_controller).pop) && nil
153
+ end
154
+
155
155
  # Loads some middleware (based on settings).
156
156
  #
157
157
  # @return nil
158
158
  def self.load_middleware
159
- @settings.middleware.unshift(@settings.session_handler) if @settings.session_handler
160
- @settings.middleware.unshift([Rack::ContentType, @settings.default_content_type]) if
161
- @settings.default_content_type
162
- (@settings.middleware.unshift([Rack::ShowExceptions]) if dev_mode?) && nil
159
+ middleware = @settings.middleware
160
+ session_handler = @settings.session_handler
161
+ default_content_type = @settings.default_content_type
162
+ middleware.unshift(session_handler) if session_handler
163
+ middleware.unshift([Rack::ContentType, default_content_type]) if default_content_type
164
+ (middleware.unshift([Rack::ShowExceptions]) if dev_mode?) && nil
163
165
  end
164
166
 
165
167
  # Reloads the application, making any changes to the controller configuration visible
@@ -184,8 +186,7 @@ module Racket
184
186
  # @param [Hash] env Rack environment
185
187
  # @return [Array|nil] A Rack response array if Rack::File handled the file, nil otherwise.
186
188
  def self.serve_static_file(env)
187
- return nil if @static_server.nil?
188
- @static_server.call(env)
189
+ @static_server ? @static_server.call(env) : nil
189
190
  end
190
191
 
191
192
  # Initializes routing.
@@ -222,7 +223,7 @@ module Racket
222
223
  @view_manager ||= ViewManager.new(@settings.layout_dir, @settings.view_dir)
223
224
  end
224
225
 
225
- private_class_method :application, :build_application, :inform, :init, :load_controllers,
226
- :load_middleware, :setup_routes, :setup_static_server
226
+ private_class_method :application, :build_application, :inform, :init, :load_controller_file,
227
+ :load_controllers, :load_middleware, :setup_routes, :setup_static_server
227
228
  end
228
229
  end
@@ -88,11 +88,9 @@ module Racket
88
88
  # namespace.
89
89
  def self.helper(*helpers)
90
90
  helper_modules = {}
91
- existing_helpers = settings.fetch(:helpers)
92
- if existing_helpers.nil?
93
- # No helpers has been loaded yet. Load the default helpers.
94
- existing_helpers = Application.settings.default_controller_helpers
95
- helper_modules.merge!(__load_helpers(existing_helpers))
91
+ unless settings.fetch(:helpers)
92
+ # No helpers has been loaded yet. Load the default helpers first.
93
+ helper_modules.merge!(__load_helpers(Application.settings.default_controller_helpers))
96
94
  end
97
95
  # Load new helpers
98
96
  helpers.map!(&:to_sym)
@@ -34,7 +34,7 @@ module Racket
34
34
  # @param [Array] params Parameters sent to the action
35
35
  # @return [Module] A module encapsulating all state relating to the current request
36
36
  def self.init(env, klass, action, params)
37
- klass.helper if klass.settings.fetch(:helpers).nil? # Makes sure default helpers are loaded.
37
+ klass.helper unless klass.settings.fetch(:helpers) # Makes sure default helpers are loaded.
38
38
  properties = init_properties(action, params, env)
39
39
  Module.new do
40
40
  klass.settings.fetch(:helpers).each_value { |helper| include helper }
@@ -21,42 +21,68 @@ module Racket
21
21
  module Helpers
22
22
  # Helper module that handles files
23
23
  module File
24
+ # Class for sending files.
25
+ class Response
26
+ def initialize(file, options)
27
+ @file = Utils.build_path(file)
28
+ @options = options
29
+ @response = Racket::Response.new
30
+ build
31
+ end
32
+
33
+ # Returns the current object as a Rack response array.
34
+ #
35
+ # @return [Array]
36
+ def to_a
37
+ @response.to_a
38
+ end
39
+
40
+ private
41
+
42
+ def build
43
+ if Utils.file_readable?(@file) then build_success
44
+ else build_failure
45
+ end
46
+ end
47
+
48
+ def build_failure
49
+ @response.status = 404
50
+ @response.headers['Content-Type'] = 'text/plain'
51
+ @response.write(Rack::Utils::HTTP_STATUS_CODES[@response.status])
52
+ end
53
+
54
+ def build_success
55
+ @response.status = 200
56
+ set_mime_type
57
+ set_content_disposition
58
+ @response.write(::File.read(@file))
59
+ end
60
+
61
+ def set_content_disposition
62
+ # Set Content-Disposition (and a file name) if the file should be downloaded
63
+ # instead of displayed inline.
64
+ return unless @options.fetch(:download, false)
65
+ content_disposition = 'attachment'
66
+ filename = @options.fetch(:filename, nil).to_s
67
+ content_disposition << format('; filename="%s"', filename) unless filename.empty?
68
+ @response.headers['Content-Disposition'] = content_disposition
69
+ end
70
+
71
+ def set_mime_type
72
+ mime_type = @options.fetch(:mime_type, nil)
73
+ # Calculate MIME type if it was not already specified.
74
+ mime_type = Rack::Mime.mime_type(::File.extname(@file)) unless mime_type
75
+ @response.headers['Content-Type'] = mime_type
76
+ end
77
+ end
78
+
24
79
  # Sends the contents of a file to the client.
25
80
  #
26
81
  # @param [String] file
27
82
  # @param [Hash] options
28
83
  # @return [Array]
29
84
  def send_file(file, options = {})
30
- file = Utils.build_path(file)
31
- _send_file_check_file_readable(file)
32
- headers = {}
33
- mime_type = options.fetch(:mime_type, nil)
34
- # Calculate MIME type if it was not already specified.
35
- mime_type = Rack::Mime.mime_type(::File.extname(file)) unless mime_type
36
- headers['Content-Type'] = mime_type
37
- # Set Content-Disposition (and a file name) if the file should be downloaded
38
- # instead of displayed inline.
39
- _send_file_set_content_disposition(options, headers)
40
- # Send response
41
- respond!(200, headers, ::File.read(file))
42
- end
43
-
44
- private
45
-
46
- def _send_file_check_file_readable(file)
47
- # Respond with a 404 Not Found if the file cannot be read.
48
- respond!(
49
- 404,
50
- { 'Content-Type' => 'text/plain' },
51
- Rack::Utils::HTTP_STATUS_CODES[404]
52
- ) unless Utils.file_readable?(file)
53
- end
54
-
55
- def _send_file_set_content_disposition(options, headers)
56
- return unless options.fetch(:download, false)
57
- filename = options.fetch(:filename, nil).to_s
58
- headers['Content-Disposition'] = 'attachment'
59
- headers['Content-Disposition'] << format('; filename="%s"', filename) unless filename.empty?
85
+ respond!(*(Response.new(file, options).to_a))
60
86
  end
61
87
  end
62
88
  end
data/lib/racket/router.rb CHANGED
@@ -58,7 +58,7 @@ module Racket
58
58
  params.flatten!
59
59
  route = ''
60
60
  route << @routes[controller_class]
61
- route << "/#{action}" unless action.nil?
61
+ route << "/#{action}" if action
62
62
  route << "/#{params.join('/')}" unless params.empty?
63
63
  route = route[1..-1] if route.start_with?('//') # Special case for root path
64
64
  route
@@ -95,11 +95,11 @@ module Racket
95
95
  def route(env)
96
96
  catch :response do # Catches early exits from Controller.respond.
97
97
  # Ensure that that a controller will respond to the request. If not, send a 404.
98
- return render_error(404) if (target_info = target_info(env)).nil?
98
+ return render_error(404) unless (target_info = target_info(env))
99
99
  target_klass, params, action = target_info
100
100
 
101
101
  # Rewrite PATH_INFO to reflect that we split out the parameters
102
- update_path_info(env, params.length)
102
+ Utils.update_path_info(env, params.length)
103
103
 
104
104
  # Initialize and render target
105
105
  target = target_klass.new
@@ -118,37 +118,14 @@ module Racket
118
118
  # @param [Hash] env
119
119
  # @return [Array|nil]
120
120
  def target_info(env)
121
- matching_routes = @router.recognize(env)
121
+ matching_route = @router.recognize(env).first
122
122
  # Exit early if no controller is responsible for the route
123
- return nil if matching_routes.first.nil?
123
+ return nil unless matching_route
124
124
  # Some controller is claiming to be responsible for the route
125
- result = extract_target(matching_routes)
125
+ result = Utils.extract_target(matching_route.first)
126
126
  # Exit early if action is not available on target
127
127
  return nil unless @action_cache[result.first].include?(result.last)
128
128
  result
129
129
  end
130
-
131
- # Extracts the target class, target params and target action from a list of valid routes.
132
- #
133
- # @param [Array] routes
134
- # @return [Array]
135
- def extract_target(routes)
136
- target_klass = routes.first.first.route.dest
137
- params = routes.first.first.param_values.first.reject(&:empty?)
138
- action = params.empty? ? target_klass.settings.fetch(:default_action) : params.shift.to_sym
139
- [target_klass, params, action]
140
- end
141
-
142
- # Updates the PATH_INFO environment variable.
143
- #
144
- # @param [Hash] env
145
- # @param [Fixnum] num_params
146
- # @return [nil]
147
- def update_path_info(env, num_params)
148
- env['PATH_INFO'] = env['PATH_INFO']
149
- .split('/')[0...-num_params]
150
- .join('/') unless num_params.zero?
151
- nil
152
- end
153
130
  end
154
131
  end
@@ -59,7 +59,8 @@ module Racket
59
59
  ivar = "@#{symbol}".to_sym
60
60
  define_method symbol do
61
61
  instance_variable_set(ivar, directory) unless instance_variables.include?(ivar)
62
- Utils.build_path(instance_variable_get(ivar))
62
+ return nil unless (value = instance_variable_get(ivar))
63
+ Utils.build_path(value)
63
64
  end
64
65
  attr_writer(symbol) && nil
65
66
  end
@@ -68,15 +68,14 @@ module Racket
68
68
  #
69
69
  # @param [Symbol] symbol
70
70
  # @param [Object] default
71
- # @param [true|false] writable
72
71
  # @return [nil]
73
- def self.setting(symbol, default = nil, writable = true)
72
+ def self.setting(symbol, default = nil)
74
73
  ivar = "@#{symbol}".to_sym
75
74
  define_method symbol do
76
75
  instance_variable_set(ivar, default) unless instance_variables.include?(ivar)
77
76
  instance_variable_get(ivar)
78
77
  end
79
- (attr_writer(symbol) if writable) && nil
78
+ attr_writer(symbol) && nil
80
79
  end
81
80
  end
82
81
  end
@@ -35,9 +35,9 @@ module Racket
35
35
  # fallback.
36
36
  def fetch(key, default = nil)
37
37
  return @custom[key] if @custom.key?(key)
38
- return ::Racket::Application.settings.fetch(key, default) if
39
- @owner.superclass == ::Racket::Controller
40
- @owner.superclass.settings.fetch(key, default)
38
+ parent = @owner.superclass
39
+ return ::Racket::Application.settings.fetch(key, default) if parent == ::Racket::Controller
40
+ parent.settings.fetch(key, default)
41
41
  end
42
42
  end
43
43
  end
data/lib/racket/utils.rb CHANGED
@@ -16,82 +16,27 @@
16
16
  # You should have received a copy of the GNU Affero General Public License
17
17
  # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
18
 
19
+ require_relative 'utils/exceptions.rb'
20
+ require_relative 'utils/file_system.rb'
21
+ require_relative 'utils/routing.rb'
22
+ require_relative 'utils/views.rb'
23
+
19
24
  module Racket
20
25
  # Collects utilities needed by different objects in Racket.
21
- class Utils
22
- # Handles exceptions dynamically
23
- class ExceptionHandler
24
- # Runs a block.
25
- # If no exceptions are raised, this method returns true.
26
- # If any of the provided error types are raised, this method returns false.
27
- # If any other exception is raised, this method will just forward the exception.
28
- #
29
- # @param [Array] errors
30
- # @return [true|flase]
31
- def self.run_block(errors)
32
- fail 'Need a block' unless block_given?
33
- begin
34
- true.tap { yield }
35
- rescue boolean_module(errors)
36
- false
37
- end
38
- end
39
-
40
- # Returns an anonymous module that can be used to rescue exceptions dynamically.
41
- def self.boolean_module(errors)
42
- Module.new do
43
- (class << self; self; end).instance_eval do
44
- define_method(:===) do |error|
45
- errors.any? { |e| error.class <= e }
46
- end
47
- end
48
- end
49
- end
26
+ module Utils
27
+ extend SingleForwardable
50
28
 
51
- private_class_method :boolean_module
52
- end
53
-
54
- # Builds and returns a path in the file system from the provided arguments. The first element
55
- # in the argument list can be either absolute or relative, all other arguments must be relative,
56
- # otherwise they will be removed from the final path.
29
+ # Embraces a module, making its class methods available as class methods on the current module.
57
30
  #
58
- # @param [Array] args
59
- # @return [String]
60
- def self.build_path(*args)
61
- if args.empty?
62
- path = Pathname.pwd
63
- else
64
- args.map!(&:to_s)
65
- path = Pathname.new(args.shift)
66
- path = Pathname.new(Application.settings.root_dir).join(path) if path.relative?
67
- args.each do |arg|
68
- path_part = Pathname.new(arg)
69
- next unless path_part.relative?
70
- path = path.join(path_part)
71
- end
72
- end
73
- path.cleanpath.expand_path.to_s
74
- end
75
-
76
- def self.dir_readable?(path)
77
- pathname = Pathname.new(path)
78
- pathname.exist? && pathname.directory? && pathname.readable?
79
- end
80
-
81
- def self.file_readable?(path)
82
- pathname = Pathname.new(path)
83
- pathname.exist? && pathname.file? && pathname.readable?
31
+ # @param [Module] mod
32
+ # @return [nil]
33
+ def self.__embrace(mod)
34
+ def_single_delegators(mod, *mod.singleton_methods) && nil
84
35
  end
85
36
 
86
- # Runs a block.
87
- # If no exceptions are raised, this method returns true.
88
- # If any of the provided error types are raised, this method returns false.
89
- # If any other exception is raised, this method will just forward the exception.
90
- #
91
- # @param [Array] errors
92
- # @return [true|flase]
93
- def self.run_block(*errors, &block)
94
- ExceptionHandler.run_block(errors, &block)
95
- end
37
+ __embrace(Exceptions)
38
+ __embrace(FileSystem)
39
+ __embrace(Routing)
40
+ __embrace(Views)
96
41
  end
97
42
  end
@@ -0,0 +1,68 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 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
+ # Collects utilities needed by different objects in Racket.
21
+ module Utils
22
+ # Utility functions for filesystem.
23
+ module Exceptions
24
+ # Handles exceptions dynamically
25
+ class ExceptionHandler
26
+ # Runs a block.
27
+ # If no exceptions are raised, this method returns true.
28
+ # If any of the provided error types are raised, this method returns false.
29
+ # If any other exception is raised, this method will just forward the exception.
30
+ #
31
+ # @param [Array] errors
32
+ # @return [true|flase]
33
+ def self.run_block(errors)
34
+ fail 'Need a block' unless block_given?
35
+ begin
36
+ true.tap { yield }
37
+ rescue boolean_module(errors)
38
+ false
39
+ end
40
+ end
41
+
42
+ # Returns an anonymous module that can be used to rescue exceptions dynamically.
43
+ def self.boolean_module(errors)
44
+ Module.new do
45
+ (class << self; self; end).instance_eval do
46
+ define_method(:===) do |error|
47
+ errors.any? { |err| error.class <= err }
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ private_class_method :boolean_module
54
+ end
55
+
56
+ # Runs a block.
57
+ # If no exceptions are raised, this method returns true.
58
+ # If any of the provided error types are raised, this method returns false.
59
+ # If any other exception is raised, this method will just forward the exception.
60
+ #
61
+ # @param [Array] errors
62
+ # @return [true|flase]
63
+ def self.run_block(*errors, &block)
64
+ ExceptionHandler.run_block(errors, &block)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,127 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 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
+ # Collects utilities needed by different objects in Racket.
21
+ module Utils
22
+ # Utility functions for filesystem.
23
+ module FileSystem
24
+ # Build path in the filesystem.
25
+ class PathBuilder
26
+ # Creates a new instance of PathBuilder using +args+ and then returning the final path as
27
+ # a Pathname.
28
+ #
29
+ # @param [Array] args
30
+ # @return [Pathname]
31
+ def self.to_pathname(*args)
32
+ new(args).path
33
+ end
34
+
35
+ # Creates a new instance of PathBuilder using +args+ and then returning the final path as
36
+ # a string.
37
+ #
38
+ # @param [Array] args
39
+ # @return [String]
40
+ def self.to_s(*args)
41
+ new(args).path.to_s
42
+ end
43
+
44
+ attr_reader :path
45
+
46
+ private
47
+
48
+ def initialize(args)
49
+ extract_base_path(args.dup)
50
+ build_path
51
+ clean_path
52
+ end
53
+
54
+ def clean_path
55
+ @path = @path.cleanpath.expand_path
56
+ end
57
+
58
+ def extract_base_path(args)
59
+ if (@args = args).empty?
60
+ @path = Pathname.pwd
61
+ return
62
+ end
63
+ @args.map!(&:to_s)
64
+ @path = Pathname.new(@args.shift)
65
+ @path = Pathname.new(::Racket::Application.settings.root_dir).join(@path) if
66
+ @path.relative?
67
+ end
68
+
69
+ def build_path
70
+ @args.each do |arg|
71
+ path_part = Pathname.new(arg)
72
+ next unless path_part.relative?
73
+ @path = @path.join(path_part)
74
+ end
75
+ remove_instance_variable :@args
76
+ end
77
+ end
78
+
79
+ # Builds and returns a path in the file system from the provided arguments. The first element
80
+ # in the argument list can be either absolute or relative, all other arguments must be
81
+ # relative, otherwise they will be removed from the final path.
82
+ #
83
+ # @param [Array] args
84
+ # @return [String]
85
+ def self.build_path(*args)
86
+ PathBuilder.to_s(*args)
87
+ end
88
+
89
+ # Returns whether a directory is readable or not. In order to be readable, the directory must
90
+ # a) exist
91
+ # b) be a directory
92
+ # c) be readable by the current user
93
+ #
94
+ # @param [String] path
95
+ # @return [true|false]
96
+ def self.dir_readable?(path)
97
+ pathname = PathBuilder.to_pathname(path)
98
+ pathname.exist? && pathname.directory? && pathname.readable?
99
+ end
100
+
101
+ # Returns whether a file is readable or not. In order to be readable, the file must
102
+ # a) exist
103
+ # b) be a file
104
+ # c) be readable by the current user
105
+ #
106
+ def self.file_readable?(path)
107
+ pathname = PathBuilder.to_pathname(path)
108
+ pathname.exist? && pathname.file? && pathname.readable?
109
+ end
110
+
111
+ # Returns a list of relative file paths, sorted by path (longest first).
112
+ #
113
+ # @param [String] base_dir
114
+ # @param [String] glob
115
+ # return [Array]
116
+ def self.files_by_longest_path(base_dir, glob)
117
+ Dir.chdir(base_dir) do
118
+ # Get a list of matching files
119
+ files = Pathname.glob(glob).map!(&:to_s)
120
+ # Sort by longest path.
121
+ files.sort! { |first, second| second.split('/').length <=> first.split('/').length }
122
+ files
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,47 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 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
+ # Utility functions for routing.
22
+ module Routing
23
+ # Extracts the target class, target params and target action from a list of valid routes.
24
+ #
25
+ # @param [HttpRouter::Response] response
26
+ # @return [Array]
27
+ def self.extract_target(response)
28
+ target_klass = response.route.dest
29
+ params = response.param_values.first.reject(&:empty?)
30
+ action = params.empty? ? target_klass.settings.fetch(:default_action) : params.shift.to_sym
31
+ [target_klass, params, action]
32
+ end
33
+
34
+ # Updates the PATH_INFO environment variable.
35
+ #
36
+ # @param [Hash] env
37
+ # @param [Fixnum] num_params
38
+ # @return [nil]
39
+ def self.update_path_info(env, num_params)
40
+ env['PATH_INFO'] = env['PATH_INFO']
41
+ .split('/')[0...-num_params]
42
+ .join('/') unless num_params.zero?
43
+ nil
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,117 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 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 'tilt'
20
+
21
+ module Racket
22
+ module Utils
23
+ # Utility functions for views.
24
+ module Views
25
+ # Calls a template proc. Depending on how many parameters the template proc takes, different
26
+ # types of information will be passed to the proc.
27
+ # If the proc takes zero parameters, no information will be passed.
28
+ # If the proc takes one parameter, it will contain the current action.
29
+ # If the proc takes two parameters, they will contain the current action and the current
30
+ # params.
31
+ # If the proc takes three parameters, they will contain the current action, the current params
32
+ # and the current request.
33
+ #
34
+ # @param [Proc] proc
35
+ # @param [Racket::Controller] controller
36
+ # @return [String]
37
+ def self.call_template_proc(proc, controller)
38
+ possible_proc_args =
39
+ [controller.racket.action, controller.racket.params, controller.request]
40
+ proc_args = []
41
+ 1.upto(proc.arity) { proc_args.push(possible_proc_args.shift) }
42
+ proc.call(*proc_args).to_s
43
+ end
44
+
45
+ # Returns the "url path" that should be used when searching for templates.
46
+ #
47
+ # @param [Racket::Controller] controller
48
+ # @return [String]
49
+ def self.get_template_path(controller)
50
+ template_path =
51
+ [Application.get_route(controller.class), controller.racket.action].join('/')
52
+ template_path = template_path[1..-1] if template_path.start_with?('//')
53
+ template_path
54
+ end
55
+
56
+ # Locates a file in the filesystem matching an URL path. If there exists a matching file, the
57
+ # path to it is returned. If there is no matching file, +nil+ is returned.
58
+ #
59
+ # @param [String] base_path
60
+ # @param [String] path
61
+ # @return [String|nil]
62
+ def self.lookup_template(base_path, path)
63
+ file_path = File.join(base_path, path)
64
+ action = File.basename(file_path)
65
+ file_path = File.dirname(file_path)
66
+ return nil unless Utils.dir_readable?(file_path)
67
+ matcher = File.extname(action).empty? ? "#{action}.*" : action
68
+ Dir.chdir(file_path) do
69
+ files = Pathname.glob(matcher)
70
+ return nil if files.empty?
71
+ final_path = File.join(file_path, files.first.to_s)
72
+ Utils.file_readable?(final_path) ? final_path : nil
73
+ end
74
+ end
75
+
76
+ # Locates a file in the filesystem matching an URL path. If there exists a matching file, the
77
+ # path to it is returned. If there is no matching file and +default_template+ is a String or
78
+ # a Symbol, another lookup will be performed using +default_template+. If +default_template+
79
+ # is a Proc or nil, +default_template+ will be used as is instead.
80
+ #
81
+ # @param [String] base_dir
82
+ # @param [String] path
83
+ # @param [String|Symbol|Proc|nil] default_template
84
+ # @return [String|Proc|nil]
85
+ def self.lookup_template_with_default(base_dir, path, default_template)
86
+ template = lookup_template(base_dir, path)
87
+ if !template && (default_template.is_a?(String) || default_template.is_a?(Symbol))
88
+ path = File.join(File.dirname(path), default_template)
89
+ template = lookup_template(base_dir, path)
90
+ end
91
+ template || default_template
92
+ end
93
+
94
+ # Renders a template/layout combo using Tilt and returns it as a string.
95
+ #
96
+ # @param [Racket::Controller] controller
97
+ # @param [String] view
98
+ # @param [String|nil] layout
99
+ # @return [String]
100
+ def self.render_template(controller, view, layout)
101
+ output = Tilt.new(view).render(controller)
102
+ output = Tilt.new(layout).render(controller) { output } if layout
103
+ output
104
+ end
105
+
106
+ # Sends response to client.
107
+ #
108
+ # @param [Racket::Response] response
109
+ # @param [String] output
110
+ # @return nil
111
+ def self.send_response(response, output)
112
+ response.write(output)
113
+ response.finish
114
+ end
115
+ end
116
+ end
117
+ end
@@ -25,7 +25,7 @@ module Racket
25
25
  # Minor version
26
26
  MINOR = 3
27
27
  # Teeny version
28
- TEENY = 0
28
+ TEENY = 1
29
29
  # Is it a prerelease?
30
30
  PRERELEASE = false
31
31
 
@@ -16,11 +16,12 @@
16
16
  # You should have received a copy of the GNU Affero General Public License
17
17
  # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
18
 
19
- require 'tilt'
20
-
21
19
  module Racket
22
20
  # Handles rendering in Racket applications.
23
21
  class ViewManager
22
+ # Struct for holding view parameters.
23
+ ViewParams = Struct.new(:controller, :path, :type)
24
+
24
25
  attr_reader :layout_cache
25
26
  attr_reader :view_cache
26
27
 
@@ -37,127 +38,59 @@ module Racket
37
38
  # @param [Controller] controller
38
39
  # @return [Hash]
39
40
  def render(controller)
40
- template_path = get_template_path(controller)
41
- view = get_template(template_path, controller, :view)
42
- layout = view ? get_template(template_path, controller, :layout) : nil
43
- output = view ? render_template(controller, view, layout) : controller.racket.action_result
44
- controller.response.write(output)
45
- controller.response.finish
41
+ template_path = Utils.get_template_path(controller)
42
+ view = get_template(ViewParams.new(controller, template_path, :view))
43
+ layout = view ? get_template(ViewParams.new(controller, template_path, :layout)) : nil
44
+ if view then output = Utils.render_template(controller, view, layout)
45
+ else output = controller.racket.action_result
46
+ end
47
+ Utils.send_response(controller.response, output)
46
48
  end
47
49
 
48
50
  private
49
51
 
50
- # Calls a template proc. Depending on how many parameters the template proc takes, different
51
- # types of information will be passed to the proc.
52
- # If the proc takes zero parameters, no information will be passed.
53
- # If the proc takes one parameter, it will contain the current action.
54
- # If the proc takes two parameters, they will contain the current action and the current params.
55
- # If the proc takes three parameters, they will contain the current action, the current params
56
- # and the current request.
57
- #
58
- # @param [Proc] proc
59
- # @param [Racket::Controller] controller
60
- # @return [String]
61
- def call_template_proc(proc, controller)
62
- possible_proc_args = [controller.racket.action, controller.racket.params, controller.request]
63
- proc_args = []
64
- 1.upto(proc.arity) { proc_args.push(possible_proc_args.shift) }
65
- proc.call(*proc_args).to_s
66
- end
67
-
68
52
  # Returns a cached template. If the template has not been cached yet, this method will run a
69
53
  # lookup against the provided parameters.
70
54
  #
71
- # @param [String] path
72
- # @param [Racket::Controller] controller
73
- # @param [Symbol] type
55
+ # @param [ViewParams] view_params
74
56
  # @return [String|Proc|nil]
75
- def ensure_in_cache(path, controller, type)
57
+ def ensure_in_cache(view_params)
58
+ _, path, type = view_params.to_a
76
59
  store = instance_variable_get("@#{type}_cache".to_sym)
77
60
  return store[path] if store.key?(path)
78
- store_in_cache(store, path, controller, type)
61
+ store_in_cache(store, view_params)
79
62
  end
80
63
 
81
64
  # Tries to locate a template matching +path+ in the file system and returns the path if a
82
65
  # matching file is found. If no matching file is found, +nil+ is returned. The result is cached,
83
66
  # meaning that the filesystem lookup for a specific path will only happen once.
84
67
  #
85
- # @param [String] path
86
- # @param [Racket::Controller] controller
87
- # @param [Symbol] type
68
+ # @param [ViewParams] view_params
88
69
  # @return [String|nil]
89
- def get_template(path, controller, type)
90
- template = ensure_in_cache(path, controller, type)
70
+ def get_template(view_params)
71
+ template = ensure_in_cache(view_params)
91
72
  # If template is a Proc, call it
92
- template =
93
- lookup_template(
94
- instance_variable_get("@#{type}_base_dir".to_sym),
95
- [File.dirname(path), call_template_proc(template, controller)].join('/')
96
- ) if template.is_a?(Proc)
97
- template
98
- end
99
-
100
- # Returns the "url path" that should be used when searching for templates.
101
- #
102
- # @param [Racket::Controller] controller
103
- # @return [String]
104
- def get_template_path(controller)
105
- template_path = [Application.get_route(controller.class), controller.racket.action].join('/')
106
- template_path = template_path[1..-1] if template_path.start_with?('//')
107
- template_path
108
- end
109
-
110
- def lookup_default_template(base_path, path, default)
111
- return lookup_template(base_path, File.join(path, default.to_s)) if
112
- default.is_a?(String) || default.is_a?(Symbol)
113
- default
114
- end
115
-
116
- # Locates a file in the filesystem matching an URL path. If there exists a matching file, the
117
- # path to it is returned. If there is no matching file, +nil+ is returned.
118
- #
119
- # @param [String] base_path
120
- # @param [String] path
121
- # @return [String|nil]
122
- def lookup_template(base_path, path)
123
- file_path = File.join(base_path, path)
124
- action = File.basename(file_path)
125
- file_path = File.dirname(file_path)
126
- return nil unless Utils.dir_readable?(file_path)
127
- matcher = File.extname(action).empty? ? "#{action}.*" : action
128
- Dir.chdir(file_path) do
129
- files = Pathname.glob(matcher)
130
- return nil if files.empty?
131
- final_path = File.join(file_path, files.first.to_s)
132
- Utils.file_readable?(final_path) ? final_path : nil
73
+ if template.is_a?(Proc)
74
+ controller, path, type = view_params.to_a
75
+ template =
76
+ Utils.lookup_template(
77
+ instance_variable_get("@#{type}_base_dir".to_sym),
78
+ [File.dirname(path), Utils.call_template_proc(template, controller)].join('/')
79
+ )
133
80
  end
134
- end
135
-
136
- # Renders a template/layout combo using Tilt and returns it as a string.
137
- #
138
- # @param [Racket::Controller] controller
139
- # @param [String] view
140
- # @param [String|nil] layout
141
- # @return [String]
142
- def render_template(controller, view, layout)
143
- output = Tilt.new(view).render(controller)
144
- output = Tilt.new(layout).render(controller) { output } if layout
145
- output
81
+ template
146
82
  end
147
83
 
148
84
  # Stores the location of a template (not its contents) in the cache.
149
85
  #
150
86
  # @param [Object] store Where to store the location
151
- # @param [String] path
152
- # @param [Racket::Controller] controller
153
- # @param [Symbol] type
87
+ # @param [ViewParams] view_params
154
88
  # @return [String|Proc|nil]
155
- def store_in_cache(store, path, controller, type)
89
+ def store_in_cache(store, view_params)
90
+ controller, path, type = view_params.to_a
156
91
  base_dir = instance_variable_get("@#{type}_base_dir".to_sym)
157
92
  default_template = controller.settings.fetch("default_#{type}".to_sym)
158
- template = lookup_template(base_dir, path)
159
- template =
160
- lookup_default_template(base_dir, File.dirname(path), default_template) unless template
93
+ template = Utils.lookup_template_with_default(base_dir, path, default_template)
161
94
  Application.inform_dev(
162
95
  "Using #{type} #{template.inspect} for #{controller.class}.#{controller.racket.action}."
163
96
  )
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: racket-mvc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Olsson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-03 00:00:00.000000000 Z
11
+ date: 2015-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http_router
@@ -160,6 +160,10 @@ files:
160
160
  - lib/racket/settings/base.rb
161
161
  - lib/racket/settings/controller.rb
162
162
  - lib/racket/utils.rb
163
+ - lib/racket/utils/exceptions.rb
164
+ - lib/racket/utils/file_system.rb
165
+ - lib/racket/utils/routing.rb
166
+ - lib/racket/utils/views.rb
163
167
  - lib/racket/version.rb
164
168
  - lib/racket/view_manager.rb
165
169
  - rake/utils.rb