matter_compiler 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +13 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +47 -0
- data/LICENSE +22 -0
- data/README.md +37 -0
- data/Rakefile +21 -0
- data/bin/matter_compiler +3 -0
- data/features/compose.feature +364 -0
- data/features/step_definitions/file_content_step.rb +8 -0
- data/features/support/setup.rb +1 -0
- data/lib/matter_compiler/blueprint.rb +522 -0
- data/lib/matter_compiler/cli.rb +82 -0
- data/lib/matter_compiler/composer.rb +89 -0
- data/lib/matter_compiler/version.rb +3 -0
- data/lib/matter_compiler.rb +5 -0
- data/lib/object.rb +17 -0
- data/matter_compiler.gemspec +28 -0
- data/test/action_test.rb +48 -0
- data/test/blueprint_test.rb +43 -0
- data/test/composer_test.rb +10 -0
- data/test/headers_test.rb +75 -0
- data/test/metadata_test.rb +28 -0
- data/test/parameters_test.rb +84 -0
- data/test/payload_test.rb +166 -0
- data/test/resource_group_test.rb +32 -0
- data/test/resource_test.rb +53 -0
- data/test/transaction_example_test.rb +38 -0
- metadata +143 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'aruba/cucumber'
|
@@ -0,0 +1,522 @@
|
|
1
|
+
module MatterCompiler
|
2
|
+
|
3
|
+
# The classes in this document should be 1:1 with relevant Snow Crash
|
4
|
+
# counterparts (https://github.com/apiaryio/snowcrash/blob/master/src/Blueprint.h)
|
5
|
+
# until Matter Compiler becomes a wrapper for Snow Crash.
|
6
|
+
|
7
|
+
#
|
8
|
+
# Blueprint AST node
|
9
|
+
#
|
10
|
+
class BlueprintNode
|
11
|
+
ONE_INDENTATION_LEVEL = " "
|
12
|
+
|
13
|
+
def initialize(hash = nil)
|
14
|
+
load_ast_hash!(hash) if hash
|
15
|
+
end
|
16
|
+
|
17
|
+
# Load AST has into block
|
18
|
+
def load_ast_hash!(hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Serialize block to a Markdown string
|
22
|
+
# \param level ... optional requested indentation level
|
23
|
+
def serialize(level = 0)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Named Blueprint AST node
|
29
|
+
#
|
30
|
+
class NamedBlueprintNode < BlueprintNode
|
31
|
+
def load_ast_hash!(hash)
|
32
|
+
@name = hash[:name]
|
33
|
+
@description = hash[:description]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Key-value collection
|
39
|
+
#
|
40
|
+
class KeyValueCollection < BlueprintNode
|
41
|
+
attr_accessor :collection
|
42
|
+
|
43
|
+
def load_ast_hash!(hash)
|
44
|
+
return if hash.empty?
|
45
|
+
|
46
|
+
@collection = Array.new
|
47
|
+
hash.each do |key, value_hash|
|
48
|
+
@collection << Hash[key, value_hash[:value]]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Serialize key value pairs
|
53
|
+
# \param ignore_keys ... optional array of keys to NOT be serialized
|
54
|
+
def serialize(level = 0, ignore_keys = nil)
|
55
|
+
buffer = ""
|
56
|
+
@collection.each do |hash|
|
57
|
+
# Skip ignored keys
|
58
|
+
unless ignore_keys && ignore_keys.include?(hash.keys.first)
|
59
|
+
level.times { buffer << ONE_INDENTATION_LEVEL }
|
60
|
+
buffer << "#{hash.keys.first}: #{hash.values.first}\n"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
buffer << "\n" unless buffer.empty?
|
65
|
+
buffer
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns collection without ignored keys
|
69
|
+
def ignore(ignore_keys)
|
70
|
+
return @collection if ignore_keys.blank?
|
71
|
+
@collection.select { |kv_item| !ignore_keys.include?(kv_item.keys.first) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Collection of metadata
|
77
|
+
#
|
78
|
+
class Metadata < KeyValueCollection
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Collection of headers
|
83
|
+
#
|
84
|
+
class Headers < KeyValueCollection
|
85
|
+
|
86
|
+
CONTENT_TYPE_HEADER_KEY = :'Content-Type'
|
87
|
+
|
88
|
+
def serialize(level = 0, ignore_keys = nil)
|
89
|
+
return "" if @collection.blank? || ignore(ignore_keys).blank?
|
90
|
+
|
91
|
+
buffer = ""
|
92
|
+
level.times { buffer << ONE_INDENTATION_LEVEL }
|
93
|
+
buffer << "+ Headers\n\n"
|
94
|
+
|
95
|
+
buffer << super(level + 2, ignore_keys)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the value of Content-Type header, if any.
|
99
|
+
def content_type
|
100
|
+
content_type_header = @collection.detect { |header| header.has_key?(CONTENT_TYPE_HEADER_KEY) }
|
101
|
+
return (content_type_header.nil?) ? nil : content_type_header[CONTENT_TYPE_HEADER_KEY]
|
102
|
+
end
|
103
|
+
end;
|
104
|
+
|
105
|
+
#
|
106
|
+
# One URI parameter
|
107
|
+
#
|
108
|
+
class Parameter < BlueprintNode
|
109
|
+
attr_accessor :name
|
110
|
+
attr_accessor :description
|
111
|
+
attr_accessor :type
|
112
|
+
attr_accessor :use
|
113
|
+
attr_accessor :default_value
|
114
|
+
attr_accessor :example_value
|
115
|
+
attr_accessor :values
|
116
|
+
|
117
|
+
def initialize(name = nil, hash = nil)
|
118
|
+
super(hash)
|
119
|
+
@name = name.to_s if name
|
120
|
+
end
|
121
|
+
|
122
|
+
def load_ast_hash!(hash)
|
123
|
+
@description = hash[:description]
|
124
|
+
@type = hash[:type] if hash[:type]
|
125
|
+
@use = (hash[:required] && hash[:required] == true) ? :required : :optional
|
126
|
+
@default_value = hash[:default] if hash[:default]
|
127
|
+
@example_value = hash[:example] if hash[:example]
|
128
|
+
|
129
|
+
unless hash[:values].blank?
|
130
|
+
@values = Array.new
|
131
|
+
hash[:values].each { |value| @values << value }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def serialize
|
136
|
+
# Parameter name
|
137
|
+
buffer = "#{ONE_INDENTATION_LEVEL}+ #{@name}"
|
138
|
+
|
139
|
+
# Default value
|
140
|
+
buffer << " = `#{@default_value}`" if @default_value
|
141
|
+
|
142
|
+
# Attributes
|
143
|
+
unless @type.blank? && @example_value.blank? && @use == :required
|
144
|
+
attribute_buffer = ""
|
145
|
+
|
146
|
+
buffer << " ("
|
147
|
+
|
148
|
+
# Type
|
149
|
+
attribute_buffer << @type unless @type.blank?
|
150
|
+
|
151
|
+
# Use
|
152
|
+
if (@use == :optional)
|
153
|
+
attribute_buffer << ", " unless attribute_buffer.empty?
|
154
|
+
attribute_buffer << "optional"
|
155
|
+
end
|
156
|
+
|
157
|
+
# Example value
|
158
|
+
unless (@example_value.blank?)
|
159
|
+
attribute_buffer << ", " unless attribute_buffer.empty?
|
160
|
+
attribute_buffer << "`#{@example_value}`"
|
161
|
+
end
|
162
|
+
|
163
|
+
buffer << attribute_buffer
|
164
|
+
buffer << ")"
|
165
|
+
end
|
166
|
+
|
167
|
+
# Description
|
168
|
+
if @description.blank?
|
169
|
+
buffer << "\n"
|
170
|
+
else
|
171
|
+
if @description.lines.count == 1
|
172
|
+
# One line description
|
173
|
+
buffer << " ... #{@description}"
|
174
|
+
buffer << "\n" if @description[-1, 1] != "\n" # Additional newline needed if no provided
|
175
|
+
else
|
176
|
+
# Multi-line description
|
177
|
+
buffer << "\n\n"
|
178
|
+
@description.each_line do |line|
|
179
|
+
2.times { buffer << ONE_INDENTATION_LEVEL }
|
180
|
+
buffer << "#{line}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Value
|
186
|
+
unless @values.blank?
|
187
|
+
buffer << "\n"
|
188
|
+
2.times { buffer << ONE_INDENTATION_LEVEL }
|
189
|
+
buffer << "+ Values\n"
|
190
|
+
@values.each do |value|
|
191
|
+
3.times { buffer << ONE_INDENTATION_LEVEL }
|
192
|
+
buffer << "+ `#{value}`\n"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
buffer
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Collection of URI parameters
|
202
|
+
#
|
203
|
+
class Parameters < BlueprintNode
|
204
|
+
attr_accessor :collection
|
205
|
+
|
206
|
+
def load_ast_hash!(hash)
|
207
|
+
return if hash.empty?
|
208
|
+
|
209
|
+
@collection = Array.new
|
210
|
+
hash.each do |key, value_hash|
|
211
|
+
@collection << Parameter.new(key, value_hash)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def serialize
|
216
|
+
return "" if :collection.blank?
|
217
|
+
|
218
|
+
buffer = "+ Parameters\n"
|
219
|
+
@collection.each do |parameter|
|
220
|
+
buffer << parameter.serialize
|
221
|
+
end
|
222
|
+
|
223
|
+
buffer << "\n" unless @collection.blank?
|
224
|
+
buffer
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
#
|
229
|
+
# Generic payload base class
|
230
|
+
#
|
231
|
+
class Payload < NamedBlueprintNode
|
232
|
+
attr_accessor :name
|
233
|
+
attr_accessor :description
|
234
|
+
attr_accessor :parameters
|
235
|
+
attr_accessor :headers
|
236
|
+
attr_accessor :body
|
237
|
+
attr_accessor :schema
|
238
|
+
|
239
|
+
def load_ast_hash!(hash)
|
240
|
+
super(hash)
|
241
|
+
|
242
|
+
@headers = Headers.new(hash[:headers]) unless hash[:headers].blank?
|
243
|
+
@body = hash[:body] unless hash[:body].blank?
|
244
|
+
@schema = hash[:schema] unless hash[:schema].blank?
|
245
|
+
end
|
246
|
+
|
247
|
+
def serialize
|
248
|
+
# Name is serialized in Payload successors
|
249
|
+
buffer = ""
|
250
|
+
|
251
|
+
unless @description.blank?
|
252
|
+
buffer << "\n"
|
253
|
+
@description.each_line { |line| buffer << "#{ONE_INDENTATION_LEVEL}#{line}" }
|
254
|
+
buffer << "\n"
|
255
|
+
end
|
256
|
+
|
257
|
+
unless @headers.blank?
|
258
|
+
buffer << @headers.serialize(1, [Headers::CONTENT_TYPE_HEADER_KEY])
|
259
|
+
end
|
260
|
+
|
261
|
+
unless @body.blank?
|
262
|
+
abbreviated_synax = (headers.blank? || headers.ignore([Headers::CONTENT_TYPE_HEADER_KEY]).blank?) \
|
263
|
+
& description.blank? \
|
264
|
+
& schema.blank?
|
265
|
+
asset_indent_level = 2
|
266
|
+
unless abbreviated_synax
|
267
|
+
asset_indent_level = 3
|
268
|
+
buffer << "#{ONE_INDENTATION_LEVEL}+ Body\n"
|
269
|
+
end
|
270
|
+
buffer << "\n"
|
271
|
+
|
272
|
+
@body.each_line do |line|
|
273
|
+
asset_indent_level.times { buffer << ONE_INDENTATION_LEVEL }
|
274
|
+
buffer << "#{line}"
|
275
|
+
end
|
276
|
+
buffer << "\n"
|
277
|
+
end
|
278
|
+
|
279
|
+
unless @schema.blank?
|
280
|
+
buffer << "#{ONE_INDENTATION_LEVEL}+ Schema\n\n"
|
281
|
+
@schema.each_line do |line|
|
282
|
+
3.times { buffer << ONE_INDENTATION_LEVEL }
|
283
|
+
buffer << "#{line}"
|
284
|
+
end
|
285
|
+
buffer << "\n"
|
286
|
+
end
|
287
|
+
|
288
|
+
buffer << "\n" if buffer.empty? # Separate empty payloads by a newline
|
289
|
+
|
290
|
+
buffer
|
291
|
+
end
|
292
|
+
|
293
|
+
# Serialize payload section lead-in (begin)
|
294
|
+
# \param section ... section keyword name
|
295
|
+
# \param ignore_name ... true to ignore section's name, false otherwise
|
296
|
+
def serialize_lead_in(section, ignore_name = false)
|
297
|
+
buffer = ""
|
298
|
+
buffer << "+ #{section}"
|
299
|
+
buffer << " #{@name}" unless ignore_name || @name.blank?
|
300
|
+
|
301
|
+
unless @headers.blank? || @headers.content_type.blank?
|
302
|
+
buffer << " (#{@headers.content_type})"
|
303
|
+
end
|
304
|
+
|
305
|
+
buffer << "\n"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
#
|
310
|
+
# Model Payload
|
311
|
+
#
|
312
|
+
class Model < Payload
|
313
|
+
def serialize
|
314
|
+
buffer = serialize_lead_in("Model", true)
|
315
|
+
buffer << super
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
#
|
320
|
+
# Request Payload
|
321
|
+
#
|
322
|
+
class Request < Payload
|
323
|
+
def serialize
|
324
|
+
buffer = serialize_lead_in("Request")
|
325
|
+
buffer << super
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
#
|
330
|
+
# Response Payload
|
331
|
+
#
|
332
|
+
class Response < Payload;
|
333
|
+
def serialize
|
334
|
+
buffer = serialize_lead_in("Response")
|
335
|
+
buffer << super
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
#
|
340
|
+
# Transaction Example
|
341
|
+
#
|
342
|
+
class TransactionExample < NamedBlueprintNode
|
343
|
+
attr_accessor :name
|
344
|
+
attr_accessor :description
|
345
|
+
attr_accessor :requests
|
346
|
+
attr_accessor :responses
|
347
|
+
|
348
|
+
def load_ast_hash!(hash)
|
349
|
+
super(hash)
|
350
|
+
|
351
|
+
unless hash[:requests].blank?
|
352
|
+
@requests = Array.new
|
353
|
+
hash[:requests].each { |request_hash| @requests << Request.new(request_hash) }
|
354
|
+
end
|
355
|
+
|
356
|
+
unless hash[:responses].blank?
|
357
|
+
@responses = Array.new
|
358
|
+
hash[:responses].each { |response_hash| @responses << Response.new(response_hash) }
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def serialize
|
363
|
+
buffer = ""
|
364
|
+
@requests.each { |request| buffer << request.serialize } unless @requests.nil?
|
365
|
+
@responses.each { |response| buffer << response.serialize } unless @responses.nil?
|
366
|
+
buffer
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
#
|
371
|
+
# Action
|
372
|
+
#
|
373
|
+
class Action < NamedBlueprintNode
|
374
|
+
attr_accessor :method
|
375
|
+
attr_accessor :name
|
376
|
+
attr_accessor :description
|
377
|
+
attr_accessor :parameters
|
378
|
+
attr_accessor :headers
|
379
|
+
attr_accessor :examples
|
380
|
+
|
381
|
+
def load_ast_hash!(hash)
|
382
|
+
super(hash)
|
383
|
+
|
384
|
+
@method = hash[:method]
|
385
|
+
@parameters = Parameters.new(hash[:parameters]) unless hash[:parameters].blank?
|
386
|
+
@headers = Headers.new(hash[:headers]) unless hash[:headers].blank?
|
387
|
+
|
388
|
+
unless hash[:examples].blank?
|
389
|
+
@examples = Array.new
|
390
|
+
hash[:examples].each { |example_hash| @examples << TransactionExample.new(example_hash) }
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
def serialize
|
395
|
+
buffer = ""
|
396
|
+
if @name.blank?
|
397
|
+
buffer << "### #{@method}\n"
|
398
|
+
else
|
399
|
+
buffer << "### #{@name} [#{@method}]\n"
|
400
|
+
end
|
401
|
+
|
402
|
+
buffer << "#{@description}" unless @description.blank?
|
403
|
+
|
404
|
+
buffer << @parameters.serialize unless @parameters.nil?
|
405
|
+
buffer << @headers.serialize unless @headers.nil?
|
406
|
+
|
407
|
+
@examples.each { |example| buffer << example.serialize } unless @examples.nil?
|
408
|
+
buffer
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
#
|
413
|
+
# Resource
|
414
|
+
#
|
415
|
+
class Resource < NamedBlueprintNode
|
416
|
+
attr_accessor :uri_template
|
417
|
+
attr_accessor :name
|
418
|
+
attr_accessor :description
|
419
|
+
attr_accessor :model
|
420
|
+
attr_accessor :parameters
|
421
|
+
attr_accessor :headers
|
422
|
+
attr_accessor :actions
|
423
|
+
|
424
|
+
def load_ast_hash!(hash)
|
425
|
+
super(hash)
|
426
|
+
|
427
|
+
@uri_template = hash[:uriTemplate]
|
428
|
+
@model = Model.new(hash[:model]) unless hash[:model].blank?
|
429
|
+
@parameters = Parameters.new(hash[:parameters]) unless hash[:parameters].blank?
|
430
|
+
@headers = Headers.new(hash[:headers]) unless hash[:headers].blank?
|
431
|
+
|
432
|
+
unless hash[:actions].blank?
|
433
|
+
@actions = Array.new
|
434
|
+
hash[:actions].each { |action_hash| @actions << Action.new(action_hash) }
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def serialize
|
439
|
+
buffer = ""
|
440
|
+
if @name.blank?
|
441
|
+
buffer << "## #{@uri_template}\n"
|
442
|
+
else
|
443
|
+
buffer << "## #{@name} [#{@uri_template}]\n"
|
444
|
+
end
|
445
|
+
|
446
|
+
buffer << "#{@description}" unless @description.blank?
|
447
|
+
|
448
|
+
buffer << @model.serialize unless @model.nil?
|
449
|
+
buffer << @parameters.serialize unless @parameters.nil?
|
450
|
+
buffer << @headers.serialize unless @headers.nil?
|
451
|
+
|
452
|
+
@actions.each { |action| buffer << action.serialize } unless @actions.nil?
|
453
|
+
buffer
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
#
|
458
|
+
# Resource Group
|
459
|
+
#
|
460
|
+
class ResourceGroup < NamedBlueprintNode
|
461
|
+
attr_accessor :name
|
462
|
+
attr_accessor :description
|
463
|
+
attr_accessor :resources
|
464
|
+
|
465
|
+
def load_ast_hash!(hash)
|
466
|
+
super(hash)
|
467
|
+
|
468
|
+
unless hash[:resources].blank?
|
469
|
+
@resources = Array.new
|
470
|
+
hash[:resources].each { |resource_hash| @resources << Resource.new(resource_hash) }
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
def serialize
|
475
|
+
buffer = ""
|
476
|
+
buffer << "# Group #{@name}\n" unless @name.blank?
|
477
|
+
buffer << "#{@description}" unless @description.blank?
|
478
|
+
|
479
|
+
@resources.each { |resource| buffer << resource.serialize } unless @resources.nil?
|
480
|
+
buffer
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
#
|
485
|
+
# Blueprint
|
486
|
+
#
|
487
|
+
class Blueprint < NamedBlueprintNode
|
488
|
+
attr_accessor :metadata
|
489
|
+
attr_accessor :name
|
490
|
+
attr_accessor :description
|
491
|
+
attr_accessor :resource_groups
|
492
|
+
|
493
|
+
VERSION_KEY = :_version
|
494
|
+
SUPPORTED_VERSIONS = ["1.0"]
|
495
|
+
|
496
|
+
def load_ast_hash!(hash)
|
497
|
+
super(hash)
|
498
|
+
|
499
|
+
# Load Metadata
|
500
|
+
unless hash[:metadata].blank?
|
501
|
+
@metadata = Metadata.new(hash[:metadata])
|
502
|
+
end
|
503
|
+
|
504
|
+
# Load Resource Groups
|
505
|
+
unless hash[:resourceGroups].blank?
|
506
|
+
@resource_groups = Array.new
|
507
|
+
hash[:resourceGroups].each { |group_hash| @resource_groups << ResourceGroup.new(group_hash) }
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
def serialize
|
512
|
+
buffer = ""
|
513
|
+
buffer << "#{@metadata.serialize}" unless @metadata.nil?
|
514
|
+
buffer << "# #{@name}\n" unless @name.blank?
|
515
|
+
buffer << "#{@description}" unless @description.blank?
|
516
|
+
|
517
|
+
@resource_groups.each { |group| buffer << group.serialize } unless @resource_groups.nil?
|
518
|
+
buffer
|
519
|
+
end
|
520
|
+
|
521
|
+
end
|
522
|
+
end
|