dox 1.0.0.alpha → 1.0.0

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.
@@ -4,32 +4,34 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'dox/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "dox"
7
+ spec.name = 'dox'
8
8
  spec.version = Dox::VERSION
9
- spec.authors = ["Melita Kokot"]
10
- spec.email = ["melita.kokot@gmail.com"]
9
+ spec.authors = ['Melita Kokot', 'Vedran Hrnčić']
10
+ spec.email = ['melita.kokot@gmail.com', 'vrabac266@gmail.com']
11
11
 
12
- spec.summary = "Generates API documentation for rspec in api blueprint format."
13
- spec.homepage = "https://github.com/infinum/dox"
14
- spec.license = "MIT"
12
+ spec.summary = 'Generates API documentation for rspec in api blueprint format.'
13
+ spec.homepage = 'https://github.com/infinum/dox'
14
+ spec.license = 'MIT'
15
15
 
16
16
  # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
17
17
  # delete this section to allow pushing this gem to any host.
18
18
  if spec.respond_to?(:metadata)
19
- spec.metadata['allowed_push_host'] = "https://rubygems.org"
19
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
20
20
  else
21
- raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
21
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
22
22
  end
23
23
 
24
24
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
- spec.bindir = "exe"
25
+ spec.bindir = 'exe'
26
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
- spec.require_paths = ["lib"]
27
+ spec.require_paths = ['lib']
28
28
 
29
- spec.add_development_dependency "bundler", "~> 1.11"
30
- spec.add_development_dependency "rake", "~> 10.0"
31
- spec.add_development_dependency "rspec", "~> 3.0"
32
- spec.add_development_dependency "pry"
33
- spec.add_runtime_dependency "rspec-core"
34
- spec.add_runtime_dependency "rails", ">= 4.0"
29
+ spec.add_development_dependency 'bundler', '~> 1.11'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ spec.add_development_dependency 'rspec', '~> 3.0'
32
+ spec.add_development_dependency 'pry'
33
+ spec.add_development_dependency 'simplecov'
34
+ spec.add_development_dependency 'codeclimate-test-reporter'
35
+ spec.add_runtime_dependency 'rspec-core'
36
+ spec.add_runtime_dependency 'rails', '>= 4.0'
35
37
  end
data/exe/dox ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ puts `bundle exec rspec --tag apidoc -f Dox::Formatter --order defined #{ARGV.to_a.join(' ')}`
Binary file
data/lib/dox.rb CHANGED
@@ -1,3 +1,7 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/string'
3
+ require 'forwardable'
4
+
1
5
  require 'dox/config'
2
6
  require 'dox/dsl/attr_proxy'
3
7
  require 'dox/dsl/action'
@@ -21,19 +25,22 @@ require 'dox/printers/document_printer'
21
25
  require 'dox/printers/example_printer'
22
26
  require 'dox/printers/resource_group_printer'
23
27
  require 'dox/printers/resource_printer'
28
+ require 'dox/util/http'
24
29
  require 'dox/version'
25
30
 
26
-
27
31
  module Dox
28
- class << self
29
- attr_writer :config
30
- end
32
+ Error = Class.new(StandardError)
31
33
 
32
34
  def self.configure
33
35
  yield(config) if block_given?
34
36
  end
35
37
 
36
38
  def self.config
37
- @conifg ||= Dox::Config.new
39
+ @config ||= Dox::Config.new
40
+ end
41
+
42
+ DEFAULT_HEADERS_WHITELIST = ['Accept', 'Content-Type'].freeze
43
+ def self.full_headers_whitelist
44
+ (config.headers_whitelist.to_a + DEFAULT_HEADERS_WHITELIST).uniq
38
45
  end
39
46
  end
@@ -1,18 +1,16 @@
1
1
  module Dox
2
2
  class Config
3
-
4
3
  attr_reader :header_file_path, :desc_folder_path
