documentary 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3ead34348f2423c01e41ed552a763454622c704a
4
+ data.tar.gz: 30fe4374be10722e56c6d9f24f1ed532fb51752d
5
+ SHA512:
6
+ metadata.gz: 405f93a4c0352eefbc7c9f98cfaecd597a813c5413bff0803abf7f6d7b04e4d5a5a7e17ad0a57d6d564bf2c845d72a8301cc3d6b2cefee60f566e7cf4bc1c186
7
+ data.tar.gz: 649f53fa435e6aae7e299057340545d2488e89302ac0b233a70709414c06f347d2982d71e34cdb3620c4e53cde41c0b6ea3ea26dfacb84a9acbf0c6ea1ced143
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ *.rbc
3
+ /pkg/
4
+
5
+ ## Environment normalisation:
6
+ /.bundle/
7
+ /lib/bundler/man/
8
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'byebug'
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ documentary (0.1.0)
5
+ activesupport
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (4.2.0)
11
+ i18n (~> 0.7)
12
+ json (~> 1.7, >= 1.7.7)
13
+ minitest (~> 5.1)
14
+ thread_safe (~> 0.3, >= 0.3.4)
15
+ tzinfo (~> 1.1)
16
+ byebug (9.0.5)
17
+ i18n (0.7.0)
18
+ json (1.8.2)
19
+ minitest (5.4.3)
20
+ rake (10.3.2)
21
+ thread_safe (0.3.4)
22
+ tzinfo (1.2.2)
23
+ thread_safe (~> 0.1)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ byebug
30
+ documentary!
31
+ minitest (~> 5.4.3)
32
+ rake (~> 10.3.2)
33
+
34
+ BUNDLED WITH
35
+ 1.12.5
data/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # Documentary
2
+
3
+ A simple tool that will allow you to generate API runnable documentation quickly.
4
+
5
+ > If you want them to RTFM, make a better FM.
6
+ >
7
+ > -- <cite>Kathy Sierra</cite>
8
+
9
+ ## Todos
10
+
11
+ * [X] Build the parser
12
+ * [X] Integration Test
13
+ * [X] Title Blocks
14
+ * [X] Ordering
15
+ * [X] Resource Blocks
16
+ * [X] Endpoint Blocks
17
+ * [X] Introduce Generation config
18
+ * [X] Introduce view helpers
19
+ * [ ] CLI design
20
+ * [ ] TOC for documentation
21
+ * [ ] Start being more strict on specification for MD generation?
22
+ * [ ] Create testing suite
23
+
24
+ ## Specification
25
+
26
+ Documentary works using good old fashioned docblocks. I believe documentation for APIs should live with the code they document. Creating a documentary docblock is as simple as
27
+
28
+ ```
29
+ # --- documentary
30
+ # Docblock content in here
31
+ # --- end
32
+ ```
33
+
34
+ The contents of a documentary docblock is YAML which will generate markdown documentation.
35
+
36
+ All docblocks take an order attribute. Without it documentary will build teh documentation as it finds it in the file hierarchy.
37
+
38
+ ### Title Blocks
39
+
40
+ Title blocks are to provide important supplamentary information about the API. They are formed as follows:
41
+
42
+ ```
43
+ # --- documentary
44
+ # type: title_block
45
+ # title: The Heading for the Title Block
46
+ # content: >
47
+ # Here is the example content, using the YAML escaping character '>'.
48
+ # --- end
49
+ ```
50
+
51
+ ### Resources
52
+
53
+ Resource blocks let you describe the domain of your API. They are formed as follows:
54
+
55
+ ```
56
+ # --- documentary
57
+ # type: resource
58
+ # title: The Resource Name
59
+ # description: >
60
+ # A description of the resource would go in here
61
+ # attributes:
62
+ # - string_attribute:
63
+ # required: true
64
+ # type: string
65
+ # --- end
66
+ ```
67
+
68
+ Attributes are formed using the attribute name as the key and specifing if they are required (validation present) and the type to be passed.
69
+
70
+ ### Endpoints
71
+
72
+ Endpoint blocks specify the public endpoints of your API. They are formed as follows:
73
+
74
+ ```
75
+ # --- documentary
76
+ # type: endpoint
77
+ # title: List users
78
+ # notes: >
79
+ # This enpoint will list all the users.
80
+ # verb: GET
81
+ # endpoint: '/users'
82
+ # example_response:
83
+ # page: 1
84
+ # total_pages: 1
85
+ # count: 1
86
+ # users:
87
+ # - id: 1
88
+ # name: Testy McTesterson
89
+ # email: test@email.com
90
+ # --- end
91
+ ```
92
+
93
+ Of course not all enpoints are this simple, for `GET` requests especially you may wish to pass in extra parameters for things like pagination of filtering. This is accomplished using the `params` key. Parameters take on the structure of the param name, whether it is required and any notes (example below). Better still you can provide an example request using the `example_request` key.
94
+
95
+ ```
96
+ # --- documentary
97
+ # type: endpoint
98
+ # title: List users
99
+ # verb: GET
100
+ # endpoint: '/users'
101
+ # params:
102
+ # - page:
103
+ # required: false
104
+ # notes: >
105
+ # The page desired from the set
106
+ # - count:
107
+ # required: false
108
+ # notes: >
109
+ # The number of results to return
110
+ # - filter:
111
+ # required: false
112
+ # notes: >
113
+ # The filter for a specific user to find for example: `filter=Testy`
114
+ # example_request: >
115
+ # /users?filter=Testy&page=1&count=3
116
+ # example_response:
117
+ # page: 1
118
+ # total_pages: 1
119
+ # count: 1
120
+ # users:
121
+ # - id: 1
122
+ # name: Testy McTesterson
123
+ # email: test@email.com
124
+ # --- end
125
+ ```
126
+
127
+ There is a common practice on create and update (`POST` and `PUT`/`PATCH`) to return an empty response body and provide the `location` (the cannonical URL) in the response header. If no `example_response` is provided documentary will simply not generate documentation for it. We do suggest for clarity you mention this in the endpoint notes.
128
+
129
+ ```
130
+ # --- documentary
131
+ # type: endpoint
132
+ # title: Create a user
133
+ # notes: >
134
+ # The body of this response will be empty.
135
+ # information:
136
+ # authenticated: true
137
+ # response_formats: JSON, XML
138
+ # verb: POST
139
+ # endpoint: '/users'
140
+ # params:
141
+ # - name:
142
+ # required: true
143
+ # notes: >
144
+ # The name of the user
145
+ # - email:
146
+ # required: true
147
+ # notes: >
148
+ # The email of the user
149
+ # example_request: >
150
+ # /users?name=Testy%20McTesterson&email=test%40email.com
151
+ # --- end
152
+ ```
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'test'
8
+ t.pattern = "test/**/*_test.rb"
9
+ end
10
+
11
+ desc 'Run tests'
12
+ task default: :test
data/bin/documentary ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'documentary/cli'
4
+
5
+ Documentary::Cli.run ARGV
@@ -0,0 +1,20 @@
1
+ basedir = File.expand_path(File.dirname(__FILE__))
2
+ require "#{basedir}/lib/documentary/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'documentary'
6
+ s.version = Documentary::VERSION
7
+ s.summary = 'Documentary'
8
+ s.description = 'Simple, useable, runnable API documentation'
9
+ s.authors = ['Dave Kennedy']
10
+ s.email = 'david@bangline.co.uk'
11
+ s.files = `git ls-files`.split("\n")
12
+ s.require_path = 'lib'
13
+ s.executables = ['documentary']
14
+ s.homepage = 'http://rubygems.org/gems/documentary'
15
+ s.license = 'MIT'
16
+
17
+ s.add_dependency 'activesupport'
18
+ s.add_development_dependency 'minitest', '~> 5.4.3'
19
+ s.add_development_dependency 'rake', '~> 10.3.2'
20
+ end
@@ -0,0 +1,5 @@
1
+ # <%= project_name %>
2
+
3
+ <%= title_blocks %>
4
+ <%= resource_blocks %>
5
+ <%= endpoint_blocks %>
@@ -0,0 +1,39 @@
1
+ require 'optparse'
2
+ require 'documentary'
3
+
4
+ module Documentary
5
+ class Cli
6
+ def self.run(args, out=STDOUT)
7
+ file_glob = "./**/*.rb"
8
+ project_name = File.basename(Dir.getwd).split.each {|w| w.capitalize! }.join(' ')
9
+ output = 'api.md'
10
+
11
+ OptionParser.new do |opts|
12
+ opts.on("-v", "--version", "Print version number") do
13
+ require "documentary/version"
14
+ out << "Documentary #{Documentary::VERSION}\n"
15
+ exit
16
+ end
17
+
18
+ opts.on("-d", "--directory glob", "Set directory to search for docblocks") do |glob|
19
+ file_glob = glob
20
+ end
21
+
22
+ opts.on("-p", "--project project", "Set project name") do |project|
23
+ project_name = project
24
+ end
25
+
26
+ opts.on("-o", "--output output", "Set the output file") do |op|
27
+ output = op
28
+ end
29
+ end.parse!
30
+
31
+ files = Dir.glob(file_glob)
32
+ config = {
33
+ project: project_name,
34
+ op: output
35
+ }
36
+ Documentary::Generator.new(files, config).generate
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ module Documentary
2
+ class Docblock < OpenStruct
3
+ def initialize(hsh)
4
+ super(hsh)
5
+ self.type = self.type.to_sym
6
+ self.order = self.order || 9999
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ module Documentary
2
+ class DocblockCollection
3
+ require 'forwardable'
4
+ extend Forwardable
5
+
6
+ def_delegators :@collection, :<<, :concat
7
+
8
+ def initialize
9
+ @collection = []
10
+ end
11
+
12
+ def title_blocks
13
+ fetch_subset :title_block
14
+ end
15
+
16
+ def resources
17
+ fetch_subset :resource
18
+ end
19
+
20
+ def endpoints
21
+ fetch_subset :endpoint
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :collection
27
+
28
+ def fetch_subset(type)
29
+ collection.select { |docblock| docblock.type == type }.sort_by { |block| block.order }
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,61 @@
1
+ module Documentary
2
+ class Parser
3
+ require 'yaml'
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ @docblocks = []
8
+ @line_number = 0
9
+ begin
10
+ parse_file
11
+ rescue Psych::SyntaxError
12
+ raise Documentary::InvalidDocblock.new form_exception_message
13
+ end
14
+ end
15
+
16
+ attr_reader :docblocks
17
+
18
+ private
19
+
20
+ attr_reader :path
21
+
22
+ def parse_file
23
+ File.open(path, 'r') do |file_content|
24
+ in_docblock = false
25
+ block_body = ""
26
+ comment_delimeter = ""
27
+ file_content.readlines.each do |line|
28
+ line.rstrip!
29
+ @line_number += 1
30
+ if in_docblock && line.match(/---( |)end/)
31
+ in_docblock = false
32
+ yml = YAML.load(block_body)
33
+ self.docblocks << Docblock.new(yml)
34
+ block_body = ""
35
+ end
36
+
37
+ if line.match(/---( |)documentary/)
38
+ comment_delimeter = line.match(/(.+)---/)[1]
39
+ @current_docblock_start = @line_number
40
+ in_docblock = true
41
+ end
42
+
43
+ if in_docblock
44
+ if line.match(/---( |)documentary/)
45
+ block_body << "---\n"
46
+ elsif line == comment_delimeter.rstrip
47
+ block_body << "\n"
48
+ else
49
+ regex = /^#{comment_delimeter}/
50
+ block_body << "#{line.gsub(regex, '')}\n"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def form_exception_message
58
+ "Invalid docblock YAML in file #{path}:#{@current_docblock_start}"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,3 @@
1
+ module Documentary
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,106 @@
1
+ module Documentary
2
+ module View
3
+ require 'stringio'
4
+
5
+ def title_blocks
6
+ io = StringIO.new
7
+ docblocks.title_blocks.each do |title_block|
8
+ io.puts "## #{title_block.title}"
9
+ io.puts new_line
10
+ io.puts title_block.content
11
+ io.puts new_line
12
+ end
13
+ io.string
14
+ end
15
+
16
+ def resource_blocks
17
+ io = StringIO.new
18
+ io.puts '## Resources'
19
+ io.puts new_line
20
+ docblocks.resources.each do |resource|
21
+ io.puts "### #{resource.title}"
22
+ io.puts new_line
23
+ io.puts resource.description
24
+ io.puts new_line
25
+ resource_attributes(resource, io)
26
+ end
27
+ io.puts new_line
28
+ io.string
29
+ end
30
+
31
+ def endpoint_blocks
32
+ io = StringIO.new
33
+ io.puts '## Endpoints'
34
+ io.puts new_line
35
+ docblocks.endpoints.each do |endpoint|
36
+ io.puts "### #{endpoint.title}"
37
+ io.puts new_line
38
+ io.puts endpoint.notes if endpoint.notes
39
+ io.puts new_line
40
+ io.puts '#### Enpoint URL'
41
+ io.puts new_line
42
+ io.puts start_code_block
43
+ io.puts "#{endpoint.verb} #{endpoint.endpoint}"
44
+ io.puts end_code_block
45
+ if endpoint.information
46
+ io.puts '#### Endpoint Information'
47
+ io.puts new_line
48
+ endpoint.information.each do |key, value|
49
+ io.puts "* **#{key.titleize}**: #{value}"
50
+ end
51
+ end
52
+ if endpoint.params
53
+ io.puts new_line
54
+ io.puts '#### Parameters'
55
+ io.puts new_line
56
+ io.puts 'Name | Required | Description'
57
+ io.puts '-------- | -------- | -----------'
58
+ endpoint.params.each do |param|
59
+ io.puts "#{param.keys.first} | #{param['required']} | #{param['notes'].strip}"
60
+ end
61
+ end
62
+ if endpoint.example_request
63
+ io.puts new_line
64
+ io.puts '#### Example Request'
65
+ io.puts new_line
66
+ io.puts start_code_block
67
+ io.puts "#{endpoint.verb} #{endpoint.example_request.strip}"
68
+ io.puts end_code_block
69
+ end
70
+ if endpoint.example_response
71
+ io.puts new_line
72
+ io.puts '#### Example Response'
73
+ io.puts new_line
74
+ io.puts start_code_block
75
+ io.puts JSON.pretty_generate(endpoint.example_response)
76
+ io.puts end_code_block
77
+ end
78
+ end
79
+ io.string
80
+ end
81
+
82
+ def code_block
83
+ '```'
84
+ end
85
+
86
+ private
87
+
88
+ def resource_attributes(resource, io)
89
+ io.puts '#### Attributes'
90
+ io.puts new_line
91
+ io.puts 'Name | Type | Required'
92
+ io.puts '------------- | ------------- | -----------------'
93
+ resource.attributes.each do |attribute|
94
+ io.puts "#{attribute.keys.first} | #{attribute['type'] } | #{attribute['required'] }"
95
+ end
96
+ end
97
+
98
+ def new_line
99
+ "\n"
100
+ end
101
+
102
+ alias_method :start_code_block, :code_block
103
+ alias_method :end_code_block, :code_block
104
+
105
+ end
106
+ end
@@ -0,0 +1,49 @@
1
+ module Documentary
2
+ require 'ostruct'
3
+ require 'documentary/version'
4
+ require 'documentary/docblock'
5
+ require 'documentary/docblock_collection'
6
+ require 'documentary/parser'
7
+ require 'documentary/view/helpers'
8
+
9
+ class InvalidDocblock < StandardError; end
10
+
11
+ class Generator
12
+ require 'erb'
13
+ require 'json'
14
+ require 'active_support/inflector'
15
+
16
+ include View
17
+
18
+ def initialize(file_tree, config={})
19
+ @docblocks = DocblockCollection.new
20
+ @file_tree = file_tree
21
+ @config = config
22
+ end
23
+
24
+ def generate
25
+ file_tree.each do |path|
26
+ parsed_file = Parser.new(path)
27
+ docblocks.concat parsed_file.docblocks
28
+ template = File.expand_path('../default_layout.erb', __FILE__)
29
+ erb = ERB.new(File.new(template).read, nil, '<>')
30
+ File.open(output_file, 'w+') do |file|
31
+ file.write erb.result(binding)
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :file_tree, :config
39
+ attr_accessor :docblocks
40
+
41
+ def project_name
42
+ config[:project]
43
+ end
44
+
45
+ def output_file
46
+ config[:op]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ require 'test_helper'
2
+
3
+ class Documentary::DocblockCollectionTest < MiniTest::Test
4
+
5
+ test 'dockblocks are split by type' do
6
+ title_block = Documentary::Docblock.new(type: 'title_block')
7
+ resource_block = Documentary::Docblock.new(type: 'resource')
8
+ endpoint_block = Documentary::Docblock.new(type: 'endpoint')
9
+ collection = Documentary::DocblockCollection.new
10
+ collection << title_block << resource_block << endpoint_block
11
+ assert_equal title_block, collection.title_blocks.first
12
+ assert_equal resource_block, collection.resources.first
13
+ assert_equal endpoint_block, collection.endpoints.first
14
+ end
15
+
16
+ test 'docblocks are sorted by order' do
17
+ title_block1 = Documentary::Docblock.new(type: 'title_block')
18
+ title_block2 = Documentary::Docblock.new(type: 'title_block', order: 99)
19
+ collection = Documentary::DocblockCollection.new
20
+ collection << title_block1 << title_block2
21
+ assert_equal collection.title_blocks, [title_block2, title_block1]
22
+ end
23
+
24
+ end
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+
3
+ class Documentary::DocblockTest < MiniTest::Test
4
+
5
+ test 'type is stored as a symbol' do
6
+ docblock = Documentary::Docblock.new({type: 'something'})
7
+ assert_equal :something, docblock.type
8
+ end
9
+
10
+ test 'the order is defaulted if not given' do
11
+ docblock = Documentary::Docblock.new({type: 'something'})
12
+ assert_equal 9999, docblock.order
13
+ end
14
+
15
+ test 'the order is set if given' do
16
+ docblock = Documentary::Docblock.new({type: 'something', order: 1})
17
+ assert_equal 1, docblock.order
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,5 @@
1
+ # --- documentary
2
+ # type title_block
3
+ # content: >
4
+ # This is the title block.
5
+ # --- end
@@ -0,0 +1,60 @@
1
+ # --- documentary
2
+ # type: title_block
3
+ # title: Salutations
4
+ # content: |
5
+ # Hello world
6
+ # --- end
7
+
8
+ // --- documentary
9
+ // type: title_block
10
+ // title: Salutations Again
11
+ // content: |
12
+ // Hello world Again
13
+ // --- end
14
+
15
+ # --- documentary
16
+ # type: resource
17
+ # title: A Resource
18
+ # description: This is a resource
19
+ # attributes:
20
+ # - email:
21
+ # required: true
22
+ # type: string
23
+ # - another:
24
+ # required: no
25
+ # type: boolean
26
+ # --- end
27
+
28
+ # --- documentary
29
+ # type: endpoint
30
+ # title: List endpoint
31
+ # notes: Some notes about the endpoint
32
+ # information:
33
+ # authenticated: true
34
+ # response_formats: JSON
35
+ # verb: GET
36
+ # endpoint: '/some/path'
37
+ # params:
38
+ # - page:
39
+ # required: false
40
+ # notes: >
41
+ # The page desired from the set
42
+ # - count:
43
+ # required: false
44
+ # notes: >
45
+ # The number of results to return
46
+ # - filter:
47
+ # required: false
48
+ # notes: >
49
+ # The filter for a specific user to find for example: `filter=Testy`
50
+ # example_request: >
51
+ # /some/path?filter=Testy&page=1&count=3
52
+ # example_response:
53
+ # page: 1
54
+ # total_pages: 1
55
+ # count: 1
56
+ # users:
57
+ # - id: 1
58
+ # name: Testy McTesterson
59
+ # email: test@email.com
60
+ # --- end
@@ -0,0 +1,30 @@
1
+ # --- documentary
2
+ # type: title_block
3
+ # title: Hashes
4
+ # content: >
5
+ # This is the title block. With added [markdown](http://daringfireball.net/projects/markdown/syntax)
6
+ # --- end
7
+
8
+ Some other text that should be here
9
+
10
+ # this is not documentary
11
+ # nor this. end of!
12
+
13
+ We can have wierdly indented comments like the one below!
14
+
15
+ // --- documentary
16
+ // type: title_block
17
+ // title: Slashes
18
+ // content: >
19
+ // Dont care about comment chars
20
+ // --- end
21
+
22
+ # --- documentary
23
+ # type: title_block
24
+ # title: Multiple Lines
25
+ # content: |
26
+ # This is the title block.
27
+ #
28
+ # With multiple
29
+ # Significant lines
30
+ # --- end
@@ -0,0 +1,37 @@
1
+ class Minitest::Test
2
+ def generated_docs_path
3
+ File.expand_path('../../api.md', __FILE__)
4
+ end
5
+
6
+ def generated_docs
7
+ @docs ||= File.read generated_docs_path
8
+ end
9
+
10
+ def assert_has_title_block_header(title)
11
+ assert_includes generated_docs, "## #{title}"
12
+ end
13
+
14
+ def assert_content_for_title_block(title, content)
15
+ assert_match /## #{title}\n\n#{content}/, generated_docs
16
+ end
17
+
18
+ def assert_has_resource_title
19
+ assert_includes generated_docs, "## Resources"
20
+ end
21
+
22
+ def assert_has_enpoints_title
23
+ assert_includes generated_docs, "## Endpoints"
24
+ end
25
+
26
+ def assert_has_third_level_heading(heading)
27
+ assert_includes generated_docs, "### #{heading}"
28
+ end
29
+
30
+ def assert_content_for_resource(resource, content)
31
+ assert_match /### #{resource}\n\n#{content}/, generated_docs
32
+ end
33
+
34
+ def assert_table_entry_for_resource(resource, row_content)
35
+ assert_match /### #{resource}[.+]#{row_content[:name]} | #{row_content[:required]} | #{row_content[:type]}/, generated_docs
36
+ end
37
+ end
@@ -0,0 +1,80 @@
1
+ require 'test_helper'
2
+ require 'integration_helper'
3
+
4
+ class Documentary::IntegrationTest < MiniTest::Test
5
+
6
+ def config
7
+ {
8
+ project: 'Test Project',
9
+ op: 'api.md'
10
+ }
11
+ end
12
+
13
+ setup do
14
+ Documentary::Generator.new([fetch_fixture('kitchen_sink.txt')], config).generate
15
+ end
16
+
17
+ teardown do
18
+ File.unlink generated_docs_path
19
+ end
20
+
21
+ test 'the document title is generated correctly' do
22
+ assert_includes generated_docs, "# #{config[:project]}"
23
+ end
24
+
25
+ test 'the title blocks are correctly generated' do
26
+ assert_has_title_block_header 'Salutations'
27
+ end
28
+
29
+ test 'the content of the title block is correctly generated' do
30
+ assert_content_for_title_block 'Salutations', "Hello world"
31
+ end
32
+
33
+ test 'resources are generated' do
34
+ assert_has_resource_title
35
+ end
36
+
37
+ test 'named resources are correctly generated' do
38
+ assert_has_third_level_heading 'A Resource'
39
+ end
40
+
41
+ test 'named resources descriptions are correctly generated' do
42
+ assert_content_for_resource 'A Resource', 'This is a resource'
43
+ end
44
+
45
+ test 'named resource has table of attributes' do
46
+ assert_table_entry_for_resource 'A Resource', {name: 'email', required: 'true', type: 'string'}
47
+ end
48
+
49
+ test 'endpoints are generated' do
50
+ assert_has_enpoints_title
51
+ end
52
+
53
+ test 'named enpoints are correctly generated' do
54
+ assert_includes generated_docs, '### List endpoint'
55
+ end
56
+
57
+ test 'enpoint url is generated' do
58
+ assert_includes generated_docs, "```\nGET /some/path\n```"
59
+ end
60
+
61
+ test 'endpoint information is correctly generated' do
62
+ assert_includes generated_docs, '* **Authenticated**: true'
63
+ assert_includes generated_docs, '* **Response Formats**: JSON'
64
+ end
65
+
66
+ test 'enpoint params are correctly generated' do
67
+ assert_includes generated_docs, 'page | false | The page desired from the set'
68
+ assert_includes generated_docs, 'count | false | The number of results to return'
69
+ assert_includes generated_docs, 'filter | false | The filter for a specific user to find for example: `filter=Testy`'
70
+ end
71
+
72
+ test 'enpoint example request is generated' do
73
+ assert_includes generated_docs, "```\nGET /some/path?filter=Testy&page=1&count=3\n```"
74
+ end
75
+
76
+ test 'enpoint example response is generated' do
77
+ assert_includes generated_docs, "#### Example Response\n\n```\n"
78
+ assert_includes generated_docs, "\"page\": 1,\n"
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ require 'test_helper'
2
+
3
+ class Documentary::ParserTest < MiniTest::Test
4
+
5
+ test 'that docblocks will be collected from a file' do
6
+ file_with_title_block = fetch_fixture('title_block.txt')
7
+ parser = Documentary::Parser.new(file_with_title_block)
8
+ assert_equal 3, parser.docblocks.count
9
+ assert_kind_of Documentary::Docblock, parser.docblocks.first
10
+ end
11
+
12
+ test 'invalid docblocks will raise invalid docblock error' do
13
+ file_with_title_block = fetch_fixture('invalid_block.txt')
14
+ assert_raises Documentary::InvalidDocblock do
15
+ Documentary::Parser.new(file_with_title_block)
16
+ end
17
+ end
18
+
19
+ test 'invalid docblock errors will have a helpful message' do
20
+ file_with_title_block = fetch_fixture('invalid_block.txt')
21
+ begin
22
+ Documentary::Parser.new(file_with_title_block)
23
+ rescue => e
24
+ assert_includes e.message, 'Invalid docblock YAML in file'
25
+ assert_includes e.message, '/invalid_block.txt:1'
26
+ end
27
+ end
28
+
29
+ test 'other exceptions bubble up' do
30
+ assert_raises Errno::ENOENT do
31
+ Documentary::Parser.new('not_there.txt')
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ require 'documentary'
2
+ require 'minitest/autorun'
3
+
4
+ # Plagiarized and bastardized from Tilt, they like tests like me. :raised_hands:
5
+ # https://github.com/rtomayko/tilt/blob/master/test/test_helper.rb
6
+ class Minitest::Test
7
+ def self.setup(&block)
8
+ define_method :setup do
9
+ super(&block)
10
+ instance_eval(&block)
11
+ end
12
+ end
13
+
14
+ def self.teardown(&block)
15
+ define_method :teardown do
16
+ instance_eval(&block)
17
+ super(&block)
18
+ end
19
+ end
20
+
21
+ def self.test(name, &block)
22
+ define_method(test_name(name), &block)
23
+ end
24
+
25
+ def fetch_fixture(name)
26
+ File.expand_path("../fixtures/#{name}", __FILE__)
27
+ end
28
+
29
+ private
30
+
31
+ def self.test_name(name)
32
+ "test_#{sanitize_name(name).gsub(/\s+/,'_')}".to_sym
33
+ end
34
+
35
+ def self.sanitize_name(name)
36
+ name.gsub(/\W+/, ' ').strip
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: documentary
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dave Kennedy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 5.4.3
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 5.4.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 10.3.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 10.3.2
55
+ description: Simple, useable, runnable API documentation
56
+ email: david@bangline.co.uk
57
+ executables:
58
+ - documentary
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - Gemfile.lock
65
+ - README.md
66
+ - Rakefile
67
+ - bin/documentary
68
+ - documentary.gemspec
69
+ - lib/default_layout.erb
70
+ - lib/documentary.rb
71
+ - lib/documentary/cli.rb
72
+ - lib/documentary/docblock.rb
73
+ - lib/documentary/docblock_collection.rb
74
+ - lib/documentary/parser.rb
75
+ - lib/documentary/version.rb
76
+ - lib/documentary/view/helpers.rb
77
+ - test/docblock_collection_test.rb
78
+ - test/docblock_test.rb
79
+ - test/fixtures/invalid_block.txt
80
+ - test/fixtures/kitchen_sink.txt
81
+ - test/fixtures/title_block.txt
82
+ - test/integration_helper.rb
83
+ - test/integration_test.rb
84
+ - test/parser_test.rb
85
+ - test/test_helper.rb
86
+ homepage: http://rubygems.org/gems/documentary
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.4.5.1
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Documentary
110
+ test_files: []