kenji 1.1.2 → 1.2.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
  SHA1:
3
- metadata.gz: cf14e401da97852b1581ed7c36fecb4ca5730150
4
- data.tar.gz: 640b48b7bd7caaedd92c7a7071c74b09917c8cb2
3
+ metadata.gz: c5fdf8390ae9d6d9eec304907eeb51dfac87e44e
4
+ data.tar.gz: 6d7e9930d844f336ae34af1d0bcf54073ce99a48
5
5
  SHA512:
6
- metadata.gz: ec8185bb579998a5108aa4059d7c229500fb0340ef098c5888897d76a3e66166939d1e99052f0071b9fae161c12d5675c9f44747a5753a053bfed08b8b8dbe4f
7
- data.tar.gz: 8074e6d539c044abbb5bc031cb6f9a3c5978ed81440ea3482574f26663ff08e785b591f9458c60835379b4cc49a86ac814ef4971da4e9885b3f216189f78faef
6
+ metadata.gz: 13094758c7771669041badcffff388ca387cb547bb4965c5010dd176e82cf66e9b6e50d78baa891aa0206a26044bf3e2e6ed08238544dcf58b2a6c917e2ed415
7
+ data.tar.gz: c2e5133ae4a52328ce095843f6e7f78757035e5f46136aaf685909c3a273822b6052f314144c6179afd4540d86f9fc7b1ef6b250dc1e5a42ef791c301419f1e8
data/.hound.yml ADDED
@@ -0,0 +1,2 @@
1
+ ruby:
2
+ config_file: .rubocop.yml
data/.rubocop.yml ADDED
@@ -0,0 +1,261 @@
1
+ # Ignore Rubocop on some files...
2
+
3
+ AllCops:
4
+ Exclude:
5
+ - 'spec/**/*'
6
+
7
+ # Enabling of disabled-by-default checks:
8
+
9
+ Style/CollectionMethods:
10
+ Enabled: true
11
+
12
+ Style/Encoding:
13
+ Enabled: true
14
+
15
+ ######
16
+
17
+ # Align the elements of a hash literal if they span more than one line.
18
+ Style/AlignHash:
19
+ EnforcedHashRocketStyle: table
20
+ EnforcedColonStyle: table
21
+
22
+ # Align with the style guide.
23
+ Style/CollectionMethods:
24
+ # Mapping from undesired method to desired_method
25
+ # e.g. to use `detect` over `find`:
26
+ #
27
+ # CollectionMethods:
28
+ # PreferredMethods:
29
+ # find: detect
30
+ PreferredMethods:
31
+ collect: 'map'
32
+ collect!: 'map!'
33
+ inject: 'reduce'
34
+ detect: 'find'
35
+ find_all: 'select'
36
+
37
+ # Multi-line method chaining should be done with leading dots.
38
+ Style/DotPosition:
39
+ EnforcedStyle: leading
40
+ SupportedStyles:
41
+ - leading
42
+ - trailing
43
+
44
+ # Use empty lines between defs.
45
+ Style/EmptyLineBetweenDefs:
46
+ # If true, this parameter means that single line method definitions don't
47
+ # need an empty line between them.
48
+ AllowAdjacentOneLineDefs: false
49
+
50
+ Style/EmptyLinesAroundClassBody:
51
+ Enabled: false
52
+
53
+ Style/EmptyLinesAroundModuleBody:
54
+ Enabled: false
55
+
56
+ # Checks whether the source file has a utf-8 encoding comment or not
57
+ # AutoCorrectEncodingComment must match the regex
58
+ # /#.*coding\s?[:=]\s?(?:UTF|utf)-8/
59
+ Style/Encoding:
60
+ Enabled: false
61
+
62
+
63
+ # Checks use of for or each in multiline loops.
64
+ Style/For:
65
+ EnforcedStyle: each
66
+
67
+ # Built-in global variables are allowed by default.
68
+ Style/GlobalVars:
69
+ AllowedVariables: []
70
+
71
+ # `MinBodyLength` defines the number of lines of the a body of an if / unless
72
+ # needs to have to trigger this cop
73
+ Style/GuardClause:
74
+ MinBodyLength: 1
75
+
76
+ # Checks the indentation of the first key in a hash literal.
77
+ Style/IndentHash:
78
+ EnforcedStyle: special_inside_parentheses
79
+
80
+ Style/LambdaCall:
81
+ EnforcedStyle: call
82
+ SupportedStyles:
83
+ - call
84
+ - braces
85
+
86
+ Style/NonNilCheck:
87
+ IncludeSemanticChanges: true
88
+
89
+ Style/MethodDefParentheses:
90
+ EnforcedStyle: require_parentheses
91
+
92
+ Style/NumericLiterals:
93
+ MinDigits: 5
94
+
95
+ # Allow safe assignment in conditions.
96
+ Style/ParenthesesAroundCondition:
97
+ AllowSafeAssignment: true
98
+
99
+ Style/PercentLiteralDelimiters:
100
+ PreferredDelimiters:
101
+ '%': ()
102
+ '%i': ()
103
+ '%q': ()
104
+ '%Q': ()
105
+ '%r': '{}'
106
+ '%s': ()
107
+ '%w': ()
108
+ '%W': ()
109
+ '%x': ()
110
+
111
+ Style/Semicolon:
112
+ # Allow ; to separate several expressions on the same line.
113
+ AllowAsExpressionSeparator: true
114
+
115
+ Style/SignalException:
116
+ EnforcedStyle: only_raise
117
+
118
+ Style/SingleLineMethods:
119
+ AllowIfMethodIsEmpty: true
120
+
121
+ Style/StringLiterals:
122
+ EnforcedStyle: single_quotes
123
+
124
+ Style/StringLiteralsInInterpolation:
125
+ EnforcedStyle: single_quotes
126
+
127
+ Style/SpaceAroundBlockParameters:
128
+ EnforcedStyleInsidePipes: no_space
129
+
130
+ Style/SpaceAroundEqualsInParameterDefault:
131
+ EnforcedStyle: space
132
+
133
+ Style/SpaceAroundOperators:
134
+ MultiSpaceAllowedForOperators:
135
+ - '='
136
+ - '=>'
137
+
138
+ Style/SpaceBeforeBlockBraces:
139
+ EnforcedStyle: space
140
+
141
+ Style/SpaceInsideBlockBraces:
142
+ EnforcedStyle: space
143
+ # Valid values are: space, no_space
144
+ EnforcedStyleForEmptyBraces: no_space
145
+ # Space between { and |. Overrides EnforcedStyle if there is a conflict.
146
+ SpaceBeforeBlockParameters: false
147
+
148
+ Style/SpaceInsideHashLiteralBraces:
149
+ EnforcedStyle: no_space
150
+ EnforcedStyleForEmptyBraces: no_space
151
+
152
+ Style/SymbolProc:
153
+ # A list of method names to be ignored by the check.
154
+ # The names should be fairly unique, otherwise you'll end up ignoring lots of code.
155
+ IgnoredMethods:
156
+ - respond_to
157
+
158
+ Style/TrailingBlankLines:
159
+ EnforcedStyle: final_newline
160
+
161
+ Style/TrailingComma:
162
+ # If EnforcedStyleForMultiline is comma, the cop requires a comma after the
163
+ # last item of a list, but only for lists where each item is on its own line.
164
+ # If EnforcedStyleForMultiline is consistent_comma, the cop requires a comma
165
+ # after the last item of a list, for all lists.
166
+ EnforcedStyleForMultiline: comma
167
+ SupportedStyles:
168
+ - comma
169
+ - consistent_comma
170
+ - no_comma
171
+
172
+ # TrivialAccessors doesn't require exact name matches and doesn't allow
173
+ # predicated methods by default.
174
+ Style/TrivialAccessors:
175
+ ExactNameMatch: false
176
+ AllowPredicates: false
177
+ # Allows trivial writers that don't end in an equal sign. e.g.
178
+ #
179
+ # def on_exception(action)
180
+ # @on_exception=action
181
+ # end
182
+ # on_exception :restart
183
+ #
184
+ # Commonly used in DSLs
185
+ AllowDSLWriters: false
186
+ IgnoreClassMethods: false
187
+ Whitelist:
188
+ - to_ary
189
+ - to_a
190
+ - to_c
191
+ - to_enum
192
+ - to_h
193
+ - to_hash
194
+ - to_i
195
+ - to_int
196
+ - to_io
197
+ - to_open
198
+ - to_path
199
+ - to_proc
200
+ - to_r
201
+ - to_regexp
202
+ - to_str
203
+ - to_s
204
+ - to_sym
205
+
206
+
207
+ Style/WhileUntilModifier:
208
+ MaxLineLength: 80
209
+
210
+ Style/WordArray:
211
+ MinSize: 0
212
+ # The regular expression WordRegex decides what is considered a word.
213
+ WordRegex: !ruby/regexp '/\A[\p{Word}]+\z/'
214
+
215
+ ##################### Metrics ##################################
216
+
217
+ Metrics/AbcSize:
218
+ # The ABC size is a calculated magnitude, so this number can be a Fixnum or
219
+ # a Float.
220
+ Max: 20
221
+
222
+ Metrics/BlockNesting:
223
+ Max: 3
224
+
225
+ Metrics/ClassLength:
226
+ CountComments: false # count full line comments?
227
+ Max: 300
228
+
229
+ # Avoid complex methods.
230
+ Metrics/CyclomaticComplexity:
231
+ Max: 6
232
+
233
+ Metrics/LineLength:
234
+ Max: 80
235
+ # To make it possible to copy or click on URIs in the code, we allow lines
236
+ # contaning a URI to be longer than Max.
237
+ AllowURI: true
238
+ URISchemes:
239
+ - http
240
+ - https
241
+
242
+ Metrics/MethodLength:
243
+ CountComments: false # count full line comments?
244
+ Max: 30
245
+
246
+ Metrics/ParameterLists:
247
+ Max: 5
248
+ CountKeywordArgs: true
249
+
250
+ Metrics/PerceivedComplexity:
251
+ Max: 7
252
+
253
+ ##################### Lint ##################################
254
+
255
+ # Allow safe assignment in conditions.
256
+ Lint/AssignmentInCondition:
257
+ AllowSafeAssignment: true
258
+
259
+ # Align ends correctly.
260
+ Lint/EndAlignment:
261
+ AlignWith: keyword
data/Gemfile CHANGED
@@ -3,4 +3,4 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in factory_baby.gemspec
4
4
  gemspec
