openapi-sourcetools 0.9.2 → 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: 625fca7f208b4a1e4c041265a1b5aba0d0685bbda6dcdd831af5db701aff4de0
4
- data.tar.gz: bfe98b2ef3fa42ca2edccdf7b57eda9d653637e23a6b6325e41297e00100e1b6
3
+ metadata.gz: 2b790daa9dc40626f360f32ba98853c2b5410e5214d0d2ef32334df1858a9262
4
+ data.tar.gz: 842e1b54d72995112b38f678f3b0f853d9e0e6ea03652d56797715cb215e2c35
5
5
  SHA512:
6
- metadata.gz: 7895833d61f1954c4d6b056e2b350c72d767c7ec031222bcec9f8aa2820836eced6e5432f14465f567967bce674006e699bc65180d73951a1ff63b7d40c5fc57
7
- data.tar.gz: b86546e0d0cf07d19ba082e43f29b4f0c35bef6d05f6e678695f7d821bf38e1e761eaa95ecf10a7c0a00e0a41710fdf088eba13b8c3bd9f4b46a7bac7db49f49
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
 
@@ -1,37 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2024-2025 Ismo Kärkkäinen
4
+ # Copyright © 2024-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  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
- exit(main) if defined?($unit_test).nil?
57
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2024-2025 Ismo Kärkkäinen
4
+ # Copyright © 2024-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
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 = ' '
@@ -87,7 +87,8 @@ replaces the original with reference.
87
87
 
88
88
  Optionally adds parameters to all operations objects by copying parameters
89
89
  from path item and replacing ones referred to in operations object. The
90
- path item parameters are left in place. Even empty arrays are added.
90
+ path item parameters are left in place. Even empty arrays are added. This
91
+ feature is intended to simplify code generation.
91
92
 
92
93
  #{components.help}
93
94
  )
@@ -96,17 +97,17 @@ path item parameters are left in place. Even empty arrays are added.
96
97
  end
97
98
  parser.order!
98
99
 
99
- doc = Common.load_source(input_name)
100
+ doc = OpenAPISourceTools::Common.load_source(input_name)
100
101
  return 2 if doc.nil?
101
102
 
102
103
  components.items = doc.dig(*path) || {}
103
104
  replace_parameters(doc.fetch('paths', {}), components)
104
- Common.bury(doc, path, components.items) unless components.items.empty?
105
+ OpenAPISourceTools::Common.bury(doc, path, components.items) unless components.items.empty?
105
106
  # If nothing has parameters, this adds empty arrays.
106
107
  # Consistency might be desired for code generation templates.
107
108
  add_operations_parameters(doc.fetch('paths', {}), components.items) if add2operations
108
109
 
109
- Common.dump_result(output_name, doc, 3)
110
+ OpenAPISourceTools::Common.dump_result(output_name, doc, 3)
110
111
  end
111
112
 
112
- exit(main) unless defined?($unit_test)
113
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -1,36 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2024-2025 Ismo Kärkkäinen
4
+ # Copyright © 2024-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
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
- 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
- exit(main) if defined?($unit_test).nil?
55
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2024-2025 Ismo Kärkkäinen
4
+ # Copyright © 2024-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
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
- exit(main) if defined?($unit_test).nil?
129
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2025 Ismo Kärkkäinen
4
+ # Copyright © 2025-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/openapi/sourcetools/apiobjects'
@@ -105,4 +105,4 @@ replaces the original with reference.
105
105
  Common.dump_result(output_name, doc, 3)
106
106
  end
107
107
 
108
- exit(main) unless defined?($unit_test)
108
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2024-2025 Ismo Kärkkäinen
4
+ # Copyright © 2024-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/openapi/sourcetools/apiobjects'
@@ -214,4 +214,4 @@ input, as inlined types in requests, for example, are ignored.
214
214
  Common.dump_result(output_name, YAML.dump(report, line_width: 80), 3)
215
215
  end
216
216
 
217
- exit(main) if defined?($unit_test).nil?
217
+ 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)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021-2025 Ismo Kärkkäinen
4
+ # Copyright © 2021-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/openapi/sourcetools/apiobjects'
@@ -73,4 +73,4 @@ into each path object matching count as "x-openapi-sourcetools-freq".
73
73
  Common.dump_result(output_name, doc, 3)
74
74
  end
75
75
 
76
- exit(main) unless defined?($unit_test)
76
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
data/bin/openapi-generate CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021-2025 Ismo Kärkkäinen
4
+ # Copyright © 2021-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/openapi/sourcetools/common'
@@ -58,4 +58,4 @@ After all generators have loaded succesfully, tasks are run.
58
58
  gen.run
59
59
  end
60
60
 
61
- exit(main) unless defined?($unit_test)
61
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
data/bin/openapi-merge CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021-2025 Ismo Kärkkäinen
4
+ # Copyright © 2021-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/openapi/sourcetools/common'
@@ -34,18 +34,23 @@ def add_missing(existing, incoming, field)
34
34
  end
35
35
  end
36
36
 
37
- def add_undefined(merged, incoming, filename, path, max_depths)
37
+ def add_undefined(merged, incoming, filename, path, max_depths, firsts, lasts)
38
38
  incoming.each_pair do |key, value|
39
39
  unless merged.key? key
40
40
  merged[key] = value
41
41
  next
42
42
  end
43
+ next if firsts.member?(key) # When key appears the second or more times.
44
+ if lasts.member?(key) # If last is kept, always copy value.
45
+ merged[key] = value
46
+ next
47
+ end
43
48
  m = merged[key]
44
49
  raise_se("Path #{path_combo(path, false)} merged type #{m.class} differs from type #{value.class} in #{filename}") unless m.instance_of?(value.class)
45
50
  raise_se("Re-definition of #{key} #{path_combo(path)} in #{filename}") if too_deep(path, max_depths)
46
51
  if m.is_a? Hash # paths or similar
47
52
  path.push key
48
- add_undefined(m, value, filename, path, max_depths)
53
+ add_undefined(m, value, filename, path, max_depths, firsts, lasts)
49
54
  path.pop
50
55
  elsif m.is_a? Array
51
56
  case key
@@ -121,6 +126,8 @@ end
121
126
  def main
122
127
  output_file = nil
123
128
  keep = false
129
+ firsts = Set.new
130
+ lasts = Set.new
124
131
 
125
132
  parser = OptionParser.new do |opts|
126
133
  opts.summary_indent = ' '
@@ -137,6 +144,12 @@ def main
137
144
  opts.on('-p', '--prune', 'Prune all unreferenced objects under components (default).') do
138
145
  keep = false
139
146
  end
147
+ opts.on('-f', '--first KEY', 'Keep only the first occurrence of top-level KEY.') do |key|
148
+ firsts.add(key)
149
+ end
150
+ opts.on('-l', '--last KEY', 'Keep only the last occurrence of top-level KEY.') do |key|
151
+ lasts.add(key)
152
+ end
140
153
  opts.on('-h', '--help', 'Print this help and exit.') do
