avocado-docs 2.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +21 -0
- data/app/assets/javascripts/avocado/application.js +21 -0
- data/app/assets/stylesheets/avocado/application.css.scss +163 -0
- data/app/assets/stylesheets/avocado/bootstrap.css +5785 -0
- data/app/controllers/avocado/avocado_controller.rb +22 -0
- data/app/controllers/avocado/specs_controller.rb +4 -0
- data/app/helpers/avocado/application_helper.rb +4 -0
- data/app/models/avocado/endpoint.rb +13 -0
- data/app/models/avocado/parser.rb +56 -0
- data/app/models/avocado/request.rb +37 -0
- data/app/models/avocado/resource.rb +17 -0
- data/app/views/layouts/avocado/avocado.html.erb +14 -0
- data/app/views/template.html.erb +54 -0
- data/config/routes.rb +4 -0
- data/lib/avocado.rb +31 -0
- data/lib/avocado/cache.rb +20 -0
- data/lib/avocado/config.rb +33 -0
- data/lib/avocado/controller.rb +22 -0
- data/lib/avocado/engine.rb +5 -0
- data/lib/avocado/middleware.rb +37 -0
- data/lib/avocado/middleware/defaults.rb +15 -0
- data/lib/avocado/middleware/document_if_configuration.rb +12 -0
- data/lib/avocado/middleware/document_metadata.rb +14 -0
- data/lib/avocado/middleware/example_serialization.rb +10 -0
- data/lib/avocado/middleware/request_serialization.rb +46 -0
- data/lib/avocado/middleware/resource_serialization.rb +22 -0
- data/lib/avocado/middleware/response_serialization.rb +19 -0
- data/lib/avocado/rspec.rb +27 -0
- data/lib/avocado/uploader.rb +26 -0
- data/lib/avocado/version.rb +3 -0
- metadata +159 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
module Avocado
|
2
|
+
class AvocadoController < ActionController::Base
|
3
|
+
|
4
|
+
def create
|
5
|
+
File.open(yaml, 'w+') { |f| f.write params[:file].read }
|
6
|
+
head :ok
|
7
|
+
end
|
8
|
+
|
9
|
+
def index
|
10
|
+
hash = YAML.load File.read(yaml)
|
11
|
+
@data = Avocado::Parser.new(hash)
|
12
|
+
render '/template'
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def yaml
|
18
|
+
Avocado::Config.yaml_path.join('avocado.yml')
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Avocado
|
2
|
+
class Parser
|
3
|
+
|
4
|
+
attr_reader :resources, :endpoints, :requests
|
5
|
+
|
6
|
+
def initialize(json)
|
7
|
+
@resources = SortedSet.new
|
8
|
+
@endpoints = Set.new
|
9
|
+
@requests = Set.new
|
10
|
+
parse(json)
|
11
|
+
end
|
12
|
+
|
13
|
+
def resource(name)
|
14
|
+
@resources.find { |r| r.name == name }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def parse(json)
|
20
|
+
json.each do |req|
|
21
|
+
resource = find_or_create_resource_by_name req[:resource][:name]
|
22
|
+
endpoint = find_or_create_endpoint_by_signature resource, req[:request]
|
23
|
+
request = instantiate_request_object_from(req)
|
24
|
+
request.endpoint = endpoint
|
25
|
+
|
26
|
+
if request.unique?(@requests)
|
27
|
+
endpoint.requests << request
|
28
|
+
resource.endpoints << endpoint
|
29
|
+
@resources << resource
|
30
|
+
@endpoints << endpoint
|
31
|
+
@requests << request
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def instantiate_request_object_from(params)
|
37
|
+
Avocado::Request.new \
|
38
|
+
path: params[:request][:path],
|
39
|
+
params: params[:request][:params],
|
40
|
+
headers: params[:request][:headers],
|
41
|
+
status: params[:response][:status],
|
42
|
+
body: params[:response][:body],
|
43
|
+
description: params[:description]
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_or_create_resource_by_name(name)
|
47
|
+
resource(name) || Avocado::Resource.new(name: name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_or_create_endpoint_by_signature(resource, request)
|
51
|
+
signature = "#{request[:method]} #{request[:path]}"
|
52
|
+
resource.endpoints.find { |e| e.signature == signature } || Avocado::Endpoint.new(signature: signature)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Avocado
|
2
|
+
class Request
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
attr_accessor :uid, :url, :path, :params, :status, :body, :headers
|
6
|
+
attr_accessor :endpoint, :description
|
7
|
+
|
8
|
+
def initialize(*)
|
9
|
+
super
|
10
|
+
self.uid = SecureRandom.hex
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
other.path == path &&
|
15
|
+
other.params == params &&
|
16
|
+
other.status == status &&
|
17
|
+
other.body == body
|
18
|
+
end
|
19
|
+
|
20
|
+
def <=>(other)
|
21
|
+
status <=> other.status
|
22
|
+
end
|
23
|
+
|
24
|
+
def unique?(requests)
|
25
|
+
requests.none? { |req| self == req }
|
26
|
+
end
|
27
|
+
|
28
|
+
def params
|
29
|
+
@params || {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def body
|
33
|
+
@body || {}
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>API Docs</title>
|
5
|
+
<%= stylesheet_link_tag "avocado/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "avocado/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
@@ -0,0 +1,54 @@
|
|
1
|
+
<div id="avocado">
|
2
|
+
|
3
|
+
<ul id="sidebar">
|
4
|
+
<% @data.resources.each do |resource| %>
|
5
|
+
<li><a href="#<%= resource.name %>"><%= resource.name %></a></li>
|
6
|
+
<ul class="dropdown">
|
7
|
+
<% @data.resource(resource.name).endpoints.map(&:signature).uniq.each do |signature| %>
|
8
|
+
<li><a href="#<%= resource.name %>"><%= signature %></a></li>
|
9
|
+
<% end %>
|
10
|
+
</ul>
|
11
|
+
<% end %>
|
12
|
+
</ul>
|
13
|
+
|
14
|
+
<div id="requests">
|
15
|
+
<% @data.resources.each do |resource| %>
|
16
|
+
<a name="<%= resource.name %>"></a>
|
17
|
+
<h4><%= resource.name %></h4>
|
18
|
+
<ul class="request-endpoints">
|
19
|
+
<% resource.endpoints.each do |endpoint| %>
|
20
|
+
<% endpoint.requests.each do |request| %>
|
21
|
+
<li>
|
22
|
+
<a href="#" data-request-uid="<%= request.uid %>">
|
23
|
+
<span class="status"><%= request.status %></span>
|
24
|
+
<span class="url"><%= endpoint.signature %></span>
|
25
|
+
<span class="description"><%= request.description %></span>
|
26
|
+
</a>
|
27
|
+
</li>
|
28
|
+
<% end %>
|
29
|
+
<% end %>
|
30
|
+
</ul>
|
31
|
+
<% end %>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<div id="docs">
|
35
|
+
<% @data.requests.each do |req| %>
|
36
|
+
<div class="request" data-uid="<%= req.uid %>">
|
37
|
+
<h4><%= req.endpoint.signature %></h4>
|
38
|
+
<code class="request-parameters">
|
39
|
+
<%= JSON.pretty_generate req.params.to_h %>
|
40
|
+
</code>
|
41
|
+
<code class="example-headers">
|
42
|
+
<% req.headers.each do |name, value| %>
|
43
|
+
<%= name %>: <%= value %>
|
44
|
+
<% end %>
|
45
|
+
</code>
|
46
|
+
<code class="response-status"><%= req.status %></code>
|
47
|
+
<code class="response-body">
|
48
|
+
<%= JSON.pretty_generate JSON[req.body] rescue '' %>
|
49
|
+
<div class="clearfix"></div>
|
50
|
+
</code>
|
51
|
+
</div>
|
52
|
+
<% end %>
|
53
|
+
|
54
|
+
</div>
|
data/config/routes.rb
ADDED
data/lib/avocado.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'avocado/engine'
|
2
|
+
require 'avocado/config'
|
3
|
+
require 'avocado/uploader'
|
4
|
+
require 'avocado/controller'
|
5
|
+
require 'avocado/cache'
|
6
|
+
require 'avocado/middleware'
|
7
|
+
require 'avocado/middleware/defaults'
|
8
|
+
|
9
|
+
require 'yaml'
|
10
|
+
require 'net/http/post/multipart'
|
11
|
+
|
12
|
+
module Avocado
|
13
|
+
@payload = []
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :payload
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.upload!
|
20
|
+
return if @payload.empty?
|
21
|
+
File.open('avocado.yml', 'w+') { |file| file.write payload.to_yaml }
|
22
|
+
Avocado::Uploader.new.upload('avocado.yml') if Avocado::Config.url
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.reset!
|
26
|
+
self.payload = []
|
27
|
+
Avocado::Config.reset!
|
28
|
+
Avocado::Cache.clean
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Temporarily store a JSON request/response. Ultimately RSpec will determine
|
2
|
+
# if this request/response gets documented or not in an after(:each) block
|
3
|
+
module Avocado
|
4
|
+
class Cache
|
5
|
+
class << self
|
6
|
+
|
7
|
+
attr_accessor :request, :response, :json
|
8
|
+
|
9
|
+
def store(request, response)
|
10
|
+
@json = {}
|
11
|
+
@request, @response = request, response
|
12
|
+
end
|
13
|
+
|
14
|
+
def clean
|
15
|
+
@json = @request = @response = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Avocado
|
2
|
+
class Config
|
3
|
+
class << self
|
4
|
+
|
5
|
+
attr_accessor :url, :headers, :document_if, :yaml_path, :ignored_params
|
6
|
+
|
7
|
+
def configure(&block)
|
8
|
+
yield self
|
9
|
+
end
|
10
|
+
|
11
|
+
def headers
|
12
|
+
Array(@headers) || []
|
13
|
+
end
|
14
|
+
|
15
|
+
def document_if
|
16
|
+
@document_if || -> { true }
|
17
|
+
end
|
18
|
+
|
19
|
+
def yaml_path
|
20
|
+
@yaml_path || Rails.application.root
|
21
|
+
end
|
22
|
+
|
23
|
+
def ignored_params
|
24
|
+
@ignored_params ||= ['controller', 'action']
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset!
|
28
|
+
@headers = @document_if = @yaml_path = @ignored_params = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# This concern gets patched into ActionController::Base during testing
|
2
|
+
# The after_action will ensure every request gets documented regardless of the
|
3
|
+
# type of test (controller, integration, etc)
|
4
|
+
module Avocado
|
5
|
+
module Controller
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
after_action -> do
|
10
|
+
Avocado::Cache.clean
|
11
|
+
Avocado::Cache.store(request, response) if documentable?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def documentable?
|
16
|
+
response.body.blank? || !!JSON.parse(response.body)
|
17
|
+
rescue
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Avocado
|
2
|
+
class Middleware
|
3
|
+
|
4
|
+
attr_accessor :entries
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@entries = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(klass)
|
11
|
+
@entries << klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def invoke(*args, &finally)
|
15
|
+
chain = entries.map(&:new).dup
|
16
|
+
traversal = -> (continue = true) do
|
17
|
+
return if !continue
|
18
|
+
if chain.empty?
|
19
|
+
finally.call
|
20
|
+
else
|
21
|
+
chain.shift.call(*args, &traversal)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
traversal.call
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.configure(&block)
|
28
|
+
@chain ||= new
|
29
|
+
yield @chain
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.invoke(*args, &block)
|
33
|
+
@chain.invoke(*args, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'avocado/middleware/document_metadata'
|
2
|
+
require 'avocado/middleware/document_if_configuration'
|
3
|
+
require 'avocado/middleware/request_serialization'
|
4
|
+
require 'avocado/middleware/response_serialization'
|
5
|
+
require 'avocado/middleware/resource_serialization'
|
6
|
+
require 'avocado/middleware/example_serialization'
|
7
|
+
|
8
|
+
Avocado::Middleware.configure do |chain|
|
9
|
+
chain << Avocado::Middleware::DocumentMetadata
|
10
|
+
chain << Avocado::Middleware::DocumentIfConfiguration
|
11
|
+
chain << Avocado::Middleware::RequestSerialization
|
12
|
+
chain << Avocado::Middleware::ResponseSerialization
|
13
|
+
chain << Avocado::Middleware::ResourceSerialization
|
14
|
+
chain << Avocado::Middleware::ExampleSerialization
|
15
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Avocado
|
2
|
+
class Middleware::DocumentIfConfiguration
|
3
|
+
|
4
|
+
# return false if document_if returns false, so it will
|
5
|
+
# stop the middleware chain and not upload anything
|
6
|
+
def call(*)
|
7
|
+
continue = Avocado::Config.document_if.call
|
8
|
+
yield continue
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Avocado
|
2
|
+
class Middleware::DocumentMetadata
|
3
|
+
|
4
|
+
# return false if the :document metadata is given and is explicitly false
|
5
|
+
def call(example, *)
|
6
|
+
if example.metadata[:document] == false
|
7
|
+
yield false
|
8
|
+
else
|
9
|
+
yield
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|