jellyfish 1.0.2 → 1.1.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.
data/config.ru CHANGED
@@ -17,20 +17,6 @@ class Jelly
17
17
  end
18
18
  }
19
19
 
20
- def self.info
21
- {:title => 'Jellyfish Swagger UI',
22
- :description =>
23
- 'This is a simple example for using Jellyfish and' \
24
- ' Swagger UI altogether. You could also try the' \
25
- ' <a href="http://swagger.wordnik.com/">official Swagger UI app</a>,' \
26
- ' and fill it with the swagger URL.'
27
- }
28
- end
29
-
30
- def self.swagger_apiVersion
31
- '1.0.0'
32
- end
33
-
34
20
  handle Jellyfish::NotFound do |e|
35
21
  status 404
36
22
  body %Q|{"error":{"name":"NotFound"}}\n|
@@ -46,30 +32,15 @@ class Jelly
46
32
  body render('error' => {'name' => name, 'message' => message})
47
33
  end
48
34
 
49
- get '/users',
50
- :summary => 'List users',
51
- :notes => 'Note that we do not really have users.' do
35
+ get '/users' do
52
36
  render [:name => 'jellyfish']
53
37
  end
54
38
 
55
- post '/users',
56
- :summary => 'Create a user',
57
- :notes => 'Here we demonstrate how to write the swagger doc.',
58
- :parameters => {:name => {:type => :string, :required => true,
59
- :description => 'The name of the user'},
60
- :sane => {:type => :boolean,
61
- :description => 'If the user is sane'},
62
- :type => {:type => :string,
63
- :description => 'What kind of user',
64
- :enum => %w[good neutral evil]}},
65
- :responseMessages => [{:code => 400, :message => 'Invalid name'}] do
39
+ post '/users' do
66
40
  render :message => "jellyfish #{request.params['name']} created."
67
41
  end
68
42
 
69
- put %r{\A/users/(?<id>\d+)},
70
- :summary => 'Update a user',
71
- :parameters => {:id => {:type => :integer,
72
- :description => 'The id of the user'}} do |match|
43
+ put %r{\A/users/(?<id>\d+)} do |match|
73
44
  render :message => "jellyfish ##{match[:id]} updated."
74
45
  end
75
46
 
@@ -77,8 +48,7 @@ class Jelly
77
48
  render :message => "jellyfish ##{match[:id]} deleted."
78
49
  end
79
50
 
80
- get %r{\A/posts/(?<year>\d+)-(?<month>\d+)/(?<name>\w+)},
81
- :summary => 'Get a post' do |match|
51
+ get %r{\A/posts/(?<year>\d+)-(?<month>\d+)/(?<name>\w+)} do |match|
82
52
  render Hash[match.names.zip(match.captures)]
83
53
  end
84
54
 
@@ -87,16 +57,12 @@ class Jelly
87
57
  end
88
58
  end
89
59
 
90
- App = Rack::Builder.app do
60
+ App = Jellyfish::Builder.app do
91
61
  use Rack::CommonLogger
92
62
  use Rack::Chunked
93
63
  use Rack::ContentLength
94
64
  use Rack::Deflater
95
65
 
96
- map '/swagger' do
97
- run Jellyfish::Swagger.new('', Jelly)
98
- end
99
-
100
66
  run Rack::Cascade.new([Rack::File.new('public/index.html'),
101
67
  Rack::File.new('public'),
102
68
  Jelly.new])
@@ -1,15 +1,15 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: jellyfish 1.0.2 ruby lib
2
+ # stub: jellyfish 1.1.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "jellyfish"
6
- s.version = "1.0.2"
6
+ s.version = "1.1.0"
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib"]
10
10
  s.authors = ["Lin Jen-Shin (godfat)"]
11
- s.date = "2014-12-09"
12
- s.description = "Pico web framework for building API-centric web applications.\nFor Rack applications or Rack middlewares. Around 250 lines of code."
11
+ s.date = "2015-09-25"
12
+ s.description = "Pico web framework for building API-centric web applications.\nFor Rack applications or Rack middleware. Around 250 lines of code.\n\nCheck [jellyfish-contrib][] for extra extensions.\n\n[jellyfish-contrib]: https://github.com/godfat/jellyfish-contrib"
13
13
  s.email = ["godfat (XD) godfat.org"]
14
14
  s.files = [
15
15
  ".gitignore",
@@ -21,59 +21,53 @@ Gem::Specification.new do |s|
21
21
  "README.md",
22
22
  "Rakefile",
23
23
  "TODO.md",
24
+ "bench/bench_builder.rb",
24
25
  "config.ru",
25
26
  "jellyfish.gemspec",
26
27
  "jellyfish.png",
27
28
  "lib/jellyfish.rb",
29
+ "lib/jellyfish/builder.rb",
28
30
  "lib/jellyfish/chunked_body.rb",
29
31
  "lib/jellyfish/json.rb",
30
- "lib/jellyfish/multi_actions.rb",
31
32
  "lib/jellyfish/newrelic.rb",
32
33
  "lib/jellyfish/normalized_params.rb",
33
34
  "lib/jellyfish/normalized_path.rb",
34
35
  "lib/jellyfish/public/302.html",
35
36
  "lib/jellyfish/public/404.html",
36
37
  "lib/jellyfish/public/500.html",
37
- "lib/jellyfish/sinatra.rb",
38
- "lib/jellyfish/swagger.rb",
39
38
  "lib/jellyfish/test.rb",
39
+ "lib/jellyfish/urlmap.rb",
40
40
  "lib/jellyfish/version.rb",
41
41
  "lib/jellyfish/websocket.rb",
42
- "public/css/screen.css",
43
- "public/index.html",
44
- "public/js/shred.bundle.js",
45
- "public/js/shred/content.js",
46
- "public/js/swagger-ui.js",
47
- "public/js/swagger.js",
48
42
  "task/README.md",
49
43
  "task/gemgem.rb",
44
+ "test/rack/test_builder.rb",
45
+ "test/rack/test_urlmap.rb",
50
46
  "test/sinatra/test_base.rb",
51
47
  "test/sinatra/test_chunked_body.rb",
52
48
  "test/sinatra/test_error.rb",
53
- "test/sinatra/test_multi_actions.rb",
54
49
  "test/sinatra/test_routing.rb",
55
50
  "test/test_from_readme.rb",
56
51
  "test/test_inheritance.rb",
57
52
  "test/test_log.rb",
58
53
  "test/test_misc.rb",
59
- "test/test_swagger.rb",
60
54
  "test/test_threads.rb",
61
55
  "test/test_websocket.rb"]
62
56
  s.homepage = "https://github.com/godfat/jellyfish"
63
57
  s.licenses = ["Apache License 2.0"]
64
- s.rubygems_version = "2.4.5"
58
+ s.rubygems_version = "2.4.8"
65
59
  s.summary = "Pico web framework for building API-centric web applications."
66
60
  s.test_files = [
61
+ "test/rack/test_builder.rb",
62
+ "test/rack/test_urlmap.rb",
67
63
  "test/sinatra/test_base.rb",
68
64
  "test/sinatra/test_chunked_body.rb",
69
65
  "test/sinatra/test_error.rb",
70
- "test/sinatra/test_multi_actions.rb",
71
66
  "test/sinatra/test_routing.rb",
72
67
  "test/test_from_readme.rb",
73
68
  "test/test_inheritance.rb",
74
69
  "test/test_log.rb",
75
70
  "test/test_misc.rb",
76
- "test/test_swagger.rb",
77
71
  "test/test_threads.rb",
78
72
  "test/test_websocket.rb"]
79
73
  end
@@ -1,14 +1,12 @@
1
1
 
2
2
  module Jellyfish
3
3
  autoload :VERSION , 'jellyfish/version'
4
- autoload :Sinatra , 'jellyfish/sinatra'
5
- autoload :Swagger , 'jellyfish/swagger'
6
4
  autoload :NewRelic, 'jellyfish/newrelic'
7
5
 
8
- autoload :MultiActions , 'jellyfish/multi_actions'
9
6
  autoload :NormalizedParams, 'jellyfish/normalized_params'
10
7
  autoload :NormalizedPath , 'jellyfish/normalized_path'
11
8
 
9
+ autoload :Builder , 'jellyfish/builder'
12
10
  autoload :ChunkedBody, 'jellyfish/chunked_body'
13
11
  autoload :WebSocket , 'jellyfish/websocket'
14
12
 
@@ -0,0 +1,52 @@
1
+
2
+ require 'jellyfish/urlmap'
3
+
4
+ module Jellyfish
5
+ class Builder
6
+ def self.app app=nil, &block
7
+ new(app, &block).to_app
8
+ end
9
+
10
+ def initialize app=nil, &block
11
+ @use, @map, @run, @warmup = [], nil, app, nil
12
+ instance_eval(&block) if block_given?
13
+ end
14
+
15
+ def use middleware, *args, &block
16
+ if @map
17
+ current_map, @map = @map, nil
18
+ @use.unshift(lambda{ |app| generate_map(current_map, app) })
19
+ end
20
+ @use.unshift(lambda{ |app| middleware.new(app, *args, &block) })
21
+ end
22
+
23
+ def run app
24
+ @run = app
25
+ end
26
+
27
+ def warmup lam=nil, &block
28
+ @warmup = lam || block
29
+ end
30
+
31
+ def map path, &block
32
+ (@map ||= {})[path] = block
33
+ end
34
+
35
+ def to_app
36
+ run = if @map then generate_map(@map, @run) else @run end
37
+ fail 'missing run or map statement' unless run
38
+ app = @use.inject(run){ |a, m| m.call(a) }
39
+ @warmup.call(app) if @warmup
40
+ app
41
+ end
42
+
43
+ private
44
+ def generate_map current_map, app
45
+ mapped = if app then {'' => app} else {} end
46
+ current_map.each do |path, block|
47
+ mapped[path.chomp('/')] = self.class.app(app, &block)
48
+ end
49
+ URLMap.new(mapped)
50
+ end
51
+ end
52
+ end
@@ -9,15 +9,15 @@ Pork::Executor.__send__(:include, Muack::API)
9
9
  copy :jellyfish do
10
10
  module_eval(%w[options get head post put delete patch].map{ |method|
11
11
  <<-RUBY
12
- def #{method} path='/', app=app, env={}
12
+ def #{method} path='/', a=app, env={}
13
13
  File.open(File::NULL) do |input|
14
- app.call({'PATH_INFO' => path ,
15
- 'REQUEST_METHOD' => '#{method}'.upcase,
16
- 'SCRIPT_NAME' => '' ,
17
- 'rack.input' => input ,
18
- 'rack.url_scheme'=> 'https' ,
19
- 'SERVER_NAME' => 'localhost' ,
20
- 'SERVER_PORT' => '8080'}.merge(env))
14
+ a.call({'PATH_INFO' => path ,
15
+ 'REQUEST_METHOD' => '#{method}'.upcase,
16
+ 'SCRIPT_NAME' => '' ,
17
+ 'rack.input' => input ,
18
+ 'rack.url_scheme'=> 'https' ,
19
+ 'SERVER_NAME' => 'localhost' ,
20
+ 'SERVER_PORT' => '8080'}.merge(env))
21
21
  end
22
22
  end
23
23
  RUBY
@@ -0,0 +1,26 @@
1
+
2
+ module Jellyfish
3
+ class URLMap
4
+ def initialize mapped
5
+ string = mapped.keys.sort_by{ |k| -k.size }.
6
+ map{ |k| Regexp.escape(k).gsub('/', '/+') }.
7
+ join('|')
8
+
9
+ @mapped = mapped
10
+ @routes = Regexp.new("\\A(?:#{string})(?:/|\\z)", 'i', 'n')
11
+ end
12
+
13
+ def call env
14
+ path_info = env['PATH_INFO']
15
+ matched = @routes.match(path_info).to_s.chomp('/')
16
+ squeezed = matched.squeeze('/')
17
+
18
+ if app = @mapped[squeezed]
19
+ app.call(env.merge('PATH_INFO' => path_info[matched.size..-1],
20
+ 'SCRIPT_NAME' => env['SCRIPT_NAME'] + squeezed))
21
+ else
22
+ [404, {}, []]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module Jellyfish
3
- VERSION = '1.0.2'
3
+ VERSION = '1.1.0'
4
4
  end
@@ -79,6 +79,11 @@ module Gemgem
79
79
  end
80
80
 
81
81
  def gem_check
82
+ unless git('status', '--porcelain').empty?
83
+ puts("\e[35mWorking copy is not clean.\e[0m")
84
+ exit(3)
85
+ end
86
+
82
87
  ver = spec.version.to_s
83
88
 
84
89
  if ENV['VERSION'].nil?
@@ -100,8 +105,12 @@ module Gemgem
100
105
  if ENV['COV'] || ENV['CI']
101
106
  require 'simplecov'
102
107
  if ENV['CI']
103
- require 'coveralls'
104
- SimpleCov.formatter = Coveralls::SimpleCov::Formatter
108
+ begin
109
+ require 'coveralls'
110
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
111
+ rescue LoadError => e
112
+ puts "Cannot load coveralls, skip: #{e}"
113
+ end
105
114
  end
106
115
  SimpleCov.start do
107
116
  add_filter('test/')
@@ -0,0 +1,154 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ require 'rack/lint'
5
+ require 'rack/mock'
6
+ require 'rack/showexceptions'
7
+ require 'rack/urlmap'
8
+
9
+ describe Jellyfish::Builder do
10
+ class NothingMiddleware
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+ def call(env)
15
+ @@env = env
16
+ response = @app.call(env)
17
+ response
18
+ end
19
+ def self.env
20
+ @@env
21
+ end
22
+ end
23
+
24
+ def builder_to_app(&block)
25
+ Rack::Lint.new Jellyfish::Builder.app(&block)
26
+ end
27
+
28
+ would "supports mapping" do
29
+ app = builder_to_app do
30
+ map '/' do |outer_env|
31
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
32
+ end
33
+ map '/sub' do
34
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
35
+ end
36
+ end
37
+ Rack::MockRequest.new(app).get("/").body.to_s.should.eq 'root'
38
+ Rack::MockRequest.new(app).get("/sub").body.to_s.should.eq 'sub'
39
+ end
40
+
41
+ would "chains apps by default" do
42
+ app = builder_to_app do
43
+ use Rack::ShowExceptions
44
+ run lambda { |env| raise "bzzzt" }
45
+ end
46
+
47
+ Rack::MockRequest.new(app).get("/").should.server_error?
48
+ Rack::MockRequest.new(app).get("/").should.server_error?
49
+ Rack::MockRequest.new(app).get("/").should.server_error?
50
+ end
51
+
52
+ would "supports blocks on use" do
53
+ app = builder_to_app do
54
+ use Rack::ShowExceptions
55
+ use Rack::Auth::Basic do |username, password|
56
+ 'secret' == password
57
+ end
58
+
59
+ run lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hi Boss']] }
60
+ end
61
+
62
+ response = Rack::MockRequest.new(app).get("/")
63
+ response.should.client_error?
64
+ response.status.should.eq 401
65
+
66
+ # with auth...
67
+ response = Rack::MockRequest.new(app).get("/",
68
+ 'HTTP_AUTHORIZATION' => 'Basic ' + ["joe:secret"].pack("m*"))
69
+ response.status.should.eq 200
70
+ response.body.to_s.should.eq 'Hi Boss'
71
+ end
72
+
73
+ would "has explicit #to_app" do
74
+ app = builder_to_app do
75
+ use Rack::ShowExceptions
76
+ run lambda { |env| raise "bzzzt" }
77
+ end
78
+
79
+ Rack::MockRequest.new(app).get("/").should.server_error?
80
+ Rack::MockRequest.new(app).get("/").should.server_error?
81
+ Rack::MockRequest.new(app).get("/").should.server_error?
82
+ end
83
+
84
+ would "can mix map and run for endpoints" do
85
+ app = builder_to_app do
86
+ map '/sub' do
87
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
88
+ end
89
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
90
+ end
91
+
92
+ Rack::MockRequest.new(app).get("/").body.to_s.should.eq 'root'
93
+ Rack::MockRequest.new(app).get("/sub").body.to_s.should.eq 'sub'
94
+ end
95
+
96
+ would "accepts middleware-only map blocks" do
97
+ app = builder_to_app do
98
+ map('/foo') { use Rack::ShowExceptions }
99
+ run lambda { |env| raise "bzzzt" }
100
+ end
101
+
102
+ proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError)
103
+ Rack::MockRequest.new(app).get("/foo").should.server_error?
104
+ end
105
+
106
+ would "yields the generated app to a block for warmup" do
107
+ warmed_up_app = nil
108
+
109
+ app = Rack::Builder.new do
110
+ warmup { |a| warmed_up_app = a }
111
+ run lambda { |env| [200, {}, []] }
112
+ end.to_app
113
+
114
+ warmed_up_app.should.eq app
115
+ end
116
+
117
+ would "initialize apps once" do
118
+ app = builder_to_app do
119
+ class AppClass
120
+ def initialize
121
+ @called = 0
122
+ end
123
+ def call(env)
124
+ raise "bzzzt" if @called > 0
125
+ @called += 1
126
+ [200, {'Content-Type' => 'text/plain'}, ['OK']]
127
+ end
128
+ end
129
+
130
+ use Rack::ShowExceptions
131
+ run AppClass.new
132
+ end
133
+
134
+ Rack::MockRequest.new(app).get("/").status.should.eq 200
135
+ Rack::MockRequest.new(app).get("/").should.server_error?
136
+ end
137
+
138
+ would "allows use after run" do
139
+ app = builder_to_app do
140
+ run lambda { |env| raise "bzzzt" }
141
+ use Rack::ShowExceptions
142
+ end
143
+
144
+ Rack::MockRequest.new(app).get("/").should.server_error?
145
+ Rack::MockRequest.new(app).get("/").should.server_error?
146
+ Rack::MockRequest.new(app).get("/").should.server_error?
147
+ end
148
+
149
+ would 'complains about a missing run' do
150
+ proc do
151
+ Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions }
152
+ end.should.raise(RuntimeError)
153
+ end
154
+ end