5
5
 
6
- gem 'inifile'
6
+ gem 'inifile'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kenji (1.1.1)
4
+ kenji (1.1.2)
5
5
  json
6
6
  rack (~> 1.5.2)
7
7
 
@@ -10,7 +10,7 @@ GEM
10
10
  specs:
11
11
  diff-lcs (1.2.5)
12
12
  inifile (2.0.2)
13
- json (1.8.1)
13
+ json (1.8.2)
14
14
  rack (1.5.2)
15
15
  rack-test (0.6.2)
16
16
  rack (>= 1.0)
data/README.md CHANGED
@@ -39,9 +39,9 @@ look like this, in `controller/hello.rb`:
39
39
 
40
40
  ```ruby
41
41
  class HelloController < Kenji::Controller
42
- get '/world' do
43
- {hello: :world}
44
- end
42
+ get '/world' do
43
+ {hello: :world}
44
+ end
45
45
  end
46
46
  ```
47
47
 
@@ -50,19 +50,19 @@ A more representative example might be:
50
50
  ```ruby
51
51
  class UserController < Kenji::Controller
52
52
 
53
- # ...
53
+ # ...
54
54
 
55
- get '/:id/friends' do |id|
56
- # list friends for id
57
- end
55
+ get '/:id/friends' do |id|
56
+ # list friends for id
57
+ end
58
58
 
59
- post '/:id/friend/:id' do |id, friend_id|
60
- # add connection from user id to friend_id
61
- end
59
+ post '/:id/friend/:id' do |id, friend_id|
60
+ # add connection from user id to friend_id
61
+ end
62
62
 
63
- delete '/:id/friend/:id' do |id, friend_id|
64
- # delete connection from user id to friend_id
65
- end
63
+ delete '/:id/friend/:id' do |id, friend_id|
64
+ # delete connection from user id to friend_id
65
+ end
66
66
  end
67
67
  ```
