kenji 1.0 → 1.1.1

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.
data/README.md CHANGED
@@ -81,7 +81,7 @@ have:
81
81
  Getting started with Kenji could not be any easier. All it takes is a few lines
82
82
  and a terminal:
83
83
 
84
- $ gem install kenji # (once kenji is on the rubygems main source)
84
+ $ gem install kenji
85
85
  $ kenji init app_name; cd app_name
86
86
  $ rackup # launch the webserver
87
87
 
@@ -100,6 +100,30 @@ And already, your app is ready to go:
100
100
 
101
101
  ## Changelog
102
102
 
103
+ #### 1.1.1
104
+
105
+ - No longer catching ArgumentErrors when calling the block for a route. This
106
+ fixes a bug where Kenji incorrectly responds with a 404 when the block is
107
+ passed the wrong number of arguments.
108
+ - Fixed logic for matching a pass. the path now must match the pass exactly
109
+ whereas before the pass would match if any subset of the path matched the
110
+ pass.
111
+
112
+ #### 1.1
113
+
114
+ - Kenji::App is a simply wrapper that can and should be used in `config.ru`
115
+ files. It avoids the need to wrap the Kenji initialization in a lambda.
116
+ - Kenji's stderr is now configurable as an option.
117
+ - The new option `catch_exceptions` (default true) configures whether Kenji
118
+ will automatically rescue and log exceptions.
119
+ - The root path argument to initializing Kenji is now deprecated, and replaced
120
+ with the `directory` named option. It is only necessary to set this when not
121
+ using a `root_controller`.
122
+
123
+ #### 1.0
124
+
125
+ - ? TODO: fill me in
126
+
103
127
  #### 0.7
104
128
 
105
129
  - Pass can now contain variables, that get set as @ivars on the controller.
data/lib/kenji/app.rb ADDED
@@ -0,0 +1,25 @@
1
+
2
+ module Kenji
3
+
4
+ # Kenji::App is a simple wrapper class that helps avoid the awkward wrapping
5
+ # in a lambda typically necessary for using Kenji as a Rack app. Instead,
6
+ # simply do the following:
7
+ #
8
+ # run Kenji::App.new(directory: Dir.getwd)
9
+ #
10
+ # Any options passed will also be forwarded to Kenji.
11
+ #
12
+ # Kenji::App has one instance for the app, unlike Kenji::Kenji which has one
13
+ # instance per request.
14
+ #
15
+ class App
16
+
17
+ def initialize(opts={})
18
+ @opts = opts
19
+ end
20
+
21
+ def call(env)
22
+ Kenji.new(env, @opts).call
23
+ end
24
+ end
25
+ end
@@ -6,11 +6,14 @@ module Kenji
6
6
  attr_accessor :kenji
7
7
 
8
8
  # Routes below will accept routes in the format, eg.:
9
+ #
9
10
  # /hello/:id/children
11
+ #
10
12
  # Can contain any number of :id, but must be in their own url segment.
11
- # Colon-prefixed segments become variables, passed onto the given block as arguments.
12
- # The name given to the variable is irrelevant, and is thrown away: /hello/:/children is equivalent to the example above.
13
- # Given block must have correct arity.
13
+ # Colon-prefixed segments become variables, passed onto the given block as
14
+ # arguments. The name given to the variable is irrelevant, and is thrown
15
+ # away: /hello/:/children is equivalent to the example above. Given block
16
+ # must have correct arity.
14
17
 
15
18
  # Route GET
16
19
  def self.get(path, &block)
@@ -46,9 +49,9 @@ module Kenji
46
49
 
47
50
  # Route a given path to the correct block, for any given methods
48
51
  #
49
- # Note: this works by building a tree for the path,
50
- # each node being a path segment or variable segment, and the leaf @action being the block
51
-
52
+ # Note: this works by building a tree for the path, each node being a path
53
+ # segment or variable segment, and the leaf @action being the block
54
+ #
52
55
  def self.route(*methods, path, &block)
53
56
  # bind the block to self as an instance method, so its context is correct
54
57
  define_method(:_tmp_route_action, &block)
@@ -139,16 +142,11 @@ module Kenji
139
142
  node = node[:':'] # attempt to find a variable segment
140
143
  variables << segment # either we've found a variable, or the `unless` below will trigger