141
154
  $stdout.puts %(#{opts}
142
155
 
@@ -148,20 +161,27 @@ allowed only to append to the merged document, not re-define anything in it.
148
161
  end
149
162
  parser.order!
150
163
 
151
- max_depths = Hash.new(0)
152
- max_depths['openapi'] = 1
153
- max_depths['info'] = 1
154
- max_depths['servers'] = 1
155
- max_depths['paths'] = 2 # Allows get, post, etc. from different files.
156
- max_depths['webhooks'] = 2
157
- max_depths['components'] = 2
158
- max_depths['security'] = 1
159
- max_depths['tags'] = 1
164
+ overlap = firsts & lasts
165
+ unless overlap.empty?
166
+ return Common.aargh("Key can only be in first or last, not both: #{overlap.to_a.sort!.join(' ')}", 1)
167
+ end
168
+
169
+ max_depths = Hash.new(0).merge!({
170
+ 'openapi' => 1,
171
+ 'info' => 1,
172
+ 'servers' => 1,
173
+ 'paths' => 2, # Allows get, post, etc. from different files.
174
+ 'webhooks' => 2,
175
+ 'components' => 2,
176
+ 'security' => 1,
177
+ 'tags' => 1
178
+ })
179
+
160
180
  merged = {}
161
181
  ARGV.each do |filename|
162
182
  s = Common.load_source(filename)
163
183
  return 2 if s.nil?
164
- add_undefined(merged, s, filename, [], max_depths)
184
+ add_undefined(merged, s, filename, [], max_depths, firsts, lasts)
165
185
  rescue StandardError => e
166
186
  return Common.aargh(e.to_s, 4)
167
187
  end
@@ -171,4 +191,4 @@ allowed only to append to the merged document, not re-define anything in it.
171
191
  Common.dump_result(output_file, merged, 3)
172
192
  end
173
193
 
174
- exit(main) if defined?($unit_test).nil?
194
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2024-2025 Ismo Kärkkäinen
4
+ # Copyright © 2024-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/openapi/sourcetools/common'
@@ -121,4 +121,4 @@ STR and PREFIX are expected to be parts of a path surrounded by /.
121
121
  Common.dump_result(output_name, doc, 3)
122
122
  end
123
123
 
124
- exit(main) if defined?($unit_test).nil?
124
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2025 Ismo Kärkkäinen
4
+ # Copyright © 2025-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/openapi/sourcetools/common'
@@ -163,4 +163,4 @@ strings for testing generated code.
163
163
  Common.dump_result(output_name, ex, 3)
164
164
  end
165
165
 
166
- exit(main) if defined?($unit_test).nil?
166
+ exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021-2025 Ismo Kärkkäinen
4
+ # Copyright © 2021-2026 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/openapi/sourcetools/apiobjects'
@@ -70,4 +70,4 @@ key. Checks for paths that may be ambiguous.
70
70
  Common.dump_result(output_name, doc, 3)
71
71
  end
72
72
 
73
- exit(main) unless defined?($unit_test)
73
+ 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?
@@ -251,5 +355,83 @@ module OpenAPISourceTools
251
355
  @servers <=> other.servers
252
356
  end
253
357
  end
358
+
359
+ # Simple holder if security scheme objects.
360
+ class SecurityScheme
361
+ include Comparable
362
+
363
+ attr_reader :name, :scheme
364
+
365
+ def initialize(name, scheme)
366
+ @name = name
367
+ @scheme = scheme
368
+ end
369
+
370
+ def <=>(other)
371
+ @name <=> other.name
372
+ end
373
+ end
374
+
375
+ # Simple holder that determines where parameters are placed.
376
+ class SecurityRequirementObject
377
+ include Comparable
378
+
379
+ attr_reader :reqs, :extras
380
+
381
+ def initialize(reqs, schemes)
382
+ @reqs = reqs
383
+ @extras = {}
384
+ @reqs.each do |name, scopes|
385
+ scheme = schemes[name].scheme
386
+ case scheme['type']
387
+ when 'apiKey'
388
+ if scheme['in'] == 'header'
389
+ h = @extras[:headers] || {}
390
+ h[scheme['name']] = scopes
391
+ @extras[:headers] = h
392
+ elsif scheme['in'] == 'query'
393
+ q = @extras[:query] || {}
394
+ q[scheme['name']] = scopes
395
+ @extras[:query] = q
396
+ end
397
+ when 'http', 'oauth2', 'openIdConnect'
398
+ h = @extras[:headers] || {}
399
+ h['authorization'] = scopes
400
+ @extras[:headers] = h
401
+ else
402
+ raise "Unknown security type: #{scheme['type']} for #{name}"
403
+ end
404
+ end
405
+ end
406
+
407
+ def <=>(other)
408
+ d = @reqs.size <=> other.reqs.size
409
+ return d unless d.zero?
410
+ d = @reqs.keys.sort! <=> other.reqs.keys.sort!
411
+ return d unless d.zero?
412
+ @reqs.keys.sort!.each do |name|
413
+ d = @reqs[name].sort <=> other.reqs[name].sort
414
+ return d unless d.zero?
415
+ end
416
+ 0
417
+ end
418
+ end
419
+
420
+ # Available possibilities.
421
+ class SecurityAlternatives
422
+ include Comparable
423
+
424
+ attr_reader :sros, :schemes
425
+ attr_accessor :set_id
426
+
427
+ def initialize(sros, schemes)
428
+ @sros = sros
429
+ @schemes = schemes
430
+ end
431
+
432
+ def <=>(other)
433
+ @sros.sort <=> other.sros.sort
434
+ end
435
+ end
254
436
  end
255
437
  end
@@ -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
@@ -4,6 +4,7 @@
4
4
  # Copyright © 2024-2025 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
+ require 'fileutils'
7
8
  require_relative 'common'
8
9
  require_relative 'loaders'
9
10
  require_relative 'gen'
@@ -1,21 +1,30 @@
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
+ require 'json'
7
+ require 'yaml'
8
+
6
9
 
7
10
  module OpenAPISourceTools
8
11
  # Output configuration settings for easy storage.
9
12
  # You can have it in configuration and pass hash to initialize.
10
13
  class OutputConfiguration
11
14
  attr_reader :indent_character, :indent_step
12
- attr_reader :tab, :tab_replaces_count
15
+ attr_reader :tab, :tab_replaces_count, :prettified_json
13
16
 
14
17
  def initialize(cfg = {})
15
18
  @indent_character = cfg['indent_character'] || ' '
16
19
  @indent_step = cfg['indent_step'] || 4
17
20
  @tab = cfg['tab'] || "\t"
18
21
  @tab_replaces_count = cfg['tab_replaces_count'] || 0
22
+ @prettified_json = {
23
+ array_nl: "\n",
24
+ object_nl: "\n",
25
+ indent: ' ',
26
+ space: ' '
27
+ }.merge(cfg['prettified_json'] || {})
19
28
  end
20
29
  end
21
30
 
@@ -81,5 +90,17 @@ module OpenAPISourceTools
81
90
  @last_indent = indent
82
91
  indented.join(separator)
83
92
  end
93
+
94
+ def yaml(something)
95
+ YAML.dump(something)
96
+ end
97
+
98
+ def json(something)
99
+ JSON.generate(something)
100
+ end
101
+
102
+ def pretty_json(something)
103
+ JSON.generate(something, @config.prettified_json)
104
+ end
84
105
  end
85
106
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright © 2025 Ismo Kärkkäinen
3
+ # Copyright © 2025-2026 Ismo Kärkkäinen
4
4
  # Licensed under Universal Permissive License. See LICENSE.txt.
5
5
 
6
6
  module OpenAPISourceTools
7
7
  NAME = 'openapi-sourcetools'
8
- VERSION = '0.9.2'
8
+ VERSION = '0.11.0'
9
9
 
10
10
  def self.info(separator = ': ')
11
11
  "#{NAME}#{separator}#{VERSION}"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi-sourcetools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismo Kärkkäinen
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-05-07 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: deep_merge
@@ -43,6 +42,7 @@ executables:
43
42
  - openapi-addschemas
44
43
  - openapi-addsecurityschemes
45
44
  - openapi-checkschemas
45
+ - openapi-clearrefs
46
46
  - openapi-frequencies
47
47
  - openapi-generate
48
48
  - openapi-merge
@@ -59,6 +59,7 @@ files:
59
59
  - bin/openapi-addschemas
60
60
  - bin/openapi-addsecurityschemes
61
61
  - bin/openapi-checkschemas
62
+ - bin/openapi-clearrefs
62
63
  - bin/openapi-frequencies
63
64
  - bin/openapi-generate
64
65
  - bin/openapi-merge
@@ -82,7 +83,6 @@ licenses:
82
83
  - UPL-1.0
83
84
  metadata:
84
85
  rubygems_mfa_required: 'true'
85
- post_install_message:
86
86
  rdoc_options: []
87
87
  require_paths:
88
88
  - lib
@@ -97,8 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
97
  - !ruby/object:Gem::Version
98
98
  version: '0'
99
99
  requirements: []
100
- rubygems_version: 3.4.19
101
- signing_key:
100
+ rubygems_version: 3.6.9
102
101
  specification_version: 4
103
102
  summary: Tools for creating source code from API specification.
104
103
  test_files: []