68
68
 
@@ -100,6 +100,14 @@ And already, your app is ready to go:
100
100
 
101
101
  ## Changelog
102
102
 
103
+ #### 1.2.0
104
+
105
+ - A new `exception_in_body` option (defaults to false) defines whether options
106
+ are returned to the HTTP response instead of the default “Something went
107
+ wrong...”
108
+ - `kenji init` no longer generates a useless binary.
109
+ - Internally, lots of style and best practices refactors.
110
+
103
111
  #### 1.1.2
104
112
 
105
113
  - The `respond_raw` method allows responding with raw data, instead of the
@@ -150,7 +158,7 @@ And already, your app is ready to go:
150
158
 
151
159
  - `before` command.
152
160
  - specs
153
- - passing
161
+ - passing
154
162
 
155
163
  ## Still to do
156
164
 
data/Rakefile CHANGED
@@ -2,6 +2,6 @@ require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
3
  require 'rspec/core'
4
4
  require 'rspec/core/rake_task'
5
- task :default => :spec
5
+ task default: :spec
6
+ task test: :spec
6
7
  RSpec::Core::RakeTask.new(:spec)
7
-
data/bin/kenji CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
-
4
3
  unless ARGV.first == 'init'
5
4
  puts 'Usage: kenji init [project-name]'
6
5
  Process.exit
@@ -17,28 +16,32 @@ path = File.realpath(path)
17
16
  init_source = File.realpath(File.dirname(File.dirname(__FILE__))) << '/inited'
18
17
 
19
18
  for_earch_item = lambda do |item, base|
20
- next if item == '.' || item == '..'
21
- item_to_create = item.gsub('__APP_NAME__', app_name)
22
-
23
-
24
- if File.directory?("#{init_source}/#{base}/#{item}")
25
- Dir.mkdir("#{path}/#{base}/#{item_to_create}") unless File.directory?("#{path}/#{base}/#{item}")
26
- puts "Initializing directory #{base}/#{item_to_create}"
27
- Dir.foreach("#{init_source}/#{base}/#{item}") { |i| for_earch_item.call(i, "#{base}/#{item}") }
28
- else
29
- puts "Generating content for file #{base}/#{item_to_create}"
30
- data = ''
31
- f = File.open("#{init_source}/#{base}/#{item}", 'r')
32
- while line = f.gets
33
- data += (line.gsub('__APP_NAME__', app_name))
34
- end
35
- f.close
36
- f = File.open("#{path}/#{base}/#{item_to_create}", 'w')
37
- f.puts data
38
- f.close
19
+ next if item == '.' || item == '..'
20
+ item_to_create = item.gsub('__APP_NAME__', app_name)
21
+
22
+
23
+ if File.directory?("#{init_source}/#{base}/#{item}")
24
+ unless File.directory?("#{path}/#{base}/#{item}")
25
+ Dir.mkdir("#{path}/#{base}/#{item_to_create}")
26
+ end
27
+ puts "Initializing directory #{base}/#{item_to_create}"
28
+ Dir.foreach("#{init_source}/#{base}/#{item}") do |i|
29
+ for_earch_item.call(i, "#{base}/#{item}")
30
+ end
31
+ else
32
+ puts "Generating content for file #{base}/#{item_to_create}"
33
+ data = ''
34
+ f = File.open("#{init_source}/#{base}/#{item}", 'r')
35
+ while (line = f.gets)
36
+ data += (line.gsub('__APP_NAME__', app_name))
39
37
  end