4
+ attr_accessor :headers_whitelist
5
5
 
6
6
  def header_file_path=(file_path)
7
- raise(Errors::FileNotFoundError, file_path) unless File.exists?(file_path)
7
+ raise(Errors::FileNotFoundError, file_path) unless File.exist?(file_path)
8
8
  @header_file_path = file_path
9
9
  end
10
10
 
11
11
  def desc_folder_path=(folder_path)
12
- raise(Errors::FolderNotFoundError, folder_path) unless Dir.exists?(folder_path)
12
+ raise(Errors::FolderNotFoundError, folder_path) unless Dir.exist?(folder_path)
13
13
  @desc_folder_path = folder_path
14
14
  end
15
-
16
15
  end
17
16
  end
18
-
@@ -2,7 +2,7 @@ module Dox
2
2
  module DSL
3
3
  module AttrProxy
4
4
  def method_missing(name, value)
5
- setter = "#{name.to_s}="
5
+ setter = "#{name}="
6
6
  return super unless respond_to? setter
7
7
 
8
8
  public_send setter, value
@@ -19,7 +19,7 @@ module Dox
19
19
  end
20
20
 
21
21
  def group(name, &block)
22
- self._group = ResourceGroup.new(name: name, &block)
22
+ self._group = ResourceGroup.new(name, &block)
23
23
  end
24
24
 
25
25
  def config
@@ -10,23 +10,49 @@ module Dox
10
10
  @desc = details[:action_desc]
11
11
  @verb = details[:action_verb] || request.method
12
12
  @path = details[:action_path] || template_path
13
- @uri_params = details[:action_params]
13
+ @uri_params = details[:action_params] || template_path_params
14
14
  @examples = []
15
+
16
+ validate!
15
17
  end
16
18
 
17
19
  private
18
20
 
19
21
  attr_reader :request
20
22
 
23
+ # /pokemons/1 => pokemons/{id}
21
24
  def template_path
22
- path_params = request.path_parameters.symbolize_keys.except(:action, :controller)
23
- path = request.path.dup
25
+ path = request.path.dup.presence || request.fullpath.split("?").first
24
26
  path_params.each do |key, value|
