openapi-sourcetools 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9320a1d6d4202c453fb7065f00bc534206b1c780560ae443cd30293f27827595
4
- data.tar.gz: af45eabe9a8f392c705398957cf276acb39923655207ffda12ee8d93e1c072a0
3
+ metadata.gz: 71267174b8f95f5a510b93590f8fcdb8daf2737df19db6d2d7ebf48410444cf8
4
+ data.tar.gz: cf682ab07dcc557c9f3a2b9785f46aded117c4b50d840a6e8dd0b31e80840a63
5
5
  SHA512:
6
- metadata.gz: 1f81cae3adb06d42d3bf5361615a0a21ca42346d08b1f8b83462116668d2ad61ec05ac707e723d910bbe55eb29699129ba13570b44504581e92597242ab3d810
7
- data.tar.gz: 656959f866321cd68814fed2ef1c9121fe1db36674cb7fbd49f9f2a652e5f43a29d6b1d491a2197278577084d1328e01f99fd1eff8fca1c7ef7d6299ed512b17
6
+ metadata.gz: efe4f67e9903c4b65e404fe349e668ff659e538ddff7bc7e28a59bd7f98167e16faabb4876d5b45e1142562ad6e6c4e22c7471e1f374c3debc4e204855a7e169
7
+ data.tar.gz: 3ab4a1cf50426d6405cb1281f4c159d43480ead4f11414f8642b6e0a5d7cb4eca4f3769fb43cff4c5f1884ef5a72d88fdcc9b9c09bbdeb80f4624b008cfba059
@@ -32,7 +32,6 @@ def main
32
32
  path = %w[components headers]
33
33
  components = Components.new(path, 'Header')
34
34
 
35
- ENV['POSIXLY_CORRECT'] = '1'
36
35
  parser = OptionParser.new do |opts|
37
36
  opts.summary_indent = ' '
38
37
  opts.summary_width = 26
@@ -42,7 +41,7 @@ def main
42
41
  opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
43
42
  input_name = f
44
43
  end
45
- opts.on('-o', '--output FILE', 'Output to FILE, not stdout .') do |f|
44
+ opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
46
45
  output_name = f
47
46
  end
48
47
  components.add_options(opts)
@@ -57,7 +56,7 @@ replaces the original with reference.
57
56
  exit 0
58
57
  end
59
58
  end
60
- parser.parse!
59
+ parser.order!
61
60
 
62
61
  doc = load_source(input_name)
63
62
  return 2 if doc.nil?
@@ -67,7 +66,7 @@ replaces the original with reference.
67
66
  replace_headers(doc.fetch('paths', {}), components)
68
67
  bury(doc, path, components.items) unless components.items.empty?
69
68
 
70
- dump_result(output_name, YAML.dump(doc, line_width: 1_000_000), 3)
69
+ dump_result(output_name, doc, 3)
71
70
  end
72
71
 
73
- exit(main) if (defined? $unit_test).nil?
72
+ exit(main) if defined?($unit_test).nil?
@@ -32,7 +32,6 @@ def main
32
32
  path = %w[components parameters]
33
33
  components = Components.new(path, 'Parameter')
34
34
 
35
- ENV['POSIXLY_CORRECT'] = '1'
36
35
  parser = OptionParser.new do |opts|
37
36
  opts.summary_indent = ' '
38
37
  opts.summary_width = 26
@@ -42,7 +41,7 @@ def main
42
41
  opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
43
42
  input_name = f
44
43
  end
45
- opts.on('-o', '--output FILE', 'Output to FILE, not stdout .') do |f|
44
+ opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
46
45
  output_name = f
47
46
  end
48
47
  components.add_options(opts)
@@ -57,7 +56,7 @@ replaces the original with reference.
57
56
  exit 0
58
57
  end
59
58
  end
60
- parser.parse!
59
+ parser.order!
61
60
 
62
61
  doc = load_source(input_name)
63
62
  return 2 if doc.nil?
@@ -66,7 +65,7 @@ replaces the original with reference.
66
65
  replace_parameters(doc.fetch('paths', {}), components)
67
66
  bury(doc, path, components.items) unless components.items.empty?
68
67
 
69
- dump_result(output_name, YAML.dump(doc, line_width: 1_000_000), 3)
68
+ dump_result(output_name, doc, 3)
70
69
  end
71
70
 
72
71
  exit(main) if defined?($unit_test).nil?
@@ -32,7 +32,6 @@ def main
32
32
  path = %w[components responses]
33
33
  components = Components.new(path, 'Response')
34
34
 
35
- ENV['POSIXLY_CORRECT'] = '1'
36
35
  parser = OptionParser.new do |opts|
37
36
  opts.summary_indent = ' '
38
37
  opts.summary_width = 26
@@ -42,7 +41,7 @@ def main
42
41
  opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
43
42
  input_name = f
44
43
  end
45
- opts.on('-o', '--output FILE', 'Output to FILE, not stdout .') do |f|
44
+ opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
46
45
  output_name = f
47
46
  end
48
47
  components.add_options(opts)
@@ -57,7 +56,7 @@ replaces the original with reference.
57
56
  exit 0
58
57
  end
59
58
  end
60
- parser.parse!
59
+ parser.order!
61
60
 
