openapi-sourcetools 0.10.1 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b692fd4b41d1d925fc0a51d94faabe7ee1f5ec8ff26c7ee393e37cafd9b9dffe
4
- data.tar.gz: 19df615a73ec7f006fffd49f1474fb188dbe0f616c49739f0b28c692f7035ec5
3
+ metadata.gz: 2b790daa9dc40626f360f32ba98853c2b5410e5214d0d2ef32334df1858a9262
4
+ data.tar.gz: 842e1b54d72995112b38f678f3b0f853d9e0e6ea03652d56797715cb215e2c35
5
5
  SHA512:
6
- metadata.gz: d52cdb9cbdea83bbb3ff510c0b73f61d47eb13049b74e452f90eb597d5a3f697dd414b8f4ca6dd7215d8ea9ac3fd397864c8589365e2a295aa575f2cb3d18c7e
7
- data.tar.gz: 26b623377edc1c7cbf58721d1bc3a6a649d2bd79098c5b30502f7810678a09f27c6f401452f96020f4ddc8ff2a5546fce40c2904b63ad968421e7563730182ee
6
+ metadata.gz: 5fa1f5ac001251e2d5b56b701bac7d8489efd01fdbbfc2884a0be84c7c5f86dd1a85d1e1cff6120ae09d0d462912711a70ad269d2c5c740c4639a6e495057e78
7
+ data.tar.gz: ec9dc7d99bfcd12387d163a88c7502052c44cc0159c7bd5865d86a17af0511e979224289ec56f7f049400f4280e02fa583498a553791be9f1b762153cac4b903
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2021-2024 Ismo Kärkkäinen
1
+ Copyright (c) 2021-2026 Ismo Kärkkäinen
2
2
 
3
3
  The Universal Permissive License (UPL), Version 1.0
4
4
 
@@ -8,30 +8,14 @@ require_relative '../lib/openapi/sourcetools/apiobjects'
8
8
  require_relative '../lib/openapi/sourcetools/common'
9
9
  require 'optparse'
10
10
  require 'yaml'
11
- include OpenAPISourceTools
12
11
 
13
12
 
14
- def replace_headers(obj, components)
15
- return unless obj.is_a?(Hash)
16
- obj.each do |k, v|
17
- if k == 'headers'
18
- v.keys.sort!.each do |name|
19
- r = v[name]
20
- next unless r.is_a?(Hash) # Could complain.
21
- next if r.key?('$ref')
22
- v[name] = { '$ref' => components.reference(r) }
23
- end
24
- else
25
- replace_headers(v, components)
26
- end
27
- end
28
- end
29
-
30
13
  def main
31
14
  input_name = nil
32
15
  output_name = nil
33
16
  path = %w[components headers]
34
- components = ApiObjects::Components.new(path, 'Header')
17
+ components = OpenAPISourceTools::ApiObjects::Components.new(path, 'Header')
18
+ replacer = OpenAPISourceTools::ApiObjects::ValueSubValueReplacer.new('headers', components)
35
19
 
36
20
  parser = OptionParser.new do |opts|
37
21
  opts.summary_indent = ' '
@@ -59,15 +43,15 @@ replaces the original with reference.
59
43
  end
60
44
  parser.order!
61
45
 
62
- doc = Common.load_source(input_name)
46
+ doc = OpenAPISourceTools::Common.load_source(input_name)
63
47
  return 2 if doc.nil?
64
48
 
65
49
  components.items = doc.dig(*path) || {}
66
- replace_headers(doc.dig('components', 'responses') || {}, components)
67
- replace_headers(doc.fetch('paths', {}), components)
68
- Common.bury(doc, path, components.items) unless components.items.empty?
50
+ replacer.replace(doc.dig('components', 'responses') || {})
51
+ replacer.replace(doc.fetch('paths', {}))
52
+ OpenAPISourceTools::Common.bury(doc, path, components.items) unless components.items.empty?
69
53
 
70
- Common.dump_result(output_name, doc, 3)
54
+ OpenAPISourceTools::Common.dump_result(output_name, doc, 3)
71
55
  end
72
56
 
73
57
  exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -7,12 +7,12 @@
7
7
  require_relative '../lib/openapi/sourcetools/apiobjects'
8
8
  require_relative '../lib/openapi/sourcetools/common'
9
9
  require 'optparse'
10
- include OpenAPISourceTools
10
+
11
11
 
12
12
  def replace_parameter(p, components)
13
13
  return p unless p.is_a?(Hash) # Could complain.
14
14
  return p if p.key?('$ref')
15
- { '$ref' => components.reference(p) }
15
+ components.to_reference_object(p)
16
16
  end
17
17
 
18
18
  def replace_parameters(obj, components)
@@ -46,7 +46,7 @@ def add_operations_parameters(paths, referenced)
46
46
  # Below that are operations objects.
47
47
  paths.each_value do |item|
48
48
  top_nameloc2ref = nameloc2ref(item['parameters'] || [], referenced)
49
- oos = ApiObjects.operation_objects(item)
49
+ oos = OpenAPISourceTools::ApiObjects.operation_objects(item)
50
50
  oos.each_value do |operation|
51
51
  nl2r = nameloc2ref(operation['parameters'] || [], referenced)
52
52
  params = top_nameloc2ref.merge(nl2r)
@@ -61,7 +61,7 @@ def main
61
61
  output_name = nil
62
62
  add2operations = false
63
63
  path = %w[components parameters]
64
- components = ApiObjects::Components.new(path, 'Parameter')
64
+ components = OpenAPISourceTools::ApiObjects::Components.new(path, 'Parameter')
65
65
 
66
66
  parser = OptionParser.new do |opts|
67
67
  opts.summary_indent = ' '
@@ -97,17 +97,17 @@ feature is intended to simplify code generation.
97
97
  end
98
98
  parser.order!
99
99
 
100
- doc = Common.load_source(input_name)
100
+ doc = OpenAPISourceTools::Common.load_source(input_name)
101
101
  return 2 if doc.nil?
102
102
 
103
103
  components.items = doc.dig(*path) || {}
104
104
  replace_parameters(doc.fetch('paths', {}), components)
105
- Common.bury(doc, path, components.items) unless components.items.empty?
105
+ OpenAPISourceTools::Common.bury(doc, path, components.items) unless components.items.empty?
106
106
  # If nothing has parameters, this adds empty arrays.
107
107
  # Consistency might be desired for code generation templates.
108
108
  add_operations_parameters(doc.fetch('paths', {}), components.items) if add2operations
109
109
 
110
- Common.dump_result(output_name, doc, 3)
110
+ OpenAPISourceTools::Common.dump_result(output_name, doc, 3)
111
111
  end
112
112
 
113
113
  exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -7,30 +7,14 @@
7
7
  require_relative '../lib/openapi/sourcetools/apiobjects'
8
8
  require_relative '../lib/openapi/sourcetools/common'
9
9
  require 'optparse'
10
- include OpenAPISourceTools
11
10
 
12
11
 
13
- def replace_responses(obj, components)
14
- return unless obj.is_a?(Hash)
15
- obj.each do |k, v|
16
- if k == 'responses'
17
- v.keys.sort!.each do |code|
18
- r = v[code]
19
- next unless r.is_a?(Hash) # Could complain.
20
- next if r.key?('$ref')
21
- v[code] = { '$ref' => components.reference(r) }
22
- end
23
- else
24
- replace_responses(v, components)
25
- end
26
- end
27
- end
28
-
29
12
  def main
30
13
  input_name = nil
31
14
  output_name = nil
32
15
  path = %w[components responses]
33
- components = ApiObjects::Components.new(path, 'Response')
16
+ components = OpenAPISourceTools::ApiObjects::Components.new(path, 'Response')
17
+ replacer = OpenAPISourceTools::ApiObjects::ValueSubValueReplacer.new('responses', components)
34
18
 
35
19
  parser = OptionParser.new do |opts|
36
20
  opts.summary_indent = ' '
@@ -58,14 +42,14 @@ replaces the original with reference.
58
42
  end
59
43
  parser.order!
60
44
 
61
- doc = Common.load_source(input_name)
45
+ doc = OpenAPISourceTools::Common.load_source(input_name)
62
46
  return 2 if doc.nil?
63
47
 
64
48
  components.items = doc.dig(*path) || {}
65
- replace_responses(doc.fetch('paths', {}), components)
66
- Common.bury(doc, path, components.items) unless components.items.empty?
49
+ replacer.replace(doc.fetch('paths', {}))
50
+ OpenAPISourceTools::Common.bury(doc, path, components.items) unless components.items.empty?
67
51
 
68
- Common.dump_result(output_name, doc, 3)
52
+ OpenAPISourceTools::Common.dump_result(output_name, doc, 3)
69
53
  end
70
54
 
71
55
  exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -7,7 +7,6 @@
7
7
  require_relative '../lib/openapi/sourcetools/apiobjects'
8
8
  require_relative '../lib/openapi/sourcetools/common'
9
9
  require 'optparse'
10
- include OpenAPISourceTools
11
10
 
12
11
 
13
12
  def remove_subitem(obj, path)
@@ -27,7 +26,6 @@ def replace_inlines(obj, components, top_level_name = nil)
27
26
  return true unless obj.is_a?(Hash)
28
27
  if obj.key?('$ref')
29
28
  components.store_anchor(obj)
30
- # Here would be the place to get rid of other keys if so desired.
31
29
  return true
32
30
  end
33
31
  # Is inlined, process parts recursively.
@@ -58,7 +56,7 @@ def replace_inlines(obj, components, top_level_name = nil)
58
56
  end
59
57
  r = components.ref_string(top_level_name) || components.reference(obj)
60
58
  components.store_anchor(obj, r)
61
- obj.replace({ '$ref' => r }) if top_level_name.nil?
59
+ components.to_reference_object(obj, r) if top_level_name.nil?
62
60
  true
63
61
  end
64
62
 
@@ -82,7 +80,7 @@ def main
82
80
  input_name = nil
83
81
  output_name = nil
84
82
  path = %w[components schemas]
85
- components = ApiObjects::Components.new(path, 'Schema')
83
+ components = OpenAPISourceTools::ApiObjects::Components.new(path, 'Schema')
86
84
 
87
85
  parser = OptionParser.new do |opts|
88
86
  opts.summary_indent = ' '
@@ -109,7 +107,7 @@ Loads API document in OpenAPI format and adds a schema for each inline type.
109
107
  end
110
108
  parser.order!
111
109
 
112
- doc = Common.load_source(input_name)
110
+ doc = OpenAPISourceTools::Common.load_source(input_name)
113
111
  return 2 if doc.nil?
114
112
 
115
113
  # Find schema object and remove it temporarily to prevent being
@@ -122,10 +120,10 @@ Loads API document in OpenAPI format and adds a schema for each inline type.
122
120
  end
123
121
  return 4 unless replace_inlines(doc, components)
124
122
  components.alter_anchors
125
- Common.bury(doc, path, components.items) unless components.items.empty?
123
+ OpenAPISourceTools::Common.bury(doc, path, components.items) unless components.items.empty?
126
124
  replace_anchor_refs(doc, components)
127
125
 
128
- Common.dump_result(output_name, doc, 3)
126
+ OpenAPISourceTools::Common.dump_result(output_name, doc, 3)
129
127
  end
130
128
 
131
129
  exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -0,0 +1,46 @@
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
+ require 'yaml'
11
+
12
+
13
+ def main
14
+ input_name = nil
15
+ output_name = nil
16
+ retainer = OpenAPISourceTools::ApiObjects::ValueRetainer.new('$ref')
17
+
18
+ parser = OptionParser.new do |opts|
19
+ opts.summary_indent = ' '
20
+ opts.summary_width = 26
21
+ opts.banner = 'Usage: openapi-clearrefs [options]'
22
+ opts.separator ''
23
+ opts.separator 'Options:'
24
+ opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
25
+ input_name = f
26
+ end
27
+ opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
28
+ output_name = f
29
+ end
30
+ opts.on('-h', '--help', 'Print this help and exit.') do
31
+ $stdout.puts %(#{opts}
32
+ Loads API document in OpenAPI format and from objects with '$ref' key, deletes
33
+ all other keys.
34
+ )
35
+ exit 0
36
+ end
37
+ end
38
+ parser.order!
39
+
40
+ doc = OpenAPISourceTools::Common.load_source(input_name)
41
+ return 2 if doc.nil?
42
+ retainer.process(doc)
43
+ OpenAPISourceTools::Common.dump_result(output_name, doc, 3)
44
+ end
45
+
46
+ 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?
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright © 2021-2025 Ismo Kärkkäinen
3
+ # Copyright © 2021-2026 Ismo Kärkkäinen
4
4
  # Licensed under Universal Permissive License. See LICENSE.txt.
5
5
 
6
6
  require 'pathname'
@@ -56,6 +56,19 @@ module OpenAPISourceTools
56
56
  parts
57
57
  end
58
58
 
59
+ # Occasionally saved YAML has aliases. This is added to avoid them.
60
+ def self.ensure_separate(obj)
61
+ if obj.is_a?(Array)
62
+ return obj.map { |v| ensure_separate(v) }
63
+ end
64
+ return obj unless obj.is_a?(Hash)
65
+ out = {}
66
+ obj.each do |key, value|
67
+ out[key] = ensure_separate(value)
68
+ end
69
+ out
70
+ end
71
+
59
72
  def self.load_source(input)
60
73
  YAML.safe_load(input.nil? ? $stdin : File.read(input))
61
74
  rescue Errno::ENOENT
@@ -65,7 +78,9 @@ module OpenAPISourceTools
65
78
  end
66
79
 
67
80
  def self.dump_result(output, doc, error_return)
68
- doc = YAML.dump(doc, line_width: 1_000_000) unless doc.is_a?(String)
81
+ # Safe load did not allow aliases, but saved output may have them.
82
+ # Observed with empty object or array.
83
+ doc = YAML.dump(ensure_separate(doc), line_width: 1_000_000) unless doc.is_a?(String)
69
84
  if output.nil?
70
85
  $stdout.puts doc
71
86
  else
@@ -5,7 +5,7 @@
5
5
 
6
6
  module OpenAPISourceTools
7
7
  NAME = 'openapi-sourcetools'
8
- VERSION = '0.10.1'
8
+ VERSION = '0.11.0'
9
9
 
10
10
  def self.info(separator = ': ')
11
11
  "#{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.10.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismo Kärkkäinen
@@ -42,6 +42,7 @@ executables:
42
42
  - openapi-addschemas
43
43
  - openapi-addsecurityschemes
44
44
  - openapi-checkschemas
45
+ - openapi-clearrefs
45
46
  - openapi-frequencies
46
47
  - openapi-generate
47
48
  - openapi-merge
@@ -58,6 +59,7 @@ files:
58
59
  - bin/openapi-addschemas
59
60
  - bin/openapi-addsecurityschemes
60
61
  - bin/openapi-checkschemas
62
+ - bin/openapi-clearrefs
61
63
  - bin/openapi-frequencies
62
64
  - bin/openapi-generate
63
65
  - bin/openapi-merge