25
- # /pokemons/1 => pokemons/{id}
26
- path.sub!(/\/#{value}(\/|$)/, "/{#{key}}\\1")
27
+ path.sub!(%r{\/#{value}(\/|$)}, "/{#{key}}\\1")
27
28
  end
28
29
  path
29
30
  end
31
+
32
+ def path_params
33
+ @path_params ||= request.path_parameters.symbolize_keys.except(:action, :controller, :format)
34
+ end
35
+
36
+ def template_path_params
37
+ h = {}
38
+ path_params.each do |param, value|
39
+ param_type = guess_param_type(value)
40
+ h[param] = { type: param_type, required: :required, value: value }
41
+ end
42
+ h
43
+ end
44
+
45
+ def guess_param_type(param)
46
+ if param =~ /^\d+$/
47
+ :number
48
+ else
49
+ :string
50
+ end
51
+ end
52
+
53
+ def validate!
54
+ raise(Error, "Unrecognized HTTP verb #{verb}") unless Util::Http.verb?(verb)
55
+ end
30
56
  end
31
57
  end
32
58
  end
@@ -1,8 +1,13 @@
1
1
  module Dox
2
2
  module Entities
3
3
  class Example
4
+ extend Forwardable
4
5
 
5
- attr_reader :desc, :request, :response
6
+ def_delegator :response, :status, :response_status
7
+ def_delegator :response, :content_type, :response_content_type
8
+ def_delegator :response, :body, :response_body
9
+ def_delegator :request, :content_type, :request_content_type
10
+ def_delegator :request, :method, :request_method
6
11
 
7
12
  def initialize(details, request, response)
8
13
  @desc = details[:description]
@@ -11,29 +16,57 @@ module Dox
11
16
  end
12
17
 
13
18
  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
+ request.parameters
20
+ .except(*request.path_parameters.keys.map(&:to_s))
21
+ .except(*request.query_parameters.keys.map(&:to_s))
19
22
  end
20
23
 
21
24
  def request_identifier
22
25
  @desc
23
26
  end
24
27
 
25
- def response_status
26
- response.status
28
+ def response_headers
29
+ @response_headers ||= filter_headers(response)
30
+ end
31
+
32
+ def request_headers
33
+ @request_headers ||= filter_headers(request)
34
+ end
35
+
36
+ # Rails 4 includes the body params in the request_fullpath
37
+ def request_fullpath
38
+ if request.query_parameters.present?
39
+ "#{request_path}?#{request_url_query_parameters}"
40
+ else
41
+ request_path
42
+ end
27
43
  end
28
44
 
29
- def response_content_type
30
- response.content_type
45
+ private
46
+
47
+ # Rails 5.0.2 returns "" for request.path
48
+ def request_path
49
+ request.path.presence || request.fullpath.split("?")[0]
31
50
  end
32
51
 
33
- def response_body
34
- response.body
52
+ attr_reader :desc, :request, :response
53
+
54
+ def filter_headers(obj)
55
+ headers_whitelist.map do |header|
56
+ header_val = obj.headers[header]
57
+ next if header_val.blank?
58
+
59
+ [header, header_val]
60
+ end.compact
35
61
  end
36
62
 
63
+ def headers_whitelist
64
+ @headers_whitelist ||= Dox.full_headers_whitelist
65
+ end
66
+
67
+ def request_url_query_parameters
68
+ CGI.unescape(request.query_parameters.to_query)
69
+ end
37
70
  end
38
71
  end
39
72
  end
@@ -1,86 +1,110 @@
1
+ require 'rspec/core'
1
2
  require 'rspec/core/formatters/base_formatter'
2
- require 'pry'
3
3
 
4
4
  module Dox
5
5
  class Formatter < RSpec::Core::Formatters::BaseFormatter
6
+ extend Forwardable
6
7
 
7
- RSpec::Core::Formatters.register self, :example_passed, :example_started, :stop
8
+ RSpec::Core::Formatters.register self, :example_passed, :stop
8
9
 
9
10
  def initialize(output)
10
11
  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
12
+ self.passed_examples = {}
17
13
  end
18
14
 
19
15
  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
16
+ self.current_example = CurrentExample.new(passed.example)
17
+ move_example_to_passed if current_example.document?
25
18
  end
26
19
 
27
20
  def stop(_notification)
28
- printer.print(@passed_examples)
21
+ printer.print(passed_examples)
29
22
  end
30
23
 
31
24
  private
32
25
 
26
+ attr_accessor :passed_examples
27
+ attr_accessor :current_example
28
+
33
29
  def load_or_save_group
34
- group_name = @current_example_data[:resource_group_name]
35
- group = @passed_examples[group_name]
30
+ group_name = current_example.resource_group_name
31
+ group = passed_examples[group_name]
36
32
 
37
33
  if group
38
- group.desc ||= @current_example_data[:resource_group_desc]
39
- group
34
+ group.tap { |g| g.desc ||= current_example.resource_group_desc }
40
35
  else
41
- @passed_examples[group_name] = Entities::ResourceGroup.new(group_name, @current_example_data)
36
+ passed_examples[group_name] = Entities::ResourceGroup.new(group_name,
37
+ current_example.metadata)
42
38
  end
43
39
  end
44
40
 
45
41
  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)
42
+ resource_name = current_example.resource_name
43
+ group.resources[resource_name] ||= Entities::Resource.new(resource_name,
44
+ current_example.metadata)
48
45
  end
49
46
 
50
47
  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, request)
48
+ action_name = current_example.action_name
49
+ resource.actions[action_name] ||= Entities::Action.new(action_name, current_example.metadata,
50
+ current_example.request)
53
51
  end
54
52
 
55
53
  def move_example_to_passed
56
54
  group = load_or_save_group
57
55
  resource = load_or_save_resource_to_group(group)
58
56
  action = load_or_save_action_to_resource(resource)
59
- action.examples << Entities::Example.new(@current_example_data, request, response)
57
+ action.examples << Entities::Example.new(current_example.metadata, current_example.request, current_example.response)
60
58
  end
61
59
 
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]
60
+ def printer
61
+ @printer ||= Printers::DocumentPrinter.new(output)
71
62
  end
