openapi-sourcetools 0.10.1 → 0.12.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 +4 -4
- data/LICENSE.txt +1 -1
- data/bin/openapi-addheaders +7 -23
- data/bin/openapi-addparameters +7 -7
- data/bin/openapi-addresponses +6 -22
- data/bin/openapi-addschemas +32 -26
- data/bin/openapi-clearrefs +46 -0
- data/bin/openapi-order +573 -0
- data/lib/openapi/sourcetools/apiobjects.rb +105 -1
- data/lib/openapi/sourcetools/common.rb +17 -2
- data/lib/openapi/sourcetools/config.rb +4 -0
- data/lib/openapi/sourcetools/order.rb +271 -0
- data/lib/openapi/sourcetools/version.rb +1 -1
- metadata +7 -2
data/bin/openapi-order
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Copyright © 2026 Ismo Kärkkäinen
|
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
|
6
|
+
|
|
7
|
+
require_relative '../lib/openapi/sourcetools/common'
|
|
8
|
+
require_relative '../lib/openapi/sourcetools/order'
|
|
9
|
+
require 'optparse'
|
|
10
|
+
require 'yaml'
|
|
11
|
+
require 'json'
|
|
12
|
+
|
|
13
|
+
# Functions related to order instructions.
|
|
14
|
+
|
|
15
|
+
def metadata
|
|
16
|
+
'openapi-order-metadata'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def metadata_key
|
|
20
|
+
'key'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def metadata_regexp
|
|
24
|
+
'regexp'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def default_order_data(order_key, regexp_prefix)
|
|
28
|
+
<<EOF
|
|
29
|
+
---
|
|
30
|
+
#{metadata}:
|
|
31
|
+
#{metadata_key}: "#{order_key}"
|
|
32
|
+
#{metadata_regexp}: "#{regexp_prefix}"
|
|
33
|
+
# Generic orders for any location of a key.
|
|
34
|
+
'**':
|
|
35
|
+
servers:
|
|
36
|
+
#{order_key}: &ServerObject_order
|
|
37
|
+
- url
|
|
38
|
+
- name
|
|
39
|
+
- description
|
|
40
|
+
- variables
|
|
41
|
+
'*':
|
|
42
|
+
#{order_key}: &ServerObject
|
|
43
|
+
- name
|
|
44
|
+
- description
|
|
45
|
+
variables:
|
|
46
|
+
#{order_key}:
|
|
47
|
+
- '*'
|
|
48
|
+
'*':
|
|
49
|
+
#{order_key}: &ServerVariableObject
|
|
50
|
+
- description
|
|
51
|
+
- enum
|
|
52
|
+
- default
|
|
53
|
+
server: *ServerObject
|
|
54
|
+
parameters:
|
|
55
|
+
#{order_key}: &ParameterObject_order
|
|
56
|
+
- name
|
|
57
|
+
- in
|
|
58
|
+
'*': &ParameterObject
|
|
59
|
+
#{order_key}:
|
|
60
|
+
- '$ref'
|
|
61
|
+
- name
|
|
62
|
+
- in
|
|
63
|
+
- required
|
|
64
|
+
- deprecated
|
|
65
|
+
- description
|
|
66
|
+
- content
|
|
67
|
+
- schema
|
|
68
|
+
- '*'
|
|
69
|
+
- #{regexp_prefix}^example[s]?$
|
|
70
|
+
schema: &Schema
|
|
71
|
+
#{order_key}:
|
|
72
|
+
- '$ref'
|
|
73
|
+
- title
|
|
74
|
+
- description
|
|
75
|
+
- deprecated
|
|
76
|
+
- type
|
|
77
|
+
- format
|
|
78
|
+
- enum
|
|
79
|
+
- const
|
|
80
|
+
- default
|
|
81
|
+
- readOnly
|
|
82
|
+
- writeOnly
|
|
83
|
+
# Numeric types.
|
|
84
|
+
- minimum
|
|
85
|
+
- exclusiveMinimum
|
|
86
|
+
- exclusiveMaximum
|
|
87
|
+
- maximum
|
|
88
|
+
- multipleOf
|
|
89
|
+
# Strings.
|
|
90
|
+
- minLength
|
|
91
|
+
- maxLength
|
|
92
|
+
- pattern
|
|
93
|
+
# Core.
|
|
94
|
+
- allOf
|
|
95
|
+
- anyOf
|
|
96
|
+
- oneOf
|
|
97
|
+
- not
|
|
98
|
+
- if
|
|
99
|
+
- then
|
|
100
|
+
- else
|
|
101
|
+
# Objects.
|
|
102
|
+
- required
|
|
103
|
+
- dependentRequired
|
|
104
|
+
- minProperties
|
|
105
|
+
- maxProperties
|
|
106
|
+
- properties
|
|
107
|
+
- #{regexp_prefix}^.+Properties$
|
|
108
|
+
- propertyNames
|
|
109
|
+
- dependentSchemas
|
|
110
|
+
- discriminator
|
|
111
|
+
# Arrays.
|
|
112
|
+
- minItems
|
|
113
|
+
- maxItems
|
|
114
|
+
- uniqueItems
|
|
115
|
+
- items
|
|
116
|
+
- #{regexp_prefix}^.+Items$
|
|
117
|
+
- contains
|
|
118
|
+
- minContains
|
|
119
|
+
- maxContains
|
|
120
|
+
# Content
|
|
121
|
+
- #{regexp_prefix}^content.*$
|
|
122
|
+
- examples
|
|
123
|
+
required:
|
|
124
|
+
#{order_key}:
|
|
125
|
+
- '*'
|
|
126
|
+
dependentRequired:
|
|
127
|
+
#{order_key}:
|
|
128
|
+
- '*'
|
|
129
|
+
'*':
|
|
130
|
+
#{order_key}:
|
|
131
|
+
- '*'
|
|
132
|
+
allOf: &Schema_map
|
|
133
|
+
'*': *Schema
|
|
134
|
+
anyOf: *Schema_map
|
|
135
|
+
oneOf: *Schema_map
|
|
136
|
+
not: *Schema
|
|
137
|
+
if: *Schema
|
|
138
|
+
then: *Schema
|
|
139
|
+
else: *Schema
|
|
140
|
+
properties: &Schema_ordered_map
|
|
141
|
+
#{order_key}:
|
|
142
|
+
- '*'
|
|
143
|
+
'*': *Schema
|
|
144
|
+
patternProperties: *Schema_ordered_map
|
|
145
|
+
additionalProperties: *Schema
|
|
146
|
+
propertyNames: *Schema
|
|
147
|
+
items: *Schema
|
|
148
|
+
contains: *Schema
|
|
149
|
+
prefixItems: *Schema_map
|
|
150
|
+
unevaluatedItems: *Schema
|
|
151
|
+
unevaluatedProperties: *Schema
|
|
152
|
+
dependentSchemas: *Schema_ordered_map
|
|
153
|
+
discriminator:
|
|
154
|
+
#{order_key}:
|
|
155
|
+
- propertyName
|
|
156
|
+
- mapping
|
|
157
|
+
- defaultMapping
|
|
158
|
+
content:
|
|
159
|
+
#{order_key}:
|
|
160
|
+
- '*'
|
|
161
|
+
'*': &MediaTypeObject
|
|
162
|
+
#{order_key}:
|
|
163
|
+
- schema
|
|
164
|
+
- #{regexp_prefix}.*Schema$
|
|
165
|
+
- #{regexp_prefix}^(.*E|e)ncoding$
|
|
166
|
+
- '*'
|
|
167
|
+
- #{regexp_prefix}^example[s]?$
|
|
168
|
+
headers: &HeaderObject
|
|
169
|
+
#{order_key}:
|
|
170
|
+
- description
|
|
171
|
+
- required
|
|
172
|
+
- '*'
|
|
173
|
+
- schema
|
|
174
|
+
'*': *ParameterObject
|
|
175
|
+
responses:
|
|
176
|
+
#{order_key}:
|
|
177
|
+
- '*'
|
|
178
|
+
- default
|
|
179
|
+
'*': &ResponseObject
|
|
180
|
+
#{order_key}:
|
|
181
|
+
- '$ref'
|
|
182
|
+
- summary
|
|
183
|
+
- description
|
|
184
|
+
- headers
|
|
185
|
+
- content
|
|
186
|
+
- links
|
|
187
|
+
links:
|
|
188
|
+
#{order_key}:
|
|
189
|
+
- '*'
|
|
190
|
+
'*': &LinkObject
|
|
191
|
+
#{order_key}:
|
|
192
|
+
- description
|
|
193
|
+
- #{regexp_prefix}^operation.*
|
|
194
|
+
- parameters
|
|
195
|
+
- requestBody
|
|
196
|
+
requestBody: &RequestBodyObject
|
|
197
|
+
#{order_key}:
|
|
198
|
+
- description
|
|
199
|
+
- required
|
|
200
|
+
- content
|
|
201
|
+
security:
|
|
202
|
+
#{order_key}: &SecurityRequirementObject_order
|
|
203
|
+
- '*'
|
|
204
|
+
webhooks:
|
|
205
|
+
#{order_key}:
|
|
206
|
+
- '*'
|
|
207
|
+
'*': &PathItemObject
|
|
208
|
+
#{order_key}:
|
|
209
|
+
- '$ref'
|
|
210
|
+
- summary
|
|
211
|
+
- description
|
|
212
|
+
- servers
|
|
213
|
+
- parameters
|
|
214
|
+
- '*'
|
|
215
|
+
- additionalOperations
|
|
216
|
+
'*':
|
|
217
|
+
#{order_key}: &OperationObject
|
|
218
|
+
- '$ref'
|
|
219
|
+
- operationId
|
|
220
|
+
- summary
|
|
221
|
+
- description
|
|
222
|
+
- parameters
|
|
223
|
+
- requestBody
|
|
224
|
+
- responses
|
|
225
|
+
additionalOperations:
|
|
226
|
+
#{order_key}:
|
|
227
|
+
- '*'
|
|
228
|
+
'*': *OperationObject
|
|
229
|
+
callbacks:
|
|
230
|
+
#{order_key}:
|
|
231
|
+
- '*'
|
|
232
|
+
'*': *PathItemObject
|
|
233
|
+
#{order_key}:
|
|
234
|
+
- openapi
|
|
235
|
+
- '$self'
|
|
236
|
+
- jsonSchemaDialect
|
|
237
|
+
- info
|
|
238
|
+
- servers
|
|
239
|
+
- '*'
|
|
240
|
+
- paths
|
|
241
|
+
- components
|
|
242
|
+
info:
|
|
243
|
+
#{order_key}:
|
|
244
|
+
- title
|
|
245
|
+
- version
|
|
246
|
+
- summary
|
|
247
|
+
- description
|
|
248
|
+
- contact
|
|
249
|
+
- license
|
|
250
|
+
- termsOfService
|
|
251
|
+
contact:
|
|
252
|
+
#{order_key}:
|
|
253
|
+
- name
|
|
254
|
+
license:
|
|
255
|
+
#{order_key}:
|
|
256
|
+
- name
|
|
257
|
+
servers:
|
|
258
|
+
#{order_key}: *ServerObject_order
|
|
259
|
+
'*': *ServerObject
|
|
260
|
+
security:
|
|
261
|
+
#{order_key}: *SecurityRequirementObject_order
|
|
262
|
+
tags: # Do not order tags array but do order Tag Objects.
|
|
263
|
+
'*':
|
|
264
|
+
#{order_key}:
|
|
265
|
+
- name
|
|
266
|
+
- summary
|
|
267
|
+
- description
|
|
268
|
+
paths:
|
|
269
|
+
#{order_key}:
|
|
270
|
+
- '*'
|
|
271
|
+
'*': *PathItemObject
|
|
272
|
+
components:
|
|
273
|
+
#{order_key}:
|
|
274
|
+
- schemas
|
|
275
|
+
- '*'
|
|
276
|
+
- securitySchemes
|
|
277
|
+
- links
|
|
278
|
+
- callbacks
|
|
279
|
+
- examples
|
|
280
|
+
# Regular expressions are for names from various openapi-add* programs.
|
|
281
|
+
# Variations for number of digits ensure ordering by number.
|
|
282
|
+
schemas:
|
|
283
|
+
#{order_key}:
|
|
284
|
+
- '*'
|
|
285
|
+
- #{regexp_prefix}^Schema[0-9]{1}x$
|
|
286
|
+
- #{regexp_prefix}^Schema[0-9]{2}x$
|
|
287
|
+
- #{regexp_prefix}^Schema[0-9]{3,}x$
|
|
288
|
+
'*': *Schema
|
|
289
|
+
responses:
|
|
290
|
+
#{order_key}:
|
|
291
|
+
- '*'
|
|
292
|
+
- #{regexp_prefix}^Response[0-9]{1}x$
|
|
293
|
+
- #{regexp_prefix}^Response[0-9]{2}x$
|
|
294
|
+
- #{regexp_prefix}^Response[0-9]{3,}x$
|
|
295
|
+
'*': *ResponseObject
|
|
296
|
+
parameters:
|
|
297
|
+
#{order_key}:
|
|
298
|
+
- '*'
|
|
299
|
+
- #{regexp_prefix}^Parameter[0-9]{1}x$
|
|
300
|
+
- #{regexp_prefix}^Parameter[0-9]{2}x$
|
|
301
|
+
- #{regexp_prefix}^Parameter[0-9]{3,}x$
|
|
302
|
+
'*': *ParameterObject
|
|
303
|
+
requestBodies:
|
|
304
|
+
#{order_key}:
|
|
305
|
+
- '*'
|
|
306
|
+
- #{regexp_prefix}^RequestBody[0-9]{1}x$
|
|
307
|
+
- #{regexp_prefix}^RequestBody[0-9]{2}x$
|
|
308
|
+
- #{regexp_prefix}^RequestBody[0-9]{3,}x$
|
|
309
|
+
'*': *RequestBodyObject
|
|
310
|
+
headers:
|
|
311
|
+
#{order_key}:
|
|
312
|
+
- '*'
|
|
313
|
+
- #{regexp_prefix}^Header[0-9]{1}x$
|
|
314
|
+
- #{regexp_prefix}^Header[0-9]{2}x$
|
|
315
|
+
- #{regexp_prefix}^Header[0-9]{3,}x$
|
|
316
|
+
'*': *HeaderObject
|
|
317
|
+
securitySchemes:
|
|
318
|
+
#{order_key}:
|
|
319
|
+
- '*'
|
|
320
|
+
links:
|
|
321
|
+
#{order_key}:
|
|
322
|
+
- '*'
|
|
323
|
+
'*': *LinkObject
|
|
324
|
+
callbacks:
|
|
325
|
+
#{order_key}:
|
|
326
|
+
- '*'
|
|
327
|
+
'*': *PathItemObject
|
|
328
|
+
pathItems:
|
|
329
|
+
#{order_key}:
|
|
330
|
+
- '*'
|
|
331
|
+
'*': *PathItemObject
|
|
332
|
+
mediaTypes:
|
|
333
|
+
#{order_key}:
|
|
334
|
+
- '*'
|
|
335
|
+
- #{regexp_prefix}^MediaType[0-9]{1}x$
|
|
336
|
+
- #{regexp_prefix}^MediaType[0-9]{2}x$
|
|
337
|
+
- #{regexp_prefix}^MediaType[0-9]{3,}x$
|
|
338
|
+
'*': *MediaTypeObject
|
|
339
|
+
examples:
|
|
340
|
+
#{order_key}:
|
|
341
|
+
- '*'
|
|
342
|
+
EOF
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def default_order(order_key, regexp_prefix)
|
|
346
|
+
YAML.load(default_order_data(order_key, regexp_prefix), aliases: true)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# This needs to extract all path/order pairs, store and sort.
|
|
350
|
+
|
|
351
|
+
def extract_orders(path, order, collection, order_key, regexp_prefix)
|
|
352
|
+
return true unless order.is_a?(Hash)
|
|
353
|
+
order.each do |key, value|
|
|
354
|
+
if key == order_key
|
|
355
|
+
unless value.is_a?(Array)
|
|
356
|
+
OpenAPISourceTools::Common.aargh("Not an array: #{path.join('.')}.#{order_key} =\n#{YAML.dump(value)}")
|
|
357
|
+
return false
|
|
358
|
+
end
|
|
359
|
+
value.each_with_index do |v, k|
|
|
360
|
+
next if v.is_a?(String)
|
|
361
|
+
OpenAPISourceTools::Common.aargh("Not a string: #{path}.#{order_key}[#{k}] =\n#{YAML.dump(v)}")
|
|
362
|
+
return false
|
|
363
|
+
end
|
|
364
|
+
key_path = OpenAPISourceTools::Ordering::KeyPath.new(path, regexp_prefix)
|
|
365
|
+
key_order = OpenAPISourceTools::Ordering::Order.new(value, regexp_prefix)
|
|
366
|
+
collection.push(OpenAPISourceTools::Ordering::KeyPathOrder.new(key_path, key_order))
|
|
367
|
+
else
|
|
368
|
+
key = key.to_s unless key.is_a?(String)
|
|
369
|
+
path.push(key)
|
|
370
|
+
return false unless extract_orders(path, value, collection, order_key, regexp_prefix)
|
|
371
|
+
path.pop
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Functions related to applying the order to the document.
|
|
377
|
+
|
|
378
|
+
def reorder(orders, path, item)
|
|
379
|
+
return false unless OpenAPISourceTools::Ordering::Order.orderable?(item)
|
|
380
|
+
orders.each do |order|
|
|
381
|
+
next unless order.path.match?(path)
|
|
382
|
+
order.order.apply(item)
|
|
383
|
+
break
|
|
384
|
+
end
|
|
385
|
+
true
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def order_document(orders, path, item)
|
|
389
|
+
return unless reorder(orders, path, item) # Scalars do not pass.
|
|
390
|
+
if item.is_a?(Array)
|
|
391
|
+
item.each_with_index do |element, idx|
|
|
392
|
+
# This needs something and using index as string matches the '*'
|
|
393
|
+
# used when elements of the array are meant to be processed. By
|
|
394
|
+
# using the index alone, the '*' can be replaced by a regular
|
|
395
|
+
# expression that matches specific indices, if so desired.
|
|
396
|
+
path.push(idx.to_s)
|
|
397
|
+
order_document(orders, path, element)
|
|
398
|
+
path.pop
|
|
399
|
+
end
|
|
400
|
+
else
|
|
401
|
+
item.each do |key, value|
|
|
402
|
+
key = key.to_s unless key.is_a?(String)
|
|
403
|
+
path.push(key)
|
|
404
|
+
order_document(orders, path, value)
|
|
405
|
+
path.pop
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Functions related to saving the output.
|
|
411
|
+
|
|
412
|
+
def to_json(doc, _line_width)
|
|
413
|
+
JSON.dump(doc)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def to_pretty_json(doc, _line_width)
|
|
417
|
+
JSON.pretty_generate(doc)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def to_yaml(doc, line_width)
|
|
421
|
+
YAML.dump(doc, line_width: line_width)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def main
|
|
425
|
+
input_name = nil
|
|
426
|
+
output_name = nil
|
|
427
|
+
order_name = nil
|
|
428
|
+
order_key = 'x-order'
|
|
429
|
+
regexp_prefix = 're:'
|
|
430
|
+
format = 'yaml'
|
|
431
|
+
formats = {
|
|
432
|
+
'yaml' => method(:to_yaml),
|
|
433
|
+
'json' => method(:to_json),
|
|
434
|
+
'pretty_json' => method(:to_pretty_json)
|
|
435
|
+
}
|
|
436
|
+
line_width = 80
|
|
437
|
+
output_default = false
|
|
438
|
+
|
|
439
|
+
parser = OptionParser.new do |opts|
|
|
440
|
+
opts.summary_indent = ' '
|
|
441
|
+
opts.summary_width = 20
|
|
442
|
+
opts.banner = 'Usage: openapi-order [options]'
|
|
443
|
+
opts.separator ''
|
|
444
|
+
opts.separator 'Options:'
|
|
445
|
+
opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
|
|
446
|
+
input_name = f
|
|
447
|
+
end
|
|
448
|
+
opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
|
|
449
|
+
output_name = f
|
|
450
|
+
end
|
|
451
|
+
opts.on('--order FILE', 'Read order instructions from FILE.') do |f|
|
|
452
|
+
order_name = f
|
|
453
|
+
end
|
|
454
|
+
opts.on('-f', '--format FORMAT', "Output format: #{formats.keys.sort!.join(', ')}, default: #{format}") do |f|
|
|
455
|
+
unless formats.key?(f.downcase)
|
|
456
|
+
exit(OpenAPISourceTools::Common.aargh("Unknown format: #{f}", 1))
|
|
457
|
+
end
|
|
458
|
+
format = f
|
|
459
|
+
end
|
|
460
|
+
opts.on('-w', '--width WIDTH', "Set YAML line width, default: #{line_width}") do |w|
|
|
461
|
+
line_width = w.to_i
|
|
462
|
+
end
|
|
463
|
+
opts.separator ''
|
|
464
|
+
opts.separator 'Order options:'
|
|
465
|
+
opts.on('--default', 'Output default order to --output, ignore input.') do
|
|
466
|
+
output_default = true
|
|
467
|
+
end
|
|
468
|
+
opts.on('-k', '--key KEY', "Use KEY as default order key, default: #{order_key}") do |k|
|
|
469
|
+
order_key = k
|
|
470
|
+
end
|
|
471
|
+
opts.on('-r', '--regexp PREFIX', "Use PREFIX to indicate regular expression, default: #{regexp_prefix}") do |p|
|
|
472
|
+
regexp_prefix = p
|
|
473
|
+
end
|
|
474
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
|
475
|
+
$stdout.puts %(#{opts}
|
|
476
|
+
Loads YAML document and applies ordering to object keys, and arrays with
|
|
477
|
+
objects, when an order matches the key path to object.
|
|
478
|
+
|
|
479
|
+
Can be used on generic YAML or JSON files. Default order is for OpenAPI
|
|
480
|
+
documents.
|
|
481
|
+
|
|
482
|
+
The order instructions key "#{order_key}" value must be an array of strings.
|
|
483
|
+
The values can be any combination of the following:
|
|
484
|
+
- Literal key name.
|
|
485
|
+
- Regular expression starting with "#{regexp_prefix}".
|
|
486
|
+
- "*" that matches any key that does not match anything else.
|
|
487
|
+
First match against a value determines the position, and "*" receives all
|
|
488
|
+
unmatched keys. If order array has no "*", every unmatched key will be placed
|
|
489
|
+
last. Only the first "*" has any effect. Multiple keys placed in the same
|
|
490
|
+
position are sorted within that position.
|
|
491
|
+
|
|
492
|
+
The path to the "#{order_key}" can have any combination of the following:
|
|
493
|
+
- Literal key name.
|
|
494
|
+
- Regular expression starting with "#{regexp_prefix}".
|
|
495
|
+
- "*" that matches one key of any value.
|
|
496
|
+
- "**" that matches one or more keys of any value.
|
|
497
|
+
The path is used to determine if a value in the document should be ordered
|
|
498
|
+
using the instructions. At top level the path is empty and applies to the root
|
|
499
|
+
of the document. Long paths are considered to be more specific than short
|
|
500
|
+
paths, because they supposedly contain more literal keys or limiting regular
|
|
501
|
+
expressions so thay are intended for a specific case. Of paths of same length
|
|
502
|
+
one with "**" is considered less specific than one without.
|
|
503
|
+
|
|
504
|
+
The path to a value in the document is matched against paths to "#{order_key}"
|
|
505
|
+
to find the most specific order. For elements in an array, the last item in
|
|
506
|
+
the path is the element index as a string. Hence you can use a "*" parallel
|
|
507
|
+
to "#{order_key}" to match any index, but also literals or regular expressions
|
|
508
|
+
to match specific index can be used.
|
|
509
|
+
|
|
510
|
+
For object value in the document, the order is used to sort the keys of the
|
|
511
|
+
object. Absence of a key in the object has no effect. Any non-string key is
|
|
512
|
+
converted to string for ordering purposes. Original keys are retained.
|
|
513
|
+
|
|
514
|
+
For array value in the document, the order is used to select values of keys
|
|
515
|
+
from array elements to use in comparison for sorting the array.
|
|
516
|
+
- Using known object property names is recommended.
|
|
517
|
+
- Values of keys should be scalars and have the same type for same key.
|
|
518
|
+
- Missing key places the element after one that has the key.
|
|
519
|
+
- Object and array values are considered to be a missing value.
|
|
520
|
+
- Elements that are not objects end up last because they have no keys.
|
|
521
|
+
Mixture of objects, arrays, and scalars is not expected in the intended use.
|
|
522
|
+
|
|
523
|
+
Scalar value in the document is not affected.
|
|
524
|
+
|
|
525
|
+
Non-string keys in the document and order paths are converted to strings for
|
|
526
|
+
processing.
|
|
527
|
+
|
|
528
|
+
The --key, --regexp, --format, and --output apply to --default output. If
|
|
529
|
+
format is YAML, the internal YAML document is output as is.
|
|
530
|
+
|
|
531
|
+
To avoid errors, the value of --key should not appear in the document so that
|
|
532
|
+
it only appears as order instructions key in the order document.
|
|
533
|
+
|
|
534
|
+
In case the document has a key that starts with "#{regexp_prefix}" you can change it in the
|
|
535
|
+
order file or use "#{regexp_prefix}^#{regexp_prefix}the_rest$" as an anchored regular
|
|
536
|
+
expression that only matches the key. The ":" is part of the prefix itself,
|
|
537
|
+
so provide it if you want to keep it. Useful for readability.
|
|
538
|
+
|
|
539
|
+
When reading an order file, --key and --regexp are over-ridden by root-level
|
|
540
|
+
object "#{metadata}" keys "#{metadata_key}" and "#{metadata_regexp}" values.
|
|
541
|
+
)
|
|
542
|
+
exit 0
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
parser.order!
|
|
546
|
+
|
|
547
|
+
if output_default
|
|
548
|
+
if format == 'yaml'
|
|
549
|
+
doc = default_order_data(order_key, regexp_prefix)
|
|
550
|
+
else
|
|
551
|
+
doc = default_order(order_key, regexp_prefix)
|
|
552
|
+
doc = formats[format].call(doc, line_width)
|
|
553
|
+
end
|
|
554
|
+
else
|
|
555
|
+
doc = OpenAPISourceTools::Common.load_source(input_name)
|
|
556
|
+
return 2 if doc.nil?
|
|
557
|
+
|
|
558
|
+
order = order_name.nil? ? default_order(order_key, regexp_prefix) : OpenAPISourceTools::Common.load_source(order_name)
|
|
559
|
+
return 4 if order.nil?
|
|
560
|
+
md = order.fetch(metadata, {})
|
|
561
|
+
order_key = md.fetch(metadata_key, order_key)
|
|
562
|
+
regexp_prefix = md.fetch(metadata_regexp, regexp_prefix)
|
|
563
|
+
path_orders = []
|
|
564
|
+
return 4 unless extract_orders([], order, path_orders, order_key, regexp_prefix)
|
|
565
|
+
path_orders.sort!.reverse! # Longest key pattern sequence first.
|
|
566
|
+
order_document(path_orders, [], doc)
|
|
567
|
+
doc = formats[format].call(doc, line_width)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
OpenAPISourceTools::Common.dump_result(output_name, doc, 3)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
|
|
@@ -37,10 +37,99 @@ module OpenAPISourceTools
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Any value with given key has all but retained keys removed.
|
|
41
|
+
class ValueRetainer
|
|
42
|
+
attr_reader :key
|
|
43
|
+
attr_accessor :retain
|
|
44
|
+
|
|
45
|
+
def initialize(trigger_key)
|
|
46
|
+
@key = trigger_key
|
|
47
|
+
@retain = [ trigger_key ]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def process(obj)
|
|
51
|
+
if obj.is_a?(Array)
|
|
52
|
+
obj.each { |item| process(item) }
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
return unless obj.is_a?(Hash)
|
|
56
|
+
if obj.key?(@key)
|
|
57
|
+
obj.delete_if { |k, _v| !@retain.include?(k) }
|
|
58
|
+
end
|
|
59
|
+
obj.each_value do |value|
|
|
60
|
+
process(value)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# A replacer for value of a given key.
|
|
66
|
+
class ValueReplacer
|
|
67
|
+
attr_reader :key, :components
|
|
68
|
+
|
|
69
|
+
def initialize(trigger_key, components)
|
|
70
|
+
@key = trigger_key
|
|
71
|
+
@components = components
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def replace(obj)
|
|
75
|
+
if obj.is_a?(Array)
|
|
76
|
+
obj.each { |item| replace(item) }
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
return unless obj.is_a?(Hash)
|
|
80
|
+
obj.each do |key, value|
|
|
81
|
+
if key == @key
|
|
82
|
+
if value.is_a?(Array)
|
|
83
|
+
value.each do |item|
|
|
84
|
+
next unless item.is_a?(Hash)
|
|
85
|
+
next if item.key?('$ref')
|
|
86
|
+
@components.to_reference_object(item)
|
|
87
|
+
end
|
|
88
|
+
elsif value.is_a?(Hash)
|
|
89
|
+
next if value.key?('$ref')
|
|
90
|
+
@components.to_reference_object(value)
|
|
91
|
+
end
|
|
92
|
+
else
|
|
93
|
+
replace(value)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# A replacer for sub-values of a value of a given key.
|
|
100
|
+
class ValueSubValueReplacer
|
|
101
|
+
attr_reader :key, :components
|
|
102
|
+
|
|
103
|
+
def initialize(trigger_key, components)
|
|
104
|
+
@key = trigger_key
|
|
105
|
+
@components = components
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def replace(obj)
|
|
109
|
+
if obj.is_a?(Array)
|
|
110
|
+
obj.each { |item| replace(item) }
|
|
111
|
+
return
|
|
112
|
+
end
|
|
113
|
+
return unless obj.is_a?(Hash)
|
|
114
|
+
obj.each do |key, value|
|
|
115
|
+
if key == @key && value.is_a?(Hash)
|
|
116
|
+
value.keys.sort!.each do |sub_key|
|
|
117
|
+
sub_value = value[sub_key]
|
|
118
|
+
next unless sub_value.is_a?(Hash)
|
|
119
|
+
next if sub_value.key?('$ref')
|
|
120
|
+
@components.to_reference_object(value[sub_key])
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
replace(value)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
40
129
|
# A component in the API specification for reference and anchor handling.
|
|
41
130
|
class Components
|
|
42
131
|
attr_reader :path, :prefix, :anchor2ref, :schema_names
|
|
43
|
-
attr_accessor :items, :ignored_keys
|
|
132
|
+
attr_accessor :items, :ignored_keys, :retain_ignored
|
|
44
133
|
|
|
45
134
|
def initialize(path, prefix, ignored_keys = %w[summary description examples example $anchor])
|
|
46
135
|
path = "#/#{path.join('/')}/" if path.is_a?(Array)
|
|
@@ -51,6 +140,7 @@ module OpenAPISourceTools
|
|
|
51
140
|
@schema_names = Set.new
|
|
52
141
|
@items = {}
|
|
53
142
|
@ignored_keys = Set.new(ignored_keys)
|
|
143
|
+
@retain_ignored = false
|
|
54
144
|
end
|
|
55
145
|
|
|
56
146
|
def add_options(opts)
|
|
@@ -60,6 +150,9 @@ module OpenAPISourceTools
|
|
|
60
150
|
opts.on('--ignore FIELD', 'Ignore FIELD in comparisons.') do |f|
|
|
61
151
|
@ignored_keys.add(f)
|
|
62
152
|
end
|
|
153
|
+
opts.on('--retain-ignored', 'Retain ignored fields in reference object.') do
|
|
154
|
+
@retain_ignored = true
|
|
155
|
+
end
|
|
63
156
|
end
|
|
64
157
|
|
|
65
158
|
def help
|
|
@@ -91,6 +184,17 @@ module OpenAPISourceTools
|
|
|
91
184
|
end
|
|
92
185
|
end
|
|
93
186
|
|
|
187
|
+
def to_reference_object(obj, ref = nil)
|
|
188
|
+
ref = reference(obj) if ref.nil?
|
|
189
|
+
if @retain_ignored
|
|
190
|
+
obj.delete_if { |k, _v| !@ignored_keys.member?(k) }
|
|
191
|
+
else
|
|
192
|
+
obj.clear
|
|
193
|
+
end
|
|
194
|
+
obj['$ref'] = ref
|
|
195
|
+
obj
|
|
196
|
+
end
|
|
197
|
+
|
|
94
198
|
def store_anchor(obj, ref = nil)
|
|
95
199
|
anchor_name = obj['$anchor']
|
|
96
200
|
return if anchor_name.nil?
|