38
+ f.close
39
+ f = File.open("#{path}/#{base}/#{item_to_create}", 'w')
40
+ f.puts data
41
+ f.close
42
+ end
40
43
  end
41
44
 
42
- Dir.foreach(init_source) { |i| for_earch_item.call(i, '.') }
45
+ Dir.foreach(init_source) {|i| for_earch_item.call(i, '.') }
43
46
 
44
47
  system("cd #{path}; bundle install")
data/kenji.gemspec CHANGED
@@ -1,4 +1,4 @@
1
- $:.push File.expand_path('../lib', __FILE__)
1
+ $LOAD_PATH.push(File.expand_path('../lib', __FILE__))
2
2
  require 'kenji/version'
3
3
 
4
4
  Gem::Specification.new do |s|
@@ -19,6 +19,6 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.files = `git ls-files`.split("\n")
21
21
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map {|f| File.basename(f) }
23
23
  s.require_paths = ['lib']
24
24
  end
data/lib/kenji.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'json'
2
2
  require 'kenji/controller'
3
3
  require 'kenji/app'
4
- require 'kenji/string_extensions'
4
+ require 'kenji/string-extensions'
5
5
  require 'rack'
6
6
 
7
+ using Kenji::StringExtensions
8
+
7
9
  module Kenji
8
10
  class Kenji
9
11
 
@@ -28,7 +30,7 @@ module Kenji
28
30
  # `options` is an options hash that accepts the following keys:
29
31
  #
30
32
  # :directory => String (path)
31
- #
33
+ #
32
34
  # this is the preferred way of setting the root directory, when
33
35
  # necessary. you should either set a root directory (which defaults to
34
36
  # the current working directory), or set a root_controller. both are
@@ -46,6 +48,10 @@ module Kenji
46
48
  # when true, Kenji will catch exceptions, print them in stderr, and and
47
49
  # return a standard 500 error
48
50
  #
51
+ # :exception_in_body => true | false
52
+ #
53
+ # if :catch_exceptions => true, return exception message in response
54
+ #
49
55
  # :stderr => IO
50
56
  #
51
57
  # an IO stread, this is where Kenji logging goes by default. defaults
@@ -58,23 +64,24 @@ module Kenji
58
64
  options ||= {}
59
65
 
60
66
  @headers = {
61
- 'Content-Type' => 'application/json'
67
+ 'Content-Type' => 'application/json',
62
68
  }
63
69
  @status = 200
64
70
  @env = env
65
71
 
66
72
  # deal with legacy root argument behavior
67
73
  options[:directory] = File.expand_path(root) if root
68
-
74
+
69
75
  @options = {
70
- catch_exceptions: true,
71
- root_controller: nil,
72
- directory: File.expand_path(Dir.getwd),
73
- stderr: $stderr
76
+ catch_exceptions: true,
77
+ exception_in_body: false,
78
+ root_controller: nil,
79
+ directory: File.expand_path(Dir.getwd),
80
+ stderr: $stderr,
74
81
  }.merge(options)
75
82
 
76
83
  @stderr = @options[:stderr]
77
- @root = @options[:directory] + '/'
84
+ @root = @options[:directory] + '/'
78
85
  end
79
86
 
80
87
  # This method does all the work!
@@ -98,16 +105,16 @@ module Kenji
98
105
 
99
106
  if @options[:root_controller]
100
107
  controller = controller_instance(@options[:root_controller])
101
- subpath = segments.join('/')
102
- out = controller.call(method, subpath).to_json
103
- success = true
108
+ subpath = segments.join('/')
109
+ out = controller.call(method, subpath).to_json
110
+ success = true
104
111
  else
105
112
  acc = ''; out = '', success = false
106
- while head = segments.shift
113
+ while (head = segments.shift)
107
114
  acc = "#{acc}/#{head}"
108
- # if we have a valid controller
109
- if controller = controller_for(acc)
110
- subpath = '/'+segments.join('/')
115
+ # if we have a valid controller
116
+ if (controller = controller_for(acc))
117
+ subpath = '/' + segments.join('/')
111
118
  out = controller.call(method, subpath).to_json
