dox 1.0.0.alpha → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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