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 +4 -4
- data/lib/racket/application.rb +23 -22
- data/lib/racket/controller.rb +3 -5
- data/lib/racket/current.rb +1 -1
- data/lib/racket/helpers/file.rb +56 -30
- data/lib/racket/router.rb +6 -29
- data/lib/racket/settings/application.rb +2 -1
- data/lib/racket/settings/base.rb +2 -3
- data/lib/racket/settings/controller.rb +3 -3
- data/lib/racket/utils.rb +16 -71
- data/lib/racket/utils/exceptions.rb +68 -0
- data/lib/racket/utils/file_system.rb +127 -0
- data/lib/racket/utils/routing.rb +47 -0
- data/lib/racket/utils/views.rb +117 -0
- data/lib/racket/version.rb +1 -1
- data/lib/racket/view_manager.rb +29 -96
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 395ab3d4006c31ae432dfa280c626166fc41a061
|
4
|
+
data.tar.gz: 0e22dba439da9506e191c0a253f49b36e6f0d050
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8cf2e4fdcb7814d204a72ff06f6774088f7903afce071039e75b69c83340767f2f6dd4b91c60bd0a197e89cf78b85dc95b5a33784423a8cfc5a257fdb3d46ac
|
7
|
+
data.tar.gz: 02af1c5652968eb149b6945d81232eda5b5291e9e79eae9aa1f4f0bb5ef649a22bfbe5c618e8d0d2112731b361229ee3fcba2853bbedad26e4486ef1d4a0f418
|
data/lib/racket/application.rb
CHANGED
@@ -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
|
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
|
-
@
|
138
|
-
|
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
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
|
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, :
|
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
|
data/lib/racket/controller.rb
CHANGED
@@ -88,11 +88,9 @@ module Racket
|
|
88
88
|
# namespace.
|
89
89
|
def self.helper(*helpers)
|
90
90
|
helper_modules = {}
|
91
|
-
|
92
|
-
|
93
|
-
|
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)
|
data/lib/racket/current.rb
CHANGED
@@ -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
|
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 }
|
data/lib/racket/helpers/file.rb
CHANGED
@@ -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
|
-
|
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}"
|
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)
|
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
|
-
|
121
|
+
matching_route = @router.recognize(env).first
|
122
122
|
# Exit early if no controller is responsible for the route
|
123
|
-
return nil
|
123
|
+
return nil unless matching_route
|
124
124
|
# Some controller is claiming to be responsible for the route
|
125
|
-
result = extract_target(
|
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
|
-
|
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
|
data/lib/racket/settings/base.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
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 [
|
59
|
-
# @return [
|
60
|
-
def self.
|
61
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
data/lib/racket/version.rb
CHANGED
data/lib/racket/view_manager.rb
CHANGED
@@ -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(
|
42
|
-
layout = view ? get_template(
|
43
|
-
output =
|
44
|
-
controller.
|
45
|
-
|
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 [
|
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(
|
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,
|
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 [
|
86
|
-
# @param [Racket::Controller] controller
|
87
|
-
# @param [Symbol] type
|
68
|
+
# @param [ViewParams] view_params
|
88
69
|
# @return [String|nil]
|
89
|
-
def get_template(
|
90
|
-
template = ensure_in_cache(
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
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 [
|
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,
|
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 =
|
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.
|
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-
|
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
|