rspec-api-documentation 1.1.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rspec_api_documentation.rb +63 -0
  3. data/lib/rspec_api_documentation/api_documentation.rb +38 -0
  4. data/lib/rspec_api_documentation/api_formatter.rb +45 -0
  5. data/lib/rspec_api_documentation/client_base.rb +82 -0
  6. data/lib/rspec_api_documentation/configuration.rb +84 -0
  7. data/lib/rspec_api_documentation/curl.rb +61 -0
  8. data/lib/rspec_api_documentation/dsl.rb +17 -0
  9. data/lib/rspec_api_documentation/dsl/callback.rb +25 -0
  10. data/lib/rspec_api_documentation/dsl/endpoint.rb +134 -0
  11. data/lib/rspec_api_documentation/dsl/resource.rb +83 -0
  12. data/lib/rspec_api_documentation/example.rb +49 -0
  13. data/lib/rspec_api_documentation/headers.rb +32 -0
  14. data/lib/rspec_api_documentation/index.rb +7 -0
  15. data/lib/rspec_api_documentation/oauth2_mac_client.rb +70 -0
  16. data/lib/rspec_api_documentation/rack_test_client.rb +58 -0
  17. data/lib/rspec_api_documentation/railtie.rb +7 -0
  18. data/lib/rspec_api_documentation/test_server.rb +31 -0
  19. data/lib/rspec_api_documentation/views/html_example.rb +16 -0
  20. data/lib/rspec_api_documentation/views/html_index.rb +14 -0
  21. data/lib/rspec_api_documentation/views/markup_example.rb +58 -0
  22. data/lib/rspec_api_documentation/views/markup_index.rb +21 -0
  23. data/lib/rspec_api_documentation/views/textile_example.rb +16 -0
  24. data/lib/rspec_api_documentation/views/textile_index.rb +14 -0
  25. data/lib/rspec_api_documentation/writers/combined_json_writer.rb +20 -0
  26. data/lib/rspec_api_documentation/writers/combined_text_writer.rb +106 -0
  27. data/lib/rspec_api_documentation/writers/formatter.rb +11 -0
  28. data/lib/rspec_api_documentation/writers/general_markup_writer.rb +42 -0
  29. data/lib/rspec_api_documentation/writers/html_writer.rb +21 -0
  30. data/lib/rspec_api_documentation/writers/index_writer.rb +16 -0
  31. data/lib/rspec_api_documentation/writers/json_iodocs_writer.rb +111 -0
  32. data/lib/rspec_api_documentation/writers/json_writer.rb +111 -0
  33. data/lib/rspec_api_documentation/writers/textile_writer.rb +21 -0
  34. data/lib/tasks/docs.rake +7 -0
  35. data/templates/rspec_api_documentation/html_example.mustache +106 -0
  36. data/templates/rspec_api_documentation/html_index.mustache +34 -0
  37. data/templates/rspec_api_documentation/textile_example.mustache +68 -0
  38. data/templates/rspec_api_documentation/textile_index.mustache +10 -0
  39. metadata +267 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7eaab971cec433522e7a0c8920b1bf3c37fee9f6
