rack-stereoscope 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Avdi Grimm
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ = rack-stereoscope
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Avdi Grimm. See LICENSE for details.
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rack-stereoscope"
8
+ gem.summary = %Q{Bringing a new dimension to your RESTful API}
9
+ gem.description = %Q{Rack::Stereoscope puts an HTML face on RESTful APIs.}
10
+ gem.email = "avdi@avdi.org"
11
+ gem.homepage = "http://github.com/avdi/rack-stereoscope"
12
+ gem.authors = ["Avdi Grimm"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "rack-stereoscope #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,83 @@
1
+ require 'sinatra'
2
+ require 'json'
3
+ require 'addressable/uri'
4
+ require 'rest_client'
5
+ require File.expand_path('rack_stereoscope.rb', File.dirname(__FILE__))
6
+
7
+ configure do
8
+ use Rack::Reloader;
9
+ use Rack::Lint;
10
+ use Rack::Stereoscope;
11
+ end
12
+
13
+ helpers do
14
+ def rel(path)
15
+ host = request.host
16
+ port = request.port
17
+ Addressable::URI.join("http://#{host}:#{port}", path).to_s
18
+ end
19
+ end
20
+
21
+ get '/' do
22
+ content_type 'application/json'
23
+ {
24
+ :explanation => "A fake API to demonstrate Stereoscope",
25
+ :twitter => rel('/twitter'),
26
+ :list => rel('/list'),
27
+ :assocations => rel('/associations'),
28
+ :uri_template => rel('/uri_template'),
29
+ :tabular => rel('/tabular')
30
+ }.to_json
31
+ end
32
+
33
+ get '/foo/*' do
34
+ content_type 'application/json'
35
+ params.to_json
36
+ end
37
+
38
+ get '/list' do
39
+ content_type 'application/json'
40
+ [
41
+ "Item 1",
42
+ "Item 2",
43
+ "Item 3"
44
+ ].to_json
45
+ end
46
+
47
+ get '/associations' do
48
+ content_type 'application/json'
49
+ {
50
+ "foo" => "bar",
51
+ "baz" => "buz"
52
+ }.to_json
53
+ end
54
+
55
+ get '/tabular' do
56
+ content_type 'application/json'
57
+ [
58
+ {
59
+ :id => 1,
60
+ :name => "Plan 9 from Outer Space",
61
+ :date => "1959-07-01"
62
+ },
63
+ {
64
+ :id => 2,
65
+ :name => "Bride of the Monster",
66
+ :date => "1956-05-11"
67
+ },
68
+ { :id => 3,
69
+ :name => "Glen or Glenda",
70
+ :date => "1953-01-01"
71
+ }
72
+ ].to_json
73
+ end
74
+
75
+ get '/uri_template' do
76
+ content_type 'application/json'
77
+ {:uri => rel('/foo/{subpath}?param1={param1}&param2={param2}')}.to_json
78
+ end
79
+
80
+ get '/twitter' do
81
+ content_type 'application/json'
82
+ RestClient.get('http://twitter.com/statuses/public_timeline.json')
83
+ end
@@ -0,0 +1,227 @@
1
+ require 'markaby'
2
+ require 'json'
3
+ require 'nokogiri'
4
+ require 'rack/accept_media_types'
5
+ require 'addressable/template'
6
+
7
+ # Rack::Stereoscope - bringing a new dimension to your RESTful API
8
+ #
9
+ # Stereoscope is inspired by the idea that software should be explorable. Put
10
+ # stereoscope in front of your RESTful API, and you get an interactive,
11
+ # explorable HTML interface to your API for free. Use it to manually test your
12
+ # API from a browser. Use it to make your API self-documenting. Use it to
13
+ # quickly prototype new API features and get a visual feel for the data
14
+ # structures.
15
+ #
16
+ # Stereoscope is designed to be unobtrusive. It will not interpose itself unless
17
+ # the request asks for HTML (i.e. it comes from a browser). If the request
18
+ # requests no explicit content type; or if it requests a content-type other than
19
+ # HTML, Stereoscope stays out of the way.
20
+ #
21
+ # This middleware is especially well-suited to presenting APIs that are heavily
22
+ # hyperlinked (and if your API doesn't have hyperlinks, why
23
+ # not?[1]). Stereoscope does it's best to recognize URLs and make them
24
+ # clickable. What's more, Stereoscope supports URI Templates[2]. If your data
25
+ # includes URL templates such as the following:
26
+ #
27
+ # http://example.org/{foo}?bar={bar}
28
+ #
29
+ # Stereoscope will render a form which enables the user to experiment with
30
+ # different expansions of the URI template.
31
+ #
32
+ # Limitations:
33
+ # * Currently only supports JSON data
34
+ # * Only link-ifies fully-qualified URLs; relative URLs are not supported
35
+ # * Read-only exploration; no support for POSTs, PUTs, or DELETEs.
36
+ #
37
+ # [1] http://www.theamazingrando.com/blog/?p=107
38
+ # [2] http://bitworking.org/projects/URI-Templates/
39
+ module Rack
40
+ class Stereoscope
41
+ def initialize(app)
42
+ @app = app
43
+ end
44
+
45
+ def call(env)
46
+ request = Rack::Request.new(env)
47
+ if Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT']).include?('text/html')
48
+ status, headers, body = @app.call(env)
49
+ if request.path == '/__stereoscope_expand_template__'
50
+ expand_template(request)
51
+ else
52
+ present_data(request, status, headers, body)
53
+ end
54
+ else
55
+ @app.call(env)
56
+ end
57
+ end
58
+
59
+ def present_data(request, status, headers, body)
60
+ response = Rack::Response.new("", status, headers)
61
+ response.write(build_page(body, request, response))
62
+ response['Content-Type'] = 'text/html'
63
+ response.finish
64
+ end
65
+
66
+ def expand_template(request)
67
+ template = Addressable::Template.new(request['__template__'])
68
+ url = template.expand(request.params)
69
+ response = Rack::Response.new
70
+ response.redirect(url.to_s)
71
+ response.finish
72
+ end
73
+
74
+ def build_page(content, request, response)
75
+ this = self
76
+ mab = Markaby::Builder.new
77
+ mab.html do
78
+ head do
79
+ title request.path
80
+ end
81
+ body do
82
+
83
+ h1 "#{response.status} #{request.url}"
84
+ if !content.to_s.empty?
85
+ h2 "Response:"
86
+ case response.content_type
87
+ when 'application/json' then
88
+ div do
89
+ this.data_to_html(JSON.parse(content.join), mab)
90
+ end
91
+ when 'text/plain' then
92
+ p content.join
93
+ else
94
+ text Nokogiri::HTML(content.join).css('body').inner_html
95
+ end
96
+ else
97
+ p "(No content)"
98
+ end
99
+ h2 "Raw:"
100
+ tt do
101
+ raw_content = case response.content_type
102
+ when 'application/json'
103
+ JSON.pretty_generate(JSON.parse(content.join))
104
+ else
105
+ content.join
106
+ end
107
+ pre raw_content
108
+ end
109
+ end
110
+ end
111
+ mab.to_s
112
+ end
113
+
114
+ def data_to_html(data, builder)
115
+ this = self
116
+ case data
117
+ when Hash
118
+ builder.dl do
119
+ data.each_pair do |key, value|
120
+ dt do
121
+ this.data_to_html(key, builder)
122
+ end
123
+ dd do
124
+ this.data_to_html(value, builder)
125
+ end
126
+ end
127
+ end
128
+ when Array
129
+ if tabular?(data)
130
+ table_to_html(data, builder)
131
+ else
132
+ list_to_html(data, builder)
133
+ end
134
+ when String
135
+ if url?(data)
136
+ if url_template?(data)
137
+ template_to_html(data, builder)
138
+ else
139
+ url_to_html(data, builder)
140
+ end
141
+ else
142
+ builder.div do
143
+ data.split("\n").each do |line|
144
+ builder.span line
145
+ builder.br
146
+ end
147
+ end
148
+ end
149
+ else
150
+ builder.span do data end
151
+ end
152
+ end
153
+
154
+ def url?(text)
155
+ Addressable::URI.parse(text.to_s).ip_based?
156
+ end
157
+
158
+ def url_template?(text)
159
+ !Addressable::Template.new(text.to_s).variables.empty?
160
+ end
161
+
162
+ def tabular?(data)
163
+ data.kind_of?(Array) &&
164
+ data.all?{|e| e.kind_of?(Hash)} &&
165
+ data[1..-1].all?{|e| e.keys == data.first.keys}
166
+ end
167
+
168
+ def url_to_html(url, builder)
169
+ builder.a(url.to_s, :href => url.to_s)
170
+ end
171
+
172
+ def template_to_html(text, builder)
173
+ template = Addressable::Template.new(text)
174
+ builder.div(:class => 'url-template-form') do
175
+ p text
176
+ form(:method => 'GET', :action => '/__stereoscope_expand_template__') do
177
+ input(:type => 'hidden', :name => '__template__', :value => text)
178
+ template.variables.each do |variable|
179
+ div(:class => 'url-template-variable') do
180
+ label do
181
+ text "#{variable}: "
182
+ input(:type => 'text', :name => variable)
183
+ end
184
+ end
185
+ end
186
+ input(:type => 'submit')
187
+ end
188
+ end
189
+ end
190
+
191
+ def list_to_html(data, builder)
192
+ this = self
193
+ builder.ol do
194
+ data.each do |value|
195
+ li do
196
+ this.data_to_html(value, builder)
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ def table_to_html(data, builder)
203
+ this = self
204
+ builder.table do
205
+ headers = data.first.keys
206
+ thead do
207
+ headers.each do |header|
208
+ th do
209
+ this.data_to_html(header, builder)
210
+ end
211
+ end
212
+ end
213
+ tbody do
214
+ data.each do |row|
215
+ tr do
216
+ row.each do |key, value|
217
+ td do
218
+ this.data_to_html(value, builder)
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "RackStereoscope" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rack-stereoscope'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-stereoscope
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Avdi Grimm
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-15 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.9
24
+ version:
25
+ description: Rack::Stereoscope puts an HTML face on RESTful APIs.
26
+ email: avdi@avdi.org
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - example.rb
42
+ - lib/rack/stereoscope.rb
43
+ - spec/rack-stereoscope_spec.rb
44
+ - spec/spec.opts
45
+ - spec/spec_helper.rb
46
+ has_rdoc: true
47
+ homepage: http://github.com/avdi/rack-stereoscope
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.3.5
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Bringing a new dimension to your RESTful API
74
+ test_files:
75
+ - spec/rack-stereoscope_spec.rb
76
+ - spec/spec_helper.rb