141
144
  else
142
- variables << segment # dump the remaining segments if we cannot drill down further
143
- searching = false
145
+ return attempt_fallback(path) # route failed to match variable or segment node so attempt fallback
144
146
  end
145
147
  end
146
148
  if node && action = node[:@action] # the block is stored in the @action leaf
147
- begin
148
- return action.bind(self).call(*variables)
149
- rescue ArgumentError # assuming argument error means route not defined
150
- return attempt_fallback(path) # TODO: might want to check arity instead
151
- end
149
+ return action.bind(self).call(*variables)
152
150
  else # or, fallback if necessary store the block for each method
153
151
  return attempt_fallback(path)
154
152
  end
@@ -197,8 +195,9 @@ module Kenji
197
195
  variables[match[1].to_sym] = e
198
196
  match = true
199
197
  else
200
- match = node.has_key?(e.to_sym)
201
- node = node[e.to_sym] if match
198
+ # if there is no match it should not pass
199
+ break unless match = node.has_key?(e.to_sym)
200
+ node = node[e.to_sym]
202
201
  end
203
202
 
204
203
  break if node[:@controller]
data/lib/kenji/version.rb CHANGED
@@ -1,5 +1,4 @@
1
-
2
1
  module Kenji
3
- VERSION = '1.0'
2
+ VERSION = '1.1.1'
4
3
  end
5
4
 
data/lib/kenji.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'kenji/controller'
3
+ require 'kenji/app'
3
4
  require 'kenji/string_extensions'
4
5
  require 'rack'
5
6
 
@@ -20,32 +21,65 @@ module Kenji
20
21
  #
21
22
  # `env` should be the environment hash provided by Rack.
22
23
  #
23
- # `root` is the root directory (as output by File.expand_path) of the Kenji
24
- # directory structure.
24
+ # *deprecated* `root` is the root directory (as output by File.expand_path)
25
+ # of the Kenji directory structure. This is deprecated, please use the
26
+ # :directory option below.
25
27
  #
26
28
  # `options` is an options hash that accepts the following keys:
27
29
  #
28
- # - :auto_cors => true | false # automatically deal with
29
- # CORS / Access-Control
30
- # - :root_controller => Object # when set, Kenji will not attempt to
31
- # discover controllers based on
32
- # convention, but rather will always
33
- # use this controller. use `pass` to
34
- # build a controller hierarchy
30
+ # :auto_cors => true | false
35
31
  #
36
- def initialize(env, root, options = {})
32
+ # automatically deal with CORS / Access-Control
33
+ #
34
+ # :directory => String (path)
35
+ #
36
+ # this is the preferred way of setting the root directory, when
37
+ # necessary. you should either set a root directory (which defaults to
38
+ # the current working directory), or set a root_controller. both are
39
+ # not necessary, as the directory is only used for auto-discovering
40
+ # controllers.
41
+ #
42
+ # :root_controller => Object
43
+ #
44
+ # when set, Kenji will not attempt to discover controllers based on
45
+ # convention, but rather will always use this controller. use `pass` to
46
+ # build a controller hierarchy
47
+ #
48
+ # :catch_exceptions => true | false
49
+ #
50
+ # when true, Kenji will catch exceptions, print them in stderr, and and
51
+ # return a standard 500 error
52
+ #
53
+ # :stderr => IO
54
+ #
55
+ # an IO stread, this is where Kenji logging goes by default. defaults
56
+ # to $stderr
57
+ #
58
+ def initialize(env, *rest)
59
+ raise ArgumentError unless rest.count == 2 || rest.count == 1
60
+ root, options = *rest
61
+ options, root = root, options if root.is_a?(Hash)
62
+ options ||= {}
63
+
37
64
  @headers = {
38
65
  'Content-Type' => 'application/json'
39
66
  }
40
67
  @status = 200
41
- @root = File.expand_path(root) + '/'
42
- @stderr = $stderr
43
68
  @env = env
69
+
70
+ # deal with legacy root argument behavior
71
+ options[:directory] = File.expand_path(root) if root
44
72
 
45
73
  @options = {
46
74
  auto_cors: true,
47
- root_controller: nil
75
+ catch_exceptions: true,
76
+ root_controller: nil,
77
+ directory: File.expand_path(Dir.getwd),
78
+ stderr: $stderr
48
79
  }.merge(options)
