dox 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fd403721b5cfc4b7c0b7e1ac2c5605b9dc2eb935
4
- data.tar.gz: 5717471c12c20d20c0e2cf33ebbdf46a4c89e57c
3
+ metadata.gz: 77769eb7c66afabb92fdfd03814b9ba19fd4c532
4
+ data.tar.gz: 810167221ebe04073d1a33f5eecc6db0f9683589
5
5
  SHA512:
6
- metadata.gz: c8106a3112d98b5418d4cee81cebe6038f006c6d77fdf94ea0dd0d02aefef2741b0c6e4d7b43839dbd6e79e971a89869dca61545fc9e00082d2abdc9c6873654
7
- data.tar.gz: cccfa6d0735c1e94f0eac444fcbd07563bd364d86491ea4e3283b331862c024ba5fce0cdfebe080daf5aa88176b3561fdb6ac87fe7b97609660a1a3106a6c727
6
+ metadata.gz: 47deb48ed84dbd87868adb2b360e77e3a7ecf3762e607a23e13af2024e262c8ab2b1f81a837efcca5fff386490a66f0c6b7d36100f00974738b4a3cb5380be7b
7
+ data.tar.gz: 10e5dd7824ded8945f169d9336cb0413d60121b6b4656332e43e5c860f452c073b8f9f32411dbf6cdfcb21799ba8b7f3c7add4de27979828c4c1597942267be2
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Dox
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dox`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Dox formats the rspec output in the [api blueprint](https://apiblueprint.org/) format.
6
4
 
7
5
  ## Installation
8
6
 
@@ -22,7 +20,94 @@ Or install it yourself as:
22
20
 
23
21
  ## Usage
24
22
 
25
- TODO: Write usage instructions here
23
+ ### Code example
24
+
25
+ Example documentation module for a resource:
26
+
27
+ ``` ruby
28
+ module ApiDoc
29
+ module V1
30
+ module Bids
31
+ include Dox::DSL::Syntax
32
+
33
+ document :api do
34
+ group do
35
+ name 'Bids'
36
+ end
37
+
38
+ resource do
39
+ name 'Bids'
40
+ endpoint '/bids'
41
+ group 'Bids'
42
+ desc 'bid_resource.md'
43
+ end
44
+ end
45
+
46
+ document :index do
47
+ action do
48
+ name 'Get bids'
49
+ verb 'GET'
50
+ path '/bids'
51
+ desc 'Returns list of user bids'
52
+ end
53
+ end
54
+
55
+ document :create do
56
+ action do
57
+ name 'Post bids'
58
+ verb 'POST'
59
+ path '/bids'
60
+ desc 'Creates bid'
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ ```
67
+ Description can be included inline or relative path of a markdown file with the description (relative to configured folder for markdown descriptions).
68
+
69
+ Including the documentation module in a controller:
70
+
71
+ ``` ruby
72
+ describe Api::V1::BidsController, type: :controller do
73
+ include ApiDoc::V1::Bids::Api
74
+
75
+ describe 'GET #index' do
76
+ include ApiDoc::V1::Bids::Index
77
+
78
+ it 'returns a list of bids' do
79
+ get :index
80
+ expect(response).to have_http_status(:ok)
81
+ end
82
+
83
+ end
84
+ end
85
+ ```
86
+
87
+ ### Configuration
88
+
89
+ You have to specify **root api file** and **descriptions folder**.
90
+
91
+ Root api file is a markdown file that will be included in the top of the documentation. It should contain title and some basic info about the api.
92
+
93
+ Descriptions folder is a fullpath of a folder that contains markdown files with descriptions which behave like partials and are included in the final concatenated markdown. Root api file should also be in this folder.
94
+
95
+ ``` ruby
96
+ Dox.configure do |config|
97
+ config.root_api_file = 'api.md'
98
+ config.desc_folder_path = Rails.root.join('spec/support/api_doc/v1/markdown_descriptions')
99
+ end
100
+ ```
101
+
102
+ ### Generate HTML documentation
103
+ You have to install [aglio](https://www.npmjs.com/package/aglio).
104
+
105
+ Rake task for generating HTML:
106
+
107
+ ``` ruby
108
+ `bundle exec rspec spec --tag apidoc -f Dox::Formatter --order defined --out spec/apispec.md`
109
+ `aglio --include-path / -i spec/apispec.md -o public/api/docs/index.html`
110
+ ```
26
111
 
27
112
  ## Development
28
113
 
data/lib/dox.rb CHANGED
@@ -1,5 +1,35 @@
1
+ require "rspec/rails"
1
2
  require "dox/version"
3
+ require "dox/config"
4
+ require "dox/formatter"
5
+ require "dox/dsl/attr_proxy"
6
+ require "dox/dsl/action"
7
+ require "dox/dsl/resource"
8
+ require "dox/dsl/resource_group"
9
+ require "dox/dsl/documentation"
10
+ require "dox/dsl/syntax"
11
+ require "dox/entities/action"
12
+ require "dox/entities/example"
13
+ require "dox/entities/resource"
14
+ require "dox/entities/resource_group"
15
+ require "dox/printers/base_printer"
16
+ require "dox/printers/action_printer"
17
+ require "dox/printers/document_printer"
18
+ require "dox/printers/example_printer"
19
+ require "dox/printers/resource_group_printer"
20
+ require "dox/printers/resource_printer"
21
+
2
22
 
3
23
  module Dox
4
- # Your code goes here...
24
+ class << self
25
+ attr_writer :config
26
+ end
27
+
28
+ def self.configure
29
+ yield(config) if block_given?
30
+ end
31
+
32
+ def self.config
33
+ @conifg ||= Dox::Config.new
34
+ end
5
35
  end
data/lib/dox/config.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Dox
2
+ class Config
3
+
4
+ attr_accessor :root_api_file, :desc_folder_path
5
+
6
+ def initialize
7
+ @root_api_file = 'api.md'
8
+ @desc_folder_path = Rails.root.join('')
9
+ end
10
+
11
+ end
12
+ end
13
+
@@ -0,0 +1,35 @@
1
+ module Dox
2
+ module DSL
3
+ class Action
4
+ include AttrProxy
5
+
6
+ attr_writer :name
7
+ attr_writer :verb
8
+ attr_writer :path
9
+ attr_writer :desc
10
+ attr_writer :params
11
+
12
+ def initialize(opts = {})
13
+ self.name = opts.fetch(:name, nil)
14
+ self.verb = opts.fetch(:verb, nil)
15
+ self.path = opts.fetch(:path, nil)
16
+ self.desc = opts.fetch(:desc, nil)
17
+ self.params = opts.fetch(:params, nil)
18
+ end
19
+
20
+ def param(signature)
21
+ params << signature
22
+ end
23
+
24
+ def config
25
+ Hash.new.tap do |config|
26
+ config[:action_name] = @name if @name
27
+ config[:action_verb] = @verb if @verb
28
+ config[:action_path] = @path if @path
29
+ config[:action_desc] = @desc if @desc
30
+ config[:action_params] = @params if @params
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ module Dox
2
+ module DSL
3
+ module AttrProxy
4
+ def method_missing(name, value)
5
+ setter = "#{name.to_s}="
6
+ return super unless respond_to? setter
7
+
8
+ public_send setter, value
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module Dox
2
+ module DSL
3
+ class Documentation
4
+ attr_accessor :subject
5
+ attr_accessor :_resource
6
+ attr_accessor :_action
7
+ attr_accessor :_group
8
+
9
+ def initialize(opts = {})
10
+ self.subject = opts.fetch :subject
11
+ end
12
+
13
+ def resource(name = nil, &block)
14
+ self._resource = Resource.new(name: name)
15
+ _resource.instance_eval(&block)
16
+ end
17
+
18
+ def action(name = nil, &block)
19
+ self._action = Action.new(name: name)
20
+ _action.instance_eval(&block)
21
+ end
22
+
23
+ def group(name = nil, &block)
24
+ self._group = ResourceGroup.new(name: name)
25
+ _group.instance_eval(&block)
26
+ end
27
+
28
+ def config
29
+ {}.merge(_resource ? _resource.config : {})
30
+ .merge(_action ? _action.config : {})
31
+ .merge(_group ? _group.config : {})
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ module Dox
2
+ module DSL
3
+ class Resource
4
+ include AttrProxy
5
+
6
+ attr_writer :name
7
+ attr_writer :group
8
+ attr_writer :desc
9
+ attr_writer :endpoint
10
+
11
+ def initialize(opts = {})
12
+ self.name = opts.fetch(:name, nil)
13
+ self.desc = opts.fetch(:desc, nil)
14
+ self.group = opts.fetch(:group, nil)
15
+ self.endpoint = opts.fetch(:endpoint, nil)
16
+ end
17
+
18
+ def config
19
+ {}.tap do |config|
20
+ config[:resource_name] = @name if @name
21
+ config[:resource_desc] = @desc if @desc
22
+ config[:resource_group_name] = @group if @group
23
+ config[:resource_endpoint] = @endpoint if @endpoint
24
+ config[:apidoc] = true
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ module Dox
2
+ module DSL
3
+ class ResourceGroup
4
+ include AttrProxy
5
+
6
+ attr_writer :name
7
+ attr_writer :desc
8
+
9
+ def initialize(opts = {})
10
+ self.name = opts.fetch(:name, nil)
11
+ self.desc = opts.fetch(:desc, nil)
12
+ end
13
+
14
+ def config
15
+ {}.tap do |config|
16
+ config[:resource_group_name] = @name if @name
17
+ config[:resource_group_desc] = @desc if @desc
18
+ config[:apidoc] = true
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ module Dox
2
+ module DSL
3
+ module Syntax
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def document(subject, &block)
8
+ documentation = _subjects[subject] = Documentation.new(subject: subject)
9
+ documentation.instance_eval(&block)
10
+ end
11
+
12
+ def const_missing(name)
13
+ documentation = _subjects[infer_subject(name)]
14
+ return super unless documentation
15
+
16
+ Module.new do
17
+ define_singleton_method :included do |base|
18
+ base.metadata.merge! documentation.config
19
+ end
20
+ end
21
+ end
22
+
23
+ def infer_subject(name)
24
+ name.to_s.underscore.to_sym
25
+ end
26
+
27
+ def _subjects
28
+ @_subjects ||= {}
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ module Dox
2
+ module Entities
3
+ class Action
4
+
5
+ attr_accessor :name, :desc, :verb, :path, :uri_params, :examples
6
+
7
+ def initialize(name, details)
8
+ @name = name
9
+ @desc = details[:action_desc]
10
+ @verb = details[:action_verb]
11
+ @path = details[:action_path]
12
+ @uri_params = details[:action_params]
13
+ @examples = []
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,52 @@
1
+ module Dox
2
+ module Entities
3
+ class Example
4
+
5
+ attr_reader :desc, :request, :response
6
+
7
+ def initialize(details, request, response)
8
+ @desc = details[:description]
9
+ @request = request
10
+ @response = response
11
+ end
12
+
13
+ def request_parameters
14
+ request.parameters.except(*request.path_parameters.keys.map(&:to_s)).except(*request.query_parameters.keys.map(&:to_s))
15
+ end
16
+
17
+ def request_content_type
18
+ request.content_type
19
+ end
20
+
21
+ def request_identifier
22
+ fullpath
23
+ end
24
+
25
+ def response_status
26
+ response.status
27
+ end
28
+
29
+ def response_content_type
30
+ response.content_type
31
+ end
32
+
33
+ def response_body
34
+ response.body
35
+ end
36
+
37
+ private
38
+
39
+ def path
40
+ request.path.sub(/\.json[^\?]*/, '')
41
+ end
42
+
43
+ def fullpath
44
+ if request.query_parameters.present?
45
+ URI::HTTP.build(path: path, query: request.query_parameters.to_query)
46
+ else
47
+ path
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,16 @@
1
+ module Dox
2
+ module Entities
3
+ class Resource
4
+
5
+ attr_accessor :name, :desc, :endpoint, :actions
6
+
7
+ def initialize(name, details)
8
+ @name = name
9
+ @desc = details[:resource_desc]
10
+ @endpoint = details[:resource_endpoint]
11
+ @actions = {}
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module Dox
2
+ module Entities
3
+ class ResourceGroup
4
+
5
+ attr_reader :name
6
+ attr_accessor :desc, :resources
7
+
8
+ def initialize(name, details)
9
+ @name = name
10
+ @desc = details[:resource_group_desc]
11
+ @resources = {}
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,86 @@
1
+ require 'rspec/core/formatters/base_formatter'
2
+ require 'pry'
3
+
4
+ module Dox
5
+ class Formatter < RSpec::Core::Formatters::BaseFormatter
6
+
7
+ RSpec::Core::Formatters.register self, :example_passed, :example_started, :stop
8
+
9
+ def initialize(output)
10
+ super
11
+ @passed_examples = {}
12
+ @group_level = 0
13
+ end
14
+
15
+ def example_started(notification)
16
+ @example_group_instance = notification.example.example_group_instance
17
+ end
18
+
19
+ def example_passed(passed)
20
+ @current_example_data = passed.example.metadata
21
+ if should_document_example?
22
+ move_example_to_passed
23
+ end
24
+ @example_group_instance = nil
25
+ end
26
+
27
+ def stop(_notification)
28
+ printer.print(@passed_examples)
29
+ end
30
+
31
+ private
32
+
33
+ def load_or_save_group
34
+ group_name = @current_example_data[:resource_group_name]
35
+ group = @passed_examples[group_name]
36
+
37
+ if group
38
+ group.desc ||= @current_example_data[:resource_group_desc]
39
+ group
40
+ else
41
+ @passed_examples[group_name] = Entities::ResourceGroup.new(group_name, @current_example_data)
42
+ end
43
+ end
44
+
45
+ def load_or_save_resource_to_group(group)
46
+ resource_name = @current_example_data[:resource_name]
47
+ group.resources[resource_name] ||= Entities::Resource.new(resource_name, @current_example_data)
48
+ end
49
+
50
+ def load_or_save_action_to_resource(resource)
51
+ action_name = @current_example_data[:action_name]
52
+ resource.actions[action_name] ||= Entities::Action.new(action_name, @current_example_data)
53
+ end
54
+
55
+ def move_example_to_passed
56
+ group = load_or_save_group
57
+ resource = load_or_save_resource_to_group(group)
58
+ action = load_or_save_action_to_resource(resource)
59
+ action.examples << Entities::Example.new(@current_example_data, request, response)
60
+ end
61
+
62
+ def should_document_example?
63
+ @current_example_data[:apidoc] &&
64
+ !@current_example_data[:nodoc]
65
+ # error check
66
+ #&&
67
+ # @current_example_data[:resource_group] &&
68
+ # @current_example_data[:resource] &&
69
+ # @current_example_data[:action] &&
70
+ # !@current_example_data[:nodoc]
71
+ end
72
+
73
+ def request
74
+ @example_group_instance.request
75
+ end
76
+
77
+ def response
78
+ @example_group_instance.response
79
+ end
80
+
81
+ def printer
82
+ @printer ||= Printers::DocumentPrinter.new(output)
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,31 @@
1
+ module Dox
2
+ module Printers
3
+ class ActionPrinter < BasePrinter
4
+
5
+ def print(action)
6
+ @output.puts "### #{action.name}\n\n#{print_desc(action.desc)}\n\n"
7
+
8
+ if action.uri_params.present?
9
+ @output.puts("+ Parameters\n#{formatted_params(action.uri_params)}")
10
+ end
11
+
12
+ action.examples.each do |example|
13
+ example_printer.print(example)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def example_printer
20
+ @example_printer ||= ExamplePrinter.new(@output)
21
+ end
22
+
23
+ def formatted_params(uri_params)
24
+ uri_params.map do |param, details|
25
+ " + #{CGI.escape(param.to_s)}: `#{CGI.escape(details[:value].to_s)}` (#{details[:type]}, #{details[:required]}) - #{details[:description]}"
26
+ end.flatten.join("\n")
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module Dox
2
+ module Printers
3
+ class BasePrinter
4
+
5
+ attr_reader :descriptions_folder_path
6
+
7
+ def initialize(output)
8
+ @output = output
9
+ @descriptions_folder_path = Dox.config.desc_folder_path
10
+ end
11
+
12
+ def print
13
+ raise NotImplementedError
14
+ end
15
+
16
+ private
17
+
18
+ def print_desc(desc)
19
+ return unless desc.present?
20
+
21
+ if desc.to_s =~ /.*\.md$/
22
+ path = descriptions_folder_path.join(desc).to_s
23
+ "<!-- include(#{path}) -->"
24
+ else
25
+ desc
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ module Dox
2
+ module Printers
3
+ class DocumentPrinter < BasePrinter
4
+
5
+ def print(passed_examples)
6
+ print_meta_info
7
+
8
+ passed_examples.sort.each do |_, resource_group|
9
+ group_printer.print(resource_group)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def group_printer
16
+ @group_printer ||= ResourceGroupPrinter.new(@output)
17
+ end
18
+
19
+ def print_meta_info
20
+ @output.puts(print_desc(api_desc_path))
21
+ end
22
+
23
+ def api_desc_path
24
+ Dox.config.root_api_file
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ module Dox
2
+ module Printers
3
+ class ExamplePrinter < BasePrinter
4
+
5
+ def print(example)
6
+ @output.puts "\n+ Request #{example.request_identifier} (#{example.request_content_type})"
7
+
8
+ if example.request_parameters.present?
9
+ @output.puts "\n#{indent_lines(8, pretty_json(example.request_parameters))}\n"
10
+ end
11
+
12
+ @output.puts "+ Response #{example.response_status} (#{example.response_content_type})"
13
+
14
+ if example.response_body.present?
15
+ @output.puts "\n#{indent_lines(8, pretty_json(safe_json_parse(example.response_body)))}\n"
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def safe_json_parse(json_string)
22
+ json_string.length >= 2 ? JSON.parse(json_string) : nil
23
+ end
24
+
25
+ def pretty_json(json_string)
26
+ if json_string.present?
27
+ JSON.pretty_generate(json_string)
28
+ else
29
+ ''
30
+ end
31
+ end
32
+
33
+ def indent_lines(number_of_spaces, string)
34
+ string
35
+ .split("\n")
36
+ .map { |a| a.prepend(' ' * number_of_spaces) }
37
+ .join("\n")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ module Dox
2
+ module Printers
3
+ class ResourceGroupPrinter < BasePrinter
4
+
5
+ def print(resource_group)
6
+ @output.puts "\n# Group #{resource_group.name}\n\n#{print_desc(resource_group.desc)}\n"
7
+
8
+ resource_group.resources.each do |_, resource|
9
+ resource_printer.print(resource)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def resource_printer
16
+ @resource_printer ||= ResourcePrinter.new(@output)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ module Dox
2
+ module Printers
3
+ class ResourcePrinter < BasePrinter
4
+
5
+ def print(resource)
6
+ if resource.endpoint.present?
7
+ @output.puts "\n## #{resource.name} [#{resource.endpoint}]\n\n#{print_desc(resource.desc)}\n"
8
+ else
9
+ @output.puts "## #{resource.name}"
10
+ end
11
+
12
+ resource.actions.each do |_, action|
13
+ action_printer.print(action)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def action_printer
20
+ @action_printer ||= ActionPrinter.new(@output)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
data/lib/dox/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dox
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Melita Kokot
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-06-06 00:00:00.000000000 Z
11
+ date: 2016-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -71,6 +71,24 @@ files:
71
71
  - bin/setup
