rage-rb 0.2.0 → 0.3.0

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
  SHA256:
3
- metadata.gz: 4570bfdf4f86125be1d6c35b9ccba08a956fe8a20dac392e5ae914db347ad396
4
- data.tar.gz: '019e919339f97e641e65f9aecc91c02740875ebcbe71edf14a07144950e47f07'
3
+ metadata.gz: 7684c979952ee81bee165b9b667dea87199f3fa28dadadc4878b29a12211fdac
4
+ data.tar.gz: 2428443ee2456ef375990decfbf36a2952670ee517ac07cf4c994f5b9b7076f9
5
5
  SHA512:
6
- metadata.gz: 9ad7b89eb46407831ae723c1c15b87ea12b898ae1784989374b07fcffcc3afcff2a237861c94565afe4c5f486b99edb15022b539ab9cad142778e4b2718b92ea
7
- data.tar.gz: 471cb6bcbf294d9a4eb3e5fd69827f26661effafc1df20447b8e7eddd0816aff411266d22104988978c46318dc08c4fa753f1e1a4fa1327268a5f24d8851ac5a
6
+ metadata.gz: f59da6b77e69fa2b89761b30a497b4c0fa8f02cbabeed65c953b0f5e807b349e5b305bf31564bcc3eaeed4957215766e8781af5a5b8bbd9754e0461fa18f7b87
7
+ data.tar.gz: 733a15ef91abcbfc079a3bbbb83f82686038059b3c518c1c0a279e8000ad885c65cc5019cc691e2731fa1e59ca1f62e8fe95c8f454837ed0bdc65e181157ffc7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2023-10-08
4
+
5
+ ### Added
6
+
7
+ - CLI `routes` task.
8
+ - CLI `console` task.
9
+ - `:if` and `:unless` options in `before_action`.
10
+ - Allow to set response headers.
11
+ - Block version of `before_action`.
12
+
3
13
  ## [0.2.0] - 2023-09-27
4
14
 
5
15
  ### Added
data/Gemfile CHANGED
@@ -11,6 +11,3 @@ gem "rspec", "~> 3.0"
11
11
 
12
12
  gem "pg"
13
13
  gem "mysql2"
14
-
15
- gem "benchmark-ips"
16
- gem "mustache"
data/README.md CHANGED
@@ -142,7 +142,7 @@ end
142
142
  Version | Changes
143
143
  ------- |------------
144
144
  0.2 :white_check_mark: | ~~Gem configuration by env.<br>Add `skip_before_action`.<br>Add `rescue_from`.<br>Router updates:<br>&emsp;• make the `root` helper work correctly with `scope`;<br>&emsp;• support the `defaults` option;~~
145
- 0.3 | CLI updates:<br>&emsp;• `routes` task;<br>&emsp;• `console` task;<br>Support the `:if` and `:unless` options in `before_action`.<br>Allow to set response headers.
145
+ 0.3 :white_check_mark: | ~~CLI updates:<br>&emsp;• `routes` task;<br>&emsp;• `console` task;<br>Support the `:if` and `:unless` options in `before_action`.<br>Allow to set response headers.~~
146
146
  0.4 | Expose the `params` object.<br>Support header authentication with `authenticate_with_http_token`.<br>Router updates:<br>&emsp;• add the `resources` route helper;<br>&emsp;• add the `namespace` route helper;<br>&emsp;• support regexp constraints;
147
147
  0.5 | Implement Iodine-based equivalent of `ActionController::Live`.<br>Use `ActionDispatch::RemoteIp`.
148
148
  0.6 | Expose the `cookies` object.<br>Expose the `send_data` and `send_file` methods.<br>Support conditional get with `etag` and `last_modified`.
@@ -19,7 +19,7 @@ class Rage::Application
19
19
  end
20
20
 
21
21
  rescue => e
22
- [500, {}, ["#{e.class}:#{e.message}\n\n#{e.backtrace.join("\n")}"]] # TODO: check Rage.env
22
+ [500, {}, ["#{e.class}:#{e.message}\n\n#{e.backtrace.join("\n")}"]]
23
23
 
24
24
  ensure
25
25
  # notify Iodine the request can now be served
data/lib/rage/cli.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  require "thor"
4
3
  require "rage"
4
+ require "irb"
5
5
 
6
6
  module Rage
7
7
  class CLI < Thor
@@ -25,11 +25,81 @@ module Rage
25
25
 
