apitecto 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bd83508eada2e33298523b90167c48faa0bcd81d
4
+ data.tar.gz: b8cefda97c4f6f322c5574299a12738d470deca5
5
+ SHA512:
6
+ metadata.gz: 2a2fa4f1116f036edac8c3e6bf42305024361e631ad58208a5f51249e3ab2a1329f0d42ffeae464593b71220d2a5aab07fd604329678d8726d69f7bb8ce60c9e
7
+ data.tar.gz: 3b0ffd44e28c115e7fcc07870b4c1fdaf8f4a8b6c0ee2820cdef244c251582a35729925973c6867509c4cf649f0282ad2dd1bb6c5c3c784e41223300cc0e14c3
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in apitecto.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Roberto Quintanilla
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ # Apitecto
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'apitecto'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install apitecto
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/vovimayhem/apitecto/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'apitecto/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "apitecto"
8
+ spec.version = Apitecto::VERSION
9
+ spec.authors = ["Roberto Quintanilla"]
10
+ spec.email = ["roberto.quintanilla@gmail.com"]
11
+ spec.summary = %q{A ruby library for generating REST Api documentation using API Blueprint}
12
+ spec.description = %q{A ruby library for generating REST Api documentation using API Blueprint}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "matter_compiler", "~> 0.5"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,304 @@
1
+ require "apitecto/version"
2
+ require "matter_compiler/blueprint"
3
+
4
+ module Apitecto
5
+ mattr_reader :blueprints, :lines_of_spec
6
+ mattr_accessor :output_dir
7
+
8
+ def self.blueprints; @@blueprints ||= {}; end
9
+ def self.blueprint_exists?(blueprint_name); blueprints.has_key?(blueprint_name); end
10
+
11
+
12
+ def self.lines_of_spec; @@lines_of_spec ||= {}; end
13
+ def self.get_lines_of_spec(spec_file_path)
14
+ unless lines_of_spec[spec_file_path].present?
15
+ lines_of_spec[spec_file_path] = File.read(spec_file_path).each_line.to_a.map(&:strip).inject({}) do |lines, line|
16
+ lines[lines.values.count + 1] = (line =~ /\A#/i || line == "") ? (line == "" ? "" : line[1..-1]) : nil
17
+ lines
18
+ end
19
+ end
20
+ lines_of_spec[spec_file_path]
21
+ end
22
+ end
23
+
24
+ RSpec.configure do |config|
25
+
26
+ config.before(:suite) do
27
+ Apitecto.output_dir = File.join((defined?(Rails) ? Rails.root : File.expand_path('.')), 'doc')
28
+ end
29
+
30
+ # Formar el AST de Blueprint a partir de los examples de request...
31
+ config.after(:each, type: :request) do |rspec_example|
32
+
33
+ if rspec_example.metadata[:rest_api_name].present?
34
+
35
+ ################################################################################################
36
+ #
37
+ lines_of_spec = Apitecto.get_lines_of_spec rspec_example.metadata[:file_path]
38
+
39
+ ##############################################################################
40
+ # Find or create the Root AST node:
41
+ rest_api_name = rspec_example.metadata[:rest_api_name] || :default
42
+ rest_api_key = rest_api_name.to_s.downcase.split.join("-").dasherize
43
+
44
+ blueprint_ast = if Apitecto.blueprint_exists? rest_api_key
45
+ Apitecto.blueprints[rest_api_key]
46
+ else
47
+ Apitecto.blueprints[rest_api_key] = { name: rest_api_name, description: nil, resourceGroups: [] }
48
+ end
49
+
50
+ blueprint_ast[:description] = blueprint_ast[:description] || rspec_example.metadata[:rest_api_description]
51
+
52
+ ##############################################################################################
53
+ # Find the rspec_example's Example Groups metadata mappable to
54
+ # API Blueprint's Resource Group, Resource and Action:
55
+ rspec_action_metadata = rspec_example.metadata[:example_group]
56
+ rspec_resource_metadata = rspec_action_metadata[:parent_example_group]
57
+ rspec_resource_group_metadata = rspec_resource_metadata[:parent_example_group]
58
+
59
+ ##############################################################################################
60
+ # Retrieve or form the resource group AST:
61
+
62
+ # ...find:
63
+ resource_group_ast = blueprint_ast[:resourceGroups].detect do |resource_group|
64
+ resource_group[:rspec_file_path] == rspec_resource_group_metadata[:file_path]
65
+ end
66
+
67
+ # ...create unless found:
68
+ unless resource_group_ast.present?
69
+ resource_group_comment_end_line_number = rspec_resource_group_metadata[:line_number]
70
+ resource_group_comment_start_line_number = resource_group_comment_end_line_number
71
+ while (!lines_of_spec[resource_group_comment_start_line_number-1].nil?) do
72
+ resource_group_comment_start_line_number -= 1
73
+ end
74
+
75
+ resource_group_comment_range = resource_group_comment_start_line_number..resource_group_comment_end_line_number
76
+ resource_group_comment_lines = lines_of_spec.select { |key| resource_group_comment_range.include? key }.values.compact
77
+
78
+ resource_group_name = resource_group_comment_lines.detect { |line| line =~ /\A#\s+(.+)/i }
79
+ resource_group_name = $1 unless resource_group_name.blank?
80
+ resource_group_name = rspec_resource_group_metadata[:description] unless resource_group_name.present?
81
+
82
+ resource_group_description = resource_group_comment_lines.select { |line| line !~ /\A#|\s+\+/i }.map(&:strip)
83
+ resource_group_description.shift if resource_group_description.first.blank?
84
+ resource_group_description.pop if resource_group_description.last.blank?
85
+ resource_group_description = resource_group_description.join("\n")
86
+
87
+ resource_group_sort_order = rspec_resource_group_metadata[:doc_sort_order] || 90000000000000
88
+
89
+ blueprint_ast[:resourceGroups] << {
90
+ sort_order: resource_group_sort_order, # Ignored as it's not part of the API Blueprint AST
91
+ rspec_file_path: rspec_resource_group_metadata[:file_path], # Ignored as it's not part of the API Blueprint AST
92
+ name: resource_group_name,
93
+ description: resource_group_description,
94
+ resources: []
95
+ }
96
+ resource_group_ast = blueprint_ast[:resourceGroups].last
97
+ end
98
+
99
+ ##############################################################################################
100
+ # Retrieve or form the example's resource AST:
101
+
102
+ # ...find:
103
+ resource_ast = resource_group_ast[:resources].detect do |r|
104
+ r[:sort_order] == rspec_resource_metadata[:line_number]
105
+ end
106
+
107
+ # ...create unless found:
108
+ unless resource_ast.present?
109
+
110
+ resource_comment_start_line_number = rspec_resource_metadata[:line_number]
111
+ while (!lines_of_spec[resource_comment_start_line_number-1].nil?) do
112
+ resource_comment_start_line_number -= 1
113
+ end
114
+
115
+ resource_comment_range = resource_comment_start_line_number..rspec_resource_metadata[:line_number]
116
+ resource_comment_lines = lines_of_spec.select { |key| resource_comment_range.include? key }.values.compact
117
+
118
+ resource_name = resource_comment_lines.detect { |line| line =~ /\A#\s+(.+)\[(.+)\]/i }
119
+ resource_name = $1.strip unless resource_name.blank?
120
+ resource_uri_template = $2.strip unless resource_name.blank?
121
+
122
+ # Name & UriTemplate last try... rspec example_group metadata:
123
+ resource_name = rspec_resource_metadata[:description] unless resource_name.present?
124
+ resource_uri_template = rspec_resource_metadata[:uri_template] unless resource_uri_template.present?
125
+
126
+ resource_description = resource_comment_lines.select { |line| line !~ /\A#|\s+\+/i }.map(&:strip)
127
+ resource_description.shift if resource_description.first.blank?
128
+ resource_description.pop if resource_description.last.blank?
129
+ resource_description = resource_description.join("\n")
130
+
131
+ resource_group_ast[:resources] << {
132
+ sort_order: rspec_resource_metadata[:line_number], # Ignored as it's not part of the API Blueprint AST
133
+ name: resource_name,
134
+ description: resource_description, # TODO: Extract from somewhere, or default to...
135
+
136
+ uriTemplate: resource_uri_template,
137
+ model: nil, # No Model, as rspec request examples generate some output.
138
+ parameters: [],
139
+ actions: []
140
+ }
141
+
142
+ resource_ast = resource_group_ast[:resources].last
143
+ end
144
+
145
+ #############################################################################################
146
+ # Retrieve the rspec example's request & response:
147
+ request ||= respond_to?(:last_request) ? last_request : @request
148
+ response ||= respond_to?(:last_response) ? last_response : @response
149
+
150
+ #############################################################################################
151
+ # Retrieve or form the example's action AST:
152
+
153
+ action_ast = resource_ast[:actions].detect { |a| a[:sort_order] == rspec_action_metadata[:line_number] }
154
+ unless action_ast.present?
155
+
156
+ action_comment_start_line_number = rspec_action_metadata[:line_number]
157
+ while (!lines_of_spec[action_comment_start_line_number-1].nil?) do
158
+ action_comment_start_line_number -= 1
159
+ end
160
+
161
+ action_comment_range = action_comment_start_line_number..rspec_action_metadata[:line_number]
162
+ action_comment_lines = lines_of_spec.select { |key| action_comment_range.include? key }.values.compact
163
+
164
+ # 1st try: Group Comments in MD:
165
+ action_name = action_comment_lines.detect { |line| line =~ /\A#\s+(.+)\[(.+)\]/i }
166
+ action_name = $1.strip unless action_name.blank?
167
+ action_method = $2.strip unless action_name.blank?
168
+
169
+ # 2nd try: extract the method from the RSpec Example Group Metadata's description:
170
+ action_method = $1.strip if action_method.blank? && rspec_action_metadata[:description] =~ /\A(GET|POST|PUT|PATCH|DELETE)\s/
171
+
172
+ # Last try: extract the method from a custom metadata tag:
173
+ action_method = rspec_action_metadata[:method] if action_method.blank?
174
+
175
+ # abort ast if no method was found...
176
+ if action_method.present?
177
+
178
+ action_description = action_comment_lines.select { |line| line !~ /\A#|\s+\+/i }.map(&:strip)
179
+ action_description.shift if action_description.first.blank?
180
+ action_description.pop if action_description.last.blank?
181
+ action_description = action_description.join("\n")
182
+
183
+ resource_ast[:actions] << {
184
+ sort_order: rspec_action_metadata[:line_number], # Ignored as it's not part of the API Blueprint AST
185
+ name: action_name,
186
+ description: action_description,
187
+ method: action_method,
188
+ parameters: [], # TODO: Extract from somewhere, or default to...
189
+ examples: []
190
+ }
191
+ action_ast = resource_ast[:actions].last
192
+ end
193
+ end
194
+
195
+ if action_ast.present?
196
+
197
+ ##############################################################################################
198
+ # Form the transaction example AST + add request & response AST's
199
+ example_comment_start_line_number = rspec_example.metadata[:line_number]
200
+ while (!lines_of_spec[example_comment_start_line_number-1].nil?) do
201
+ example_comment_start_line_number -= 1
202
+ end
203
+
204
+ example_comment_range = example_comment_start_line_number..rspec_example.metadata[:line_number]
205
+ example_comment_lines = lines_of_spec.select { |key| example_comment_range.include? key }.values.compact
206
+
207
+ # 1st try: Group Comments in MD:
208
+ example_name = example_comment_lines.detect { |line| line =~ /\A#\s+(.+)\s+\((.+)\)|#\s+(.+)\z/i }
209
+ example_content_type = $2.strip if example_name.present? && $2.present?
210
+ example_name = ($1 || $3).strip unless example_name.blank?
211
+
212
+ # Last try: extract the example_name from the rspec_example metadata description:
213
+ example_name = rspec_example.metadata[:description] if example_name.blank?
214
+ example_content_type = request.headers["CONTENT_TYPE"] unless example_content_type.present?
215
+
216
+ example_content_type = example_content_type.present? ? "(#{example_content_type})" : ""
217
+ #example_name = "#{example_name} #{example_content_type}".strip
218
+
219
+ example_description = example_comment_lines.select { |line| line !~ /\A#|\s+\+/i }.map(&:strip)
220
+ example_description.shift if example_description.first.blank?
221
+ example_description.pop if example_description.last.blank?
222
+ example_description = example_description.join("\n")
223
+
224
+ ##############################################################################################
225
+ # Form the request AST:
226
+ request_ast = {
227
+ name: example_name,
228
+ description: example_description,
229
+ parameters: [], # TODO: Extract from somewhere, or default to...
230
+
231
+ # Filter headers from the request.headers (env) hash, convert header names:
232
+ headers: request.headers.select { |k,v| (k =~ /\AHTTP_/ && k !~ /HOST|COOKIE|USER_AGENT\z/i) || k == "CONTENT_TYPE" }
233
+ .map { |k, v| { name: (k == "CONTENT_TYPE" ? k : k[5..-1]).titleize.split().join('-'), value: v } },
234
+ body: request.body.read,
235
+ schema: nil # TODO: Extract from somewhere, or default to...
236
+ }
237
+
238
+ ##############################################################################################
239
+ # Form the response AST Hash:
240
+
241
+ response_name = response.status # TODO: el status es sufijo? Se puede agregar mas palabras?
242
+
243
+ response_ast = {
244
+ name: response_name,
245
+ parameters: [], # TODO: Extract from somewhere, or default to...
246
+
247
+ # Assign the response headers
248
+ # TODO: Filter-out irrelevant headers:
249
+ headers: response.headers.select { |k,v| k !~ /\AX-/i && k !~ /Cookie\z/i }
250
+ .map { |k, v| { name: k, value: v } },
251
+ body: response.body,
252
+ schema: nil # TODO: Extract from somewhere, or default to...
253
+ }
254
+
255
+ #########################################################################################################
256
+
257
+ transaction_example_ast = {
258
+ sort_order: rspec_example.metadata[:line_number], # Ignored as it's not part of the API Blueprint AST
259
+ #name: example_name,
260
+ #description: example_description,
261
+ requests: [ request_ast ],
262
+ responses: [ response_ast ]
263
+ }
264
+
265
+ # append the transaction_example_ast
266
+ action_ast[:examples] << transaction_example_ast
267
+ end
268
+ end
269
+ end
270
+
271
+ # Escribir los archivos a partir de los AST's recolectados
272
+ config.after(:suite) do
273
+
274
+ Apitecto.blueprints.each do |api_name, blueprint_ast|
275
+
276
+ # RSpec corre los ejemplos en random order (eso es bueno para las pruebas... pero malo para documentacion)
277
+ # Ordenar los resource groups, resources, actions y examples del AST por su sort_order:
278
+ sorter = ->(x,y) { x[:sort_order] <=> y[:sort_order] }
279
+
280
+ blueprint_ast[:resourceGroups].each do |resource_group_ast|
281
+ resource_group_ast[:resources].each do |resource_ast|
282
+ resource_ast[:actions].each do |action_ast|
283
+ action_ast[:examples].sort!(&sorter)
284
+ end
285
+ resource_ast[:actions].sort!(&sorter)
286
+ end
287
+ resource_group_ast[:resources].sort!(&sorter)
288
+ end
289
+ blueprint_ast[:resourceGroups].sort!(&sorter)
290
+
291
+ # Generar el directorio a partir del nombre del api:
292
+ api_docs_path = File.join(Apitecto.output_dir, api_name)
293
+ Dir.mkdir(api_docs_path) unless Dir.exists?(api_docs_path)
294
+
295
+ # Generar el archivo de API Blueprint:
296
+ api_blueprint_path = File.join(api_docs_path, "api_blueprint.md")
297
+
298
+ blueprint = MatterCompiler::Blueprint.new(blueprint_ast)
299
+
300
+ File.open(api_blueprint_path, "w") { |f| f.write blueprint.serialize }
301
+
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,3 @@
1
+ module Apitecto
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apitecto do
4
+ it 'has a version number' do
5
+ expect(Apitecto::VERSION).not_to be nil
6
+ end
7
+
8
+ it 'does something useful' do
9
+ expect(false).to eq(true)
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'apitecto'
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apitecto
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Roberto Quintanilla
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: matter_compiler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
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.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A ruby library for generating REST Api documentation using API Blueprint
70
+ email:
71
+ - roberto.quintanilla@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - .travis.yml
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - apitecto.gemspec
84
+ - lib/apitecto.rb
85
+ - lib/apitecto/version.rb
86
+ - spec/apitecto_spec.rb
87
+ - spec/spec_helper.rb
88
+ homepage: ''
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.0.14
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: A ruby library for generating REST Api documentation using API Blueprint
112
+ test_files:
113
+ - spec/apitecto_spec.rb
114
+ - spec/spec_helper.rb