betterdocs 0.2.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +5 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +9 -0
  6. data/COPYING +202 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE +202 -0
  9. data/README.md +124 -0
  10. data/Rakefile +25 -0
  11. data/VERSION +1 -0
  12. data/betterdocs.gemspec +48 -0
  13. data/lib/betterdocs.rb +27 -0
  14. data/lib/betterdocs/controller_collector.rb +50 -0
  15. data/lib/betterdocs/dsl.rb +9 -0
  16. data/lib/betterdocs/dsl/common.rb +26 -0
  17. data/lib/betterdocs/dsl/controller.rb +9 -0
  18. data/lib/betterdocs/dsl/controller/action.rb +126 -0
  19. data/lib/betterdocs/dsl/controller/action/param.rb +25 -0
  20. data/lib/betterdocs/dsl/controller/action/response.rb +47 -0
  21. data/lib/betterdocs/dsl/controller/controller.rb +31 -0
  22. data/lib/betterdocs/dsl/controller/controller_base.rb +21 -0
  23. data/lib/betterdocs/dsl/json_params.rb +8 -0
  24. data/lib/betterdocs/dsl/json_params/param.rb +31 -0
  25. data/lib/betterdocs/dsl/json_type_mapper.rb +27 -0
  26. data/lib/betterdocs/dsl/naming.rb +32 -0
  27. data/lib/betterdocs/dsl/representer.rb +29 -0
  28. data/lib/betterdocs/dsl/result.rb +10 -0
  29. data/lib/betterdocs/dsl/result/collection_property.rb +9 -0
  30. data/lib/betterdocs/dsl/result/link.rb +37 -0
  31. data/lib/betterdocs/dsl/result/property.rb +53 -0
  32. data/lib/betterdocs/generator/config_shortcuts.rb +28 -0
  33. data/lib/betterdocs/generator/markdown.rb +151 -0
  34. data/lib/betterdocs/generator/markdown/templates/README.md.erb +9 -0
  35. data/lib/betterdocs/generator/markdown/templates/section.md.erb +132 -0
  36. data/lib/betterdocs/global.rb +143 -0
  37. data/lib/betterdocs/json_params_representer.rb +37 -0
  38. data/lib/betterdocs/json_params_representer_collector.rb +48 -0
  39. data/lib/betterdocs/mix_into_controller.rb +19 -0
  40. data/lib/betterdocs/rake_tasks.rb +5 -0
  41. data/lib/betterdocs/representer.rb +42 -0
  42. data/lib/betterdocs/result_representer.rb +68 -0
  43. data/lib/betterdocs/result_representer_collector.rb +82 -0
  44. data/lib/betterdocs/section.rb +6 -0
  45. data/lib/betterdocs/tasks/doc.rake +55 -0
  46. data/lib/betterdocs/version.rb +8 -0
  47. data/spec/controller_dsl_spec.rb +143 -0
  48. data/spec/generator/markdown_spec.rb +5 -0
  49. data/spec/json_params_representer_spec.rb +79 -0
  50. data/spec/json_type_mapper_spec.rb +33 -0
  51. data/spec/result_representer_dsl_spec.rb +183 -0
  52. data/spec/result_representer_spec.rb +182 -0
  53. data/spec/spec_helper.rb +19 -0
  54. metadata +234 -0
@@ -0,0 +1,82 @@
1
+ module Betterdocs
2
+ class ResultRepresenterCollector
3
+ def initialize
4
+ @properties = {}
5
+ @links = {}
6
+ end
7
+
8
+ attr_reader :properties
9
+
10
+ attr_reader :links
11
+
12
+ def property(property_name)
13
+ property_name = property_name.to_sym
14
+ @properties[property_name]
15
+ end
16
+
17
+ def link(link_name)
18
+ link_name = link_name.to_sym
19
+ @links[link_name]
20
+ end
21
+
22
+ def add_element(representer, type, name, **options, &block)
23
+ element = build_element(representer, type, name, options, &block)
24
+ element.add_to_collector(self)
25
+ end
26
+
27
+ def representer
28
+ (@properties.values + @links.values).find { |v|
29
+ v.representer and break v.representer
30
+ }
31
+ end
32
+
33
+ def nested_properties(path = [])
34
+ properties.values.each_with_object([]) do |property, result|
35
+ result << property.below_path(path)
36
+ if sr = property.sub_representer?
37
+ result.concat sr.docs.nested_properties(path + property.path)
38
+ end
39
+ end
40
+ end
41
+
42
+ def nested_links(path = [])
43
+ result = links.values.map { |l| l.below_path(path) }
44
+ properties.values.each_with_object(result) do |property, result|
45
+ if sr = property.sub_representer?
46
+ nested_property = property.below_path(path)
47
+ links = sr.docs.nested_links(nested_property.path)
48
+ result.concat links
49
+ end
50
+ end
51
+ result
52
+ end
53
+
54
+ def to_s
55
+ result = "*** #{representer} ***\n"
56
+ if properties = @properties.values.full?
57
+ result << "\nProperties:"
58
+ nested_properties.each_with_object(result) do |property, r|
59
+ r << "\n#{property.full_name}: (#{property.types * '|'}): #{property.description}\n"
60
+ end
61
+ end
62
+ if links = @links.values.full?
63
+ result << "\nLinks:"
64
+ links.each_with_object(result) do |link, r|
65
+ r << "\n#{link.full_name}: #{link.description}\n" # TODO resolve link.url in some useful way
66
+ end
67
+ end
68
+ result
69
+ end
70
+
71
+ private
72
+
73
+ def build_element(representer, type, *args, &block)
74
+ begin
75
+ element = Dsl::Result.const_get(type.to_s.camelcase)
76
+ rescue NameError => e
77
+ raise ArgumentError, "unknown documentation element type #{type.inspect}"
78
+ end
79
+ element.new(representer, *args, &block)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,6 @@
1
+ require 'tins/named_set'
2
+
3
+ module Betterdocs
4
+ class Section < Tins::NamedSet
5
+ end
6
+ end
@@ -0,0 +1,55 @@
1
+ namespace :doc do
2
+ desc "Create the API documentation"
3
+ task :api => :'doc:api:sandbox' do
4
+ Betterdocs::Global.config do |config|
5
+ Betterdocs::Generator::Markdown.new(only: ENV['ONLY']).generate
6
+ cd config.output_directory do
7
+ File.open('.gitignore', 'w') { |ignore| ignore.puts config.ignore }
8
+ end
9
+ end
10
+ end
11
+
12
+ task :set_betterdocs_env do
13
+ ENV['BETTERDOCS'] = '1'
14
+ end
15
+
16
+ namespace :api do
17
+ desc 'Let database transactions run in a sandboxed environment'
18
+ task :sandbox => [:'doc:set_betterdocs_env', :environment] do
19
+
20
+ ActiveRecord::Base.connection.begin_db_transaction
21
+ at_exit do
22
+ ActiveRecord::Base.connection.rollback_db_transaction
23
+ end
24
+ end
25
+
26
+ desc "Push the newly created API documentation to the remote git repo"
27
+ task :push => :api do
28
+ Betterdocs::Global.config do |config|
29
+ config.publish_git or fail "Configure a git repo as publish_git to publish to"
30
+ cd config.output_directory do
31
+ File.directory?('.git') or sh "git init"
32
+ sh "git remote rm publish_git || true"
33
+ sh "git remote add publish_git #{config.publish_git}"
34
+ sh "git add -A"
35
+ sh 'git commit -m "Add some more changes to API documentation" || true'
36
+ sh 'git push -f publish_git master'
37
+ end
38
+ end
39
+ end
40
+
41
+ desc "Publish the newly created API documentation"
42
+ task :publish => [ :push ]
43
+
44
+ desc "Publish and view the newly created API documentation"
45
+ task :view => :publish do
46
+ Betterdocs::Global.config do |config|
47
+ url = config.publish_git
48
+ if url !~ /\Ahttps?:/
49
+ url.sub!(/.*?([^@]*):/, 'http://\1/')
50
+ end
51
+ sh "open #{url.inspect}"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,8 @@
1
+ module Betterdocs
2
+ # Betterdocs version
3
+ VERSION = '0.2.0'
4
+ VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
7
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
8
+ end
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'controller dsl' do
4
+ let :docs do
5
+ Betterdocs::ControllerCollector.new
6
+ end
7
+
8
+ let :rails do
9
+ double(application: double(routes: double(url_for: 'http://foo/bar')))
10
+ end
11
+
12
+ let :controller do
13
+ Module.new do
14
+ class << self
15
+ def name
16
+ 'MyTestController'
17
+ end
18
+
19
+ alias to_s name
20
+ end
21
+
22
+
23
+ def foo
24
+ end
25
+ end
26
+ end
27
+
28
+ it "cannot add unknown element types" do
29
+ expect {
30
+ docs.add_element controller, :foobar, 'my_foobar' do
31
+ end
32
+ }.to raise_error(ArgumentError)
33
+ end
34
+
35
+ context 'controller' do
36
+ it "can add a new controller" do
37
+ docs.add_element controller, :controller do
38
+ description 'my description'
39
+ section :test_section
40
+ end
41
+ allow(Betterdocs).to receive(:rails).and_return rails
42
+ my_controller = docs.controller
43
+ expect(my_controller).to be_present
44
+ expect(my_controller.name).to eq :my_test
45
+ expect(my_controller.section).to eq :test_section
46
+ expect(my_controller.controller).to eq controller
47
+ expect(my_controller.description).to eq 'my description'
48
+ expect(my_controller.url).to eq 'http://foo/bar'
49
+ expect(docs.to_s).to eq(<<EOT)
50
+ MyTestController
51
+
52
+ url: http://foo/bar
53
+
54
+ my description
55
+
56
+ ===============================================================================
57
+
58
+ EOT
59
+ end
60
+
61
+ it 'can be represented as a string if empty' do
62
+ expect(docs.to_s).to eq(
63
+ "\n===============================================================================\n\n"
64
+ )
65
+ end
66
+ end
67
+
68
+ module MyActionJsonParams
69
+ include Betterdocs::JsonParamsRepresenter
70
+
71
+ param :baz do
72
+ description 'Some string'
73
+ types String
74
+ value 'baz'
75
+ end
76
+ end
77
+
78
+ SomeParamsTrait = Betterdocs.trait do
79
+ param :quux do
80
+ end
81
+ end
82
+
83
+ context 'action' do
84
+ it "can add a new action" do
85
+ docs.add_element controller, :controller do
86
+ description 'my controller description'
87
+ section :test_section
88
+ end
89
+ docs.add_element controller, :action do
90
+ description 'my description'
91
+ section :test_section2
92
+ http_method :GET
93
+
94
+ param :bar do
95
+ end
96
+
97
+ param :baz do
98
+ use_in_url no
99
+ end
100
+
101
+ include_params SomeParamsTrait
102
+
103
+ json_params_like MyActionJsonParams
104
+ end
105
+ allow(Betterdocs).to receive(:rails).and_return rails
106
+ expect(docs.actions).to be_empty
107
+ docs.configure_current_element(:foo)
108
+ expect(docs.actions.size).to eq 1
109
+ expect(action = docs.action(:foo)).to be_present
110
+ expect(action.controller).to eq controller
111
+ expect(action.name).to eq :foo
112
+ expect(action.section).to eq :test_section2
113
+ expect(docs.section).to eq :test_section
114
+ expect(action.action_method).to eq controller.instance_method(:foo)
115
+ expect(action.http_method).to eq :GET
116
+ expect(action.params).to have_key :bar
117
+ expect(action.params).to have_key :quux
118
+ expect(action.json_params).to have_key :baz
119
+ expect(action.url).to eq 'http://foo/bar'
120
+ expect(docs.to_s.sub(%r(.*(betterdocs/.*)), '\1')).to eq(<<EOT)
121
+ MyTestController
122
+
123
+ url: http://foo/bar
124
+
125
+ my controller description
126
+
127
+ ===============================================================================
128
+ GET http://foo/bar
129
+
130
+ MyTestController#foo(bar, baz, quux)
131
+
132
+ bar(=1): TODO
133
+ baz(=2): TODO
134
+ quux(=3): TODO
135
+
136
+ my description
137
+
138
+ betterdocs/spec/controller_dsl_spec.rb:23
139
+
140
+ EOT
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Betterdocs::Generator::Markdown do
4
+ it "does all kinds of stuff correctly"
5
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Betterdocs::JsonParamsRepresenter do
4
+ module MyJsonParams
5
+ include Betterdocs::JsonParamsRepresenter
6
+
7
+ param :string do
8
+ description 'Some string'
9
+ types String
10
+ value 'peter.paul@betterplace.org'
11
+ end
12
+
13
+ param :number do
14
+ description 'some integer number'
15
+ types Integer
16
+ value 666
17
+ required yes
18
+ end
19
+
20
+ param :flag do
21
+ description 'some boolean flag'
22
+ types [ true, false ]
23
+ value true
24
+ required no
25
+ end
26
+ end
27
+
28
+ let :object do
29
+ OpenStruct.new.tap do |o|
30
+ o.string = 'some string'
31
+ o.number = 666
32
+ o.flag = true
33
+ MyJsonParams.apply(o)
34
+ end
35
+ end
36
+
37
+ let :docs do
38
+ MyJsonParams.docs
39
+ end
40
+
41
+ it 'can be converted into a ActionController::Parameters instance' do
42
+ expect(object.as_json).to be_a ActionController::Parameters
43
+ end
44
+
45
+ it 'it can be turned into a hash' do
46
+ expect(object.as_json).to eq("string" => "some string", "number" => 666, "flag" => true)
47
+ end
48
+
49
+ it 'it can be turned into json' do
50
+ expect(object.to_json).to eq '{"string":"some string","number":666,"flag":true}'
51
+ end
52
+
53
+ it 'can check a parameter hash' do
54
+ skip
55
+ end
56
+
57
+ it 'can return all the documented parameters as a hash' do
58
+ expect(docs.params.keys).to eq %i[ string number flag ]
59
+ end
60
+
61
+ context '#param' do
62
+ let :param do
63
+ docs.param(:string)
64
+ end
65
+
66
+ it 'can return a single documented parameter' do
67
+ expect(param).to be_a Betterdocs::Dsl::JsonParams::Param
68
+ expect(param.description).to eq 'Some string'
69
+ expect(param.value).to eq 'peter.paul@betterplace.org'
70
+ expect(param.types).to eq %w[ string ]
71
+ expect(param.required).to eq true
72
+ end
73
+ end
74
+
75
+ it 'foos' do
76
+ puts docs.to_s
77
+ skip
78
+ end
79
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Betterdocs::Dsl::JsonTypeMapper do
4
+ let :jtm do Betterdocs::Dsl::JsonTypeMapper end
5
+
6
+ it "derives json types" do
7
+ expect(jtm.derive_json_type_from(true)).to eq 'boolean'
8
+ expect(jtm.derive_json_type_from(TrueClass)).to eq 'boolean'
9
+ expect(jtm.derive_json_type_from(false)).to eq 'boolean'
10
+ expect(jtm.derive_json_type_from(FalseClass)).to eq 'boolean'
11
+ expect(jtm.derive_json_type_from(nil)).to eq 'null'
12
+ expect(jtm.derive_json_type_from(NilClass)).to eq 'null'
13
+ expect(jtm.derive_json_type_from(42)).to eq 'number'
14
+ expect(jtm.derive_json_type_from(Fixnum)).to eq 'number'
15
+ expect(jtm.derive_json_type_from(42)).to eq 'number'
16
+ expect(jtm.derive_json_type_from(Fixnum)).to eq 'number'
17
+ expect(jtm.derive_json_type_from(Math::PI)).to eq 'number'
18
+ expect(jtm.derive_json_type_from(Float)).to eq 'number'
19
+ expect(jtm.derive_json_type_from([])).to eq 'array'
20
+ expect(jtm.derive_json_type_from(Array)).to eq 'array'
21
+ expect(jtm.derive_json_type_from({})).to eq 'object'
22
+ expect(jtm.derive_json_type_from(Hash)).to eq 'object'
23
+ expect(jtm.derive_json_type_from('foo')).to eq 'string'
24
+ expect(jtm.derive_json_type_from(String)).to eq 'string'
25
+ end
26
+
27
+ it "maps arrays of ruby types correctly" do
28
+ expect(jtm.map_types([])).to eq %w[ array ]
29
+ expect(jtm.map_types([ [] ])).to eq %w[ array ]
30
+ expect(jtm.map_types([ [], {}, Array, nil, Hash ])).to eq %w[ array null object ]
31
+ expect(jtm.map_types("foo")).to eq %w[ string ]
32
+ end
33
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'representer dsl' do
4
+ let :docs do
5
+ Betterdocs::ResultRepresenterCollector.new
6
+ end
7
+
8
+ let :representer do
9
+ Module.new do
10
+ def self.property(*)
11
+ @property_set = true
12
+ end
13
+
14
+ def self.has_property_set?
15
+ @property_set
16
+ end
17
+
18
+ def self.link(*)
19
+ @link_set = true
20
+ end
21
+
22
+ def self.has_link_set?
23
+ @link_set
24
+ end
25
+
26
+ def self.to_s
27
+ 'MyRepresenter'
28
+ end
29
+ end
30
+ end
31
+
32
+ it "cannot add unknown element types" do
33
+ expect {
34
+ docs.add_element representer, :foobar, 'my_foobar' do
35
+ end
36
+ }.to raise_error(ArgumentError)
37
+ end
38
+
39
+ context 'property' do
40
+ it "can add a new property" do
41
+ docs.add_element representer, :property, 'my_property' do
42
+ # XXX as :foo_bar
43
+ description 'my description'
44
+ types [ String, nil ]
45
+ end
46
+ property = docs.property(:my_property)
47
+ expect(property).to be_present
48
+ expect(property.name).to eq :my_property
49
+ expect(property.representer).to eq representer
50
+ expect(property.description).to eq 'my description'
51
+ expect(property.types).to eq %w[ null string ]
52
+ expect(property.example).to eq 'TODO' # TODO
53
+ end
54
+
55
+ it "can define a property on representer" do
56
+ docs.add_element representer, :property, 'my_property' do
57
+ end
58
+ property = docs.property(:my_property)
59
+ expect(property).to be_present
60
+ end
61
+ end
62
+
63
+ context 'link' do
64
+ it "cannot add a new link without url" do
65
+ docs.add_element representer, :link, 'my_link' do
66
+ end
67
+ link = docs.link(:my_link)
68
+ expect { link.url }.to raise_error(ArgumentError)
69
+ end
70
+
71
+ it "can add a new link" do
72
+ docs.add_element representer, :link, 'my_link' do
73
+ description 'my URL description'
74
+ url { 'http://foo.bar' }
75
+ end
76
+ link = docs.link(:my_link)
77
+ expect(link).to be_present
78
+ expect(link.name).to eq :my_link
79
+ expect(link.representer).to eq representer
80
+ expect(link.description).to eq 'my URL description'
81
+ end
82
+
83
+ it "can define a templated link" do
84
+ docs.add_element representer, :link, 'my_link' do
85
+ description 'my URL description'
86
+ url { 'http://foo.bar' }
87
+ templated yes
88
+ end
89
+ link = docs.link(:my_link)
90
+ expect(link).to be_present
91
+ expect(link.templated).to eq true
92
+ end
93
+ end
94
+
95
+ it 'can return a string representation of all its links/properties' do
96
+ docs.add_element representer, :property, 'my_property' do
97
+ # XXX as :foo_bar
98
+ description 'my description'
99
+ types [ String, nil ]
100
+ end
101
+ docs.add_element representer, :property, 'my_property2' do
102
+ description 'my description2'
103
+ types [ true, false ]
104
+ end
105
+ docs.add_element representer, :link, 'my_link' do
106
+ description 'my URL description'
107
+ url { 'http://foo.bar' }
108
+ end
109
+ docs.add_element representer, :link, 'my_link2' do
110
+ description 'my URL description2'
111
+ url { 'http://foo.baz' }
112
+ end
113
+ expect(docs.to_s).to eq <<EOT
114
+ *** MyRepresenter ***
115
+
116
+ Properties:
117
+ my_property: (null|string): my description
118
+
119
+ my_property2: (boolean): my description2
120
+
121
+ Links:
122
+ my_link: my URL description
123
+
124
+ my_link2: my URL description2
125
+ EOT
126
+ end
127
+
128
+ context 'nesting representers' do
129
+ module Location
130
+ include Betterdocs::ResultRepresenter
131
+
132
+ property :latitude
133
+
134
+ property :longitude
135
+
136
+ link :dot do
137
+ url { 'http://foo.bar/dot' }
138
+ end
139
+ end
140
+
141
+ module Address
142
+ include Betterdocs::ResultRepresenter
143
+
144
+ property :city
145
+
146
+ property :location do
147
+ represent_with Location
148
+ end
149
+
150
+ link :map do
151
+ url { 'http://foo.bar/map' }
152
+ end
153
+ end
154
+
155
+
156
+ module Person
157
+ include Betterdocs::ResultRepresenter
158
+
159
+ property :name
160
+
161
+ property :address do
162
+ represent_with Address
163
+ end
164
+
165
+ link :self do
166
+ url { 'http://foo.bar' }
167
+ end
168
+ end
169
+
170
+ it 'can return an array of its nested properties' do
171
+ expect(Person.docs.nested_properties.size).to eq 6
172
+ expect(Person.docs.nested_properties.map(&:full_name)).to eq [ "name",
173
+ "address", "address.city", "address.location",
174
+ "address.location.latitude", "address.location.longitude" ]
175
+ end
176
+
177
+ it 'can return an array of its nested links' do
178
+ expect(Person.docs.nested_links.size).to eq 3
179
+ expect(Person.docs.nested_links.map(&:full_name)).to eq [
180
+ "self", "address.map", "address.location.dot" ]
181
+ end
182
+ end
183
+ end