4
+ data.tar.gz: 9f0eb16b2fd3a70892e36f73d5e3d9e5c54fb738
5
+ SHA512:
6
+ metadata.gz: a786afed2c20a213956ef0cface62a978e41fbea4943e1f0c2da94a1d7f001a470714e06ec17ba8e1598195b577d2dbc51fee24f99f7c835a002917e39d7db31
7
+ data.tar.gz: 15e97461f543a737d32e4a226e6b9e9384bda8fbec5c8f5c9af6c4991c9a105bb243c9a9480bd91d4dc0411511ce3d8d261e97084a8352c80e92bb025bd0442f
@@ -0,0 +1,63 @@
1
+ require 'active_support'
2
+ require 'active_support/inflector'
3
+ require 'cgi'
4
+ require 'json'
5
+
6
+ module RspecApiDocumentation
7
+ extend ActiveSupport::Autoload
8
+
9
+ require 'rspec_api_documentation/railtie' if defined?(Rails)
10
+ include ActiveSupport::JSON
11
+
12
+ eager_autoload do
13
+ autoload :Configuration
14
+ autoload :ApiDocumentation
15
+ autoload :ApiFormatter
16
+ autoload :Example
17
+ autoload :Index
18
+ autoload :ClientBase
19
+ autoload :Headers
20
+ end
21
+
22
+ autoload :DSL
23
+ autoload :RackTestClient
24
+ autoload :OAuth2MACClient, "rspec_api_documentation/oauth2_mac_client"
25
+ autoload :TestServer
26
+ autoload :Curl
27
+
28
+ module Writers
29
+ extend ActiveSupport::Autoload
30
+
31
+ autoload :GeneralMarkupWriter
32
+ autoload :HtmlWriter
33
+ autoload :TextileWriter
34
+ autoload :JsonWriter
35
+ autoload :JsonIodocsWriter
36
+ autoload :IndexWriter
37
+ autoload :CombinedTextWriter
38
+ autoload :CombinedJsonWriter
39
+ end
40
+
41
+ module Views
42
+ extend ActiveSupport::Autoload
43
+
44
+ autoload :MarkupIndex
45
+ autoload :MarkupExample
46
+ autoload :HtmlIndex
47
+ autoload :HtmlExample
48
+ autoload :TextileIndex
49
+ autoload :TextileExample
50
+ end
51
+
52
+ def self.configuration
53
+ @configuration ||= Configuration.new
54
+ end
55
+
56
+ def self.documentations
57
+ @documentations ||= configuration.map { |config| ApiDocumentation.new(config) }
58
+ end
59
+
60
+ def self.configure
61
+ yield configuration if block_given?
62
+ end
63
+ 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::Writers.const_get("#{format}_writer".classify)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ require 'rspec/core/formatters/base_text_formatter'
2
+
3
+ module RspecApiDocumentation
4
+ class ApiFormatter < RSpec::Core::Formatters::BaseTextFormatter
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,82 @@
1
+ module RspecApiDocumentation
2
+ class ClientBase < Struct.new(:context, :options)
3
+ include Headers
4
+
5
+ delegate :example, :app, :to => :context
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 head(*args)
25
+ process :head, *args
26
+ end
27
+
28
+ def patch(*args)
29
+ process :patch, *args
30
+ end
31
+
32
+ def response_status
33
+ status
34
+ end
35
+
36
+ private
37
+
38
+ def process(method, path, params = {}, headers ={})
39
+ do_request(method, path, params, headers)
40
+ document_example(method.to_s.upcase, path)
41
+ end
42
+
43
+ def document_example(method, path)
44
+ return unless metadata[:document]
45
+
46
+ input = last_request.env["rack.input"]
47
+ input.rewind
48
+ request_body = input.read
49
+
50
+ request_metadata = {}
51
+
52
+ request_metadata[:request_method] = method
53
+ request_metadata[:request_path] = path
54
+ request_metadata[:request_body] = request_body.empty? ? nil : request_body
55
+ request_metadata[:request_headers] = request_headers
56
+ request_metadata[:request_query_parameters] = query_hash
57
+ request_metadata[:request_content_type] = request_content_type
58
+ request_metadata[:response_status] = status
59
+ request_metadata[:response_status_text] = Rack::Utils::HTTP_STATUS_CODES[status]
60
+ request_metadata[:response_body] = response_body.empty? ? nil : response_body
61
+ request_metadata[:response_headers] = response_headers
62
+ request_metadata[:response_content_type] = response_content_type
63
+ request_metadata[:curl] = Curl.new(method, path, request_body, request_headers)
64
+
65
+ metadata[:requests] ||= []
66
+ metadata[:requests] << request_metadata
67
+ end
68
+
69
+ def query_hash
70
+ strings = query_string.split("&")
71
+ arrays = strings.map do |segment|
72
+ k,v = segment.split("=")
73
+ [k, v && CGI.unescape(v)]
74
+ end
75
+ Hash[arrays]
76
+ end
77
+
78
+ def headers(method, path, params, request_headers)
79
+ request_headers || {}
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,84 @@
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("doc", "api")
40
+ else
41
+ Pathname.new("doc/api")
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
+ add_setting :curl_host, :default => nil
58
+ add_setting :keep_source_order, :default => false
59
+ add_setting :api_name, :default => "API Documentation"
60
+ add_setting :io_docs_protocol, :default => "http"
61
+
62
+ def client_method=(new_client_method)
63
+ RspecApiDocumentation::DSL::Resource.module_eval <<-RUBY
64
+ alias :#{new_client_method} #{client_method}
65
+ undef #{client_method}
66
+ RUBY
67
+
68
+ @client_method = new_client_method
69
+ end
70
+
71
+ def client_method
72
+ @client_method ||= :client
73
+ end
74
+
75
+ def settings
76
+ @settings ||= {}
77
+ end
78
+
79
+ def each(&block)
80
+ yield self
81
+ groups.map { |g| g.each &block }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,61 @@
1
+ require 'active_support/core_ext/object/to_query'
2
+
3
+ module RspecApiDocumentation
4
+ class Curl < Struct.new(:method, :path, :data, :headers)
5
+ attr_accessor :host
6
+
7
+ def output(config_host)
8
+ self.host = config_host
9
+ send(method.downcase)
10
+ end
11
+
12
+ def post
13
+ "curl \"#{url}\" #{post_data} -X POST #{headers}"
14
+ end
15
+
16
+ def get
17
+ "curl \"#{url}#{get_data}\" -X GET #{headers}"
18
+ end
19
+
20
+ def put
21
+ "curl \"#{url}\" #{post_data} -X PUT #{headers}"
22
+ end
23
+
24
+ def delete
25
+ "curl \"#{url}\" #{post_data} -X DELETE #{headers}"
26
+ end
27
+
28
+ def head
29
+ "curl \"#{url}#{get_data}\" -X HEAD #{headers}"
30
+ end
31
+
32
+ def patch
33
+ "curl \"#{url}\" #{post_data} -X PATCH #{headers}"
34
+ end
35
+
36
+ def url
37
+ "#{host}#{path}"
38
+ end
39
+
40
+ def headers
41
+ super.map do |k, v|
42
+ "\\\n\t-H \"#{format_header(k, v)}\""
43
+ end.join(" ")
44
+ end
45
+
46
+ def get_data
47
+ "?#{data}" unless data.blank?
48
+ end
49
+
50
+ def post_data
51
+ escaped_data = data.to_s.gsub("'", "\\u0027")
52
+ "-d '#{escaped_data}'"
53
+ end
54
+
55
+ private
56
+ def format_header(header, value)
57
+ formatted_header = header.gsub(/^HTTP_/, '').titleize.split.join("-")
58
+ "#{formatted_header}: #{value}"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,17 @@
1
+ require "rspec_api_documentation/dsl/resource"
2
+ require "rspec_api_documentation/dsl/endpoint"
3
+ require "rspec_api_documentation/dsl/callback"
4
+
5
+ def self.resource(*args, &block)
6
+ options = if args.last.is_a?(Hash) then args.pop else {} end
7
+ options[:api_doc_dsl] = :resource
8
+ options[:resource_name] = args.first
9
+ options[:document] ||= :all
10
+ args.push(options)
11
+ describe(*args, &block)
12
+ end
13
+
14
+ RSpec.configuration.include RspecApiDocumentation::DSL::Resource, :api_doc_dsl => :resource
15
+ RSpec.configuration.include RspecApiDocumentation::DSL::Endpoint, :api_doc_dsl => :endpoint
16
+ RSpec.configuration.include RspecApiDocumentation::DSL::Callback, :api_doc_dsl => :callback
17
+ RSpec.configuration.backtrace_exclusion_patterns << %r{lib/rspec_api_documentation/dsl\.rb}
@@ -0,0 +1,25 @@
1
+ module RspecApiDocumentation::DSL
2
+ module Callback
3
+ extend ActiveSupport::Concern
4
+
5
+ delegate :request_method, :request_headers, :request_body, :to => :destination
6
+
7
+ module ClassMethods
8
+ def trigger_callback(&block)
9
+ define_method(:do_callback) do
10
+ require 'rack'
11
+ stub_request(:any, callback_url).to_rack(destination)
12
+ instance_eval &block
13
+ end
14
+ end
15
+ end
16
+
17
+ def destination
18
+ @destination ||= RspecApiDocumentation::TestServer.new(self)
19
+ end
20
+
21
+ def callback_url
22
+ raise "You must define callback_url"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,134 @@
1
+ require 'rspec/core/formatters/base_formatter'
2
+ require 'rack/utils'
3
+ require 'rack/test/utils'
4
+
5
+ module RspecApiDocumentation::DSL
6
+ module Endpoint
7
+ extend ActiveSupport::Concern
8
+ include Rack::Test::Utils
9
+
10
+ delegate :response_headers, :status, :response_status, :response_body, :to => :rspec_api_documentation_client
11
+
12
+ module ClassMethods
13
+ def example_request(description, params = {}, &block)
14
+ file_path = caller.first[0, caller.first =~ /:/]
15
+
16
+ location = caller.first[0, caller.first =~ /(:in|$)/]
17
+ location = relative_path(location)
18
+
19
+ example description, :location => location, :file_path => file_path do
20
+ do_request(params)
21
+ instance_eval &block if block_given?
22
+ end
23
+ end
24
+
25
+ private
26
+ # from rspec-core
27
+ def relative_path(line)
28
+ line = line.sub(File.expand_path("."), ".")
29
+ line = line.sub(/\A([^:]+:\d+)$/, '\\1')
30
+ return nil if line == '-e:1'
31
+ line
32
+ end
33
+ end
34
+
35
+ def do_request(extra_params = {})
36
+ @extra_params = extra_params
37
+
38
+ params_or_body = nil
39
+ path_or_query = path
40
+
41
+ if method == :get && !query_string.blank?
42
+ path_or_query += "?#{query_string}"
43
+ else
44
+ params_or_body = respond_to?(:raw_post) ? raw_post : params
45
+ end
46
+
47
+ rspec_api_documentation_client.send(method, path_or_query, params_or_body, headers)
48
+ end
49
+
50
+ def query_string
51
+ build_nested_query(params || {})
52
+ end
53
+
54
+ def params
55
+ parameters = example.metadata.fetch(:parameters, {}).inject({}) do |hash, param|
56
+ set_param(hash, param)
57
+ end
58
+ parameters.merge!(extra_params)
59
+ parameters
60
+ end
61
+
62
+ def headers
63
+ return unless example.metadata[:headers]
64
+ example.metadata[:headers].inject({}) do |hash, (header, value)|
65
+ if value.is_a?(Symbol)
66
+ hash[header] = send(value) if respond_to?(value)
67
+ else
68
+ hash[header] = value
69
+ end
70
+ hash
71
+ end
72
+ end
73
+
74
+ def method
75
+ example.metadata[:method]
76
+ end
77
+
78
+ def in_path?(param)
79
+ path_params.include?(param)
80
+ end
81
+
82
+ def path_params
83
+ example.metadata[:route].scan(/:(\w+)/).flatten
84
+ end
85
+
86
+ def path
87
+ example.metadata[:route].gsub(/:(\w+)/) do |match|
88
+ if extra_params.keys.include?($1)
89
+ delete_extra_param($1)
90
+ elsif respond_to?($1)
91
+ send($1)
92
+ else
93
+ match
94
+ end
95
+ end
96
+ end
97
+
98
+ def explanation(text)
99
+ example.metadata[:explanation] = text
100
+ end
101
+
102
+ private
103
+
104
+ def rspec_api_documentation_client
105
+ send(RspecApiDocumentation.configuration.client_method)
106
+ end
107
+
108
+ def extra_params
109
+ return {} if @extra_params.nil?
110
+ @extra_params.inject({}) do |h, (k, v)|
111
+ h[k.to_s] = v
112
+ h
113
+ end
114
+ end
115
+
116
+ def delete_extra_param(key)
117
+ @extra_params.delete(key.to_sym) || @extra_params.delete(key.to_s)
118
+ end
119
+
120
+ def set_param(hash, param)
121
+ key = param[:name]
122
+ return hash if !respond_to?(key) || in_path?(key)
123
+
124
+ if param[:scope]
125
+ hash[param[:scope].to_s] ||= {}
126
+ hash[param[:scope].to_s][key] = send(key)
127
+ else
128
+ hash[key] = send(key)
129
+ end
130
+
131
+ hash
132
+ end
133
+ end
134
+ end