openapi-sourcetools 0.12.1 → 0.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44a305843fc718a7b958b3afbaf42f6f215f5a23e23c5485799222b7030a1757
4
- data.tar.gz: 68d37c654b903c17d8aa64d96fdcecbca2c3e7c56b2b5629e2e80f243d3993e1
3
+ metadata.gz: edfaf16a37a66c6f64cddb2580cd5a85c6daf21937a403358ea03d28b35a47d6
4
+ data.tar.gz: 6e81dcc098183b9547fbc9dd956fb0f11e2ccf556dbf7d8baaf73821bfe9334e
5
5
  SHA512:
6
- metadata.gz: 274b22fbac33a899c3a2102772d2b3e6bd44aab656b67e74a1e65fbd07edcab86bc7fcde94e41184a51e3435ec7cbc5d826c407bbbbed322ff0e2eeec1168156
7
- data.tar.gz: a41c84712831166ef6ab0b2233a9c491ea1d87b73d6f1d67bab2ef821a937d2216e99a471ed9210427cb7b8c9824160c257cc6ef856a971d4804812a9b9a5a5a
6
+ metadata.gz: 13c4fd8d5a67c9af5072a8d7e29bee3bec6a82e0a7e9a39284e492871cfb7f9dfca736293d2218df5d91efa2fc200ec0c90b0e8887e0991793dec11a714a692f
7
+ data.tar.gz: 22ff4d3552d478a72d1e7b5c5d413e4de0c912479da1469ebbb3afa3311e23bd3cb769c69ec40547448e63cd4fbde038383cfef1122fb4a3a8de5297d14d1134
@@ -16,6 +16,14 @@ def remove_subitem(obj, path)
16
16
  parent.delete(path.last) || {}
17
17
  end
18
18
 
19
+ def replace_child_inlines(obj, components)
20
+ return true unless obj.is_a?(Hash)
21
+ obj.keys.sort!.each do |name|
22
+ return false unless replace_inlines(obj[name], components)
23
+ end
24
+ true
25
+ end
26
+
19
27
  def replace_inlines(obj, components, top_level_name = nil)
20
28
  if obj.is_a?(Array)
21
29
  obj.each do |o|
@@ -29,22 +37,30 @@ def replace_inlines(obj, components, top_level_name = nil)
29
37
  return true
30
38
  end
31
39
  # Is inlined, process parts recursively.
32
- %w[allOf anyOf oneOf items prefixItems additionalProperties unevaluatedItems
33
- unevaluatedProperties if then else not
34
- ].each do |prop_name|
35
- return false unless replace_inlines(obj[prop_name], components)
40
+ looks_like_type = false
41
+ # Array-valued.
42
+ OpenAPISourceTools::Schemas::ARRAY_VALUED.each do |prop_name|
43
+ value = obj[prop_name]
44
+ next unless value.is_a?(Array)
45
+ return false unless replace_inlines(value, components)
46
+ looks_like_type = true
47
+ end
48
+ # Other-valued things.
49
+ OpenAPISourceTools::Schemas::OTHER_VALUED.each do |prop_name|
50
+ value = obj[prop_name]
51
+ next if value.nil? || value.is_a?(Array)
52
+ return false unless replace_inlines(value, components)
53
+ looks_like_type = true
36
54
  end
37
- %w[properties patternProperties dependentSchemas].each do |prop_name|
55
+ # Keys of these have object values that should be processed.
56
+ OpenAPISourceTools::Schemas::KEY2SCHEMA.each do |prop_name|
38
57
  props = obj[prop_name]
39
- next if props.nil?
40
- props.keys.sort!.each do |name|
41
- return false unless replace_inlines(props[name], components)
42
- end
58
+ next if props.nil? || !props.is_a?(Hash)
59
+ return false unless replace_child_inlines(props, components)
60
+ looks_like_type = true
43
61
  end
44
- looks_like_type = false
45
62
  # required is omitted since it is in schema and in parameters.
46
- %w[type items prefixItems properties patternProperties additionalProperties
47
- unevaluatedItems unevaluatedProperties enum const minimum maximum multipleOf
63
+ %w[type enum const minimum maximum multipleOf
48
64
  exclusiveMinimum exclusiveMaximum maxLength minLength pattern maxItems minItems
49
65
  uniqueItems maxContains minContains maxProperties minProperties propertyNames
50
66
  dependentRequired format contentEncoding contentMediaType contentSchema
@@ -57,7 +73,7 @@ def replace_inlines(obj, components, top_level_name = nil)
57
73
  unless looks_like_type
58
74
  # Some kind of intermediate-level object.
59
75
  obj.keys.sort! { |a, b| a.to_s <=> b.to_s }.each do |key|
60
- next if key == 'securitySchemes'
76
+ next if key == 'securitySchemes' || OpenAPISourceTools::Schemas::IGNORED.include?(key) || OpenAPISourceTools::Schemas::HANDLED.include?(key)
61
77
  return false unless replace_inlines(obj[key], components)
62
78
  end
63
79
  return true
@@ -0,0 +1,96 @@
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/apiobjects'
8
+ require_relative '../lib/openapi/sourcetools/common'
9
+ require 'optparse'
10
+ include OpenAPISourceTools
11
+
12
+
13
+ def mixture?(schema)
14
+ has_logic = false
15
+ has_other = false
16
+ schema.each_key do |key|
17
+ if OpenAPISourceTools::ApiObjects::LOGIC_KEYS.member?(key)
18
+ has_logic = true
19
+ return true if has_other
20
+ elsif !OpenAPISourceTools::ApiObjects::DOCUMENT_KEYS.include?(key)
21
+ has_other = true
22
+ return true if has_logic
23
+ end
24
+ end
25
+ has_logic && has_other
26
+ end
27
+
28
+ def split(name, schema, added_schemas, schema_path)
29
+ logic = {}
30
+ other = {}
31
+ schema.each do |key, value|
32
+ if OpenAPISourceTools::ApiObjects::LOGIC_KEYS.member?(key)
33
+ logic[key] = value
34
+ else
35
+ other[key] = value
36
+ end
37
+ end
38
+ logic_name = "#{name}Logic"
39
+ other_name = "#{name}Other"
40
+ added_schemas[logic_name] = logic
41
+ added_schemas[other_name] = other
42
+ schema.clear
43
+ schema['allOf'] = [
44
+ { '$ref' => "#{schema_path}/#{logic_name}" },
45
+ { '$ref' => "#{schema_path}/#{other_name}" }
46
+ ]
47
+ end
48
+
49
+ def main
50
+ input_name = nil
51
+ output_name = nil
52
+ schema_path = '#/components/schemas'
53
+
54
+ parser = OptionParser.new do |opts|
55
+ opts.summary_indent = ' '
56
+ opts.summary_width = 26
57
+ opts.banner = 'Usage: openapi-logicsplit [options]'
58
+ opts.separator ''
59
+ opts.separator 'Options:'
60
+ opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
61
+ input_name = f
62
+ end
63
+ opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
64
+ output_name = f
65
+ end
66
+ opts.on('-h', '--help', 'Print this help and exit.') do
67
+ $stdout.puts %(#{opts}
68
+
69
+ Loads API document in OpenAPI format and splits each schema with a mixture of
70
+ logic keywords and other keywords so that the schema has only allOf with
71
+ references to logic and other schema. The aim is to simplify code generation.
72
+
73
+ The openapi-addschemas must have been run. Processes "#{schema_path}".
74
+ )
75
+ return 0
76
+ end
77
+ end
78
+ parser.order!
79
+
80
+ doc = Common.load_source(input_name)
81
+ return 2 if doc.nil?
82
+
83
+ path = schema_path.split('/').reject { |x| x == '#' || x.empty? }
84
+ schemas = doc.dig(*path)
85
+ added_schemas = {}
86
+ unless schemas.nil?
87
+ schemas.keys.sort.each do |name|
88
+ s = schemas[name]
89
+ split(name, s, added_schemas, schema_path) if mixture?(s)
90
+ end
91
+ schemas.merge!(added_schemas)
92
+ end
93
+ Common.dump_result(output_name, doc, 3)
94
+ end
95
+
96
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
data/bin/openapi-order CHANGED
@@ -53,6 +53,7 @@ def default_order_data(order_key, regexp_prefix)
53
53
  server: *ServerObject
54
54
  parameters:
55
55
  #{order_key}: &ParameterObject_order
56
+ - required: '>' # True before false.
56
57
  - name
57
58
  - in
58
59
  '*': &ParameterObject
@@ -216,7 +217,7 @@ def default_order_data(order_key, regexp_prefix)
216
217
  - re:^x-openapi-sourcetools.*$
217
218
  '*':
218
219
  #{order_key}: &OperationObject
219
- - '$ref'
220
+ - '\$ref'
220
221
  - operationId
221
222
  - summary
222
223
  - description
@@ -349,7 +350,7 @@ end
349
350
 
350
351
  # This needs to extract all path/order pairs, store and sort.
351
352
 
352
- def extract_orders(path, order, collection, order_key, regexp_prefix)
353
+ def extract_orders(path, order, collection, order_key, regexp_prefix, visited)
353
354
  return true unless order.is_a?(Hash)
354
355
  order.each do |key, value|
355
356
  if key == order_key
@@ -357,9 +358,13 @@ def extract_orders(path, order, collection, order_key, regexp_prefix)
357
358
  OpenAPISourceTools::Common.aargh("Not an array: #{path.join('.')}.#{order_key} =\n#{YAML.dump(value)}")
358
359
  return false
359
360
  end
361
+ return true if visited.member?(value.object_id)
362
+ visited.add(value.object_id)
360
363
  value.each_with_index do |v, k|
361
- next if v.is_a?(String)
362
- OpenAPISourceTools::Common.aargh("Not a string: #{path}.#{order_key}[#{k}] =\n#{YAML.dump(v)}")
364
+ ov = OpenAPISourceTools::Ordering::Order.ordervalue(v)
365
+ value[k] = ov
366
+ next unless ov.nil?
367
+ OpenAPISourceTools::Common.aargh("Not valid order value: #{path}.#{order_key}[#{k}] =\n#{YAML.dump(v)}")
363
368
  return false
364
369
  end
365
370
  key_path = OpenAPISourceTools::Ordering::KeyPath.new(path, regexp_prefix)
@@ -368,7 +373,7 @@ def extract_orders(path, order, collection, order_key, regexp_prefix)
368
373
  else
369
374
  key = key.to_s unless key.is_a?(String)
370
375
  path.push(key)
371
- return false unless extract_orders(path, value, collection, order_key, regexp_prefix)
376
+ return false unless extract_orders(path, value, collection, order_key, regexp_prefix, visited)
372
377
  path.pop
373
378
  end
374
379
  end
@@ -473,14 +478,17 @@ def main
473
478
  regexp_prefix = p
474
479
  end
475
480
  opts.on('-h', '--help', 'Print this help and exit.') do
476
- $stdout.puts %(#{opts}
481
+ $stdout.puts <<EOF
482
+ #{opts}
477
483
  Loads YAML document and applies ordering to object keys, and arrays with
478
484
  objects, when an order matches the key path to object.
479
485
 
480
486
  Can be used on generic YAML or JSON files. Default order is for OpenAPI
481
487
  documents.
482
488
 
483
- The order instructions key "#{order_key}" value must be an array of strings.
489
+ The order instructions key "#{order_key}" value must be an array of strings
490
+ or single-item map from string to "<" or ">" to indicate ascending (default)
491
+ or descending order. Hence any 'key' is same as 'key: "<"'.
484
492
  The values can be any combination of the following:
485
493
  - Literal key name.
486
494
  - Regular expression starting with "#{regexp_prefix}".
@@ -493,13 +501,13 @@ position are sorted within that position.
493
501
  The path to the "#{order_key}" can have any combination of the following:
494
502
  - Literal key name.
495
503
  - Regular expression starting with "#{regexp_prefix}".
496
- - "*" that matches one key of any value.
497
- - "**" that matches one or more keys of any value.
504
+ - "*" that matches one key of any content.
505
+ - "**" that matches one or more keys of any content.
498
506
  The path is used to determine if a value in the document should be ordered
499
507
  using the instructions. At top level the path is empty and applies to the root
500
508
  of the document. Long paths are considered to be more specific than short
501
509
  paths, because they supposedly contain more literal keys or limiting regular
502
- expressions so thay are intended for a specific case. Of paths of same length
510
+ expressions so they are intended for a specific case. Of paths of same length
503
511
  one with "**" is considered less specific than one without.
504
512
 
505
513
  The path to a value in the document is matched against paths to "#{order_key}"
@@ -539,7 +547,7 @@ so provide it if you want to keep it. Useful for readability.
539
547
 
540
548
  When reading an order file, --key and --regexp are over-ridden by root-level
541
549
  object "#{metadata}" keys "#{metadata_key}" and "#{metadata_regexp}" values.
542
- )
550
+ EOF
543
551
  exit 0
544
552
  end
545
553
  end
@@ -562,7 +570,7 @@ object "#{metadata}" keys "#{metadata_key}" and "#{metadata_regexp}" values.
562
570
  order_key = md.fetch(metadata_key, order_key)
563
571
  regexp_prefix = md.fetch(metadata_regexp, regexp_prefix)
564
572
  path_orders = []
565
- return 4 unless extract_orders([], order, path_orders, order_key, regexp_prefix)
573
+ return 4 unless extract_orders([], order, path_orders, order_key, regexp_prefix, Set.new)
566
574
  path_orders.sort!.reverse! # Longest key pattern sequence first.
567
575
  order_document(path_orders, [], doc)
568
576
  doc = formats[format].call(doc, line_width)
@@ -0,0 +1,143 @@
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/schemaio'
9
+ require 'optparse'
10
+ include OpenAPISourceTools
11
+
12
+
13
+ # Functions related to saving the output.
14
+
15
+ def to_json(doc, _line_width)
16
+ JSON.dump(doc)
17
+ end
18
+
19
+ def to_pretty_json(doc, _line_width)
20
+ JSON.pretty_generate(doc)
21
+ end
22
+
23
+ def to_yaml(doc, line_width)
24
+ YAML.dump(doc, line_width: line_width)
25
+ end
26
+
27
+ def main
28
+ input_file = nil
29
+ schema_file = nil
30
+ output_file = nil
31
+ name = nil
32
+ refs = true
33
+ format = 'yaml'
34
+ formats = {
35
+ 'yaml' => method(:to_yaml),
36
+ 'json' => method(:to_json),
37
+ 'pretty_json' => method(:to_pretty_json)
38
+ }
39
+ line_width = 80
40
+ doc_paths = :openapi
41
+ schema_paths = :jsonschema
42
+ drops = []
43
+
44
+ parser = OptionParser.new do |opts|
45
+ opts.summary_indent = ' '
46
+ opts.summary_width = 26
47
+ opts.banner = 'Usage: openapi-schema-in [options]'
48
+ opts.separator ''
49
+ opts.separator 'Options:'
50
+ opts.on('-i', '--input FILE', 'Read input from FILE, not stdin.') do |f|
51
+ input_file = f
52
+ end
53
+ opts.on('-s', '--schema FILE', 'Read schema from FILE, not stdin.') do |f|
54
+ schema_file = f
55
+ end
56
+ opts.on('-o', '--output FILE', 'Write result to FILE, not stdout.') do |f|
57
+ output_file = f
58
+ end
59
+ opts.on('-n', '--name NAME', 'Use schema NAME.') do |n|
60
+ name = n
61
+ end
62
+ opts.on('--[no-]refs', "Include referred schemas (default #{Common.yesno(refs)}).") do |r|
63
+ refs = r
64
+ end
65
+ opts.on('-f', '--format FORMAT', "Document output format: #{formats.keys.sort!.join(', ')}.") do |fmt|
66
+ format = fmt
67
+ end
68
+ opts.on('-w', '--width WIDTH', "Set YAML line width, default: #{line_width}") do |w|
69
+ line_width = w.to_i
70
+ end
71
+ opts.on('--inputpaths DATA', 'Input path data.') do |data|
72
+ doc_paths = data
73
+ end
74
+ opts.on('--schemapaths DATA', 'Schema path data.') do |data|
75
+ schema_paths = data
76
+ end
77
+ opts.on('-d', '--drop KEY', 'Drop KEY from chosen schema.') do |k|
78
+ drops.push(k)
79
+ end
80
+ opts.on('-h', '--help', 'Print this help and exit.') do
81
+ $stdout.puts <<EOF
82
+ #{opts}
83
+ Takes schema with name and places it into document.
84
+
85
+ The document and schema paths DATA is either a file name, internal data name,
86
+ or a JSON-encoded object containing paths data. Internal data names are:
87
+ #{SchemaIO::BUILTINS.keys.sort!.join(', ')}
88
+
89
+ Depending on input, schema NAME is used to find and store the schema in the
90
+ location indicated by DATA for input and output.
91
+
92
+ DATA values default to input and output being OpenAPI format, schema file is
93
+ JSON schema with the named schema at root.
94
+
95
+ Dropping keys can be used to remove for example "$schema" and "$id" keys from
96
+ the chosen schema, if present.
97
+ EOF
98
+ return 0
99
+ end
100
+ end
101
+ parser.order!
102
+
103
+ drops.sort!.uniq! unless drops.size < 2
104
+
105
+ return Common.aargh('Cannot read both document and schema from stdin.', 1) if input_file.nil? && schema_file.nil?
106
+ return Common.aargh('Schema name must be given.', 1) if name.nil?
107
+
108
+ return Common.aargh("Unknown format: #{format}", 1) unless formats.key?(format)
109
+
110
+ dp = SchemaIO.key_data(doc_paths)
111
+ return Common.aargh("Not file or internal format name or format as JSON object: #{doc_paths}", 2) if dp.nil?
112
+ sp = SchemaIO.key_data(schema_paths)
113
+ return Common.aargh("Not file or internal format name or format as JSON object: #{schema_paths}", 2) if sp.nil?
114
+
115
+ return Common.aargh(%(Name "#{name}" has document path separator "#{dp.separator}"), 1) if name.include?(dp.separator)
116
+ return Common.aargh(%(Name "#{name}" has schema path separator "#{sp.separator}"), 1) if name.include?(sp.separator)
117
+
118
+ doc = Common.load_source(input_file)
119
+ return 2 if doc.nil?
120
+ return Common.aargh('Document is not an object.', 2) unless doc.is_a?(Hash)
121
+ schema = Common.load_source(schema_file)
122
+ return 2 if schema.nil?
123
+ return Common.aargh('Schema is not an object.', 2) unless schema.is_a?(Hash)
124
+
125
+ tgt = SchemaIO::SchemaAccess.new(dp, doc)
126
+ src = SchemaIO::SchemaAccess.new(sp, schema)
127
+
128
+ desired = src.find_named_schema(name)
129
+ return Common.aargh("Schema not found: #{name}", 1) if desired.nil?
130
+ drops.each { |k| desired.delete(k) }
131
+ tgt.put_named_schema(name, desired)
132
+
133
+ if refs
134
+ refd = src.find_referenced_schemas(name, desired)
135
+ refd.each { |n, s| tgt.put_referenced_schema(n, s) }
136
+ end
137
+ SchemaIO.alter_refs(tgt.document, src, tgt) # After referenced schemas have been found.
138
+
139
+ out = formats[format].call(tgt.document, line_width)
140
+ Common.dump_result(output_file, out, 3)
141
+ end
142
+
143
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -0,0 +1,100 @@
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/schemaio'
9
+ require 'optparse'
10
+ require 'yaml'
11
+ require 'json'
12
+ include OpenAPISourceTools
13
+
14
+
15
+ # Functions related to saving the document path dump.
16
+
17
+ def to_json(doc)
18
+ JSON.dump(doc)
19
+ end
20
+
21
+ def to_pretty_json(doc)
22
+ JSON.pretty_generate(doc)
23
+ end
24
+
25
+ def to_yaml(doc)
26
+ YAML.dump(doc)
27
+ end
28
+
29
+ def main
30
+ input_file = nil
31
+ output_file = nil
32
+ doc_paths = :openapi
33
+ output_default = nil
34
+ format = 'pretty_json'
35
+ formats = {
36
+ 'yaml' => method(:to_yaml),
37
+ 'json' => method(:to_json),
38
+ 'pretty_json' => method(:to_pretty_json)
39
+ }
40
+
41
+ parser = OptionParser.new do |opts|
42
+ opts.summary_indent = ' '
43
+ opts.summary_width = 22
44
+ opts.banner = 'Usage: openapi-schema-ls [options]'
45
+ opts.separator ''
46
+ opts.separator 'Options:'
47
+ opts.on('-i', '--input FILE', 'Read input from FILE, not stdin.') do |f|
48
+ input_file = f
49
+ end
50
+ opts.on('-o', '--output FILE', 'Write result to FILE, not stdout.') do |f|
51
+ output_file = f
52
+ end
53
+ opts.on('--inputpaths DATA', 'Input path data.') do |data|
54
+ doc_paths = data
55
+ end
56
+ opts.on('--dump DATA', 'Dump internal path data to output and exit.') do |data|
57
+ output_default = data
58
+ end
59
+ opts.on('-f', '--format FORMAT', "DATA output format: #{formats.keys.sort!.join(', ')}.") do |fmt|
60
+ format = fmt
61
+ end
62
+ opts.on('-h', '--help', 'Print this help and exit.') do
63
+ $stdout.puts <<EOF
64
+ #{opts}
65
+ Lists named schemas in the input document.
66
+
67
+ The document paths DATA is either a file name, internal data name, or a
68
+ JSON-encoded object with necessary keys. Same data also applies to both
69
+ openapi-schema-in and openapi-schema-out. Internal data names are:
70
+ #{SchemaIO::BUILTINS.keys.sort!.join(', ')}
71
+
72
+ DATA defaults to OpenAPI format.
73
+ EOF
74
+ return 0
75
+ end
76
+ end
77
+ parser.order!
78
+
79
+ return Common.aargh("Unknown format: #{format}", 1) unless formats.key?(format)
80
+
81
+ unless output_default.nil?
82
+ doc = SchemaIO.get(output_default)
83
+ return Commpon.aargh("Not internal data format: #{output_default}", 1) if doc.nil?
84
+ out = (format == 'yaml') ? doc : formats[format].call(YAML.safe_load(doc))
85
+ return Common.dump_result(output_file, out, 3)
86
+ end
87
+
88
+ dp = SchemaIO.key_data(doc_paths)
89
+ return Common.aargh("Not file or internal format name or format as JSON object: #{doc_paths}", 2) if dp.nil?
90
+
91
+ doc = Common.load_source(input_file)
92
+ return 2 if doc.nil?
93
+
94
+ src = SchemaIO::SchemaAccess.new(dp, doc)
95
+ out = src.list_schema_names.join("\n")
96
+
97
+ Common.dump_result(output_file, out, 3)
98
+ end
99
+
100
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -0,0 +1,144 @@
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/schemaio'
9
+ require 'optparse'
10
+ require 'yaml'
11
+ require 'json'
12
+ include OpenAPISourceTools
13
+
14
+
15
+ # Functions related to saving the output.
16
+
17
+ def to_json(doc, _line_width)
18
+ JSON.dump(doc)
19
+ end
20
+
21
+ def to_pretty_json(doc, _line_width)
22
+ JSON.pretty_generate(doc)
23
+ end
24
+
25
+ def to_yaml(doc, line_width)
26
+ YAML.dump(doc, line_width: line_width)
27
+ end
28
+
29
+ def main
30
+ input_file = nil
31
+ output_file = nil
32
+ name = nil
33
+ refs = true
34
+ format = 'pretty_json'
35
+ formats = {
36
+ 'yaml' => method(:to_yaml),
37
+ 'json' => method(:to_json),
38
+ 'pretty_json' => method(:to_pretty_json)
39
+ }
40
+ line_width = 80
41
+ doc_paths = :openapi
42
+ schema_paths = :jsonschema
43
+ additions = {}
44
+
45
+ parser = OptionParser.new do |opts|
46
+ opts.summary_indent = ' '
47
+ opts.summary_width = 27
48
+ opts.banner = 'Usage: openapi-schema-out [options]'
49
+ opts.separator ''
50
+ opts.separator 'Options:'
51
+ opts.on('-i', '--input FILE', 'Read input from FILE, not stdin.') do |f|
52
+ input_file = f
53
+ end
54
+ opts.on('-o', '--output FILE', 'Write result to FILE, not stdout.') do |f|
55
+ output_file = f
56
+ end
57
+ opts.on('-n', '--name NAME', 'Use schema NAME.') do |n|
58
+ name = n
59
+ end
60
+ opts.on('--[no-]refs', "Include referred schemas (default #{Common.yesno(refs)}).") do |r|
61
+ refs = r
62
+ end
63
+ opts.on('-f', '--format FORMAT', "Schema output format: #{formats.keys.sort!.join(', ')}.") do |fmt|
64
+ format = fmt
65
+ end
66
+ opts.on('-w', '--width WIDTH', "Set YAML line width, default: #{line_width}") do |w|
67
+ line_width = w.to_i
68
+ end
69
+ opts.on('--inputpaths DATA', 'Input path data.') do |data|
70
+ doc_paths = data
71
+ end
72
+ opts.on('--schemapaths DATA', 'Schema path data.') do |data|
73
+ schema_paths = data
74
+ end
75
+ opts.on('-a', '--add KEY:[json:]VALUE', 'Add KEY with (JSON) VALUE to output schema.') do |kv|
76
+ k, v = kv.split(':', 2)
77
+ return Common.aargh("Invalid key-value pair: #{kv}", 1) if k.nil? || v.nil?
78
+ return Common.aargh("Empty key: #{kv}", 1) if k.empty?
79
+ v = JSON.parse(v[5..]) if v.downcase.start_with?('json:')
80
+ additions[k] = v
81
+ rescue JSON::ParserError
82
+ return Common.aargh("Invalid JSON in argument value: #{kv}", 1)
83
+ end
84
+ opts.on('-h', '--help', 'Print this help and exit.') do
85
+ $stdout.puts <<EOF
86
+ #{opts}
87
+ Takes schema with name from input and places it into output.
88
+
89
+ The document and schema paths DATA is either a file name, internal data name,
90
+ or a JSON-encoded object with necessary keys. Internal data names are:
91
+ #{SchemaIO::BUILTINS.keys.sort!.join(', ')}
92
+
93
+ Depending on input, schema NAME is used to find and store the schema in the
94
+ location indicated by DATA for output.
95
+
96
+ DATA values default to input being OpenAPI format and output is JSON schema
97
+ with schema at root and referenced schemas, if output, under "$defs" key.
98
+
99
+ Adding KEY:VALUE pairs can be used to add keys to the output schema with
100
+ string values. Using KEY:json:VALUE will parse VALUE as JSON and use that.
101
+ The format indicator is case-insensitive. Value can be empty but key cannot
102
+ be empty. Pass value starting with "json:" as 'key:json:"json:the_rest"'.
103
+ Usable for adding "$schema" and "$id" keys to the output schema.
104
+ EOF
105
+ return 0
106
+ end
107
+ end
108
+ parser.order!
109
+
110
+ return Common.aargh('Schema name must be given.', 1) if name.nil?
111
+
112
+ return Common.aargh("Unknown format: #{format}", 1) unless formats.key?(format)
113
+
114
+ dp = SchemaIO.key_data(doc_paths)
115
+ return Common.aargh("Not file or internal format name or format as JSON object: #{doc_paths}", 2) if dp.nil?
116
+ sp = SchemaIO.key_data(schema_paths)
117
+ return Common.aargh("Not file or internal format name or format as JSON object: #{schema_paths}", 2) if sp.nil?
118
+
119
+ return Common.aargh(%(Name "#{name}" has document path separator "#{dp.separator}"), 1) if name.include?(dp.separator)
120
+ return Common.aargh(%(Name "#{name}" has schema path separator "#{sp.separator}"), 1) if name.include?(sp.separator)
121
+
122
+ doc = Common.load_source(input_file)
123
+ return 2 if doc.nil?
124
+ return Common.aargh('Document is not an object.', 2) unless doc.is_a?(Hash)
125
+
126
+ src = SchemaIO::SchemaAccess.new(dp, doc)
127
+ tgt = SchemaIO::SchemaAccess.new(sp, {})
128
+
129
+ desired = src.find_named_schema(name)
130
+ return Common.aargh("Schema not found: #{name}", 1) if desired.nil?
131
+ desired.merge!(additions) unless additions.empty?
132
+ tgt.put_named_schema(name, desired)
133
+
134
+ if refs
135
+ refd = src.find_referenced_schemas(name, desired)
136
+ refd.each { |n, s| tgt.put_referenced_schema(n, s) }
137
+ end
138
+ SchemaIO.alter_refs(tgt.document, src, tgt) # After referenced schemas have been found.
139
+
140
+ out = formats[format].call(tgt.document, line_width)
141
+ Common.dump_result(output_file, out, 3)
142
+ end
143
+
144
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -1,12 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright © 2024-2025 Ismo Kärkkäinen
3
+ # Copyright © 2024-2026 Ismo Kärkkäinen
4
4
  # Licensed under Universal Permissive License. See LICENSE.txt.
5
5
 
6
6
  module OpenAPISourceTools
7
+ # Various schema-related constants.
8
+ module Schemas
9
+ ARRAY_VALUED = %w[allOf anyOf oneOf prefixItems].freeze
10
+ OTHER_VALUED = %w[items additionalProperties unevaluatedItems unevaluatedProperties if then else not].freeze
11
+ KEY2SCHEMA = %w[properties patternProperties dependentSchemas].freeze
12
+ HANDLED = [].concat(ARRAY_VALUED, OTHER_VALUED, KEY2SCHEMA).freeze
13
+ IGNORED = %w[example examples].freeze
14
+ end
15
+
7
16
  # Various classes for handling objects in the API specification.
8
17
  # Used in various programs.
9
18
  module ApiObjects
19
+ LOGIC_KEYS = Set.new(%w[allOf anyOf oneOf not if then else dependentSchemas]).freeze
20
+ DOCUMENT_KEYS = %w[example examples title summary description deprecated readOnly writeOnly].freeze
10
21
 
11
22
  def self.same(a, b, ignored_keys = Set.new(%w[summary description]))
12
23
  return a == b unless a.is_a?(Hash) && b.is_a?(Hash)
@@ -89,7 +100,7 @@ module OpenAPISourceTools
89
100
  next if value.key?('$ref')
90
101
  @components.to_reference_object(value)
91
102
  end
92
- else
103
+ elsif !Schemas::IGNORED.include?(key)
93
104
  replace(value)
94
105
  end
95
106
  end
@@ -114,6 +125,7 @@ module OpenAPISourceTools
114
125
  obj.each do |key, value|
115
126
  if key == @key && value.is_a?(Hash)
116
127
  value.keys.sort!.each do |sub_key|
128
+ next if Schemas::IGNORED.include?(sub_key)
117
129
  sub_value = value[sub_key]
118
130
  next unless sub_value.is_a?(Hash)
119
131
  next if sub_value.key?('$ref')
@@ -173,7 +185,7 @@ module OpenAPISourceTools
173
185
  @items.each do |k, v|
174
186
  return ref_string(k) if ApiObjects.same(obj, v, @ignored_keys)
175
187
  end
176
- # One of the numbers will not match existing keys. More number than keys.
188
+ # One of the numbers will not match existing keys. More numbers than keys.
177
189
  (@items.size + 1).times do |n|
178
190
  # 'x' is to simplify find and replace (Schema1x vs Schema1 and Schema10)
179
191
  cand = "#{@prefix}#{n}x"
@@ -10,7 +10,7 @@ module OpenAPISourceTools
10
10
  # Used as replacement for '*'.
11
11
  ANY = Regexp.new('^.*$')
12
12
 
13
- def self.array_item_compare(a, b)
13
+ def self.array_item_compare(a, b, patterns)
14
14
  a[:values].size.times do |k|
15
15
  av = a[:values][k]
16
16
  bv = b[:values][k]
@@ -18,8 +18,14 @@ module OpenAPISourceTools
18
18
  return 1 unless bv.nil?
19
19
  else
20
20
  return -1 if bv.nil?
21
- r = av <=> bv
22
- return r unless r.nil? || r.zero?
21
+ if (av.is_a?(TrueClass) || av.is_a?(FalseClass)) && (bv.is_a?(TrueClass) || bv.is_a?(FalseClass))
22
+ r = (av ? 1 : 0) <=> (bv ? 1 : 0)
23
+ else
24
+ r = av <=> bv
25
+ end
26
+ next if r.nil? || r.zero?
27
+ r = -r unless patterns[k].ascending
28
+ return r
23
29
  end
24
30
  end
25
31
  0
@@ -129,18 +135,23 @@ module OpenAPISourceTools
129
135
  # Hence items.size, and the more multiple, the earlier.
130
136
  d = @items.size <=> other.items.size
131
137
  return d unless d.zero?
132
- -@items.count(:multi) <=> -other.items.count(:multi)
138
+ d = -@items.count(:multi) <=> -other.items.count(:multi)
139
+ return d unless d.zero?
140
+ # Questionable if I should compare to the object_id or equality.
141
+ # Now if you use same regular expression, they are the same.
142
+ -@items.count(ANY) <=> -other.items.count(ANY)
133
143
  end
134
144
  end
135
145
 
136
146
  # Book-keeping class for pattern and its location.
137
147
  class KeyPattern
138
- attr_reader :pattern, :index, :priority
148
+ attr_reader :pattern, :index, :priority, :ascending
139
149
 
140
- def initialize(pattern, index, priority)
150
+ def initialize(pattern, index, priority, sort_operator)
141
151
  @pattern = pattern
142
152
  @index = index
143
153
  @priority = priority
154
+ @ascending = sort_operator == '<'
144
155
  end
145
156
 
146
157
  def match?(string)
@@ -160,22 +171,34 @@ module OpenAPISourceTools
160
171
  false
161
172
  end
162
173
 
174
+ def self.ordervalue(item)
175
+ return [ item, '<' ] if item.is_a?(String)
176
+ return nil unless item.is_a?(Hash)
177
+ return nil unless item.size == 1
178
+ key = item.keys.first
179
+ return nil unless key.is_a?(String)
180
+ return nil unless ['<', '>'].include?(item[key])
181
+ [ key, item[key] ]
182
+ end
183
+
163
184
  attr_reader :patterns
164
185
 
165
186
  def initialize(keys, regexp_prefix)
166
187
  @patterns = []
167
188
  has_asterisk = false
168
- keys.each_with_index do |string, idx|
189
+ keys.each_with_index do |ov, idx|
190
+ string = ov.first
191
+ sort_operator = ov.last
169
192
  if string.start_with?(regexp_prefix)
170
- @patterns.push(KeyPattern.new(Regexp.new(string[regexp_prefix.size..]), idx, idx))
193
+ @patterns.push(KeyPattern.new(Regexp.new(string[regexp_prefix.size..]), idx, idx, sort_operator))
171
194
  elsif string == '*'
172
- @patterns.push(KeyPattern.new(Regexp.new('^.*$'), idx, keys.size)) unless has_asterisk # First used.
195
+ @patterns.push(KeyPattern.new(Regexp.new('^.*$'), idx, keys.size, sort_operator)) unless has_asterisk # First used.
173
196
  has_asterisk = true
174
197
  else
175
- @patterns.push(KeyPattern.new(Regexp.new("^#{Regexp.escape(string)}$"), idx, idx))
198
+ @patterns.push(KeyPattern.new(Regexp.new("^#{Regexp.escape(string)}$"), idx, idx, sort_operator))
176
199
  end
177
200
  end
178
- @patterns.push(KeyPattern.new(Regexp.new('^.*$'), @patterns.size, keys.size + 1)) unless has_asterisk
201
+ @patterns.push(KeyPattern.new(Regexp.new('^.*$'), @patterns.size, keys.size + 1, '<')) unless has_asterisk
179
202
  end
180
203
 
181
204
  def apply_array(array)
@@ -207,13 +230,13 @@ module OpenAPISourceTools
207
230
  end
208
231
  values.push(amended)
209
232
  end
210
- values.sort! { |a, b| OpenAPISourceTools::Ordering.array_item_compare(a, b) }
233
+ values.sort! { |a, b| OpenAPISourceTools::Ordering.array_item_compare(a, b, @patterns) }
211
234
  values.each_with_index { |amended, i| array[i] = amended[:item] }
212
235
  true
213
236
  end
214
237
 
215
- def apply_hash(hash)
216
- return true if hash.size < 2
238
+ def apply_hash(unordered_hash)
239
+ return true if unordered_hash.size < 2
217
240
  groups = @patterns.map do |kp|
218
241
  {
219
242
  kp: kp,
@@ -221,7 +244,7 @@ module OpenAPISourceTools
221
244
  }
222
245
  end
223
246
  s2orig = {}
224
- hash.each_key do |key|
247
+ unordered_hash.each_key do |key|
225
248
  skey = key.is_a?(String) ? key : key.to_s
226
249
  s2orig[skey] = key
227
250
  best_priority = groups.size + 1
@@ -238,10 +261,11 @@ module OpenAPISourceTools
238
261
  ordered = {}
239
262
  groups.each do |group|
240
263
  group[:keys].sort!
241
- group[:keys].each { |k| ordered[s2orig[k]] = hash[s2orig[k]] }
264
+ group[:keys].reverse! unless group[:kp].ascending
265
+ group[:keys].each { |k| ordered[s2orig[k]] = unordered_hash[s2orig[k]] }
242
266
  end
243
- hash.clear
244
- hash.merge!(ordered)
267
+ unordered_hash.clear
268
+ unordered_hash.merge!(ordered)
245
269
  true
246
270
  end
247
271
 
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright © 2026 Ismo Kärkkäinen
4
+ # Licensed under Universal Permissive License. See LICENSE.txt.
5
+
6
+ require_relative 'common'
7
+ require 'yaml'
8
+ require 'json'
9
+
10
+
11
+ # Gem namespace.
12
+ module OpenAPISourceTools
13
+ # Methods and classes to get schemas in and out of documents.
14
+ module SchemaIO
15
+ OPENAPI_PATHS = <<EOF
16
+ ---
17
+ separator: "/" # For splitting paths.
18
+ ignore: "#" # What to ignore in split paths.
19
+ named: "#/components/schemas" # Before named schema.
20
+ reference: "$ref" # Reference key name.
21
+ referenced: "#/components/schemas" # Reference path before schema name.
22
+ EOF
23
+
24
+ JSON_SCHEMA_PATHS = <<EOF
25
+ ---
26
+ separator: "/"
27
+ ignore: "#"
28
+ named: ~
29
+ reference: "$ref"
30
+ referenced: "#/$defs"
31
+ EOF
32
+
33
+ JSON_SCHEMA_DEFS_PATHS = <<EOF
34
+ ---
35
+ separator: "/"
36
+ ignore: "#"
37
+ named: "#/$defs"
38
+ reference: "$ref"
39
+ referenced: "#/$defs"
40
+ EOF
41
+
42
+ BUILTINS = {
43
+ openapi: OPENAPI_PATHS,
44
+ jsonschema: JSON_SCHEMA_PATHS,
45
+ jsonschemadefs: JSON_SCHEMA_DEFS_PATHS
46
+ }.freeze
47
+
48
+ def self.get(name)
49
+ BUILTINS[name.is_a?(String) ? name.downcase.to_sym : name]
50
+ end
51
+
52
+ # Checks format and does some convenience preparations.
53
+ class SchemaFormat
54
+ attr_reader :separator, :named, :reference, :referenced
55
+
56
+ def initialize(format)
57
+ f = format.is_a?(Hash) ? format : YAML.safe_load(format)
58
+ raise ArgumentError, 'Format is not an object.' unless f.is_a?(Hash)
59
+ errors = []
60
+ @separator = f['separator']
61
+ errors.push('separator must be a non-empty string.') unless @separator.is_a?(String) && !@separator.empty?
62
+ @ignore = f['ignore']
63
+ errors.push('ignore must be a string or an array of strings.') unless @ignore.is_a?(String) || @ignore.is_a?(Array)
64
+ if @ignore.is_a?(Array)
65
+ errors.push('ignore array values must be non-empty strings') unless @ignore.index { |v| !v.is_a?(String) || v.empty? }.nil?
66
+ else
67
+ @ignore = [ @ignore ]
68
+ end
69
+ @ignore.push('')
70
+ @named = f['named']
71
+ @reference = f['reference']
72
+ errors.push('Reference key must be non-empty string.') if @reference.nil?
73
+ @referenced = f['referenced']
74
+ raise ArgumentError, errors.join("\n") unless errors.empty?
75
+ @named = '' if @named.nil?
76
+ @named += @separator unless @named.empty? || @named.end_with?(@separator)
77
+ @referenced += @separator unless @referenced.nil? || @referenced.empty? || @referenced.end_with?(@separator)
78
+ end
79
+
80
+ def ignore?(str)
81
+ @ignore.include?(str)
82
+ end
83
+
84
+ def path(str)
85
+ str.split(@separator).reject { |p| ignore?(p) }
86
+ end
87
+
88
+ def reference?(key, value)
89
+ # Accurate enough for now.
90
+ @reference == key && value.is_a?(String) && value.start_with?(@referenced)
91
+ end
92
+
93
+ def reference_name(ref)
94
+ ref[@referenced.size..]
95
+ end
96
+
97
+ def make_reference(name)
98
+ "#{@referenced}#{name}"
99
+ end
100
+
101
+ def root_schema?
102
+ @named.empty?
103
+ end
104
+ end
105
+
106
+ def self.load_data(data)
107
+ # File name?
108
+ if data.is_a?(String)
109
+ begin
110
+ return YAML.safe_load_file(data)
111
+ rescue StandardError
112
+ # Was not a file, it is ok.
113
+ end
114
+ end
115
+ # Internal key?
116
+ d = get(data)
117
+ return YAML.safe_load(d) unless d.nil?
118
+ # Try to parse as JSON.
119
+ if data.is_a?(String)
120
+ begin
121
+ return JSON.parse(data)
122
+ rescue JSON::ParserError
123
+ # Was not.
124
+ end
125
+ end
126
+ nil
127
+ end
128
+
129
+ def self.key_data(data)
130
+ d = load_data(data)
131
+ d.nil? ? nil : SchemaFormat.new(d)
132
+ rescue ArgumentError => e
133
+ Common.aargh(e.to_s)
134
+ nil
135
+ end
136
+
137
+ # Changes references from source format to target format.
138
+ def self.alter_refs(schema, source_access, target_access)
139
+ if schema.is_a?(Array)
140
+ schema.each do |v|
141
+ alter_refs(v, source_access, target_access)
142
+ end
143
+ end
144
+ return unless schema.is_a?(Hash)
145
+ schema.keys.each do |key|
146
+ value = schema[key]
147
+ if source_access.format.reference?(key, value)
148
+ # Change the prefix in the reference, unless equal.
149
+ unless target_access.format.referenced == source_access.format.referenced
150
+ value = target_access.format.make_reference(source_access.format.reference_name(value))
151
+ end
152
+ # Change reference key name if needed.
153
+ if source_access.format.reference == target_access.format.reference
154
+ schema[key] = value
155
+ else
156
+ schema.delete(key)
157
+ schema[target_access.format.reference] = value
158
+ end
159
+ elsif value.is_a?(Hash) || value.is_a?(Array)
160
+ alter_refs(value, source_access, target_access)
161
+ end
162
+ end
163
+ end
164
+
165
+ # Handles document with schemas in stated places. Reads or modifies.
166
+ class SchemaAccess
167
+ attr_reader :document, :format
168
+
169
+ def initialize(format, document)
170
+ @document = document
171
+ @format = format
172
+ end
173
+
174
+ # Used when reading from given document.
175
+
176
+ def list_schema_names
177
+ schemas = @document.dig(*@format.path(@format.referenced))
178
+ schemas.keys.sort!
179
+ end
180
+
181
+ def find_named_schema(name)
182
+ if @format.root_schema?
183
+ # Make a copy and remove the root key to referenced definitions.
184
+ out = {}.merge!(@document)
185
+ first = @format.path(@format.referenced).first
186
+ out.delete(first) unless first.nil?
187
+ out
188
+ else
189
+ find_by_reference(@format.make_reference(name))
190
+ end
191
+ end
192
+
193
+ def find_referenced_schemas(name, schema)
194
+ name2schema = {}
195
+ return name2schema if schema.nil?
196
+ existing = Set.new # Stops loops.
197
+ existing.add(name) unless @format.root_schema?
198
+ todo = [ schema ]
199
+ until todo.empty?
200
+ s = todo.pop
201
+ refs = Set.new
202
+ gather_refs(s, refs, existing)
203
+ # Add all new references to the todo list, if present in the file.
204
+ refs.each do |r|
205
+ existing.add(r)
206
+ idx = r.rindex(@format.separator)
207
+ next if idx.nil?
208
+ refd = find_by_reference(r)
209
+ next if refd.nil?
210
+ todo.push(refd)
211
+ name2schema[r[(idx + 1)..]] = todo.last
212
+ end
213
+ end
214
+ name2schema
215
+ end
216
+
217
+ # Used when writing to given document.
218
+
219
+ def put_named_schema(name, schema)
220
+ if @format.root_schema?
221
+ # If clear was to be done we'd need to retain "$defs" or such.
222
+ # In intended use, JSON schema document is empty when writing to root.
223
+ @document.merge!(schema)
224
+ else
225
+ path = make_path(@format.named, name)
226
+ OpenAPISourceTools::Common.bury(@document, path, schema)
227
+ end
228
+ end
229
+
230
+ # Call only after put_named_schema.
231
+ def put_referenced_schema(name, schema)
232
+ path = make_path(@format.referenced, name)
233
+ OpenAPISourceTools::Common.bury(@document, path, schema)
234
+ end
235
+
236
+ private
237
+
238
+ def make_path(start, name)
239
+ @format.path(start).push(name)
240
+ end
241
+
242
+ def find_by_reference(ref)
243
+ # Implicitly expects that references always have same start followed by name.
244
+ @document.dig(*@format.path(ref))
245
+ end
246
+
247
+ def gather_refs(schema, found, existing)
248
+ if schema.is_a?(Array)
249
+ schema.each do |v|
250
+ gather_refs(v, found, existing)
251
+ end
252
+ end
253
+ return unless schema.is_a?(Hash)
254
+ schema.each do |key, value|
255
+ if @format.reference?(key, value)
256
+ found.add(value) unless existing.member?(value)
257
+ elsif value.is_a?(Hash) || value.is_a?(Array)
258
+ gather_refs(value, found, existing)
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
@@ -6,7 +6,7 @@
6
6
  # Gem namespace.
7
7
  module OpenAPISourceTools
8
8
  NAME = 'openapi-sourcetools'
9
- VERSION = '0.12.1'
9
+ VERSION = '0.13'
10
10
 
11
11
  def self.info(separator = ': ')
12
12
  "#{NAME}#{separator}#{VERSION}"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi-sourcetools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: '0.13'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismo Kärkkäinen
@@ -48,11 +48,15 @@ executables:
48
48
  - openapi-frequencies
49
49
  - openapi-generate
50
50
  - openapi-generate-checkconfig
51
+ - openapi-logicsplit
51
52
  - openapi-merge
52
53
  - openapi-modifypaths
53
54
  - openapi-order
54
55
  - openapi-patterntests
55
56
  - openapi-processpaths
57
+ - openapi-schema-in
58
+ - openapi-schema-ls
59
+ - openapi-schema-out
56
60
  extensions: []
57
61
  extra_rdoc_files: []
58
62
  files:
@@ -69,11 +73,15 @@ files:
69
73
  - bin/openapi-frequencies
70
74
  - bin/openapi-generate
71
75
  - bin/openapi-generate-checkconfig
76
+ - bin/openapi-logicsplit
72
77
  - bin/openapi-merge
73
78
  - bin/openapi-modifypaths
74
79
  - bin/openapi-order
75
80
  - bin/openapi-patterntests
76
81
  - bin/openapi-processpaths
82
+ - bin/openapi-schema-in
83
+ - bin/openapi-schema-ls
84
+ - bin/openapi-schema-out
77
85
  - lib/openapi/sourcetools.rb
78
86
  - lib/openapi/sourcetools/apiobjects.rb
79
87
  - lib/openapi/sourcetools/common.rb
@@ -85,6 +93,7 @@ files:
85
93
  - lib/openapi/sourcetools/loaders.rb
86
94
  - lib/openapi/sourcetools/order.rb
87
95
  - lib/openapi/sourcetools/output.rb
96
+ - lib/openapi/sourcetools/schemaio.rb
88
97
  - lib/openapi/sourcetools/task.rb
89
98
  - lib/openapi/sourcetools/version.rb
90
99
  homepage: https://xn--ismo-krkkinen-gfbd.fi/openapi-sourcetools/index.html