rspec_api_documentation 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ require 'active_support'
2
+
3
+ module RspecApiDocumentation
4
+ extend ActiveSupport::Autoload
5
+
6
+ require 'rspec_api_documentation/railtie' if defined?(Rails)
7
+ include ActiveSupport::JSON
8
+
9
+ eager_autoload do
10
+ autoload :Configuration
11
+ autoload :ApiDocumentation
12
+ autoload :ApiFormatter
13
+ autoload :Example
14
+ autoload :ExampleGroup
15
+ autoload :Index
16
+ autoload :TestClient
17
+ end
18
+
19
+ autoload :DSL
20
+ autoload :TestServer
21
+ autoload :HtmlWriter
22
+ autoload :JsonWriter
23
+ autoload :IndexWriter
24
+
25
+ def self.configuration
26
+ @configuration ||= Configuration.new
27
+ end
28
+
29
+ def self.documentations
30
+ @documentations ||= configuration.map { |config| ApiDocumentation.new(config) }
31
+ end
32
+
33
+ def self.configure
34
+ yield configuration if block_given?
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ module RspecApiDocumentation
2
+ class ApiDocumentation
3
+ attr_reader :configuration, :index
4
+
5
+ delegate :docs_dir, :format, :to => :configuration
6
+
7
+ def initialize(configuration)
8
+ @configuration = configuration
9
+ @index = Index.new
10
+ end
11
+
12
+ def clear_docs
13
+ if File.exists?(docs_dir)
14
+ FileUtils.rm_rf(docs_dir, :secure => true)
15
+ end
16
+ FileUtils.mkdir_p(docs_dir)
17
+ end
18
+
19
+ def document_example(rspec_example)
20
+ example = Example.new(rspec_example, configuration)
21
+ if example.should_document?
22
+ index.examples << example
23
+ end
24
+ end
25
+
26
+ def write
27
+ writers.each do |writer|
28
+ writer.write(index, configuration)
29
+ end
30
+ end
31
+
32
+ def writers
33
+ [*configuration.format].map do |format|
34
+ RspecApiDocumentation.const_get("#{format}_writer".classify)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ require 'rspec/core/formatters/base_formatter'
2
+
3
+ module RspecApiDocumentation
4
+ class ApiFormatter < RSpec::Core::Formatters::BaseFormatter
5
+ def initialize(output)
6
+ super
7
+
8
+ output.puts "Generating API Docs"
9
+ end
10
+
11
+ def start(example_count)
12
+ super
13
+
14
+ RspecApiDocumentation.documentations.each(&:clear_docs)
15
+ end
16
+
17
+ def example_group_started(example_group)
18
+ super
19
+
20
+ output.puts " #{example_group.description}"
21
+ end
22
+
23
+ def example_passed(example)
24
+ super
25
+
26
+ output.puts " * #{example.description}"
27
+
28
+ RspecApiDocumentation.documentations.each do |documentation|
29
+ documentation.document_example(example)
30
+ end
31
+ end
32
+
33
+ def example_failed(example)
34
+ super
35
+
36
+ output.puts " ! #{example.description} (FAILED)"
37
+ end
38
+
39
+ def stop
40
+ super
41
+
42
+ RspecApiDocumentation.documentations.each(&:write)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,66 @@
1
+ module RspecApiDocumentation
2
+ class Configuration
3
+ include Enumerable
4
+
5
+ attr_reader :parent
6
+
7
+ def initialize(parent = nil)
8
+ @parent = parent
9
+ @settings = parent.settings.clone if parent
10
+ end
11
+
12
+ def groups
13
+ @groups ||= []
14
+ end
15
+
16
+ def define_group(name, &block)
17
+ subconfig = self.class.new(self)
18
+ subconfig.filter = name
19
+ subconfig.docs_dir = self.docs_dir.join(name.to_s)
20
+ yield subconfig
21
+ groups << subconfig
22
+ end
23
+
24
+ def self.add_setting(name, opts = {})
25
+ define_method("#{name}=") { |value| settings[name] = value }
26
+ define_method("#{name}") do
27
+ if settings.has_key?(name)
28
+ settings[name]
29
+ elsif opts[:default].respond_to?(:call)
30
+ opts[:default].call(self)
31
+ else
32
+ opts[:default]
33
+ end
34
+ end
35
+ end
36
+
37
+ add_setting :docs_dir, :default => lambda { |config|
38
+ if defined?(Rails)
39
+ Rails.root.join("docs")
40
+ else
41
+ Pathname.new("docs")
42
+ end
43
+ }
44
+
45
+ add_setting :format, :default => :html
46
+ add_setting :template_path, :default => File.expand_path("../../../templates", __FILE__)
47
+ add_setting :filter, :default => :all
48
+ add_setting :exclusion_filter, :default => nil
49
+ add_setting :app, :default => lambda { |config|
50
+ if defined?(Rails)
51
+ Rails.application
52
+ else
53
+ nil
54
+ end
55
+ }
56
+
57
+ def settings
58
+ @settings ||= {}
59
+ end
60
+
61
+ def each(&block)
62
+ yield self
63
+ groups.map { |g| g.each &block }
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,204 @@
1
+ require 'rack/test/methods'
2
+ require 'rack'
3
+ require 'webmock'
4
+
5
+ module RspecApiDocumentation
6
+ module DSL
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def self.define_action(method)
11
+ define_method method do |*args, &block|
12
+ options = if args.last.is_a?(Hash) then args.pop else {} end
13
+ options[:method] = method
14
+ options[:path] = args.first
15
+ args.push(options)
16
+ args[0] = "#{method.to_s.upcase} #{args[0]}"
17
+ context(*args, &block)
18
+ end
19
+ end
20
+
21
+ define_action :get
22
+ define_action :post
23
+ define_action :put
24
+ define_action :delete
25
+
26
+ def parameter(name, description, options = {})
27
+ parameters.push(options.merge(:name => name.to_s, :description => description))
28
+ end
29
+
30
+ def required_parameters(*names)
31
+ names.each do |name|
32
+ param = parameters.find { |param| param[:name] == name.to_s }
33
+ raise "Undefined parameters can not be required." unless param
34
+ param[:required] = true
35
+ end
36
+ end
37
+
38
+ def callback(description, &block)
39
+ self.send(:include, WebMock::API)
40
+ context(description, &block)
41
+ end
42
+
43
+ def trigger_callback(&block)
44
+ define_method(:do_callback) do
45
+ stub_request(:any, callback_url).to_rack(destination)
46
+ instance_eval &block
47
+ end
48
+ end
49
+
50
+ def scope_parameters(scope, keys)
51
+ return unless metadata[:parameters]
52
+
53
+ if keys == :all
54
+ keys = parameter_keys.map(&:to_s)
55
+ else
56
+ keys = keys.map(&:to_s)
57
+ end
58
+
59
+ keys.each do |key|
60
+ param = parameters.detect { |param| param[:name] == key }
61
+ param[:scope] = scope if param
62
+ end
63
+ end
64
+
65
+ def example_request(description, params = {}, &block)
66
+ example(description) do
67
+ do_request(params)
68
+ instance_eval &block if block_given?
69
+ end
70
+ end
71
+
72
+ private
73
+ def parameters
74
+ metadata[:parameters] ||= []
75
+ if superclass_metadata && metadata[:parameters].equal?(superclass_metadata[:parameters])
76
+ metadata[:parameters] = Marshal.load(Marshal.dump(superclass_metadata[:parameters]))
77
+ end
78
+ metadata[:parameters]
79
+ end
80
+
81
+ def parameter_keys
82
+ parameters.map { |param| param[:name] }
83
+ end
84
+ end
85
+
86
+ module InstanceMethods
87
+ def client
88
+ @client ||= TestClient.new(self)
89
+ end
90
+
91
+ def destination
92
+ @destination ||= TestServer.new(self)
93
+ end
94
+
95
+ def callback_url
96
+ raise "You must define callback_url"
97
+ end
98
+
99
+ def do_request(extra_params = {})
100
+ @extra_params = extra_params
101
+ params_or_body = nil
102
+ path_or_query = path
103
+
104
+ if method == :get && !query_string.blank?
105
+ path_or_query = path + "?#{query_string}"
106
+ else
107
+ params_or_body = respond_to?(:raw_post) ? raw_post : params
108
+ end
109
+
110
+ client.send(method, path_or_query, params_or_body)
111
+ end
112
+
113
+ def query_string
114
+ query = params.to_a.map do |param|
115
+ param.map! { |a| CGI.escape(a.to_s) }
116
+ param.join("=")
117
+ end
118
+ query.join("&")
119
+ end
120
+
121
+ def params
122
+ return unless example.metadata[:parameters]
123
+ parameters = example.metadata[:parameters].inject({}) do |hash, param|
124
+ set_param(hash, param)
125
+ end
126
+ parameters.merge!(extra_params)
127
+ parameters
128
+ end
129
+
130
+ def method
131
+ example.metadata[:method]
132
+ end
133
+
134
+ def in_path?(param)
135
+ path_params.include?(param)
136
+ end
137
+
138
+ def path_params
139
+ example.metadata[:path].scan(/:(\w+)/).flatten
140
+ end
141
+
142
+ def path
143
+ example.metadata[:path].gsub(/:(\w+)/) do |match|
144
+ if respond_to?($1)
145
+ send($1)
146
+ else
147
+ match
148
+ end
149
+ end
150
+ end
151
+
152
+ def app
153
+ RspecApiDocumentation.configuration.app
154
+ end
155
+
156
+ def explanation(text)
157
+ example.metadata[:explanation] = text
158
+ end
159
+
160
+ def status
161
+ last_response.status
162
+ end
163
+
164
+ def response_body
165
+ last_response.body
166
+ end
167
+
168
+ private
169
+ def extra_params
170
+ return {} if @extra_params.nil?
171
+ @extra_params.inject({}) do |h, (k, v)|
172
+ h[k.to_s] = v
173
+ h
174
+ end
175
+ end
176
+
177
+ def set_param(hash, param)
178
+ key = param[:name]
179
+ return hash if !respond_to?(key) || in_path?(key)
180
+
181
+ if param[:scope]
182
+ hash[param[:scope].to_s] ||= {}
183
+ hash[param[:scope].to_s][key] = send(key)
184
+ else
185
+ hash[key] = send(key)
186
+ end
187
+
188
+ hash
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ def self.resource(*args, &block)
195
+ options = if args.last.is_a?(Hash) then args.pop else {} end
196
+ options[:api_docs_dsl] = true
197
+ options[:resource_name] = args.first
198
+ options[:document] = true
199
+ args.push(options)
200
+ describe(*args, &block)
201
+ end
202
+
203
+ RSpec.configuration.include RspecApiDocumentation::DSL, :api_docs_dsl => true
204
+ RSpec.configuration.include Rack::Test::Methods, :api_docs_dsl => true
@@ -0,0 +1,49 @@
1
+ module RspecApiDocumentation
2
+ class Example
3
+ attr_reader :example, :configuration
4
+
5
+ def initialize(example, configuration)
6
+ @example = example
7
+ @configuration = configuration
8
+ end
9
+
10
+ def method_missing(method_sym, *args, &block)
11
+ if example.metadata.has_key?(method_sym)
12
+ example.metadata[method_sym]
13
+ else
14
+ example.send(method_sym, *args, &block)
15
+ end
16
+ end
17
+
18
+ def respond_to?(method_sym, include_private = false)
19
+ super || example.metadata.has_key?(method_sym) || example.respond_to?(method_sym, include_private)
20
+ end
21
+
22
+ def method
23
+ metadata[:method]
24
+ end
25
+
26
+ def should_document?
27
+ return false if pending? || !metadata[:resource_name] || !metadata[:document]
28
+ return true if configuration.filter == :all
29
+ return false if (Array(metadata[:document]) & Array(configuration.exclusion_filter)).length > 0
30
+ return true if (Array(metadata[:document]) & Array(configuration.filter)).length > 0
31
+ end
32
+
33
+ def public?
34
+ metadata[:public]
35
+ end
36
+
37
+ def has_parameters?
38
+ respond_to?(:parameters) && parameters.present?
39
+ end
40
+
41
+ def explanation
42
+ metadata[:explanation] || nil
43
+ end
44
+
45
+ def requests
46
+ metadata[:requests] || []
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,72 @@
1
+ require 'mustache'
2
+
3
+ module RspecApiDocumentation
4
+ class HtmlWriter
5
+ attr_accessor :index, :configuration
6
+
7
+ def initialize(index, configuration)
8
+ self.index = index
9
+ self.configuration = configuration
10
+ end
11
+
12
+ def self.write(index, configuration)
13
+ writer = new(index, configuration)
14
+ writer.write
15
+ end
16
+
17
+ def write
18
+ File.open(configuration.docs_dir.join("index.html"), "w+") do |f|
19
+ f.write HtmlIndex.new(index, configuration).render
20
+ end
21
+ index.examples.each do |example|
22
+ html_example = HtmlExample.new(example, configuration)
23
+ FileUtils.mkdir_p(configuration.docs_dir.join(html_example.dirname))
24
+ File.open(configuration.docs_dir.join(html_example.dirname, html_example.filename), "w+") do |f|
25
+ f.write html_example.render
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ class HtmlIndex < Mustache
32
+ def initialize(index, configuration)
33
+ @index = index
34
+ @configuration = configuration
35
+ self.template_path = configuration.template_path
36
+ end
37
+
38
+ def sections
39
+ IndexWriter.sections(examples)
40
+ end
41
+
42
+ def examples
43
+ @index.examples.map { |example| HtmlExample.new(example, @configuration) }
44
+ end
45
+ end
46
+
47
+ class HtmlExample < Mustache
48
+ delegate :method, :to => :@example
49
+
50
+ def initialize(example, configuration)
51
+ @example = example
52
+ self.template_path = configuration.template_path
53
+ end
54
+
55
+ def method_missing(method, *args, &block)
56
+ @example.send(method, *args, &block)
57
+ end
58
+
59
+ def respond_to?(method, include_private = false)
60
+ super || @example.respond_to?(method, include_private)
61
+ end
62
+
63
+ def dirname
64
+ resource_name.downcase.gsub(/\s+/, '_')
65
+ end
66
+
67
+ def filename
68
+ basename = description.downcase.gsub(/\s+/, '_').gsub(/[^a-z_]/, '')
69
+ "#{basename}.html"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,7 @@
1
+ module RspecApiDocumentation
2
+ class Index
3
+ def examples
4
+ @examples ||= []
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module RspecApiDocumentation
2
+ module IndexWriter
3
+ def sections(examples)
4
+ resources = examples.group_by(&:resource_name).inject([]) do |arr, (resource_name, examples)|
5
+ arr << { :resource_name => resource_name, :examples => examples.sort_by(&:description) }
6
+ end
7
+ resources.sort_by { |resource| resource[:resource_name] }
8
+ end
9
+ module_function :sections
10
+ end
11
+ end
@@ -0,0 +1,93 @@
1
+ module RspecApiDocumentation
2
+ class JsonWriter
3
+ attr_accessor :index, :configuration
4
+ delegate :docs_dir, :to => :configuration
5
+
6
+ def initialize(index, configuration)
7
+ self.index = index
8
+ self.configuration = configuration
9
+ end
10
+
11
+ def self.write(index, configuration)
12
+ writer = new(index, configuration)
13
+ writer.write
14
+ end
15
+
16
+ def write
17
+ File.open(docs_dir.join("index.json"), "w+") do |f|
18
+ f.write JsonIndex.new(index).to_json
19
+ end
20
+ index.examples.each do |example|
21
+ json_example = JsonExample.new(example)
22
+ FileUtils.mkdir_p(docs_dir.join(json_example.dirname))
23
+ File.open(docs_dir.join(json_example.dirname, json_example.filename), "w+") do |f|
24
+ f.write json_example.to_json
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ class JsonIndex
31
+ def initialize(index)
32
+ @index = index
33
+ end
34
+
35
+ def sections
36
+ IndexWriter.sections(examples)
37
+ end
38
+
39
+ def examples
40
+ @index.examples.map { |example| JsonExample.new(example) }
41
+ end
42
+
43
+ def to_json
44
+ sections.inject({:resources => []}) do |h, section|
45
+ h[:resources].push(
46
+ :name => section[:resource_name],
47
+ :examples => section[:examples].map { |example|
48
+ {
49
+ :description => example.description,
50
+ :link => "#{example.dirname}/#{example.filename}"
51
+ }
52
+ }
53
+ )
54
+ h
55
+ end.to_json
56
+ end
57
+ end
58
+
59
+ class JsonExample
60
+ delegate :method, :to => :@example
61
+
62
+ def initialize(example)
63
+ @example = example
64
+ end
65
+
66
+ def method_missing(method, *args, &block)
67
+ @example.send(method, *args, &block)
68
+ end
69
+
70
+ def respond_to?(method, include_private = false)
71
+ super || @example.respond_to?(method, include_private)
72
+ end
73
+
74
+ def dirname
75
+ resource_name.downcase.gsub(/\s+/, '_')
76
+ end
77
+
78
+ def filename
79
+ basename = description.downcase.gsub(/\s+/, '_').gsub(/[^a-z_]/, '')
80
+ "#{basename}.json"
81
+ end
82
+
83
+ def to_json
84
+ {
85
+ :resource => resource_name,
86
+ :description => description,
87
+ :explanation => explanation,
88
+ :parameters => respond_to?(:parameters) ? parameters : [],
89
+ :requests => requests
90
+ }.to_json
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,7 @@
1
+ module RspecApiDocumentation
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load "tasks/docs.rake"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,124 @@
1
+ module RspecApiDocumentation
2
+ class TestClient < Struct.new(:session, :options)
3
+ attr_accessor :user
4
+
5
+ delegate :example, :last_response, :last_request, :to => :session
6
+ delegate :metadata, :to => :example
7
+
8
+ def get(*args)
9
+ process :get, *args
10
+ end
11
+
12
+ def post(*args)
13
+ process :post, *args
14
+ end
15
+
16
+ def put(*args)
17
+ process :put, *args
18
+ end
19
+
20
+ def delete(*args)
21
+ process :delete, *args
22
+ end
23
+
24
+ def sign_in(user)
25
+ @user = user
26
+ end
27
+
28
+ def last_headers
29
+ session.last_request.env.select do |k, v|
30
+ k =~ /^(HTTP_|CONTENT_TYPE)/
31
+ end
32
+ end
33
+
34
+ def last_query_string
35
+ session.last_request.env["QUERY_STRING"]
36
+ end
37
+
38
+ def last_query_hash
39
+ strings = last_query_string.split("&")
40
+ arrays = strings.map do |segment|
41
+ segment.split("=")
42
+ end
43
+ Hash[arrays]
44
+ end
45
+
46
+ def headers(method, action, params)
47
+ if options && options[:headers]
48
+ options[:headers]
49
+ else
50
+ {}
51
+ end
52
+ end
53
+
54
+ private
55
+ def process(method, action, params = {})
56
+ session.send(method, action, params, headers(method, action, params))
57
+
58
+ document_example(method, action, params)
59
+ end
60
+
61
+ def document_example(method, action, params)
62
+ return unless metadata[:document]
63
+
64
+ input = last_request.env["rack.input"]
65
+ input.rewind
66
+ request_body = input.read
67
+
68
+ request_metadata = {}
69
+
70
+ request_metadata[:method] = method.to_s.upcase
71
+ request_metadata[:route] = action
72
+ if is_json?(request_body)
73
+ request_metadata[:request_body] = prettify_json(request_body)
74
+ else
75
+ request_metadata[:request_body] = prettify_request_body(request_body)
76
+ end
77
+ request_metadata[:request_headers] = format_headers(last_headers)
78
+ request_metadata[:request_query_parameters] = format_query_hash(last_query_hash)
79
+ request_metadata[:response_status] = last_response.status
80
+ request_metadata[:response_status_text] = Rack::Utils::HTTP_STATUS_CODES[last_response.status]
81
+ request_metadata[:response_body] = prettify_json(last_response.body)
82
+ request_metadata[:response_headers] = format_headers(last_response.headers)
83
+
84
+ metadata[:requests] ||= []
85
+ metadata[:requests] << request_metadata
86
+ end
87
+
88
+ def format_headers(headers)
89
+ headers.map do |key, value|
90
+ # HTTP_ACCEPT_CHARSET => Accept-Charset
91
+ formatted_key = key.gsub(/^HTTP_/, '').titleize.split.join("-")
92
+ "#{formatted_key}: #{value}"
93
+ end.join("\n")
94
+ end
95
+
96
+ def format_query_hash(query_hash)
97
+ return if query_hash.blank?
98
+ query_hash.map do |key, value|
99
+ "#{key}: #{CGI.unescape(value)}"
100
+ end.join("\n")
101
+ end
102
+
103
+ def prettify_json(json)
104
+ begin
105
+ JSON.pretty_generate(JSON.parse(json))
106
+ rescue
107
+ nil
108
+ end
109
+ end
110
+
111
+ def prettify_request_body(string)
112
+ return if string.blank?
113
+ CGI.unescape(string.split("&").join("\n"))
114
+ end
115
+
116
+ def is_json?(string)
117
+ begin
118
+ JSON.parse(string)
119
+ rescue
120
+ false
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,44 @@
1
+ module RspecApiDocumentation
2
+ class TestServer < Struct.new(:session)
3
+ delegate :example, :last_request, :last_response, :to => :session
4
+ delegate :metadata, :to => :example
5
+
6
+ def call(env)
7
+ env["rack.input"].rewind
8
+
9
+ request_metadata = {}
10
+
11
+ request_metadata[:method] = env["REQUEST_METHOD"]
12
+ request_metadata[:route] = env["PATH_INFO"]
13
+ request_metadata[:request_body] = prettify_json(env["rack.input"].read)
14
+ request_metadata[:request_headers] = headers(env)
15
+
16
+ metadata[:requests] ||= []
17
+ metadata[:requests] << request_metadata
18
+
19
+ return [200, {}, [""]]
20
+ end
21
+
22
+ private
23
+
24
+ def headers(env)
25
+ env.
26
+ select do |k, v|
27
+ k =~ /^(HTTP_|CONTENT_TYPE)/
28
+ end.
29
+ map do |key, value|
30
+ # HTTP_ACCEPT_CHARSET => Accept-Charset
31
+ formatted_key = key.gsub(/^HTTP_/, '').titleize.split.join("-")
32
+ "#{formatted_key}: #{value}"
33
+ end.join("\n")
34
+ end
35
+
36
+ def prettify_json(json)
37
+ begin
38
+ JSON.pretty_generate(JSON.parse(json))
39
+ rescue
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ if Rails.env.test? || Rails.env.development?
4
+ desc 'Generate API request documentation from API specs'
5
+ RSpec::Core::RakeTask.new('docs:generate') do |t|
6
+ t.pattern = 'spec/acceptance/**/*_spec.rb'
7
+ t.rspec_opts = ["--format RspecApiDocumentation::ApiFormatter"]
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec_api_documentation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Cahoon
9
+ - Sam Goldman
10
+ - Eric Oestrich
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2012-01-25 00:00:00.000000000Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ requirement: &70151618292380 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ! '>='
22
+ - !ruby/object:Gem::Version
23
+ version: '0'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: *70151618292380
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: &70151618291040 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: *70151618291040
38
+ - !ruby/object:Gem::Dependency
39
+ name: i18n
40
+ requirement: &70151618289580 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ type: :runtime
47
+ prerelease: false
48
+ version_requirements: *70151618289580
49
+ - !ruby/object:Gem::Dependency
50
+ name: rack-test
51
+ requirement: &70151618288780 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: *70151618288780
60
+ - !ruby/object:Gem::Dependency
61
+ name: mustache
62
+ requirement: &70151618287740 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: *70151618287740
71
+ - !ruby/object:Gem::Dependency
72
+ name: webmock
73
+ requirement: &70151618286920 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: *70151618286920
82
+ - !ruby/object:Gem::Dependency
83
+ name: fakefs
84
+ requirement: &70151618285800 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: *70151618285800
93
+ - !ruby/object:Gem::Dependency
94
+ name: sinatra
95
+ requirement: &70151618283740 !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: *70151618283740
104
+ description: Generate API docs from your test suite
105
+ email:
106
+ - chris@smartlogicsolutions.com
107
+ - sam@smartlogicsolutions.com
108
+ - eric@smartlogicsolutions.com
109
+ executables: []
110
+ extensions: []
111
+ extra_rdoc_files: []
112
+ files:
113
+ - lib/rspec_api_documentation/api_documentation.rb
114
+ - lib/rspec_api_documentation/api_formatter.rb
115
+ - lib/rspec_api_documentation/configuration.rb
116
+ - lib/rspec_api_documentation/dsl.rb
117
+ - lib/rspec_api_documentation/example.rb
118
+ - lib/rspec_api_documentation/html_writer.rb
119
+ - lib/rspec_api_documentation/index.rb
120
+ - lib/rspec_api_documentation/index_writer.rb
121
+ - lib/rspec_api_documentation/json_writer.rb
122
+ - lib/rspec_api_documentation/railtie.rb
123
+ - lib/rspec_api_documentation/test_client.rb
124
+ - lib/rspec_api_documentation/test_server.rb
125
+ - lib/rspec_api_documentation.rb
126
+ - lib/tasks/docs.rake
127
+ homepage: http://smartlogicsolutions.com
128
+ licenses: []
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ! '>='
143
+ - !ruby/object:Gem::Version
144
+ version: 1.3.6
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 1.8.10
148
+ signing_key:
149
+ specification_version: 3
150
+ summary: A double black belt for your docs
151
+ test_files: []