betterdocs 0.2.0

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