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 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