112
119
  success = true
113
120
  break
@@ -119,27 +126,22 @@ module Kenji
119
126
 
120
127
  [@status, @headers, [out]]
121
128
  end
122
- rescue Exception => e
129
+ rescue => e
123
130
  raise e unless @options[:catch_exceptions]
124
131
  # log exceptions
125
- @stderr.puts e.inspect
132
+ @stderr.puts(e.inspect)
126
133
  e.backtrace.each {|b| @stderr.puts " #{b}" }
127
- response_500
134
+ response_500(e)
128
135
  end
129
136
 
130
-
131
-
132
137
  # Methods for users!
133
138
 
134
-
135
139
  # Sets one or multiple headers, as named arametres. eg.
136
- #
140
+ #
137
141
  # kenji.header 'Content-Type' => 'hello/world'
138
142
  #
139
- def header(hash={})
140
- hash.each do |key, value|
141
- @headers[key] = value
142
- end
143
+ def header(hash = {})
144
+ @header.merge!(hash)
143
145
  end
144
146
 
145
147
  # Returns the response headers
@@ -161,16 +163,17 @@ module Kenji
161
163
  end if raw
162
164
  {} # default return value
163
165
  end
164
-
166
+
165
167
  # Respond to the request
166
168
  #
167
- def respond(code, message, hash={})
169
+ def respond(code, message, hash = {})
168
170
  @status = code
