kenji 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.
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