72
63
 
73
- def request
74
- @example_group_instance.request
75
- end
64
+ class CurrentExample # :nodoc:
65
+ extend Forwardable
76
66
 
77
- def response
78
- @example_group_instance.response
79
- end
67
+ delegate [:metadata] => :example
80
68
 
81
- def printer
82
- @printer ||= Printers::DocumentPrinter.new(output)
83
- end
69
+ attr_reader :example
70
+
71
+ def initialize(example)
72
+ @example = example
73
+ end
74
+
75
+ def resource_group_name
76
+ metadata[:resource_group_name]
77
+ end
84
78
 
79
+ def resource_group_desc
80
+ metadata[:resource_group_desc]
81
+ end
82
+
83
+ def resource_name
84
+ metadata[:resource_name]
85
+ end
86
+
87
+ def action_name
88
+ metadata[:action_name]
89
+ end
90
+
91
+ def request
92
+ metadata[:request]
93
+ end
94
+
95
+ def response
96
+ metadata[:response]
97
+ end
98
+
99
+ def document?
100
+ tagged_with?(:apidoc) && tagged_with?(:dox) && !tagged_with?(:nodoc)
101
+ end
102
+
103
+ private
104
+
105
+ def tagged_with?(key)
106
+ metadata.key?(key) && metadata[key] == true
107
+ end
108
+ end
85
109
  end
86
110
  end
@@ -1,13 +1,10 @@
1
1
  module Dox
2
2
  module Printers
3
3
  class ActionPrinter < BasePrinter
4
-
5
4
  def print(action)
6
- @output.puts "### #{action.name} [#{action.verb} #{action.path}]\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
5
+ self.action = action
6
+ @output.puts action_title
7
+ @output.puts action_uri_params if action.uri_params.present?
11
8
 
12
9
  action.examples.each do |example|
13
10
  example_printer.print(example)
@@ -16,16 +13,35 @@ module Dox
16
13
 
17
14
  private
18
15
 
16
+ attr_accessor :action
17
+
18
+ def action_title
19
+ <<-HEREDOC
20
+
21
+ ### #{action.name} [#{action.verb.upcase} #{action.path}]
22
+ #{print_desc(action.desc)}
23
+ HEREDOC
24
+ end
25
+
26
+ def action_uri_params
27
+ <<-HEREDOC
28
+ + Parameters
29
+ #{formatted_params(action.uri_params)}
30
+ HEREDOC
31
+ end
32
+
19
33
  def example_printer
20
34
  @example_printer ||= ExamplePrinter.new(@output)
21
35
  end
22
36
 
23
37
  def formatted_params(uri_params)
24
38
  uri_params.map do |param, details|
25
- " + #{CGI.escape(param.to_s)}: `#{CGI.escape(details[:value].to_s)}` (#{details[:type]}, #{details[:required]}) - #{details[:description]}"
39
+ desc = " + #{CGI.escape(param.to_s)}: `#{CGI.escape(details[:value].to_s)}` (#{details[:type]}, #{details[:required]})"
40
+ desc += " - #{details[:description]}" if details[:description].present?
41
+ desc += "\n + Default: #{details[:default]}" if details[:default].present?
42
+ desc
26
43
  end.flatten.join("\n")
27
44
  end
28
-
29
45
  end
30
46
  end
31
47
  end