jellyfish 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/CHANGES.md +23 -0
  2. data/Gemfile +7 -0
  3. data/README.md +2 -2
  4. data/example/config.ru +11 -0
  5. data/jellyfish.gemspec +48 -3
  6. data/lib/jellyfish.rb +21 -25
  7. data/lib/jellyfish/test.rb +22 -0
  8. data/lib/jellyfish/version.rb +1 -1
  9. data/sinatra/builder_test.rb +95 -0
  10. data/sinatra/coffee_test.rb +92 -0
  11. data/sinatra/contest.rb +98 -0
  12. data/sinatra/creole_test.rb +65 -0
  13. data/sinatra/delegator_test.rb +162 -0
  14. data/sinatra/encoding_test.rb +20 -0
  15. data/sinatra/erb_test.rb +104 -0
  16. data/sinatra/extensions_test.rb +100 -0
  17. data/sinatra/filter_test.rb +428 -0
  18. data/sinatra/haml_test.rb +101 -0
  19. data/sinatra/helper.rb +123 -0
  20. data/sinatra/helpers_test.rb +1783 -0
  21. data/sinatra/integration/app.rb +62 -0
  22. data/sinatra/integration_helper.rb +214 -0
  23. data/sinatra/integration_test.rb +85 -0
  24. data/sinatra/less_test.rb +67 -0
  25. data/sinatra/liquid_test.rb +59 -0
  26. data/sinatra/mapped_error_test.rb +259 -0
  27. data/sinatra/markaby_test.rb +80 -0
  28. data/sinatra/markdown_test.rb +81 -0
  29. data/sinatra/middleware_test.rb +68 -0
  30. data/sinatra/nokogiri_test.rb +69 -0
  31. data/sinatra/rack_test.rb +45 -0
  32. data/sinatra/radius_test.rb +59 -0
  33. data/sinatra/rdoc_test.rb +66 -0
  34. data/sinatra/readme_test.rb +136 -0
  35. data/sinatra/request_test.rb +45 -0
  36. data/sinatra/response_test.rb +61 -0
  37. data/sinatra/result_test.rb +98 -0
  38. data/sinatra/route_added_hook_test.rb +59 -0
  39. data/sinatra/routing_test.rb +1104 -0
  40. data/sinatra/sass_test.rb +116 -0
  41. data/sinatra/scss_test.rb +89 -0
  42. data/sinatra/server_test.rb +48 -0
  43. data/sinatra/settings_test.rb +538 -0
  44. data/sinatra/sinatra_test.rb +17 -0
  45. data/sinatra/slim_test.rb +88 -0
  46. data/sinatra/static_test.rb +178 -0
  47. data/sinatra/streaming_test.rb +140 -0
  48. data/sinatra/templates_test.rb +298 -0
  49. data/sinatra/textile_test.rb +65 -0
  50. data/test/sinatra/test_base.rb +123 -0
  51. metadata +48 -3
data/CHANGES.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # CHANGES
2
2
 
3
+ ## Jellyfish 0.5.0 -- 2012-10-18
4
+
5
+ ### Incompatible changes
6
+
7
+ * Some internal constants are removed.
8
+ * Renamed `Respond` to `Response`.
9
+
10
+ ### Enhancements
11
+
12
+ * Now Jellyfish would always use the custom error handler to handle the
13
+ particular exception even if `handle_exceptions` set to false. That is,
14
+ now setting `handle_exceptions` to false would only disable default
15
+ error handling. This behavior makes more sense since if you want the
16
+ exception bubble out then you shouldn't define the custom error handler
17
+ in the first place. If you define it, you must mean you want to use it.
18
+
19
+ * Eliminated some uninitialized instance variable warnings.
20
+
21
+ * Now you can access the original app via `jellyfish` in the controller.
22
+
23
+ * `Jellyfish::Controller` no longer includes `Jellyfish`, which would remove
24
+ those `DSL` methods accidentally included in previous version (0.4.0-).
25
+
3
26
  ## Jellyfish 0.4.0 -- 2012-10-14
4
27
 
5
28
  * Now you can define your own custom controller like:
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+
2
+ source 'http://rubygems.org'
3
+
4
+ gemspec
5
+
6
+ gem 'rake'
7
+ gem 'bacon'
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Jellyfish
1
+ # Jellyfish[![Build Status](https://secure.travis-ci.org/godfat/jellyfish.png?branch=master)](http://travis-ci.org/godfat/jellyfish)
2
2
 