72
72
  - dox.gemspec
73
73
  - lib/dox.rb
74
+ - lib/dox/config.rb
75
+ - lib/dox/dsl/action.rb
76
+ - lib/dox/dsl/attr_proxy.rb
77
+ - lib/dox/dsl/documentation.rb
78
+ - lib/dox/dsl/resource.rb
79
+ - lib/dox/dsl/resource_group.rb
80
+ - lib/dox/dsl/syntax.rb
81
+ - lib/dox/entities/action.rb
82
+ - lib/dox/entities/example.rb
83
+ - lib/dox/entities/resource.rb
84
+ - lib/dox/entities/resource_group.rb
85
+ - lib/dox/formatter.rb
86
+ - lib/dox/printers/action_printer.rb
87
+ - lib/dox/printers/base_printer.rb
88
+ - lib/dox/printers/document_printer.rb
89
+ - lib/dox/printers/example_printer.rb
90
+ - lib/dox/printers/resource_group_printer.rb
91
+ - lib/dox/printers/resource_printer.rb
74
92
  - lib/dox/version.rb
75
93
  homepage: https://github.com/infinum/dox
76
94
  licenses:
@@ -98,4 +116,3 @@ signing_key:
98
116
  specification_version: 4
99
117
  summary: Generates API documentation for rspec in api blueprint format.
100
118
  test_files: []
101
- has_rdoc: