avocado-docs 2.0.5
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.
- 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
|