169
- response = { # default structure.
170
- status: code,
171
+ response = { # default structure.
172
+ status: code,
171
173
  message: message,
172
174
  }.merge(hash)
173
- throw(:KenjiRespondControlFlowInterrupt, [@status, @headers, [response.to_json]])
175
+ throw(:KenjiRespondControlFlowInterrupt,
176
+ [@status, @headers, [response.to_json]])
174
177
  end
175
178
 
176
179
  # Respond with raw bytes
@@ -180,7 +183,7 @@ module Kenji
180
183
  def respond_raw(status = 200, data)
181
184
  @status = status
182
185
  body = data.is_a?(IO) ? data : [data]
183
- throw(:KenjiRespondControlFlowInterrupt, [@status, @headers, data])
186
+ throw(:KenjiRespondControlFlowInterrupt, [@status, @headers, body])
184
187
  end
185
188
 
186
189
 
@@ -188,8 +191,15 @@ module Kenji
188
191
  private
189
192
 
190
193
  # 500 error
191
- def response_500
192
- [500, @headers, [{status: 500, message: 'Something went wrong...'}.to_json]]
194
+ def response_500(exception = nil)
195
+ message =
196
+ if exception.nil? || !@options[:exception_in_body]
197
+ 'Something went wrong...'
198
+ else
199
+ exception.to_s
200
+ end
201
+
202
+ [500, @headers, [{status: 500, message: message}.to_json]]
193
203
  end
194
204
 
195
205
  # 404 error
@@ -203,10 +213,11 @@ module Kenji
203
213
  def controller_for(subpath)
204
214
  subpath = '/_' if subpath == '/'
205
215
  path = "#{@root}controllers#{subpath}.rb"
206
- return nil unless File.exists?(path)
216
+ return nil unless File.exist?(path)
207
217
  require path
208
218
  controller_name = subpath.split('/').last.sub(/^_/, 'Root')
209
- controller_class = Object.const_get(controller_name.to_s.to_camelcase+'Controller')
219
+ controller_class =
220
+ Object.const_get(controller_name.to_s.to_camelcase + 'Controller')
210
221
  controller_instance(controller_class)
211
222
  end
212
223
 
@@ -215,8 +226,8 @@ module Kenji
215
226
  #
216
227
  def controller_instance(controller_class)
217
228
  # ensure protocol compliance
218
- unless (controller_class.method_defined?(:call) &&
219
- controller_class.instance_method(:call).arity == 2)
229
+ unless controller_class.method_defined?(:call) \
230
+ && controller_class.instance_method(:call).arity == 2
220
231
  return
221
232
  end
222
233
  controller = controller_class.new
@@ -224,8 +235,6 @@ module Kenji
224
235
  return controller if controller
225
236
  nil # default return value
226
237
  end
227
-
238
+
228
239
  end
229
-
230
240
  end
231
-
data/lib/kenji/app.rb CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Kenji
3
-
3
+
4
4
  # Kenji::App is a simple wrapper class that helps avoid the awkward wrapping
5
5
  # in a lambda typically necessary for using Kenji as a Rack app. Instead,
6
6
  # simply do the following:
@@ -14,12 +14,14 @@ module Kenji
14
14
  #
15
15
  class App
16
16
 
17
- def initialize(opts={})
17
+ def initialize(opts = {})
18
18
  @opts = opts
19
19
  end
20
20
 
21
21
  def call(env)
22
22
  Kenji.new(env, @opts).call
23
23
  end
24
+
24
25
  end
26
+
25
27
  end
@@ -6,7 +6,7 @@ module Kenji
6
6
  attr_accessor :kenji
7
7
 
8
8
  # Routes below will accept routes in the format, eg.:
9
- #
9
+ #
10
10
  # /hello/:id/children
11
11
  #
12
12
  # Can contain any number of :id, but must be in their own url segment.
@@ -56,20 +56,27 @@ module Kenji
56
56
  # segment or variable segment, and the leaf @action being the block
57
57
  #
58
58
  def self.route(*methods, path, &block)
59
+
59
60
  # bind the block to self as an instance method, so its context is correct
60
61
  define_method(:_tmp_route_action, &block)
61
62
  block = instance_method(:_tmp_route_action)
62
63
  remove_method(:_tmp_route_action)
64
+
63
65
  # store the block for each method
64
66
  methods.each do |method|
67
+
65
68
  node = ((@routes ||= {})[method] ||= {})
66
69
  segments = path.split('/')
67
- segments = segments.drop(1) if segments.first == '' # discard leading /'s empty segment
68
- segments.each do |segment| # lazily create tree
69
- segment = ':' if segment =~ /^:/ # discard :variable name
70
+ # discard leading /'s empty segment
71
+ segments = segments.drop(1) if segments.first == ''
72
+ # lazily create tree
73
+ segments.each do |segment|
74
+ # discard :variable name
75
+ segment = ':' if segment =~ /^:/
70
76
  node = (node[segment.to_sym] ||= {})
71
77
  end
72
- node[:@action] = block # store block as leaf in @action
78
+ # store block as leaf in @action
79
+ node[:@action] = block
73
80
  end
74
81
  nil # void method
75
82
  end
@@ -84,7 +91,8 @@ module Kenji
84
91
  def self.pass(path, controller)
85
92
  node = (@passes ||= {})
86
93
  segments = path.split('/')
87
- segments = segments.drop(1) if segments.first == '' # discard leading /'s empty segment
94
+ # discard leading /'s empty segment
95
+ segments = segments.drop(1) if segments.first == ''
88
96
  segments.each do |segment|
89
97
  node = (node[segment.to_sym] ||= {})
90
98
  break if segment == '*'
@@ -92,7 +100,6 @@ module Kenji
92
100
  node[:@controller] = controller
93
101
  end
94
102
 
95
-
96
103
  # This lets you define before blocks.
97
104
  #
98
105
  # class MyController < Kenji::Controller
@@ -108,7 +115,6 @@ module Kenji
108
115
  (@befores ||= []) << block
109
116
  end
110
117
 
111
-
112
118
  # Most likely only used by Kenji itself.
113
119
  # Override to implement your own routing, if you'd like.
114
120
  #
@@ -117,7 +123,8 @@ module Kenji
117
123
  self.class.befores.each {|b| b.bind(self).call }
118
124
 
119
125
  segments = path.split('/')
120
- segments = segments.drop(1) if segments.first == '' # discard leading /'s empty segment
126
+ # discard leading /'s empty segment
127
+ segments = segments.drop(1) if segments.first == ''
121
128
 
122
129
  # check for passes
123
130
  node = self.class.passes
@@ -138,19 +145,25 @@ module Kenji
138
145
  node = self.class.routes[method] || {}
139
146
  variables = []
140
147
  searching = true
141
- segments.each do |segment| # traverse tree to find
148
+ # traverse tree to find
149
+ segments.each do |segment|
142
150
  if searching && node[segment.to_sym]
143
- node = node[segment.to_sym] # attempt to move down to the plain text segment
151
+ # attempt to move down to the plain text segment
152
+ node = node[segment.to_sym]
144
153
  elsif searching && node[:':']
145
- node = node[:':'] # attempt to find a variable segment
146
- variables << segment # either we've found a variable, or the `unless` below will trigger
154
+ # attempt to find a variable segment
155
+ node = node[:':']
156
+ # either we've found a variable, or the `unless` below will trigger
157
+ variables << segment
147
158
  else
148
- return attempt_fallback(path) # route failed to match variable or segment node so attempt fallback
159
+ # route failed to match variable or segment node so attempt fallback
160
+ return attempt_fallback(path)
149
161
  end
150
162
  end
151
- if node && action = node[:@action] # the block is stored in the @action leaf
163
+ # the block is stored in the @action leaf
164
+ if node && (action = node[:@action])
152
165
  return action.bind(self).call(*variables)
153
- else # or, fallback if necessary store the block for each method
166
+ else # or, fallback if necessary store the block for each method
154
167
  return attempt_fallback(path)
155
168
  end
156
169
  end
@@ -175,13 +188,17 @@ module Kenji
175
188
  end
176
189
 
177
190
  private
191
+
178
192
  # Accessor for @routes
193
+
179
194
  def self.routes
180
195
  @routes || {}
181
196
  end
197
+
182
198
  def self.passes
183
199
  @passes || {}
184
200
  end
201
+
185
202
  def self.befores
186
203
  @befores || []
187
204
  end
@@ -190,24 +207,28 @@ module Kenji
190
207
  variables = {}
191
208
  match = false
192
209
 
193
- while e = segments.shift
210
+ while (e = segments.shift)
194
211
  # return false unless node
195
212
  key = node.keys.first
196
- if match = key.to_s.match(/^\:(\w+)/)
213
+ if (match = key.to_s.match(/^\:(\w+)/))
197
214
  node = node[key.to_sym]
198
215
  variables[match[1].to_sym] = e
199
216
  match = true
200
217
  else
201
218
  # if there is no match it should not pass
202
- break unless match = node.has_key?(e.to_sym)
219
+ break unless (match = node.key?(e.to_sym))
203
220
  node = node[e.to_sym]
204
221
  end
205
222
 
206
223
  break if node[:@controller]
207
224
  end
208
225
 
209
- { :match => match, :variables => variables, :controller => node[:@controller], :remaining_segments => segments }
226
+ {
227
+ match: match,
228
+ variables: variables,
229
+ controller: node[:@controller],
230
+ remaining_segments: segments,
231
+ }
210
232
  end
211
233
  end
212
234
  end
213
-
@@ -0,0 +1,25 @@
1
+ # Kenji string extensions
2
+
3
+ module Kenji
4
+ module StringExtensions
5
+
6
+ refine String do
7
+
8
+ def to_underscore!
9
+ gsub!(/(.)([A-Z])/, '\1_\2').downcase!
10
+ end
11
+
12
+ def to_underscore
13
+ clone.to_underscore!
14
+ end
15
+
16
+ def to_camelcase!
17
+ replace(split('_').each(&:capitalize!).join(''))
18
+ end
19
+
20
+ def to_camelcase
21
+ clone.to_camelcase!
22
+ end
23
+ end
24
+ end
25
+ end
data/lib/kenji/version.rb CHANGED
@@ -1,4 +1,3 @@
1
1
  module Kenji
2
- VERSION = '1.1.2'
2
+ VERSION = '1.2.0'
3
3
  end
4
-
@@ -5,10 +5,6 @@ class MainController < Kenji::Controller
5
5
  {status: 200, hello: :world}
6
6
  end
7
7
 
8
- get '/crasher' do
9
- raise
10
- end
11
-
12
8
  post '/' do
13
9
  {status:1337}
14
10
  end
@@ -0,0 +1,8 @@
1
+
2
+ class MainController < Kenji::Controller
3
+
4
+ get '/crasher' do
5
+ raise 'kaboom!'
6
+ end
7
+
8
+ end
data/spec/kenji_spec.rb CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  require 'rack'
3
2
  require 'rack/test'
4
3
  require 'rspec'
@@ -6,7 +5,6 @@ require 'rspec/mocks'
6
5
 
7
6
  require 'kenji'
8
7
 
9
-
10
8
  # NOTE: these tests make use of the controllers defined in test/controllers.
11
9
 
12
10
  def app_for(path, opts={})
@@ -23,7 +21,6 @@ describe Kenji::Kenji, 'expected responses' do
23
21
  context '1' do
24
22
  def app; app_for('1'); end
25
23
 
26
-
27
24
  it 'should return 404 for unknown routes (no controller)' do
28
25
  get '/sdlkjhb'
29
26
  expected_response = {status: 404, message: 'Not found!'}.to_json
@@ -38,13 +35,6 @@ describe Kenji::Kenji, 'expected responses' do
38
35
  last_response.status.should == 404
39
36
  end
40
37
 
41
- it 'should return 500 for exceptions' do
42
- get '/main/crasher'
43
- expected_response = {status: 500, message: 'Something went wrong...'}.to_json
44
- last_response.body.should == expected_response
45
- last_response.status.should == 500
46
- end
47
-
48
38
  it 'should route a GET call to a defined get call' do
49
39
  get '/main/hello'
50
40
  expected_response = {status: 200, hello: :world}.to_json
@@ -52,7 +42,6 @@ describe Kenji::Kenji, 'expected responses' do
52
42
  end
53
43
 
54
44
  [:post, :put, :delete, :patch].each do |method|
55
-
56
45
  it "should route a #{method.to_s.upcase} to a defined #{method.to_s} call" do
57
46
  send(method, '/main')
58
47
  expected_response = {status: 1337}.to_json
@@ -87,7 +76,6 @@ describe Kenji::Kenji, 'expected responses' do
87
76
  last_response.body.should == expected_response
88
77
  last_response.status.should == 123
89
78
  end
90
-
91
79
  end
92
80
 
93
81
  context '2' do
@@ -204,9 +192,58 @@ describe Kenji::Kenji, 'expected responses' do
204
192
  last_response.status.should == 500
205
193
  end
206
194
  end
207
-
208
- # TODO: Write unit tests for :catch_exceptions option.
195
+
196
+ context '7' do
197
+ context 'catch_exceptions is false' do
198
+ def app
199
+ app_for('7',
200
+ catch_exceptions: false)
201
+
202
+ it 'should raise' do
203
+ -> { get '/main/crasher' }.must_raise
204
+ end
205
+ end
206
+ end
207
+
208
+ context 'catch_exceptions is true' do
209
+ context :exception_in_body do
210
+ context 'exception_in_body is false' do
211
+ def app
212
+ app_for('7',
213
+ catch_exceptions: true,
214
+ exception_in_body: false)
215
+ end
216
+
217
+ it 'should return 500 for exceptions' do
218
+ get '/main/crasher'
219
+ last_response
220
+ .body
221
+ .should == {status: 500,
222
+ message: 'Something went wrong...'}.to_json
223
+ last_response.status.should == 500
224
+ end
225
+ end
226
+
227
+ context 'exception_in_body is true' do
228
+ def app
229
+ app_for('7',
230
+ catch_exceptions: true,
231
+ exception_in_body: true)
232
+ end
233
+
234
+ it 'should return 500 for exceptions' do
235
+ get '/main/crasher'
236
+ last_response
237
+ .body
238
+ .should == {status: 500,
239
+ message: 'kaboom!'}.to_json
240
+ last_response.status.should == 500
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+
209
247
  # TODO: Write unit tests for Kenji::App
210
248
  # TODO: Write unit tests for new root directory behavior.
211
-
212
249
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kenji
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenneth Ballenegger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-09 00:00:00.000000000 Z
11
+ date: 2015-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -89,6 +89,8 @@ extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
91
  - ".gitignore"
92
+ - ".hound.yml"
93
+ - ".rubocop.yml"
92
94
  - Gemfile
93
95
  - Gemfile.lock
94
96
  - LICENSE
@@ -98,7 +100,6 @@ files:
98
100
  - inited/.gitignore
99
101
  - inited/Gemfile
100
102
  - inited/README.md
101
- - inited/__APP_NAME__
102
103
  - inited/config.ru
103
104
  - inited/controllers/main.rb
104
105
  - inited/lib/README.md
@@ -109,7 +110,7 @@ files:
109
110
  - lib/kenji.rb
110
111
  - lib/kenji/app.rb
111
112
  - lib/kenji/controller.rb
112
- - lib/kenji/string_extensions.rb
113
+ - lib/kenji/string-extensions.rb
113
114
  - lib/kenji/version.rb
114
115
  - spec/1/controllers/main.rb
115
116
  - spec/2/controllers/_.rb
@@ -119,6 +120,7 @@ files:
119
120
  - spec/4/controllers/main.rb
120
121
  - spec/4/controllers/pass.rb
121
122
  - spec/5/controllers/main.rb
123
+ - spec/7/controllers/main.rb
122
124
  - spec/kenji_spec.rb
123
125
  homepage: https://github.com/kballenegger/kenji
124
126
  licenses: []
@@ -152,5 +154,6 @@ test_files:
152
154
  - spec/4/controllers/main.rb
153
155
  - spec/4/controllers/pass.rb
154
156
  - spec/5/controllers/main.rb
157
+ - spec/7/controllers/main.rb
155
158
  - spec/kenji_spec.rb
156
159
  has_rdoc:
data/inited/__APP_NAME__ DELETED
@@ -1,52 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- class Main
4
-
5
- def self.init
6
- require 'rubygems'
7
- require 'bundler/setup'
8
- require $root+'/lib/kenji/kenji'
9
- @@k = Kenji::Kenji.new({}, $root)
10
- end
11
-
12
- def self.main args
13
- verb = args.first.to_sym unless args.empty?
14
-
15
- # case verb
16
- # when :import
17
- # puts "Calling import script..."
18
- # @@k.controller_for(:import)._caffeine @@k
19
- # when :process
20
- # puts "Processing values..."
21
- # @@k.controller_for(:processing)._value @@k
22
- # when :configure
23
- # require $root + '/lib/configure'
24
- # skip_update = (args[1] =~ /-?-?skip[-_]update/)
25
- # AnalyticsModule.configure __FILE__, skip_update
26
- # else
27
- # puts <<-EOO
28
- # No verb defined. Usage: ./main [verb]
29
- #
30
- # configure [--skip-update]:
31
- # process the value of users
32
- # optionally, skip the self-update process
33
- # import:
34
- # import install from caffeine.io
35
- # process:
36
- # process the value of users
37
- # EOO
38
- # end
39
- end
40
-
41
- def kenji
42
- @@k
43
- end
44
- end
45
-
46
- require 'pathname'
47
- $root = File.dirname Pathname.new(__FILE__).realpath.to_s
48
-
49
- Main.init unless ARGV.first == 'configure'
50
- if __FILE__ == $0
51
- Main.main ARGV
52
- end
@@ -1,16 +0,0 @@
1
- # Kenji string extensions
2
-
3
- class String
4
- def to_underscore!
5
- self.gsub!(/(.)([A-Z])/,'\1_\2').downcase!
6
- end
7
- def to_underscore
8
- self.clone.to_underscore!
9
- end
10
- def to_camelcase!
11
- self.replace self.split('_').each{ |s| s.capitalize! }.join('')
12
- end
13
- def to_camelcase
14
- self.clone.to_camelcase!
15
- end
16
- end