sinatra-api-docs 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ require "rspec"
4
+ require "rspec/core/rake_task"
5
+
6
+ $:.unshift File.expand_path("../lib", __FILE__)
7
+ require "sinatra/api_docs"
8
+
9
+ task :default => :spec
10
+
11
+ desc "Run all specs"
12
+ Rspec::Core::RakeTask.new(:spec) do |t|
13
+ t.pattern = 'spec/**/*_spec.rb'
14
+ end
15
+
16
+ desc "Generate RCov code coverage report"
17
+ task :rcov => "rcov:build" do
18
+ %x{ open coverage/index.html }
19
+ end
20
+
21
+ Rspec::Core::RakeTask.new("rcov:build") do |t|
22
+ t.pattern = 'spec/**/*_spec.rb'
23
+ t.rcov = true
24
+ t.rcov_opts = [ "--exclude", Gem.default_dir, "--exclude", "spec" ]
25
+ end
26
+
27
+ ######################################################
28
+
29
+ begin
30
+ require 'jeweler'
31
+ Jeweler::Tasks.new do |s|
32
+ s.name = "sinatra-api-docs"
33
+ s.version = Sinatra::APIDocs::VERSION
34
+
35
+ s.summary = "Create documentation for an API built in Sinatra."
36
+ s.description = s.summary
37
+ s.author = "David Dollar"
38
+ s.email = "ddollar@gmail.com"
39
+ s.homepage = "http://daviddollar.org/"
40
+
41
+ s.platform = Gem::Platform::RUBY
42
+ s.has_rdoc = false
43
+
44
+ s.files = %w(Rakefile README.markdown) + Dir["{lib,spec}/**/*"]
45
+ s.require_path = "lib"
46
+
47
+ s.add_development_dependency 'rake', '~> 0.8.7'
48
+ s.add_development_dependency 'rspec', '~> 2.0.0.beta.5'
49
+
50
+ s.add_dependency 'haml', '~> 3.0.12'
51
+ s.add_dependency 'json_pure', '~> 1.4.3'
52
+ s.add_dependency 'sinatra', '~> 1.0'
53
+ end
54
+ Jeweler::GemcutterTasks.new
55
+ rescue LoadError
56
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
57
+ end
@@ -0,0 +1,99 @@
1
+ require "sinatra/base"
2
+
3
+ module Sinatra
4
+ module APIDocs
5
+ VERSION = "0.1.1"
6
+
7
+ def self.route_added(verb, path, proc)
8
+ return if verb == "HEAD"
9
+
10
+ reset_documentation unless defined?(@@api_docs_sections)
11
+
12
+ return if @@api_docs_description == ""
13
+
14
+ @@api_docs ||= []
15
+ @@api_docs << {
16
+ :url => "#{verb} #{path}",
17
+ :section => @@api_docs_section,
18
+ :description => @@api_docs_description,
19
+ :params => @@api_docs_params,
20
+ :request => @@api_docs_request,
21
+ :response => @@api_docs_response
22
+ }
23
+
24
+ @@api_docs_description = nil
25
+ @@api_docs_params = []
26
+ @@api_docs_request = nil
27
+ @@api_docs_response = nil
28
+ end
29
+
30
+ def self.reset_documentation
31
+ @@api_docs_sections = []
32
+ @@api_docs_section = ""
33
+ @@api_docs_description = ""
34
+ @@api_docs_params = []
35
+ @@api_docs_request = ""
36
+ @@api_docs_response = ""
37
+ end
38
+
39
+ def reset_documentation
40
+ @@api_docs_sections = []
41
+ @@api_docs_section = ""
42
+ @@api_docs_description = ""
43
+ @@api_docs_params = []
44
+ @@api_docs_request = ""
45
+ @@api_docs_response = ""
46
+ end
47
+
48
+ def documentation
49
+ @@api_docs
50
+ end
51
+
52
+ def sections
53
+ @@api_docs_sections
54
+ end
55
+
56
+ def section(name=nil)
57
+ if name then
58
+ @@api_docs_section = name
59
+ @@api_docs_sections ||= []
60
+ @@api_docs_sections << name
61
+ end
62
+
63
+ @@api_docs_section
64
+ end
65
+
66
+ def desc(description=nil)
67
+ @@api_docs_description = description if description
68
+ @@api_docs_description
69
+ end
70
+
71
+ def param(name, desc=nil)
72
+ @@api_docs_params ||= []
73
+ @@api_docs_params << [name, desc] if desc
74
+ @@api_docs_params.detect { |d| d.first == name }.last
75
+ end
76
+
77
+ def request(body=nil)
78
+ @@api_docs_request = strip_left(body) if body
79
+ @@api_docs_request
80
+ end
81
+
82
+ def response(body=nil)
83
+ @@api_docs_response = strip_left(body) if body
84
+ @@api_docs_response
85
+ end
86
+
87
+ def strip_left(code)
88
+ first_line = code.split("\n").first
89
+ num_spaces = first_line.match(/\A */)[0].length
90
+ code.split("\n").map do |line|
91
+ line[num_spaces..-1]
92
+ end.join("\n")
93
+ end
94
+
95
+ extend self
96
+ end
97
+
98
+ register APIDocs
99
+ end
@@ -0,0 +1,109 @@
1
+ require "haml"
2
+ require "json/pure"
3
+ require "sinatra/api_docs"
4
+ require "sinatra/base"
5
+
6
+ class Sinatra::APIDocs::Viewer < Sinatra::Base
7
+
8
+ register Sinatra::APIDocs
9
+
10
+ def initialize(api_klass)
11
+ self.class.set :api_klass, api_klass
12
+ self.class.set :version, api_klass.name.to_s.split("::").last.downcase
13
+ self.class.set :public, File.expand_path("../public", __FILE__)
14
+ self.class.set :views, File.expand_path("../views", __FILE__)
15
+ super()
16
+ end
17
+
18
+ helpers do
19
+ def code(code)
20
+ Haml::Filters::Preserve.render(Haml::Filters::Escaped.render(code))
21
+ end
22
+
23
+ def doc(page, title)
24
+ active = (env["PATH_INFO"] == "/#{page}") ? "active" : ""
25
+ %{ <li class="#{active}"><a href="/#{page}">#{title}</a></li> }
26
+ end
27
+
28
+ def docs
29
+ settings.api_klass.documentation
30
+ end
31
+
32
+ def docs_by_section(section)
33
+ docs.select { |doc| titleify(doc[:section]) == titleify(section) }
34
+ end
35
+
36
+ def render_docs(docs)
37
+ haml :page, :locals => { :docs => docs, :sections => sections }
38
+ end
39
+
40
+ def sections
41
+ settings.api_klass.sections
42
+ end
43
+
44
+ def titleify(name)
45
+ name.downcase.gsub(' ', '_')
46
+ end
47
+ end
48
+
49
+ section "API"
50
+
51
+ desc "API documentation in JSON format"
52
+
53
+ param "section", "Only return documentation for a particular section"
54
+
55
+ request "GET /docs.json"
56
+ response <<-RESPONSE
57
+ [
58
+ {
59
+ "section":"API",
60
+ "url":"GET /docs.json",
61
+ "params":[],
62
+ "request":"GET /docs.json",
63
+ "response":"response_example",
64
+ "description":"API documentation in JSON format"
65
+ }
66
+ ]
67
+ RESPONSE
68
+
69
+ get "/docs.json" do
70
+ if params[:section] then
71
+ docs_by_section(params[:section]).to_json
72
+ else
73
+ docs.to_json
74
+ end
75
+ end
76
+
77
+ get "/assets/stylesheets.css" do
78
+ content_type "text/css"
79
+ sass :documentation
80
+ end
81
+
82
+ get "/assets/scripts.js" do
83
+ content_type "text/javascript"
84
+ File.read("#{settings.views}/documentation.js")
85
+ end
86
+
87
+ get "/" do
88
+ render_docs docs_by_section(nil)
89
+ end
90
+
91
+ get "/:section" do |section|
92
+ render_docs docs_by_section(section)
93
+ end
94
+
95
+ private ######################################################################
96
+
97
+ def markdown(template)
98
+ RDiscount.new(File.read("#{settings.views}/#{template}.md")).to_html
99
+ end
100
+
101
+ end
102
+
103
+ module Haml::Filters::Code
104
+ include Haml::Filters::Base
105
+
106
+ def render(text)
107
+ Haml::Filters::Preserve.render(Haml::Filters::Escaped.render(text))
108
+ end
109
+ end
@@ -0,0 +1,142 @@
1
+ *
2
+ :font-family Helvetica, Arial, sans-serif
3
+
4
+ =code_title
5
+ :display block
6
+ :padding 6px 10px
7
+ :font-weight bold
8
+ :background #999
9
+ :color #333
10
+ :border-bottom 1px #000 solid
11
+ :margin-left -12px
12
+ :margin-right -12px
13
+ :margin-top -12px
14
+ :margin-bottom 12px
15
+
16
+ .api
17
+ :padding-bottom 24px
18
+ :border-bottom 1px #666 dashed
19
+ :margin-bottom 24px
20
+
21
+ .url
22
+ :padding 6px 10px
23
+ :font-size 24px
24
+ :background #555
25
+ :color #eee
26
+ :font-weight bold
27
+ :border 1px #333 solid
28
+ :font-family Monaco, monospace
29
+
30
+ .description
31
+ :padding 12px
32
+
33
+ code
34
+ :font-family Monaco, monospace
35
+ :white-space pre
36
+ :background #ccc
37
+ :border 1px #333 solid
38
+ :padding 12px
39
+ :display block
40
+
41
+ .params
42
+ :margin-bottom 10px
43
+ :background #ccc
44
+ :border 1px #333 solid
45
+ :padding 12px
46
+ :display block
47
+ &:before
48
+ +code_title
49
+ :content "Parameters"
50
+ .param
51
+ :line-height 30px
52
+ .name
53
+ :display inline
54
+ :font-weight bold
55
+ .description
56
+ :display inline
57
+
58
+ .request
59
+ :margin-bottom 10px
60
+ &:before
61
+ +code_title
62
+ :content "Request"
63
+
64
+ .response
65
+ &:before
66
+ +code_title
67
+ :content "Response"
68
+
69
+ body
70
+ :margin 0
71
+
72
+ header
73
+ :background-color #333
74
+ :color #ccc
75
+ :padding 8px 16px
76
+
77
+ a
78
+ :color #ccf
79
+ :margin-left 10px
80
+
81
+ .title
82
+ :font-size 30px
83
+ :font-weight bold
84
+ :display inline
85
+
86
+ .version, .format, .search
87
+ :float right
88
+ :margin-top 8px
89
+ :margin-left 30px
90
+
91
+ .version
92
+ .number
93
+ :display inline
94
+ :font-weight bold
95
+
96
+ .search
97
+ :margin-left 60px
98
+ #search
99
+ :font-size 18px
100
+ :margin-left 6px
101
+ :margin-top -4px
102
+ :width 200px
103
+
104
+ #toc
105
+ :position absolute
106
+ :top 51px
107
+ :bottom 0px
108
+ :left 0px
109
+ :float left
110
+ :width 300px
111
+ :background eee
112
+ :padding 0
113
+ :padding-top 0px
114
+ :margin 0
115
+ :background #999
116
+ li.title
117
+ :background #aaa
118
+ :color #333
119
+ li
120
+ :background #ccc
121
+ :color #333
122
+ :font-weight bold
123
+ :font-size 20px
124
+ :list-style-type none
125
+ :border 1px #999 solid
126
+ :border-right 2px #999 solid
127
+ :padding 10px
128
+ &.active
129
+ :background #fff
130
+ :border-right 0
131
+ a
132
+ :color #333
133
+ :font-weight bold
134
+
135
+ #content
136
+ :position absolute
137
+ :left 301px
138
+ :top 51px
139
+ :right 0px
140
+ :bottom 0px
141
+ :overflow auto
142
+ :padding 20px
@@ -0,0 +1,27 @@
1
+ !!! 5
2
+
3
+ %html
4
+
5
+ %head
6
+ %title Heroku API Documentation
7
+ %link{ :rel => "stylesheet", :href => "/assets/stylesheets.css" }
8
+ %script{ :type => "text/javascript", :src => "/assets/scripts.js" }
9
+
10
+ %body
11
+ %header
12
+ .title
13
+ Heroku API Documentation
14
+ / .search
15
+ / Search
16
+ / %input#search{ :type => "text" }
17
+ / .format
18
+ / %a{ :format => "json", :href => "javascript:void(0);" } JSON
19
+ / %a{ :format => "xml", :href => "javascript:void(0);" } XML
20
+ .version
21
+ Version:
22
+ .number= settings.version.to_s[1..-1]
23
+
24
+ = haml :toc, :layout => false, :locals => { :sections => sections }
25
+
26
+ #content
27
+ = yield
@@ -0,0 +1,22 @@
1
+ - docs.each do |doc|
2
+
3
+ .api
4
+
5
+ .url
6
+ = doc[:url]
7
+
8
+ .description
9
+ = doc[:description]
10
+
11
+ - if doc[:params].length > 0
12
+ .params
13
+ - doc[:params].each do |name, description|
14
+ .param
15
+ .name= name
16
+ .description= description
17
+
18
+ %code.request
19
+ = code doc[:request]
20
+
21
+ %code.response
22
+ = code doc[:response]
@@ -0,0 +1,3 @@
1
+ %ul#toc
2
+ - sections.each do |section|
3
+ = doc titleify(section), section
@@ -0,0 +1,59 @@
1
+ require "spec_helper"
2
+ require "sinatra/api_docs"
3
+ require "sinatra/base"
4
+
5
+ class MockSinatraApp < Sinatra::Base
6
+ register Sinatra::APIDocs
7
+ end
8
+
9
+ describe Sinatra::APIDocs do
10
+ let(:app) { Class.new(MockSinatraApp) }
11
+
12
+ before(:each) { app.reset_documentation }
13
+
14
+ it "can add a section" do
15
+ app.section "test_section"
16
+ app.section.should == "test_section"
17
+ end
18
+
19
+ it "can set a description" do
20
+ app.desc "test_description"
21
+ app.desc.should == "test_description"
22
+ end
23
+
24
+ it "can set params" do
25
+ app.param "test_param", "test_param_value"
26
+ app.param("test_param").should == "test_param_value"
27
+ end
28
+
29
+ it "can set a request body" do
30
+ app.request "test_request"
31
+ app.request.should == "test_request"
32
+ end
33
+
34
+ it "can set a response body" do
35
+ app.response "test_response"
36
+ app.response.should == "test_response"
37
+ end
38
+
39
+ it "can define documentation" do
40
+ app.section "test_section"
41
+ app.desc "test_description"
42
+ app.param "test_param", "test_param_value"
43
+ app.request "test_request"
44
+ app.response "test_response"
45
+ app.get("/") do
46
+ "root"
47
+ end
48
+
49
+ app.documentation.should == [{
50
+ :url => "GET /",
51
+ :section => "test_section",
52
+ :description => "test_description",
53
+ :params => [["test_param", "test_param_value"]],
54
+ :request => "test_request",
55
+ :response => "test_response"
56
+ }]
57
+ end
58
+
59
+ end
@@ -0,0 +1,8 @@
1
+ require "rubygems"
2
+ require "rspec"
3
+
4
+ $:.unshift "lib"
5
+
6
+ Rspec.configure do |config|
7
+ config.color_enabled = true
8
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-api-docs
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - David Dollar
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-29 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rake
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 49
30
+ segments:
31
+ - 0
32
+ - 8
33
+ - 7
34
+ version: 0.8.7
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 62196457
46
+ segments:
47
+ - 2
48
+ - 0
49
+ - 0
50
+ - beta
51
+ - 5
52
+ version: 2.0.0.beta.5
53
+ type: :development
54
+ version_requirements: *id002
55
+ - !ruby/object:Gem::Dependency
56
+ name: haml
57
+ prerelease: false
58
+ requirement: &id003 !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ hash: 31
64
+ segments:
65
+ - 3
66
+ - 0
67
+ - 12
68
+ version: 3.0.12
69
+ type: :runtime
70
+ version_requirements: *id003
71
+ - !ruby/object:Gem::Dependency
72
+ name: json_pure
73
+ prerelease: false
74
+ requirement: &id004 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ hash: 1
80
+ segments:
81
+ - 1
82
+ - 4
83
+ - 3
84
+ version: 1.4.3
85
+ type: :runtime
86
+ version_requirements: *id004
87
+ - !ruby/object:Gem::Dependency
88
+ name: sinatra
89
+ prerelease: false
90
+ requirement: &id005 !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ~>
94
+ - !ruby/object:Gem::Version
95
+ hash: 15
96
+ segments:
97
+ - 1
98
+ - 0
99
+ version: "1.0"
100
+ type: :runtime
101
+ version_requirements: *id005
102
+ description: Create documentation for an API built in Sinatra.
103
+ email: ddollar@gmail.com
104
+ executables: []
105
+
106
+ extensions: []
107
+
108
+ extra_rdoc_files: []
109
+
110
+ files:
111
+ - Rakefile
112
+ - lib/sinatra/api_docs.rb
113
+ - lib/sinatra/api_docs/viewer.rb
114
+ - lib/sinatra/api_docs/views/documentation.sass
115
+ - lib/sinatra/api_docs/views/layout.haml
116
+ - lib/sinatra/api_docs/views/page.haml
117
+ - lib/sinatra/api_docs/views/toc.haml
118
+ - spec/sinatra/api_docs_spec.rb
119
+ - spec/spec_helper.rb
120
+ has_rdoc: false
121
+ homepage: http://daviddollar.org/
122
+ licenses: []
123
+
124
+ post_install_message:
125
+ rdoc_options:
126
+ - --charset=UTF-8
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ hash: 3
135
+ segments:
136
+ - 0
137
+ version: "0"
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ hash: 3
144
+ segments:
145
+ - 0
146
+ version: "0"
147
+ requirements: []
148
+
149
+ rubyforge_project:
150
+ rubygems_version: 1.3.7
151
+ signing_key:
152
+ specification_version: 3
153
+ summary: Create documentation for an API built in Sinatra.
154
+ test_files:
155
+ - spec/sinatra/api_docs_spec.rb
156
+ - spec/spec_helper.rb