dox 0.0.1 → 0.0.2

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 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: