gin 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -45,9 +45,9 @@ class Gin::Request < Rack::Request
45
45
 
46
46
  private
47
47
 
48
- M_BOOLEAN = /^true|false$/ #:nodoc:
49
- M_FLOAT = /^\d+\.\d+$/ #:nodoc:
50
- M_INTEGER = /^\d+$/ #:nodoc:
48
+ M_BOOLEAN = /^true|false$/ #:nodoc:
49
+ M_FLOAT = /^-?([^0]\d+|\d)\.\d+$/ #:nodoc:
50
+ M_INTEGER = /^-?([^0]\d+|\d)$/ #:nodoc:
51
51
 
52
52
  ##
53
53
  # Enable string or symbol key access to the nested params hash.
@@ -56,7 +56,7 @@ class Gin::Request < Rack::Request
56
56
  def process_params object
57
57
  case object
58
58
  when Hash
59
- new_hash = indifferent_hash
59
+ new_hash = Gin::StrictHash.new
60
60
  object.each { |key, value| new_hash[key] = process_params(value) }
61
61
  new_hash
62
62
  when Array
@@ -71,12 +71,4 @@ class Gin::Request < Rack::Request
71
71
  object
72
72
  end
73
73
  end
74
-
75
-
76
- ##
77
- # Creates a Hash with indifferent access.
78
-
79
- def indifferent_hash
80
- Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
81
- end
82
74
  end
@@ -8,13 +8,15 @@ class Gin::Response < Rack::Response
8
8
 
9
9
  def body= value
10
10
  value = value.body while Rack::Response === value
11
- @body = value.respond_to?(:each) ? value : [value.to_s]
11
+ @body = value.respond_to?(:each) && !value.is_a?(String) ?
12
+ value : [value.to_s]
12
13
  @body
13
14
  end
14
15
 
15
16
 
16
17
  def finish
17
18
  body_out = body
19
+ body_out = [body_out] if String === body_out
18
20
  header[CNT_TYPE] ||= 'text/html;charset=UTF-8'
19
21
 
20
22
  if NO_HEADER_STATUSES.include?(status.to_i)
@@ -43,7 +45,7 @@ class Gin::Response < Rack::Response
43
45
  l + Rack::Utils.bytesize(p)
44
46
  end.to_s
45
47
  when File
46
- header[CNT_LENGTH] = body.size.to_s
48
+ header[CNT_LENGTH] = File.size(body.path).to_s
47
49
  end
48
50
  end
49
51
  end
@@ -28,15 +28,8 @@ class Gin::Router
28
28
  # Used by the Gin::App.mount DSL.
29
29
 
30
30
  class Mount
31
- DEFAULT_ACTION_MAP = {
32
- :index => %w{get /},
33
- :show => %w{get /:id},
34
- :new => %w{get /new},
35
- :create => %w{post /:id},
36
- :edit => %w{get /:id/edit},
37
- :update => %w{put /:id},
38
- :destroy => %w{delete /:id}
39
- }
31
+
32
+ PATH_NAME_MATCHER = %r{\A[-+\w/]+\Z}m #:nodoc:
40
33
 
41
34
  VERBS = %w{get post put delete head options trace}
42
35
 
@@ -45,28 +38,50 @@ class Gin::Router
45
38
  end
46
39
 
47
40
 
48
- def initialize ctrl, base_path, &block
41
+ def initialize ctrl, base_path=nil, &block
42
+ raise ArgumentError,
43
+ "#{ctrl.inspect} must respond to `call'" unless ctrl.respond_to?(:call)
44
+
45
+ base_path ||= ctrl.controller_name if Gin::Mountable === ctrl
46
+
47
+ if !base_path
48
+ uname = Gin.underscore(ctrl.to_s)
49
+ base_path = File.join("", uname) if uname =~ PATH_NAME_MATCHER
50
+ end
51
+
52
+ raise ArgumentError,
53
+ "Could not deduce base path from #{ctrl.inspect}" unless base_path
54
+
49
55
  @ctrl = ctrl
50
56
  @routes = []
51
57
  @actions = []
52
58
  @base_path = base_path
53
59
 
54
- instance_eval(&block) if block_given?
55
- defaults unless block_given?
60
+ if block_given?
61
+ instance_eval(&block)
62
+ return
63
+ end
64
+
65
+ if !(Gin::Mountable === @ctrl)
66
+ any :call, "/"
67
+ else
68
+ defaults
69
+ end
56
70
  end
57
71
 
58
72
 
59
73
  ##
60
74
  # Create and add default restful routes if they aren't taken already.
61
75
 
62
- def defaults default_verb=nil
63
- default_verb = default_verb || 'get'
76
+ def defaults
77
+ raise TypeError,
78
+ "#{@ctrl.inspect} must inherit Gin::Mountable to support defaults" unless
79
+ Gin::Mountable === @ctrl
64
80
 
65
81
  (@ctrl.actions - @actions).each do |action|
66
- verb, path = DEFAULT_ACTION_MAP[action]
67
- verb, path = [default_verb, "/#{action}"] if verb.nil?
82
+ verb, path = @ctrl.default_route_for(action)
68
83
 
69
- add(verb, action, path) unless verb.nil? ||
84
+ add(verb, action, path) unless
70
85
  @routes.any?{|route| route === [verb, path] }
71
86
  end
72
87
  end
@@ -87,15 +102,25 @@ class Gin::Router
87
102
  path = args.shift if String === args[0]
88
103
  name = args.shift.to_sym if args[0]
89
104
 
90
- path ||= action.to_s
91
- name ||= :"#{action}_#{@ctrl.controller_name}"
105
+ if Gin::Mountable === @ctrl
106
+ path ||= @ctrl.default_route_for(action)[1]
107
+ name ||= @ctrl.route_name_for(action)
108
+ elsif !(path && name) && !action.is_a?(Array) && !action.is_a?(Hash) &&
109
+ action.to_s =~ PATH_NAME_MATCHER
110
+ path ||= action.to_s
111
+ uname = Gin.underscore(@ctrl.to_s).sub(%r{^.*/},'')
112
+ name = "#{action}_#{uname}" if !name && uname =~ PATH_NAME_MATCHER
113
+ end
114
+
115
+ raise ArgumentError, "No path could be determined for target %s %s" %
116
+ [@ctrl.inspect, action.inspect] unless path
92
117
 
93
118
  path = File.join(@base_path, path)
94
119
  target = [@ctrl, action]
95
120
 
96
121
  route = Route.new(verb, path, target, name)
97
- @routes << route
98
- @actions << action.to_sym
122
+ @routes << route
123
+ @actions << action
99
124
  end
100
125
 
101
126
 
@@ -117,6 +142,8 @@ class Gin::Router
117
142
  # #=> "/foo/123.json"
118
143
 
119
144
  class Route
145
+ include Gin::Constants
146
+
120
147
  # Parsed out path param key names.
121
148
  attr_reader :param_keys
122
149
 
@@ -132,24 +159,28 @@ class Gin::Router
132
159
  # Arbitrary name of the route.
133
160
  attr_reader :name
134
161
 
162
+ # HTTP verb used by the route.
163
+ attr_reader :verb
164
+
135
165
  SEP = "/" # :nodoc:
136
166
  VAR_MATCHER = /:(\w+)/ # :nodoc:
137
167
  PARAM_MATCHER = "(.*?)" # :nodoc:
138
168
 
139
169
 
140
- def initialize verb, path, target, name
170
+ def initialize verb, path, target=[], name=nil
141
171
  @target = target
142
- @name = name
172
+ @name = name.to_sym if name
143
173
  build verb, path
144
174
  end
145
175
 
146
176
 
147
177
  ##
148
178
  # Render a route path by giving it inline (and other) params.
149
- # route.to_path id: 123, format: "json", foo: "bar"
179
+ # route.to_path :id => 123, :format => "json", :foo => "bar"
150
180
  # #=> "/foo/123.json?foo=bar"
151
181
 
152
182
  def to_path params={}
183
+ params ||= {}
153
184
  rendered_path = @path.dup
154
185
  rendered_path = rendered_path % @param_keys.map do |k|
155
186
  val = params.delete(k) || params.delete(k.to_sym)
@@ -162,6 +193,27 @@ class Gin::Router
162
193
  end
163
194
 
164
195
 
196
+ ##
197
+ # Creates a Rack env hash with the given params and headers.
198
+
199
+ def to_env params={}, headers={}
200
+ headers ||= {}
201
+ params ||= {}
202
+
203
+ path_info, query_string = to_path(params).split('?', 2)
204
+
205
+ env = headers.merge(
206
+ PATH_INFO => path_info,
207
+ REQ_METHOD => @verb,
208
+ QUERY_STRING => query_string
209
+ )
210
+
211
+ # TODO: implement multipart streams for requests that support a body
212
+ env[RACK_INPUT] ||= ''
213
+ env
214
+ end
215
+
216
+
165
217
  ##