26
26
  ::Iodine.start
27
27
  end
28
+
29
+ desc 'routes', 'List all routes.'
30
+ option :grep, aliases: "-g", desc: "Filter routes by pattern"
31
+ def routes
32
+ # the result would be something like this:
33
+ # Action Verb Path Controller#Action
34
+ # index GET / application#index
35
+
36
+ # load config/application.rb
37
+ environment
38
+
39
+ routes = Rage.__router.routes
40
+
41
+ pattern = options[:grep]
42
+
43
+ if pattern
44
+ routes = routes.select do |route|
45
+ route[:path].match?(pattern) || route[:raw_handler].to_s.match?(pattern) || route[:method].match?(pattern)
46
+ end
47
+ end
48
+
49
+ return puts 'Action Verb Path Controller#Action' if routes.empty?
50
+
51
+ # construct a table
52
+ table = []
53
+
54
+ # longest_path is either the length of the longest path or 5
55
+ longest_path = routes.map { |route| route[:path].length }.max + 3
56
+ longest_path = longest_path > 5 ? longest_path : 5
57
+
58
+ longest_verb = routes.map { |route| route[:method].length }.max + 3
59
+ longest_verb = longest_verb > 4 ? longest_verb : 7
60
+
61
+ # longest_handler is either the length of the longest handler or 7, since DELETE is the longest HTTP method
62
+ longest_handler = routes.map { |route| route[:raw_handler].is_a?(Proc) ? 7 : route[:raw_handler].split('#').last.length }.max + 3
63
+ longest_handler = longest_handler > 7 ? longest_handler : 7
64
+
65
+ # longest_controller is either the length of the longest controller or 12, since Controller#{length} is the longest controller
66
+ longest_controller = routes.map { |route| route[:raw_handler].is_a?(Proc) ? 7 : route[:raw_handler].to_s.length }.max + 3
67
+ longest_controller = longest_controller > 12 ? longest_controller : 12
68
+
69
+ routes.each do |route|
70
+ table << [
71
+ format("%- #{longest_handler}s", route[:raw_handler].is_a?(Proc) ? 'Lambda' : route[:raw_handler].split('#').last),
72
+ format("%- #{longest_verb}s", route[:method]),
73
+ format("%- #{longest_path}s", route[:path]),
74
+ format("%- #{longest_controller}s", route[:raw_handler].is_a?(Proc) ? 'Lambda' : route[:raw_handler])
75
+ ]
76
+ end
77
+
78
+ table.unshift([format("%- #{longest_handler}s", 'Action'), format("%- #{longest_verb}s", 'Verb'), format("%- #{longest_path}s", 'Path'),
79
+ format("%- #{longest_path}s", "Controller#Action\n")])
80
+ # print the table
81
+ table.each do |row|
82
+ # this should be changed to use the main logger when added
83
+ puts row.join
84
+ end
85
+ end
86
+
87
+ desc "c", "Start the app console."
88
+ def console
89
+ environment
90
+ ARGV.clear
91
+ IRB.start
92
+ end
93
+
94
+ private
95
+
96
+ def environment
97
+ require File.expand_path("config/application.rb", Dir.pwd)
98
+ end
28
99
  end
29
100
 
30
101
  class NewAppGenerator < Thor::Group
31
102
  include Thor::Actions
32
-
33
103
  argument :path, type: :string
34
104
 
35
105
  def self.source_root
@@ -12,13 +12,21 @@ class RageController::API
12
12
 
13
13
  before_actions_chunk = if @__before_actions
14
14
  filtered_before_actions = @__before_actions.select do |h|
15
- (h[:only].nil? || h[:only].include?(action)) &&
16
- (h[:except].nil? || !h[:except].include?(action))
15
+ (!h[:only] || h[:only].include?(action)) &&
16
+ (!h[:except] || !h[:except].include?(action))
17
17
  end
18
18
 
19
19
  lines = filtered_before_actions.map do |h|
20
+ condition = if h[:if] && h[:unless]
21
+ "if #{h[:if]} && !#{h[:unless]}"
22
+ elsif h[:if]
23
+ "if #{h[:if]}"
24
+ elsif h[:unless]
25
+ "unless #{h[:unless]}"
26
+ end
27
+
20
28
  <<-RUBY
21
- #{h[:name]}
29
+ #{h[:name]} #{condition}
22
30
  return [@__status, @__headers, @__body] if @__rendered