62
61
  doc = load_source(input_name)
63
62
  return 2 if doc.nil?
@@ -66,7 +65,7 @@ replaces the original with reference.
66
65
  replace_responses(doc.fetch('paths', {}), components)
67
66
  bury(doc, path, components.items) unless components.items.empty?
68
67
 
69
- dump_result(output_name, YAML.dump(doc, line_width: 1_000_000), 3)
68
+ dump_result(output_name, doc, 3)
70
69
  end
71
70
 
72
- exit(main) if (defined? $unit_test).nil?
71
+ exit(main) if defined?($unit_test).nil?
@@ -17,7 +17,7 @@ def remove_subitem(obj, path)
17
17
  parent.delete(path.last) || {}
18
18
  end
19
19
 
20
- def replace_inlines(obj, components, top_level = false)
20
+ def replace_inlines(obj, components, top_level_name = nil)
21
21
  if obj.is_a?(Array)
22
22
  obj.each do |o|
23
23
  return false unless replace_inlines(o, components)
@@ -25,8 +25,11 @@ def replace_inlines(obj, components, top_level = false)
25
25
  return true
26
26
  end
27
27
  return true unless obj.is_a?(Hash)
28
- return true if obj.key?('$ref')
29
- # Prior return would be the place to get rid of other keys if so desired.
28
+ if obj.key?('$ref')
29
+ components.store_anchor(obj)
30
+ # Here would be the place to get rid of other keys if so desired.
31
+ return true
32
+ end
30
33
  # Is inlined, process parts recursively.
31
34
  items = obj['allOf']
32
35
  items = obj['anyOf'] if items.nil?
@@ -49,17 +52,34 @@ def replace_inlines(obj, components, top_level = false)
49
52
  return false unless replace_inlines(props[name], components)
50
53
  end
51
54
  end
52
- obj.replace({ '$ref' => components.reference(obj) }) unless top_level
55
+ r = components.ref_string(top_level_name) || components.reference(obj)
56
+ components.store_anchor(obj, r)
57
+ obj.replace({ '$ref' => r }) if top_level_name.nil?
53
58
  true
54
59
  end
55
60
 
61
+ def replace_anchor_refs(obj, components)
62
+ if obj.is_a?(Array)
63
+ obj.each do |o|
64
+ replace_anchor_refs(o, components)
65
+ end
66
+ return
67
+ end
68
+ return unless obj.is_a?(Hash)
69
+ r = obj['$ref']
70
+ obj['$ref'] = components.anchor_ref_replacement(r) unless r.nil?
71
+ obj.values.each do |value|
72
+ # Loops over $ref value but that is a string so nothing done to it.
73
+ replace_anchor_refs(value, components)
74
+ end
75
+ end
76
+
56
77
  def main
57
78
  input_name = nil
58
79
  output_name = nil
59
80
  path = %w[components schemas]
60
81
  components = Components.new(path, 'Schema')
61
82
 
62
- ENV['POSIXLY_CORRECT'] = '1'
63
83
  parser = OptionParser.new do |opts|
64
84
  opts.summary_indent = ' '
65
85
  opts.summary_width = 26
@@ -69,7 +89,7 @@ def main
69
89
  opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
70
90
  input_name = f
71
91
  end
72
- opts.on('-o', '--output FILE', 'Output to FILE, not stdout .') do |f|
92
+ opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
73
93
  output_name = f
74
94
  end
75
95
  components.add_options(opts)
@@ -83,7 +103,7 @@ Loads API document in OpenAPI format and adds a schema for each inline type.
83
103
  exit 0
84
104
  end
85
105
  end
86
- parser.parse!
106
+ parser.order!
87
107
 
88
108
  doc = load_source(input_name)
89
109
  return 2 if doc.nil?
@@ -93,12 +113,15 @@ Loads API document in OpenAPI format and adds a schema for each inline type.
93
113
  components.items = remove_subitem(doc, path)
94
114
  # Get rid of inlined types within existing schemas first.
95
115
  components.items.keys.sort!.each do |k|
96
- return 4 unless replace_inlines(components.items[k], components, true)
116
+ components.add_schema_name(k)
117
+ return 4 unless replace_inlines(components.items[k], components, k)
97
118
  end
98
119
  return 4 unless replace_inlines(doc, components)
120
+ components.alter_anchors
99
121
  bury(doc, path, components.items) unless components.items.empty?
122
+ replace_anchor_refs(doc, components)
100
123
 
101
- dump_result(output_name, YAML.dump(doc, line_width: 1_000_000), 3)
124
+ dump_result(output_name, doc, 3)
102
125
  end
103
126
 
104
- exit(main) if (defined? $unit_test).nil?
127
+ exit(main) if defined?($unit_test).nil?
@@ -142,7 +142,6 @@ def main
142
142
  count = true
143
143
  keep = false
144
144
 
145
- ENV['POSIXLY_CORRECT'] = '1'
146
145
  parser = OptionParser.new do |opts|
147
146
  opts.summary_indent = ' '
148
147
  opts.summary_width = 26
@@ -152,22 +151,22 @@ def main
152
151
  opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
153
152
  input_name = f
154
153
  end
