racket-mvc 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|