apitecto 0.0.1

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.
@@ -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