23
31
  RUBY
24
32
  end
@@ -65,6 +73,16 @@ class RageController::API
65
73
  klass.__rescue_handlers = @__rescue_handlers.freeze
66
74
  end
67
75
 
76
+ # @private
77
+ @@__tmp_name_seed = ("a".."i").to_a.permutation
78
+
79
+ # @private
80
+ # define temporary method based on a block
81
+ def define_tmp_method(block)
82
+ name = @@__tmp_name_seed.next.join
83
+ define_method("__rage_tmp_#{name}", block)
84
+ end
85
+
68
86
  ############
69
87
  #
70
88
  # PUBLIC API
@@ -89,8 +107,7 @@ class RageController::API
89
107
  def rescue_from(*klasses, with: nil, &block)
90
108
  unless with
91
109
  if block_given?
92
- name = ("a".."z").to_a.sample(15).join
93
- with = define_method("__#{name}", &block)
110
+ with = define_tmp_method(block)
94
111
  else
95
112
  raise "No handler provided. Pass the `with` keyword argument or provide a block."
96
113
  end
@@ -107,21 +124,53 @@ class RageController::API
107
124
 
108
125
  # Register a new `before_action` hook. Calls with the same `action_name` will overwrite the previous ones.
109
126
  #
110
- # @param action_name [String] the name of the callback to add
111
- # @param only [Symbol, Array<Symbol>] restrict the callback to run only for specific actions
112
- # @param except [Symbol, Array<Symbol>] restrict the callback to run for all actions except specified
127
+ # @param action_name [String, nil] the name of the callback to add
128
+ # @param [Hash] opts action options
129
+ # @option opts [Symbol, Array<Symbol>] :only restrict the callback to run only for specific actions
130
+ # @option opts [Symbol, Array<Symbol>] :except restrict the callback to run for all actions except specified
131
+ # @option opts [Symbol, Proc] :if only run the callback if the condition is true
132
+ # @option opts [Symbol, Proc] :unless only run the callback if the condition is false
113
133
  # @example
114
134
  # before_action :find_photo, only: :show
115
135
  #
116
136
  # def find_photo
117
137
  # Photo.first
118
138
  # end
119
- def before_action(action_name, only: nil, except: nil)
139
+ # @example
140
+ # before_action :require_user, unless: :logged_in?
141
+ # @example
142
+ # before_action :set_locale, if: -> { params[:locale] != "en-US" }
143
+ # @example
144
+ # before_action do
145
+ # unless logged_in? # would be `controller.send(:logged_in?)` in Rails
146
+ # head :unauthorized
147
+ # end
148
+ # end
149
+ # @note The block form doesn't receive an argument and is executed on the controller level as if it was a regular method.
150
+ def before_action(action_name = nil, **opts, &block)
151
+ if block_given?
152
+ action_name = define_tmp_method(block)
153
+ elsif action_name.nil?
154
+ raise "No handler provided. Pass the `action_name` parameter or provide a block."
155
+ end
156
+
157
+ _only, _except, _if, _unless = opts.values_at(:only, :except, :if, :unless)
158
+
120
159
  if @__before_actions && @__before_actions.frozen?
121
160
  @__before_actions = @__before_actions.dup
122
161
  end
123
162
 
124
- action = { name: action_name, only: only && Array(only), except: except && Array(except) }
163
+ action = {
164
+ name: action_name,
165
+ only: _only && Array(_only),
166
+ except: _except && Array(_except),
167
+ if: _if,
168
+ unless: _unless
169
+ }
170
+
171
+ action[:if] = define_tmp_method(action[:if]) if action[:if].is_a?(Proc)
172
+ action[:unless] = define_tmp_method(action[:unless]) if action[:unless].is_a?(Proc)
173
+
125
174
  if @__before_actions.nil?
126
175
  @__before_actions = [action]
127
176
  elsif i = @__before_actions.find_index { |a| a[:name] == action_name }
@@ -192,7 +241,7 @@ class RageController::API
192
241
  @__body << if json
193
242
  json.is_a?(String) ? json : json.to_json
194
243
  else
195
- __set_header("content-type", "text/plain; charset=utf-8")
244
+ headers["content-type"] = "text/plain; charset=utf-8"
196
245
  plain.to_s
197
246
  end
198
247
 
@@ -225,11 +274,13 @@ class RageController::API
225
274
  end
226
275
  end
227
276
 
