gin 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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