80
+
81
+ @stderr = @options[:stderr]
82
+ @root = @options[:directory] + '/'
49
83
  end
50
84
 
51
85
  # This method does all the work!
@@ -78,10 +112,8 @@ module Kenji
78
112
  while head = segments.shift
79
113
  acc = "#{acc}/#{head}"
80
114
  if controller = controller_for(acc) # if we have a valid controller
81
- begin
82
- subpath = '/'+segments.join('/')
83
- out = controller.call(method, subpath).to_json
84
- end
115
+ subpath = '/'+segments.join('/')
116
+ out = controller.call(method, subpath).to_json
85
117
  success = true
86
118
  break
87
119
  end
@@ -93,6 +125,7 @@ module Kenji
93
125
  [@status, @headers, [out]]
94
126
  end
95
127
  rescue Exception => e
128
+ raise e unless @options[:catch_exceptions]
96
129
  @stderr.puts e.inspect # log exceptions
97
130
  e.backtrace.each {|b| @stderr.puts " #{b}" }
98
131
  response_500
@@ -3,8 +3,21 @@ module Spec5
3
3
  get '/' do
4
4
  { :foo => 'bar' }
5
5
  end
6
+
6
7
  get '/path' do
7
8
  { :baz => 'bar' }
8
9
  end
10
+
11
+ get '/foo/:foo_id/:bar_id' do |foo_id|
12
+ { :baz => 'bar' }
13
+ end
14
+
15
+ get "/bar/:foo_id/:bar_id" do |foo_id, bar_id, baz_id|
16
+ { :baz => 'bar' }
17
+ end
18
+
19
+ get "/foobar/:foo_id/:bar_id" do |foo_id, bar_id|
20
+ { :foo => foo_id, :bar => bar_id }
21
+ end
9
22
  end
10
23
  end
data/spec/kenji_spec.rb CHANGED
@@ -17,7 +17,7 @@ def app_for(path, opts={})
17
17
  end
18
18
  end
19
19
 
20
- describe Kenji::Kenji, 'expected reponses' do
20
+ describe Kenji::Kenji, 'expected responses' do
21
21
  include Rack::Test::Methods
22
22
 
23
23
  context '1' do
@@ -106,6 +106,10 @@ describe Kenji::Kenji, 'expected reponses' do
106
106
  last_response.body.should == expected_response
107
107
  end
108
108
 
109
+ it 'should not match subsets of the route' do
110
+ get 'something/child/foo'
111
+ last_response.status.should == 404
112
+ end
109
113
  end
110
114
 
111
115
  context '3' do
@@ -183,6 +187,27 @@ describe Kenji::Kenji, 'expected reponses' do
183
187
  put '/main/foo/foo_id/pass/pass_id'
184
188
  last_response.status.should == 404
185
189
  end
190
+
191
+ it "should pass variables to blocks" do
192
+ get "/foobar/foo_id/bar_id"
193
+ expected_response = { :foo => "foo_id", :bar => "bar_id" }.to_json
194
+ last_response.body.should == expected_response
195
+ last_response.status.should == 200
196
+ end
197
+
198
+ it "should throw ArgumentError when there are too many arguments" do
199
+ get "foo/foo_id/bar_id"
200
+ last_response.status.should == 500
201
+ end
202
+
203
+ it "should throw ArgumentError when there are too few arguments" do
204
+ get "bar/foo_id/bar_id"
205
+ last_response.status.should == 500
206
+ end
186
207
  end
208
+
209
+ # TODO: Write unit tests for :catch_exceptions option.
210
+ # TODO: Write unit tests for Kenji::App
211
+ # TODO: Write unit tests for new root directory behavior.
187
212
 
188
213
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kenji
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.0'
4
+ version: 1.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-16 00:00:00.000000000 Z
12
+ date: 2013-08-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -117,6 +117,7 @@ files:
117
117
  - inited/tmp/always_restart.txt
118
118
  - kenji.gemspec
119
119
  - lib/kenji.rb
120
+ - lib/kenji/app.rb
120
121
  - lib/kenji/controller.rb
121
122
  - lib/kenji/string_extensions.rb
122
123
  - lib/kenji/version.rb