jellyfish 0.4.0 → 0.5.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.
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