166
218
  # Returns true if the argument matches the route_id.
167
219
  # The route id is an array with verb and original path.
@@ -174,26 +226,28 @@ class Gin::Router
174
226
  private
175
227
 
176
228
  def build verb, path
177
- verb = verb.to_s.downcase
229
+ @verb = verb.to_s.upcase
178
230
 
179
231
  @path = ""
180
232
  @param_keys = []
181
233
  @match_keys = []
182
- @route_id = [verb, path]
234
+ @route_id = [@verb, path]
183
235
 
184
- parts = [verb].concat path.split(SEP)
236
+ parts = [@verb].concat path.split(SEP)
185
237
 
186
238
  parts.each_with_index do |p, i|
187
239
  next if p.empty?
188
240
 
241
+ is_param = false
189
242
  part = Regexp.escape(p).gsub!(VAR_MATCHER) do
190
243
  @param_keys << $1
244
+ is_param = true
191
245
  PARAM_MATCHER
192
246
  end
193
247
 
194
248
  if part == PARAM_MATCHER
195
249
  part = "%s"
196
- elsif $1
250
+ elsif is_param
197
251
  part = /^#{part}$/
198
252
  else
199
253
  part = p
@@ -243,8 +297,6 @@ class Gin::Router
243
297
  # Used by Gin::App.mount.
244
298
 
245
299
  def add ctrl, base_path=nil, &block
246
- base_path ||= ctrl.controller_name
247
-
248
300
  mount = Mount.new(ctrl, base_path, &block)
249
301
 
250
302
  mount.each_route do |route|
@@ -284,7 +336,8 @@ class Gin::Router
284
336
  ##
285
337
  # Get the path to the given Controller and action combo or route name,
286
338
  # provided with the needed params. Routes with missing path params will raise
287
- # MissingParamError. Returns a String starting with "/".
339
+ # MissingParamError. Missing routes will raise a RouterError.
340
+ # Returns a String starting with "/".
288
341
  #
289
342
  # path_to FooController, :show, id: 123
290
343
  # #=> "/foo/123"
@@ -296,13 +349,32 @@ class Gin::Router
296
349
  # #=> "/foo/123?more=true"
297
350
 
298
351
  def path_to *args
352
+ params = args.pop.dup if Hash === args.last
353
+ route = route_to(*args)
354
+ route.to_path(params)
355
+ end
356
+
357
+
358
+ ##
359
+ # Get the route object to the given Controller and action combo or route name.
360
+ # MissingParamError. Returns a Gin::Router::Route instance.
361
+ # Raises a RouterError if no route can be found.
362
+ #
363
+ # route_to FooController, :show
364
+ # route_to :show_foo
365
+
366
+ def route_to *args
299
367
  key = Class === args[0] ? args.slice!(0..1) : args.shift
300
368
  route = @routes_lookup[key]
301
- raise PathArgumentError, "No route for #{Array(key).join("#")}" unless route
302
369
 
303
- params = (args.pop || {}).dup
370
+ unless route
371
+ name = Array === key && Gin::Mountable === key[0] ?
372
+ key[0].display_name(key[1]) : name.inspect
304
373
 
305
- route.to_path(params)
374
+ raise Gin::RouterError, "No route for #{name}"
375
+ end
376
+
377
+ route
306
378
  end
307
379
 
308
380
 
@@ -317,10 +389,11 @@ class Gin::Router
317
389
 
318
390
  def resources_for http_verb, path
319
391
  param_vals = []
320
- curr_node = @routes_tree[http_verb.to_s.downcase]
392
+ curr_node = @routes_tree[http_verb.to_s.upcase]
321
393
  return unless curr_node
322
394
 
323
- path.scan(%r{/([^/]+|$)}) do |(key)|
395
+ path.scan(%r{/([^/]+|$)}) do |matches|
396
+ key = matches[0]
324
397
  next if key.empty?
325
398
 
326
399
  if curr_node[key]
@@ -345,6 +418,6 @@ class Gin::Router
345
418
  path_params = param_vals.empty? ?
346
419
  {} : route.param_keys.inject({}){|h, name| h[name] = param_vals.shift; h}
347
420
 
348
- [*route.target, path_params]
421
+ [route.target, path_params]
349
422
  end
350
423
  end
@@ -0,0 +1,33 @@
1
+ class Gin::StrictHash < Hash
2
+
3
+ def [] key
4
+ super key.to_s
5
+ end
6
+
7
+ def []= key, value
8
+ super key.to_s, value
9
+ end
10
+
11
+ def delete key
12
+ super key.to_s
13
+ end
14
+
15
+ def has_key? key
16
+ super key.to_s
17
+ end
18
+
19
+ alias key? has_key?
20
+
21
+ def merge hash
22
+ return super if hash.class == self.class
23
+ new_hash = self.dup
24
+ new_hash.merge!(hash)
25
+ new_hash
26
+ end
27
+
28
+ def merge! hash
29
+ return super if hash.class == self.class
30
+ hash.each{|k,v| self[k] = v}
31
+ self
32
+ end
33
+ end
@@ -137,7 +137,7 @@ module Gin::Test::Assertions
137
137
  # assert_select '**/address/domestic=YES/../value'
138
138
 
139
139
  def assert_data path, opts={}, msg=nil
140
- assert_select path, opts.merge(selector: :rb_path), msg
140
+ assert_select path, opts.merge(:selector => :rb_path), msg
141
141
  end
142
142
 
143
143
 
@@ -153,7 +153,7 @@ module Gin::Test::Assertions
153
153
  # assert_select '.address[domestic=Yes]'
154
154
 
155
155
  def assert_css path, opts={}, msg=nil
156
- assert_select path, opts.merge(selector: :css), msg
156
+ assert_select path, opts.merge(:selector => :css), msg
157
157
  end
158
158
 
159
159
 
@@ -169,7 +169,7 @@ module Gin::Test::Assertions
169
169
  # assert_select './/address[@domestic=Yes]'
170
170
 
171
171
  def assert_xpath path, opts={}, msg=nil
172
- assert_select path, opts.merge(selector: :xpath), msg
172
+ assert_select path, opts.merge(:selector => :xpath), msg
173
173
  end
174
174
 
175
175
 
@@ -243,7 +243,7 @@ module Gin::Test::Assertions
243
243
  # controller and action.
244
244
 
245
245
  def assert_route verb, path, exp_ctrl, exp_action, msg=nil
246
- ctrl, action, = app.router.resources_for(verb, path)
246
+ ctrl, action, = Array(app.router.resources_for(verb, path))[0]
247
247
  expected = "#{exp_ctrl}##{exp_action}"
248
248
  real = "#{ctrl}##{action}"
249
249
  real_msg = ctrl && action ? "got #{real}" : "doesn't exist"
@@ -463,6 +463,10 @@ Run the following command and try again: gem install #{gemname}"
463
463
  env['REQUEST_METHOD'] = verb.to_s.upcase
464
464
  env['QUERY_STRING'] = query
465
465
  env['PATH_INFO'] = path
466
+
467
+ host, port = (app.hostname || "localhost").split(":")
468
+ env['SERVER_NAME'] = host
469
+ env['SERVER_PORT'] = port || '80'
466
470
  env.merge! headers
467
471
 
468
472
  @rack_response = app.call(env)
@@ -0,0 +1,49 @@
1
+ require 'fileutils'
2
+
3
+ class Gin::Worker
4
+
5
+ def initialize pidfile, &block
6
+ @pidfile = pidfile
7
+ @pid = nil
8
+ @block = block
9
+ end
10
+
11
+
12
+ def run
13
+ FileUtils.touch(@pidfile)
14
+ f = File.open(@pidfile, 'r+')
15
+ return unless f.flock(File::LOCK_EX | File::LOCK_NB)
16
+
17
+ if other_pid = f.read
18
+ running = Process.kill(0, other_pid) rescue false
19
+ return if running
20
+ end
21
+
22
+ @pid = fork do
23
+ begin
24
+ f.truncate(File.size(f.path))
25
+ f.write Process.pid.to_s
26
+ f.flush
27
+ @block.call
28
+ rescue Interrupt
29
+ $stderr.puts "Worker Interrupted: #{@pidfile} (#{Process.pid})"
30
+ ensure
31
+ f.close unless f.closed?
32
+ File.delete(f.path)
33
+ end
34
+ end
35
+
36
+ ensure
37
+ f.close if f && !f.closed?
38
+ end
39
+
40
+
41
+ def kill sig="INT"
42
+ Process.kill(sig, @pid) if @pid
43
+ end
44
+
45
+
46
+ def wait
47
+ Process.waitpid(@pid) if @pid
48
+ end
49
+ end