3
3
  by Lin Jen-Shin ([godfat](http://godfat.org))
4
4
 
@@ -29,7 +29,7 @@ Rack applications or Rack middlewares. Under 200 lines of code.
29
29
  * No templates
30
30
  * No ORM
31
31
  * No `dup` in `call`
32
- * Regular expression routes, e.g. `get %r{/(\d+)}`
32
+ * Regular expression routes, e.g. `get %r{^/(?<id>\d+)$}`
33
33
  * String routes, e.g. `get '/'`
34
34
  * Custom routes, e.g. `get Matcher.new`
35
35
  * Build for either Rack applications or Rack middlewares
data/example/config.ru CHANGED
@@ -1,6 +1,8 @@
1
1
 
2
2
  require 'jellyfish'
3
3
 
4
+ Overheat = Class.new(RuntimeError)
5
+
4
6
  class Tank
5
7
  include Jellyfish
6
8
  handle_exceptions false
@@ -63,10 +65,19 @@ class Tank
63
65
  get '/chunked' do
64
66
  Body.new
65
67
  end
68
+
69
+ get '/overheat' do
70
+ raise Overheat
71
+ end
66
72
  end
67
73
 
68
74
  class Heater
69
75
  include Jellyfish
76
+ handle Overheat do
77
+ status 500
78
+ "It's overheated\n"
79
+ end
80
+
70
81
  get '/status' do
71
82
  temperature
72
83
  end
data/jellyfish.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "jellyfish"
5
- s.version = "0.4.0"
5
+ s.version = "0.5.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Lin Jen-Shin (godfat)"]
9
- s.date = "2012-10-14"
9
+ s.date = "2012-10-18"
10
10
  s.description = "Pico web framework for building API-centric web applications, either\nRack applications or Rack middlewares. Under 200 lines of code."
11
11
  s.email = ["godfat (XD) godfat.org"]
12
12
  s.files = [
@@ -14,6 +14,7 @@ Gem::Specification.new do |s|
14
14
  ".gitmodules",
15
15
  ".travis.yml",
16
16
  "CHANGES.md",
17
+ "Gemfile",
17
18
  "LICENSE",
18
19
  "README.md",
19
20
  "Rakefile",
@@ -27,13 +28,57 @@ Gem::Specification.new do |s|
27
28
  "lib/jellyfish/public/404.html",
28
29
  "lib/jellyfish/public/500.html",
29
30
  "lib/jellyfish/sinatra.rb",
31
+ "lib/jellyfish/test.rb",
30
32
  "lib/jellyfish/version.rb",
33
+ "sinatra/builder_test.rb",
34
+ "sinatra/coffee_test.rb",
35
+ "sinatra/contest.rb",
36
+ "sinatra/creole_test.rb",
37
+ "sinatra/delegator_test.rb",
38
+ "sinatra/encoding_test.rb",
39
+ "sinatra/erb_test.rb",
40
+ "sinatra/extensions_test.rb",
41
+ "sinatra/filter_test.rb",
42
+ "sinatra/haml_test.rb",
43
+ "sinatra/helper.rb",
44
+ "sinatra/helpers_test.rb",
45
+ "sinatra/integration/app.rb",
46
+ "sinatra/integration_helper.rb",
47
+ "sinatra/integration_test.rb",
48
+ "sinatra/less_test.rb",
49
+ "sinatra/liquid_test.rb",
50
+ "sinatra/mapped_error_test.rb",
51
+ "sinatra/markaby_test.rb",
52
+ "sinatra/markdown_test.rb",
53
+ "sinatra/middleware_test.rb",
54
+ "sinatra/nokogiri_test.rb",
55
+ "sinatra/rack_test.rb",
56
+ "sinatra/radius_test.rb",
57
+ "sinatra/rdoc_test.rb",
58
+ "sinatra/readme_test.rb",
59
+ "sinatra/request_test.rb",
60
+ "sinatra/response_test.rb",
61
+ "sinatra/result_test.rb",
62
+ "sinatra/route_added_hook_test.rb",
63
+ "sinatra/routing_test.rb",
64
+ "sinatra/sass_test.rb",
65
+ "sinatra/scss_test.rb",
66
+ "sinatra/server_test.rb",
67
+ "sinatra/settings_test.rb",
68
+ "sinatra/sinatra_test.rb",
69
+ "sinatra/slim_test.rb",
70
+ "sinatra/static_test.rb",
71
+ "sinatra/streaming_test.rb",
72
+ "sinatra/templates_test.rb",
73
+ "sinatra/textile_test.rb",
31
74
  "task/.gitignore",
32
- "task/gemgem.rb"]
75
+ "task/gemgem.rb",
76
+ "test/sinatra/test_base.rb"]
33
77
  s.homepage = "https://github.com/godfat/jellyfish"
34
78
  s.require_paths = ["lib"]
35
79
  s.rubygems_version = "1.8.24"
36
80
  s.summary = "Pico web framework for building API-centric web applications, either"
81
+ s.test_files = ["test/sinatra/test_base.rb"]
37
82
 
38
83
  if s.respond_to? :specification_version then
39
84
  s.specification_version = 3
data/lib/jellyfish.rb CHANGED
@@ -3,14 +3,9 @@ module Jellyfish
3
3
  autoload :VERSION, 'jellyfish/version'
4
4
  autoload :Sinatra, 'jellyfish/sinatra'
5
5
 
6
- REQUEST_METHOD = 'REQUEST_METHOD'
7
- PATH_INFO = 'PATH_INFO'
8
- LOCATION = 'Location'
9
- RACK_ERRORS = 'rack.errors'
10
-
11
6
  # -----------------------------------------------------------------
12
7
 
13
- class Respond < RuntimeError
8
+ class Response < RuntimeError
14
9
  def headers
15
10
  @headers ||= {'Content-Type' => 'text/html'}
16
11
  end
@@ -20,23 +15,23 @@ module Jellyfish
20
15
  end
21
16
  end
22
17
 
23
- class InternalError < Respond; def status; 500; end; end
24
- class NotFound < Respond; def status; 404; end; end
25
- class Found < Respond
18
+ class InternalError < Response; def status; 500; end; end
19
+ class NotFound < Response; def status; 404; end; end
20
+ class Found < Response # this would be raised in redirect
26
21
  attr_reader :url
27
22
  def initialize url; @url = url ; end
28
23
  def status ; 302 ; end
29
- def headers ; super.merge(LOCATION => url) ; end
24
+ def headers ; super.merge('Location' => url) ; end
30
25
  def body ; super.map{ |b| b.gsub('VAR_URL', url) }; end
31
26
  end
32
27
 
33
28
  # -----------------------------------------------------------------
34
29
 
35
30
  class Controller
36
- include Jellyfish
37
- attr_reader :routes, :env
38
- def initialize routes
39
- @routes = routes
31
+ attr_reader :routes, :jellyfish, :env
32
+ def initialize routes, jellyfish
33
+ @routes, @jellyfish = routes, jellyfish
34
+ @status, @headers, @body = nil
40
35
  end
41
36
 
42
37
  def call env
@@ -50,12 +45,12 @@ module Jellyfish
50
45
  [status || 200, headers || {}, body]
51
46
  end
52
47
 
53
- def forward ; raise(NotFound.new) ; end
54
- def found url; raise(Found.new(url)); end
48
+ def forward ; raise(Jellyfish::NotFound.new) ; end
49
+ def found url; raise(Jellyfish:: Found.new(url)); end
55
50
  alias_method :redirect, :found
56
51
 
57
- def path_info ; env[PATH_INFO] || '/' ; end
58
- def request_method; env[REQUEST_METHOD] || 'GET'; end
52
+ def path_info ; env['PATH_INFO'] || '/' ; end
53
+ def request_method; env['REQUEST_METHOD'] || 'GET'; end
59
54
 
60
55
  %w[status headers].each do |field|
61
56
  module_eval <<-RUBY
@@ -89,7 +84,7 @@ module Jellyfish
89
84
 
90
85
  private
91
86
  def actions
92
- routes[request_method.downcase] || raise(NotFound.new)
87
+ routes[request_method.downcase] || raise(Jellyfish::NotFound.new)
93
88
  end
94
89
 
95
90
  def dispatch
@@ -101,7 +96,7 @@ module Jellyfish
101
96
  match = route.match(path_info)
102
97
  break match, block if match
103
98
  end
104
- } || raise(NotFound.new)
99
+ } || raise(Jellyfish::NotFound.new)
105
100
  end
106
101
  end
107
102
 
@@ -136,7 +131,7 @@ module Jellyfish
136
131
  def controller ; Controller; end
137
132
 
138
133
  def call env
139
- ctrl = controller.new(self.class.routes)
134
+ ctrl = controller.new(self.class.routes, self)
140
135
  ctrl.call(env)
141
136
  rescue NotFound => e # forward
142
137
  if app
@@ -145,24 +140,25 @@ module Jellyfish
145
140
  handle(ctrl, e)
146
141
  end
147
142
  rescue Exception => e
148
- handle(ctrl, e, env[RACK_ERRORS])
143
+ handle(ctrl, e, env['rack.errors'])
149
144
  end
150
145
 
151
146
  def protect ctrl, env
152
147
  yield
153
148
  rescue Exception => e
154
- handle(ctrl, e, env[RACK_ERRORS])
149
+ handle(ctrl, e, env['rack.errors'])
155
150
  end
156
151
 
157
152
  private
158
153
  def handle ctrl, e, stderr=nil
159
- raise e unless self.class.handle_exceptions
160
154
  handler = self.class.handlers.find{ |klass, block|
161
155
  break block if e.kind_of?(klass)
162
156
  }
163
157
  if handler
164
158
  ctrl.block_call(e, handler)
165
- elsif e.kind_of?(Respond) # InternalError ends up here if no handlers
159
+ elsif !self.class.handle_exceptions
160
+ raise e
161
+ elsif e.kind_of?(Response) # InternalError ends up here if no handlers
166
162
  [e.status, e.headers, e.body]
167
163
  else # fallback and see if there's any InternalError handler
168
164
  log_error(e, stderr)
@@ -0,0 +1,22 @@
1
+
2
+ require 'bacon'
3
+ require 'rack'
4
+ require 'jellyfish'
5
+
6
+ Bacon.summary_on_exit
7
+
8
+ shared :jellyfish do
9
+ %w[options get head post put delete patch].each do |method|
10
+ instance_eval <<-RUBY
11
+ def #{method} path='/', app=app
12
+ app.call('PATH_INFO' => path, 'REQUEST_METHOD' => '#{method}'.upcase)
13
+ end
14
+ RUBY
15
+ end
16
+ end
17
+
18
+ module Kernel
19
+ def eq? rhs
20
+ self == rhs
21
+ end
22
+ end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module Jellyfish
3
- VERSION = '0.4.0'
3
+ VERSION = '0.5.0'
4
4
  end