155
- opts.on('-o', '--output FILE', 'Output to FILE, not stdout .') do |f|
154
+ opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
156
155
  output_name = f
157
156
  end
158
- opts.on('-e', '--[no-]equivalent', "Report equivalent schemas, default #{equivalent}") do |b|
157
+ opts.on('-e', '--[no-]equivalent', "Report equivalent schemas, default #{yesno(equivalent)}") do |b|
159
158
  equivalent = b
160
159
  end
161
- opts.on('-t', '--[no-]typematch', "Report typematch schemas, default #{typematch}") do |b|
160
+ opts.on('-t', '--[no-]typematch', "Report typematch schemas, default #{yesno(typematch)}") do |b|
162
161
  typematch = b
163
162
  end
164
- opts.on('-r', '--[no-]reference', "Report schema references, default #{references}") do |b|
163
+ opts.on('-r', '--[no-]reference', "Report schema references, default #{yesno(references)}") do |b|
165
164
  references = b
166
165
  end
167
- opts.on('-c', '--[no-]count', "Report schema reference counts, default #{count}") do |b|
166
+ opts.on('-c', '--[no-]count', "Report schema reference counts, default #{yesno(count)}") do |b|
168
167
  count = b
169
168
  end
170
- opts.on('-k', '--[no-]keep', "Keep all schema references/counts, default #{keep}") do |b|
169
+ opts.on('-k', '--[no-]keep', "Keep all schema references/counts, default #{yesno(keep)}") do |b|
171
170
  keep = b
172
171
  end
173
172
  components.add_options(opts)
@@ -182,17 +181,17 @@ names differ but types match.
182
181
 
183
182
  Search is performed only at top schema level. References to equivalent types
184
183
  are not considered equivalent when references themselves are not equivalent.
185
- Any allOf, anyOf, oneOf checks merely check the refernces. Hence two different
184
+ Any allOf, anyOf, oneOf checks merely check the references. Hence two different
186
185
  allOf schemas may in practice result in equivalent types and that is not
187
186
  detected.
188
187
 
189
188
  Implicit expectation is the openapi-addschemas has been used to process the
190
- input, as inlined types in requests for example are ignored.
189
+ input, as inlined types in requests, for example, are ignored.
191
190
  )
192
191
  exit 0
193
192
  end
194
193
  end
195
- parser.parse!
194
+ parser.order!
196
195
 
197
196
  doc = load_source(input_name)
198
197
  return 2 if doc.nil?
@@ -214,4 +213,4 @@ input, as inlined types in requests for example are ignored.
214
213
  dump_result(output_name, YAML.dump(report, line_width: 80), 3)
215
214
  end
216
215
 
217
- exit(main) if (defined? $unit_test).nil?
216
+ exit(main) if defined?($unit_test).nil?
@@ -37,7 +37,7 @@ into each path object matching count as "freq".
37
37
  exit 0
38
38
  end
39
39
  end
40
- parser.parse!
40
+ parser.order!
41
41
 
42
42
  return aargh('Path log file name must be given.', 1) if paths_name.nil?
43
43
 
@@ -75,4 +75,4 @@ into each path object matching count as "freq".
75
75
  dump_result(output_name, YAML.dump(doc), 3)
76
76
  end
77
77
 
78
- exit(main) if (defined? $unit_test).nil?
78
+ exit(main) if defined?($unit_test).nil?
data/bin/openapi-generate CHANGED
@@ -16,7 +16,6 @@ def main
16
16
  input_name = nil
17
17
  output_dir = nil
18
18
 
19
- ENV['POSIXLY_CORRECT'] = '1'
20
19
  parser = OptionParser.new do |opts|
21
20
  opts.summary_indent = ' '
22
21
  opts.summary_width = 26
@@ -32,7 +31,7 @@ def main
32
31
  opts.on('-h', '--help', 'Print this help and exit.') do
33
32
  $stdout.puts %(#{opts}
34
33
  Loads API document in OpenAPI format and generator names. Built-in generator
35
- loaders accept the following:
34
+ or additional document loaders accept the following:
36
35
  #{Loaders.document.strip}
37
36
 
38
37
  During load each generator can add and modify tasks via Gen module:
@@ -43,7 +42,7 @@ After all generators have loaded succesfully, tasks are run.
43
42
  exit 0
44
43
  end
45
44
  end
46
- parser.parse!
45
+ parser.order!
47
46
 
48
47
  return aargh('Generator names must be given.', 1) if ARGV.empty?
49
48
 
data/bin/openapi-merge CHANGED
@@ -52,7 +52,7 @@ end
52
52
 
53
53
  def gather_refs(doc, found)
54
54
  doc.each_pair do |key, value|
55
- if key == '$ref' && value.is_a?(String) && value.start_with?('#/components/')
55
+ if key == '$ref' # Trust all refs to be valid.
56
56
  found.add(value)
57
57
  elsif value.is_a? Hash
58
58
  gather_refs(value, found)
@@ -64,6 +64,13 @@ def gather_refs(doc, found)
64
64
  end
65
65
  end
66
66
 
67
+ def has_refd_anchor(item, refs)
68
+ return !((item.index { |v| has_refd_anchor(v, refs) }).nil?) if item.is_a?(Array)
69
+ return false unless item.is_a?(Hash)
70
+ return true if refs.member?('#' + item.fetch('$anchor', ''))
71
+ !((item.values.index { |v| has_refd_anchor(v, refs) }).nil?)
72
+ end
73
+
67
74
  def prune(merged)
68
75
  prev_refs = Set.new
69
76
  loop do # May have references from deleted so repeat until nothing deleted.
@@ -74,16 +81,16 @@ def prune(merged)
74
81
  refs.add("#/components/securitySchemes/#{key}")
75
82
  end
76
83
  end
84
+ # Add schema ref for all schemas that have referenced anchor somewhere.
85
+ (merged.dig(*%w[components schemas]) || {}).each do |name, schema|
86
+ refs.add("#/components/Schemas/#{name}") if has_refd_anchor(schema, refs)
87
+ end
77
88
  used = {}
78
89
  all = merged.fetch('components', {})
79
90
  refs.each do |ref|
80
91
  p = ref.split('/')
81
92
  p.shift(2)
82
- item = all
83
- p.each do |key|
84
- item = item.fetch(key, nil)
85
- break if item.nil?
86
- end
93
+ item = all.dig(*p)
87
94
  next if item.nil?
88
95
  sub = used
89
96
  p.each_index do |k|
@@ -105,7 +112,6 @@ def main
105
112
  output_file = nil
106
113
  keep = false
107
114
 
108
- ENV['POSIXLY_CORRECT'] = '1'
109
115
  parser = OptionParser.new do |opts|
110
116
  opts.summary_indent = ' '
111
117
  opts.summary_width = 26
@@ -130,7 +136,7 @@ allowed only to append to the merged document, not re-define anything in it.
130
136
  exit 0
131
137
  end
132
138
  end
133
- parser.parse!
139
+ parser.order!
134
140
 
135
141
  max_depths = Hash.new(0)
136
142
  max_depths['openapi'] = 1
@@ -155,4 +161,4 @@ allowed only to append to the merged document, not re-define anything in it.
155
161
  dump_result(output_file, YAML.dump(merged), 3)
156
162
  end
157
163
 
158
- exit(main) if (defined? $unit_test).nil?
164
+ exit(main) if defined?($unit_test).nil?
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2024 Ismo Kärkkäinen
5
+ # Licensed under Universal Permissive License. See LICENSE.txt.
6
+
7
+ require_relative '../lib/common'
8
+ require 'optparse'
9
+ require 'yaml'
10
+
11
+ def path2pieces(s)
12
+ s.split('/').reject { |p| p.empty? }
13
+ end
14
+
15
+ def pieces2path(p)
16
+ "/#{p.join('/')}"
17
+ end
18
+
19
+ def add(s, path)
20
+ s.empty? ? path : (s + path)
21
+ end
22
+
23
+ def remove_complete_prefix(prefix, path)
24
+ return nil if prefix.empty? || path.size < prefix.size
25
+ prefix.size.times do |idx|
26
+ return nil unless prefix[idx] == path[idx]
27
+ end
28
+ path[prefix.size...path.size]
29
+ end
30
+
31
+ def remove(s, path)
32
+ remove_complete_prefix(s, path) || path
33
+ end
34
+
35
+ def replace(o, s, path)
36
+ p = remove_complete_prefix(o, path)
37
+ p.nil? ? path : (s + p)
38
+ end
39
+
40
+ def add_op(s)
41
+ s = path2pieces(s)
42
+ Proc.new { |path| add(s, path) }
43
+ end
44
+
45
+ def remove_op(s)
46
+ s = path2pieces(s)
47
+ Proc.new { |path| remove(s, path) }
48
+ end
49
+
50
+ def replace_op(orig, s)
51
+ o = path2pieces(orig)
52
+ s = path2pieces(s)
53
+ Proc.new { |path| replace(o, s, path) }
54
+ end
55
+
56
+ def perform_operations(paths, operations)
57
+ out = {}
58
+ paths.each do |path, value|
59
+ operations.each do |op|
60
+ path = pieces2path(op.call(path2pieces(path)))
61
+ end
62
+ out[path] = value
63
+ end
64
+ out
65
+ end
66
+
67
+ def main
68
+ input_name = nil
69
+ output_name = nil
70
+ operations = []
71
+ orig = nil
72
+
73
+ parser = OptionParser.new do |opts|
74
+ opts.summary_indent = ' '
75
+ opts.summary_width = 26
76
+ opts.banner = 'Usage: openapi-modifypaths [options]'
77
+ opts.separator ''
78
+ opts.separator 'Options:'
79
+ opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
80
+ exit(aargh("Expected string to replace PREFIX.", 1)) unless orig.nil?
81
+ input_name = f
82
+ end
83
+ opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
84
+ exit(aargh("Expected string to replace PREFIX.", 1)) unless orig.nil?
85
+ output_name = f
86
+ end
87
+ opts.on('-a', '--add STR', 'Add prefix STR to all paths.') do |s|
88
+ exit(aargh("Expected string to replace PREFIX.", 1)) unless orig.nil?
89
+ operations.push(add_op(s))
90
+ end
91
+ opts.on('-d', '--delete PREFIX', 'Delete PREFIX when present.') do |s|
92
+ exit(aargh("Expected string to replace PREFIX.", 1)) unless orig.nil?
93
+ operations.push(remove_op(s))
94
+ end
95
+ opts.on('-r', '--replace PREFIX STR', 'Replace PREFIX with STR when present.') do |s|
96
+ exit(aargh('Empty string to replace.', 1)) if s.empty?
97
+ orig = s
98
+ end
99
+ opts.on('-h', '--help', 'Print this help and exit.') do
100
+ $stdout.puts %(#{opts}
101
+
102
+ Loads API document in OpenAPI format and changes paths according to options.
103
+ STR and PREFIX are expected to be parts of a path surrounded by /.
104
+ )
105
+ exit 0
106
+ end
107
+ end
108
+ parser.order! do |s|
109
+ exit(aargh("String without option: #{s}", 1)) if orig.nil?
110
+ operations.push(replace_op(orig, s))
111
+ orig = nil
112
+ end
113
+
114
+ doc = load_source(input_name)
115
+ return 2 if doc.nil?
116
+
117
+ p = perform_operations(doc.fetch('paths', {}), operations)
118
+ doc['paths'] = p unless p.empty?
119
+
120
+ dump_result(output_name, doc, 3)
121
+ end
122
+
123
+ exit(main) if defined?($unit_test).nil?
@@ -38,7 +38,7 @@ later stage tools. Checks for paths that may be ambiguous.
38
38
  exit 0
39
39
  end
40
40
  end
41
- parser.parse!
41
+ parser.order!
42
42
 
43
43
  doc = load_source(input_name)
44
44
  return 2 if doc.nil?
@@ -81,4 +81,4 @@ later stage tools. Checks for paths that may be ambiguous.
81
81
  dump_result(output_name, YAML.dump(doc), 3)
82
82
  end
83
83
 
84
- exit(main) if (defined? $unit_test).nil?
84
+ exit(main) if defined?($unit_test).nil?
data/lib/apiobjects.rb CHANGED
@@ -33,15 +33,17 @@ def reference(obj, schemas, schema_path, ignored_keys = Set.new(%w[summary descr
33
33
  end
34
34
 
35
35
  class Components
36
- attr_reader :path, :prefix
36
+ attr_reader :path, :prefix, :anchor2ref, :schema_names
37
37
  attr_accessor :items, :ignored_keys
38
38
 
39
- def initialize(path, prefix, ignored_keys = %w[summary description examples example])
40
- @items = {}
39
+ def initialize(path, prefix, ignored_keys = %w[summary description examples example $anchor])
41
40
  path = "#/#{path.join('/')}/" if path.is_a?(Array)
42
41
  path = "#{path}/" unless path.end_with?('/')
43
42
  @path = path
44
43
  @prefix = prefix
44
+ @anchor2ref = {}
45
+ @schema_names = Set.new
46
+ @items = {}
45
47
  @ignored_keys = Set.new(ignored_keys)
46
48
  end
47
49
 
@@ -58,7 +60,12 @@ class Components
58
60
  %(All fields are used in object equality comparisons except:\n#{@ignored_keys.to_a.sort!.join("\n")})
59
61
  end
60
62
 
63
+ def add_schema_name(name)
64
+ @schema_names.add(name)
65
+ end
66
+
61
67
  def ref_string(name)
68
+ return nil if name.nil?
62
69
  "#{@path}#{name}"
63
70
  end
64
71
 
@@ -73,42 +80,39 @@ class Components
73
80
  cand = "#{@prefix}#{n}x"
74
81
  next if @items.key?(cand)
75
82
  @items[cand] = obj.merge
83
+ @schema_names.add(cand)
76
84
  return ref_string(cand)
77
85
  end
78
86
  end
79
- end
80
-
81
-
82
- class PathOperation
83
- attr_accessor :path, :operation, :info, :parameters
84
- attr_accessor :servers, :security, :tags
85
- attr_accessor :summary, :description
86
- end
87
87
 
88
- # One could have a convenience
89
- # method that determines how many bytes the value needs, and if it needs to be
90
- # signed.
91
-
92
- # When creating types for schemas or otherwise, the type name can be added
93
- # into the item and that way be used as an indicator that the type has been
94
- # declared or needs a declaration.
88
+ def store_anchor(obj, ref = nil)
89
+ anchor_name = obj['$anchor']
90
+ return if anchor_name.nil?
91
+ ref = obj['$ref'] if ref.nil?
92
+ raise Exception, 'ref is nil and no $ref or it is nil' if ref.nil?
93
+ @anchor2ref[anchor_name] = ref
94
+ end
95
95
 
96
- def make_path_operations(apidoc)
97
- # Check openapi
98
- # Store info as is for reference
99
- # Store servers as is for default value for PathOperation
100
- # Process components. Lazy manner, only when referenced.
101
- # Store security as is for default value for PathOperation.
102
- # Store tags as mapping from name to object for use with PathOperation.
103
- # Process paths:
104
- # Store parameters as is for default value for PathOperation.
105
- # All other fields, check if it looks like OperationObject and create a
106
- # PathOperation using it. For others, store as is for default value.
96
+ def alter_anchors
97
+ replacements = {}
98
+ @anchor2ref.each do |a, r|
99
+ next if @schema_names.member?(a)
100
+ replacements[a] = ref_string(a)
101
+ @schema_names.add(a)
102
+ end
103
+ replacements.each do |a, r|
104
+ @anchor2ref[a] = r
105
+ end
106
+ end
107
107
 
108
+ def anchor_ref_replacement(ref)
109
+ @anchor2ref[ref[1...ref.size]] || ref
110
+ end
108
111
  end
109
112
 
110
-
111
113
  class ServerPath
114
+ # Probably moves to a separate file once processpaths and frequencies receive
115
+ # some attention.
112
116
  include Comparable
113
117
 
114
118
  attr_accessor :parts
@@ -161,6 +165,8 @@ class ServerPath
161
165
  end
162
166
  end
163
167
 
168
+ # The rest probably ends up in a gem that orders schemas and does nothing else.
169
+
164
170
  # Adds all refs found in the array to refs with given required state.
165
171
  def gather_array_refs(refs, items, required)
166
172
  items.each do |s|
@@ -298,36 +304,3 @@ class SchemaOrderer
298
304
  chosen
299
305
  end
300
306
  end
301
-
302
- class HeaderOrderer
303
- end
304
-
305
- class ResponseOrderer
306
- end
307
-
308
- class PathOrderer
309
- end
310
-
311
- class Tasker
312
- attr_reader :doc
313
- def initialize(doc)
314
- @doc = doc
315
- end
316
- end
317
-
318
- class SchemaTasker < Tasker
319
- # initialize with doc
320
- # Orderer set somehow.
321
- # Method ot create one task per schema.
322
- # Method to create one task for all schemas.
323
- end
324
-
325
- class HeaderTasker < Tasker
326
- # initialize with doc
327
- # Orderer set somehow.
328
- # Method ot create one task per schema.
329
- # Method to create one task for all schemas.
330
- end
331
-
332
-
333
-
data/lib/common.rb CHANGED
@@ -12,6 +12,10 @@ def aargh(message, return_value = nil)
12
12
  return_value
13
13
  end
14
14
 
15
+ def yesno(boolean)
16
+ boolean ? 'yes' : 'no'
17
+ end
18
+
15
19
  def bury(doc, path, value)
16
20
  (path.size - 1).times do |k|
17
21
  p = path[k]
@@ -54,6 +58,7 @@ rescue StandardError => e
54
58
  end
55
59
 
56
60
  def dump_result(output, doc, error_return)
61
+ doc = YAML.dump(doc, line_width: 1_000_000) unless doc.is_a?(String)
57
62
  if output.nil?
58
63
  $stdout.puts doc
59
64
  else
data/lib/docs.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright © 2024 Ismo Kärkkäinen
4
+ # Licensed under Universal Permissive License. See LICENSE.txt.
5
+
6
+ require_relative 'common'
7
+
8
+
9
+ class Docs
10
+ attr_reader :docs
11
+
12
+ def initialize
13
+ @docs = {}
14
+ end
15
+
16
+ def method_missing(method_name, *args)
17
+ name = method_name.to_s
18
+ if name.end_with?('=')
19
+ name = name[0...(name.size - 1)]
20
+ super unless @docs.key?(name)
21
+ @docs[name] = args.first
22
+ return args.first
23
+ end
24
+ super unless @docs.key?(name)
25
+ @docs[name]
26
+ end
27
+
28
+ def add(name, content)
29
+ return false if docs.key?(name)
30
+ @docs[name] = content
31
+ true
32
+ end
33
+ end
data/lib/gen.rb CHANGED
@@ -5,56 +5,73 @@
5
5
 
6
6
  require_relative 'task'
7
7
  require_relative 'helper'
8
+ require_relative 'docs'
9
+ require_relative 'output'
8
10
 
9
11
 
10
12
  module Gen
11
- def self.mod_attr_reader(symbol, docstr = nil)
12
- attr_reader(symbol)
13
- module_function(symbol)
13
+ def self.add_doc(symbol, docstr)
14
14
  return if docstr.nil?
15
- @docsrc = [] unless Gen.instance_variable_defined?('@docsrc')
15
+ @docsrc = [] unless instance_variable_defined?('@docsrc')
16
16
  @docsrc.push("- #{symbol.to_s} : #{docstr}")
17
17
  end
18
18
 
19
- def self.mod_attr_accessor(symbol, docstr = nil)
19
+ def self.read_attr(symbol, default)
20
+ return if symbol.nil?
21
+ attr_reader(symbol)
22
+ module_function(symbol)
23
+ instance_variable_set("@#{symbol.to_s}", default)
24
+ end
25
+
26
+ def self.mod_attr2_reader(symbol, symbol2, docstr = nil, default = nil)
27
+ read_attr(symbol, default)
28
+ read_attr(symbol2, default)
29
+ add_doc(symbol, docstr)
30
+ end
31
+
32
+ def self.mod_attr_reader(symbol, docstr = nil, default = nil)
33
+ mod_attr2_reader(symbol, nil, docstr, default)
34
+ end
35
+
36
+ def self.rw_attr(symbol, default)
20
37
  attr_accessor(symbol)
21
38
  module_function(symbol)
22
39
  s = symbol.to_s
23
40
  module_function((s + '=').to_sym)
24
- return if docstr.nil?
25
- @docsrc = [] unless Gen.instance_variable_defined?('@docsrc')
26
- @docsrc.push("- #{s} : #{docstr}")
41
+ instance_variable_set("@#{s}", default)
42
+ end
43
+
44
+ def self.mod_attr2_accessor(symbol, symbol2, docstr = nil, default = nil)
45
+ rw_attr(symbol, default)
46
+ rw_attr(symbol2, default) unless symbol2.nil?
47
+ add_doc(symbol, docstr)
48
+ end
49
+
50
+ def self.mod_attr_accessor(symbol, docstr = nil, default = nil)
51
+ mod_attr2_accessor(symbol, nil, docstr, default)
27
52
  end
28
53
 
29
54
  mod_attr_reader :doc, 'OpenAPI document.'
30
55
  mod_attr_reader :outdir, 'Output directory name.'
56
+ mod_attr_reader :d, 'Other documents object.', Docs.new
31
57
  mod_attr_accessor :in_name, 'OpenAPI document name, nil if stdin.'
32
58
  mod_attr_accessor :in_basename, 'OpenAPI document basename, nil if stdin.'
33
- mod_attr_accessor :tasks, 'Tasks array.'
34
- mod_attr_accessor :g, 'Hash for storing values visible to all tasks.'
59
+ mod_attr_accessor :tasks, 'Tasks array.', []
60
+ mod_attr_accessor :g, 'Hash for storing values visible to all tasks.', {}
35
61
  mod_attr_accessor :a, 'Intended for instance with defined attributes.'
36
62
  mod_attr_accessor :h, 'Instance of class with helper methods.'
37
- mod_attr_accessor :t, 'Current task instance.'
63
+ mod_attr2_accessor :task, :t, 'Current task instance.'
38
64
  mod_attr_accessor :task_index, 'Current task index.'
39
- mod_attr_accessor :loaders, 'Array of generator loader methods.'
65
+ mod_attr_accessor :loaders, 'Array of generator loader methods.', []
66
+ mod_attr2_accessor :output, :o, 'Output-related methods.', Output.new
40
67
 
41
68
  def self.setup(document_content, input_name, output_directory)
42
69
  @doc = document_content
43
70
  @outdir = output_directory
44
- if input_name.nil?
45
- @in_name = nil
46
- @in_basename = nil
47
- else
71
+ unless input_name.nil?
48
72
  @in_name = File.basename(input_name)
49
73
  @in_basename = File.basename(input_name, '.*')
50
74
  end
51
- @tasks = []
52
- @g = {}
53
- @a = nil
54
- @h = nil
55
- @t = nil
56
- @task_index = nil
57
- @loaders = []
58
75
  add_task(task: HelperTask.new)
59
76
  end
60
77
 
data/lib/generate.rb CHANGED
@@ -8,6 +8,7 @@ require_relative 'common'
8
8
  require_relative 'loaders'
9
9
  require_relative 'gen'
10
10
 
11
+
11
12
  def executable_bits_on(mode)
12
13
  mode = mode.to_s(8).split('')
13
14
  mode.size.times do |k|
data/lib/helper.rb CHANGED
@@ -10,7 +10,7 @@ class Helper
10
10
  attr_reader :doc, :parents
11
11
  attr_accessor :parent_parameters
12
12
 
13
- # Stores the nearesh Hash for each Hash.
13
+ # Stores the nearest Hash for each Hash.
14
14
  def store_parents(obj, parent = nil)
15
15
  if obj.is_a?(Hash)
16
16
  @parents[obj.object_id] = parent
@@ -26,8 +26,6 @@ class Helper
26
26
 
27
27
  def initialize(doc)
28
28
  @doc = doc
29
- # For each hash in doc, set parent?
30
- # Build an object_id to parent object mapping and use parent method?
31
29
  @parents = {}
32
30
  store_parents(@doc)
33
31
  end
data/lib/loaders.rb CHANGED
@@ -3,6 +3,9 @@
3
3
  # Copyright © 2024 Ismo Kärkkäinen
4
4
  # Licensed under Universal Permissive License. See LICENSE.txt.
5
5
 
6
+
7
+ # Original loader functions. These are accessible via Gen.loaders. New loaders
8
+ # should be added there.
6
9
  module Loaders
7
10
 
8
11
  GEM_PREFIX = 'gem:'
@@ -37,14 +40,56 @@ module Loaders
37
40
  true
38
41
  end
39
42
 
43
+ YAML_PREFIX = 'yaml:'
44
+ YAML_EXTS = [ '.yaml', '.yml' ]
45
+
46
+ def self.yaml_loader(name)
47
+ d = name.downcase
48
+ if d.start_with?(YAML_PREFIX)
49
+ name = name.slice(YAML_PREFIX.size...name.size)
50
+ else
51
+ return false if (YAML_EXTS.index { |s| d.end_with?(s) }).nil?
52
+ end
53
+ n, sep, f = name.partition(':')
54
+ raise StandardError, "No name given." if n.empty?
55
+ raise StandardError, "No filename given." if f.empty?
56
+ doc = YAML.safe_load(File.read(f))
57
+ raise StandardError, "#{name} #{n} exists already." unless Gen.d.add(n, doc)
58
+ true
59
+ rescue Errno::ENOENT
60
+ raise StandardError, "Not found: #{f}\n#{e.to_s}"
61
+ rescue Exception => e
62
+ raise StandardError, "Failed to read as YAML: #{f}\n#{e.to_s}"
63
+ end
64
+
65
+ BIN_PREFIX = 'bin:'
66
+
67
+ def self.bin_loader(name)
68
+ return false unless name.downcase.start_with?(BIN_PREFIX)
69
+ n, sep, f = name.slice(BIN_PREFIX.size...name.size).partition(':')
70
+ raise StandardError, "No name given." if n.empty?
71
+ raise StandardError, "No filename given." if f.empty?
72
+ doc = IO.binread(f)
73
+ raise StandardError, "#{name} #{n} exists already." unless Gen.d.add(n, doc)
74
+ true
75
+ rescue Errno::ENOENT
76
+ raise StandardError, "Not found: #{f}\n#{e.to_s}"
77
+ rescue Exception => e
78
+ raise StandardError, "Failed to read #{f}\n#{e.to_s}"
79
+ end
80
+
40
81
  def self.loaders
41
- [ method(:gem_loader), method(:ruby_loader) ]
82
+ pre = @preloaders
83
+ [ method(:gem_loader), method(:ruby_loader), method(:yaml_loader), method(:bin_loader) ]
42
84
  end
43
85
 
44
86
  def self.document
45
87
  %(
46
88
  - #{Loaders::GEM_PREFIX}gem_name : requires the gem.
47
89
  - ruby_file#{Loaders::RUBY_EXT} : changes to Ruby file directory and requires the file.
90
+ - #{Loaders::YAML_PREFIX}name:filename : Loads YAML file into Gen.d.name.
91
+ - name:filename.{#{(Loaders::YAML_EXTS.map { |s| s[1...s.size] }).join('|')}} : Loads YAML file into Gen.d.name.
92
+ - #{Loaders::BIN_PREFIX}name:filename : Loads binary file into Gen.d.name.
48
93
  )
49
94
  end
50
95
 
data/lib/output.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright © 2024 Ismo Kärkkäinen
4
+ # Licensed under Universal Permissive License. See LICENSE.txt.
5
+
6
+ require_relative 'task'
7
+
8
+
9
+ class Output
10
+ # For indentation.
11
+ attr_accessor :indent_character, :indent_step
12
+ attr_accessor :tab, :tab_replaces_count
13
+ attr_accessor :last_indent
14
+
15
+ def initialize
16
+ @indent_character = ' '
17
+ @indent_step = 4
18
+ @tab = "\t"
19
+ @tab_replaces_count = 0
20
+ @last_indent = 0
21
+ end
22
+
23
+ def join(blocks, separator = "\n")
24
+ indented = []
25
+ blocks.flatten!
26
+ indent = 0
27
+ blocks.each do |block|
28
+ if block.nil?
29
+ indent = 0
30
+ elsif block.is_a?(Integer)
31
+ indent += block
32
+ elsif block.is_a?(TrueClass)
33
+ indent += @indent_step
34
+ elsif block.is_a?(FalseClass)
35
+ indent -= @indent_step
36
+ else
37
+ block = block.to_s unless block.is_a?(String)
38
+ if indent.zero?
39
+ indented.push(block)
40
+ next
41
+ end
42
+ if 0 < @tab_replaces_count
43
+ tabs = @tab * (indent / @tab_replaces_count)
44
+ chars = @indent_character * (indent % @tab_replaces_count)
45
+ else
46
+ tabs = ''
47
+ chars = @indent_character * indent
48
+ end
49
+ lines = block.lines(chomp: true)
50
+ lines.each do |line|
51
+ indented.push("#{tabs}#{chars}#{line}")
52
+ end
53
+ end
54
+ end
55
+ @last_indent = indent
56
+ indented.join(separator)
57
+ end
58
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi-sourcetools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismo Kärkkäinen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-17 00:00:00.000000000 Z
11
+ date: 2024-08-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
 
@@ -25,6 +25,7 @@ executables:
25
25
  - openapi-frequencies
26
26
  - openapi-generate
27
27
  - openapi-merge
28
+ - openapi-modifypaths
28
29
  - openapi-processpaths
29
30
  extensions: []
30
31
  extra_rdoc_files: []
@@ -38,13 +39,16 @@ files:
38
39
  - bin/openapi-frequencies
39
40
  - bin/openapi-generate
40
41
  - bin/openapi-merge
42
+ - bin/openapi-modifypaths
41
43
  - bin/openapi-processpaths
42
44
  - lib/apiobjects.rb
43
45
  - lib/common.rb
46
+ - lib/docs.rb
44
47
  - lib/gen.rb
45
48
  - lib/generate.rb
46
49
  - lib/helper.rb
47
50
  - lib/loaders.rb
51
+ - lib/output.rb
48
52
  - lib/task.rb
49
53
  homepage: https://xn--ismo-krkkinen-gfbd.fi/openapi-sourcetools/index.html
50
54
  licenses: