jellyfish 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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