avocado-docs 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,4 @@
1
+ module Avocado
2
+ class SpecsController < AvocadoController
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Avocado
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,13 @@
1
+ module Avocado
2
+ class Endpoint
3
+ include ActiveModel::Model
4
+
5
+ attr_accessor :signature, :requests
6
+
7
+ def initialize(*)
8
+ @requests = SortedSet.new
9
+ super
10
+ end
11
+
12
+ end
13
+ 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,17 @@
1
+ module Avocado
2
+ class Resource
3
+ include ActiveModel::Model
4
+
5
+ attr_accessor :name, :endpoints
6
+
7
+ def initialize(*)
8
+ @endpoints = Set.new
9
+ super
10
+ end
11
+
12
+ def <=>(other)
13
+ name <=> other.name
14
+ end
15
+
16
+ end
17
+ 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>
@@ -0,0 +1,4 @@
1
+ Avocado::Engine.routes.draw do
2
+ root to: 'specs#index'
3
+ resources :specs, only: [:create]
4
+ end
@@ -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,5 @@
1
+ module Avocado
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Avocado
4
+ end
5
+ 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
@@ -0,0 +1,10 @@
1
+ module Avocado
2
+ class Middleware::ExampleSerialization
3
+
4
+ def call(example, *)
5
+ Avocado::Cache.json[:description] = example.description
6
+ yield
7
+ end
8
+
9
+ end
10
+ end