228
- private
229
-
230
- # copy-on-write implementation for the headers object
231
- def __set_header(key, value)
232
- @__headers = @__headers.dup if DEFAULT_HEADERS.equal?(@__headers)
233
- @__headers[key] = value
277
+ # Set response headers.
278
+ #
279
+ # @example
280
+ # headers["Content-Type"] = "application/pdf"
281
+ def headers
282
+ # copy-on-write implementation for the headers object
283
+ @__headers = {}.merge!(@__headers) if DEFAULT_HEADERS.equal?(@__headers)
284
+ @__headers
234
285
  end
235
286
  end
@@ -3,6 +3,8 @@
3
3
  require "uri"
4
4
 
5
5
  class Rage::Router::Backend
6
+ attr_reader :routes
7
+
6
8
  OPTIONAL_PARAM_REGEXP = /\/?\(\/?(:\w+)\/?\)/
7
9
  STRING_HANDLER_REGEXP = /^([a-z0-9_\/]+)#([a-z_]+)$/
8
10
 
@@ -13,6 +15,7 @@ class Rage::Router::Backend
13
15
  end
14
16
 
15
17
  def on(method, path, handler, constraints: {}, defaults: nil)
18
+ raw_handler = handler
16
19
  raise "Path could not be empty" if path&.empty?
17
20
 
18
21
  if match_index = (path =~ OPTIONAL_PARAM_REGEXP)
@@ -42,7 +45,7 @@ class Rage::Router::Backend
42
45
  handler = ->(env, _params) { orig_handler.call(env) }
43
46
  end
44
47
 
45
- __on(method, path, handler, constraints, defaults)
48
+ __on(method, path, handler, raw_handler, constraints, defaults)
46
49
  end
47
50
 
48
51
  def lookup(env)
@@ -52,7 +55,7 @@ class Rage::Router::Backend
52
55
 
53
56
  private
54
57
 
55
- def __on(method, path, handler, constraints, defaults)
58
+ def __on(method, path, handler, raw_handler, constraints, defaults)
56
59
  @constrainer.validate_constraints(constraints)
57
60
  # Let the constrainer know if any constraints are being used now
58
61
  @constrainer.note_usage(constraints)
@@ -159,7 +162,7 @@ class Rage::Router::Backend
159
162
  end
160
163
  end
161
164
 
162
- route = { method: method, path: path, pattern: pattern, params: params, constraints: constraints, handler: handler, defaults: defaults }
165
+ route = { method: method, path: path, pattern: pattern, params: params, constraints: constraints, handler: handler, raw_handler: raw_handler, defaults: defaults }
163
166
  @routes << route
164
167
  current_node.add_route(route, @constrainer)
165
168
  end
data/lib/rage/setup.rb CHANGED
@@ -1,7 +1,23 @@
1
1
  Iodine.patch_rack
2
2
 
3
- project_root = Pathname.new(".").expand_path
3
+ require_relative "#{Rage.root}/config/environments/#{Rage.env}"
4
4
 
5
- require_relative "#{project_root}/config/environments/#{Rage.env}"
6
- Dir["#{project_root}/app/**/*.rb"].each { |path| require_relative path }
7
- require_relative "#{project_root}/config/routes"
5
+
6
+ # load application files
7
+ app, bad = Dir["#{Rage.root}/app/**/*.rb"], []
8
+
9
+ loop do
10
+ path = app.shift
11
+ break if path.nil?
12
+
13
+ require_relative path
14
+
15
+ # push the file to the end of the list in case it depends on another file that has not yet been required;
16
+ # re-raise if only errored out files are left
17
+ rescue NameError
18
+ raise if (app - bad).empty?
19
+ app << path
20
+ bad << path
21
+ end
22
+
23
+ require_relative "#{Rage.root}/config/routes"
data/lib/rage/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rage
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/rage-rb.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "rack"
4
4
  require "json"
5
5
  require "iodine"
6
+ require "pathname"
6
7
 
7
8
  module Rage
8
9
  def self.application
@@ -33,6 +34,10 @@ module Rage
33
34
  [:default, Rage.env.to_sym]
34
35
  end
35
36
 
37
+ def self.root
38
+ @root ||= Pathname.new(".").expand_path
39
+ end
40
+
36
41
  module Router
37
42
  module Strategies
38
43
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rage-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Samoilov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-27 00:00:00.000000000 Z
11
+ date: 2023-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor