rack-stereoscope 1.0.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.
@@ -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