@@ -0,0 +1,95 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
4
+ require 'builder'
5
+
6
+ class BuilderTest < Test::Unit::TestCase
7
+ def builder_app(options = {}, &block)
8
+ mock_app {
9
+ set :views, File.dirname(__FILE__) + '/views'
10
+ set options
11
+ get '/', &block
12
+ }
13
+ get '/'
14
+ end
15
+
16
+ it 'renders inline Builder strings' do
17
+ builder_app { builder 'xml.instruct!' }
18
+ assert ok?
19
+ assert_equal %{<?xml version="1.0" encoding="UTF-8"?>\n}, body
20
+ end
21
+
22
+ it 'defaults content type to xml' do
23
+ builder_app { builder 'xml.instruct!' }
24
+ assert ok?
25
+ assert_equal "application/xml;charset=utf-8", response['Content-Type']
26
+ end
27
+
28
+ it 'defaults allows setting content type per route' do
29
+ builder_app do
30
+ content_type :html
31
+ builder 'xml.instruct!'
32
+ end
33
+ assert ok?
34
+ assert_equal "text/html;charset=utf-8", response['Content-Type']
35
+ end
36
+
37
+ it 'defaults allows setting content type globally' do
38
+ builder_app(:builder => { :content_type => 'html' }) do
39
+ builder 'xml.instruct!'
40
+ end
41
+ assert ok?
42
+ assert_equal "text/html;charset=utf-8", response['Content-Type']
43
+ end
44
+
45
+ it 'renders inline blocks' do
46
+ builder_app {
47
+ @name = "Frank & Mary"
48
+ builder do |xml|
49
+ xml.couple @name
50
+ end
51
+ }
52
+ assert ok?
53
+ assert_equal "<couple>Frank &amp; Mary</couple>\n", body
54
+ end
55
+
56
+ it 'renders .builder files in views path' do
57
+ builder_app {
58
+ @name = "Blue"
59
+ builder :hello
60
+ }
61
+ assert ok?
62
+ assert_equal %(<exclaim>You're my boy, Blue!</exclaim>\n), body
63
+ end
64
+
65
+ it "renders with inline layouts" do
66
+ mock_app {
67
+ layout do
68
+ %(xml.layout { xml << yield })
69
+ end
70
+ get('/') { builder %(xml.em 'Hello World') }
71
+ }
72
+ get '/'
73
+ assert ok?
74
+ assert_equal "<layout>\n<em>Hello World</em>\n</layout>\n", body
75
+ end
76
+
77
+ it "renders with file layouts" do
78
+ builder_app {
79
+ builder %(xml.em 'Hello World'), :layout => :layout2
80
+ }
81
+ assert ok?
82
+ assert_equal "<layout>\n<em>Hello World</em>\n</layout>\n", body
83
+ end
84
+
85
+ it "raises error if template not found" do
86
+ mock_app {
87
+ get('/') { builder :no_such_template }
88
+ }
89
+ assert_raise(Errno::ENOENT) { get('/') }
90
+ end
91
+ end
92
+
93
+ rescue LoadError
94
+ warn "#{$!.to_s}: skipping builder tests"
95
+ end
@@ -0,0 +1,92 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
4
+ require 'coffee-script'
5
+ require 'execjs'
6
+
7
+ begin
8
+ ExecJS.compile '1'
9
+ rescue Exception
10
+ raise LoadError, 'unable to execute JavaScript'
11
+ end
12
+
13
+ class CoffeeTest < Test::Unit::TestCase
14
+ def coffee_app(options = {}, &block)
15
+ mock_app {
16
+ set :views, File.dirname(__FILE__) + '/views'
17
+ set(options)
18
+ get '/', &block
19
+ }
20
+ get '/'
21
+ end
22
+
23
+ it 'renders inline Coffee strings' do
24
+ coffee_app { coffee "alert 'Aye!'\n" }
25
+ assert ok?
26
+ assert body.include?("alert('Aye!');")
27
+ end
28
+
29
+ it 'defaults content type to javascript' do
30
+ coffee_app { coffee "alert 'Aye!'\n" }
31
+ assert ok?
32
+ assert_equal "application/javascript;charset=utf-8", response['Content-Type']
33
+ end
34
+
35
+ it 'defaults allows setting content type per route' do
36
+ coffee_app do
37
+ content_type :html
38
+ coffee "alert 'Aye!'\n"
39
+ end
40
+ assert ok?
41
+ assert_equal "text/html;charset=utf-8", response['Content-Type']
42
+ end
43
+
44
+ it 'defaults allows setting content type globally' do
45
+ coffee_app(:coffee => { :content_type => 'html' }) do
46
+ coffee "alert 'Aye!'\n"
47
+ end
48
+ assert ok?
49
+ assert_equal "text/html;charset=utf-8", response['Content-Type']
50
+ end
51
+
52
+ it 'renders .coffee files in views path' do
53
+ coffee_app { coffee :hello }
54
+ assert ok?
55
+ assert_include body, "alert(\"Aye!\");"
56
+ end
57
+
58
+ it 'ignores the layout option' do
59
+ coffee_app { coffee :hello, :layout => :layout2 }
60
+ assert ok?
61
+ assert_include body, "alert(\"Aye!\");"
62
+ end
63
+
64
+ it "raises error if template not found" do
65
+ mock_app {
66
+ get('/') { coffee :no_such_template }
67
+ }
68
+ assert_raise(Errno::ENOENT) { get('/') }
69
+ end
70
+
71
+ it "passes coffee options to the coffee engine" do
72
+ coffee_app { coffee "alert 'Aye!'\n", :no_wrap => true }
73
+ assert ok?
74
+ assert_body "alert('Aye!');"
75
+ end
76
+
77
+ it "passes default coffee options to the coffee engine" do
78
+ mock_app do
79
+ set :coffee, :no_wrap => true # default coffee style is :nested
80
+ get '/' do
81
+ coffee "alert 'Aye!'\n"
82
+ end
83
+ end
84
+ get '/'
85
+ assert ok?
86
+ assert_body "alert('Aye!');"
87
+ end
88
+ end
89
+
90
+ rescue LoadError
91
+ warn "#{$!.to_s}: skipping coffee tests"
92
+ end