sinatra-api-docs 0.1.1

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,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