openapi-sourcetools 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/bin/openapi-addheaders +73 -0
- data/bin/openapi-addresponses +72 -0
- data/bin/openapi-addschemas +104 -0
- data/bin/openapi-checkschemas +217 -0
- data/bin/openapi-frequencies +57 -84
- data/bin/openapi-merge +63 -81
- data/bin/openapi-processpaths +61 -88
- data/lib/apiobjects.rb +304 -0
- data/lib/common.rb +53 -27
- metadata +18 -8
- data/bin/openapi-generatecode +0 -128
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23e0cd1aefa69559d35171e477d8b749ec61251b67c51040b1130ce48fb57725
|
4
|
+
data.tar.gz: 1fa4a40b88aa863d3eed153db455aed0bd4d9b9b88a4aa9ca8f92874e07613f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c56924c74979a016aeb8789bd7071f0d54e3c1de746c7bd92d9166cf0a03001f76f5b7b772d128f3e278bbc7835f52f7f139e4c71fe814032c49a252c24f6ece
|
7
|
+
data.tar.gz: f7c87b497a0c154d99b7cbd33da06cce91e9a7aa8d6b8d72bc6b64f86a50fa1f93a93a0e96f922dab1fdb0c189989cd197f48d12ee5399254fe78120e286ddd4
|
data/LICENSE.txt
CHANGED
@@ -0,0 +1,73 @@
|
|
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/apiobjects'
|
8
|
+
require_relative '../lib/common'
|
9
|
+
require 'optparse'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
|
13
|
+
def replace_headers(obj, components)
|
14
|
+
return unless obj.is_a?(Hash)
|
15
|
+
obj.each do |k, v|
|
16
|
+
if k == 'headers'
|
17
|
+
v.keys.sort!.each do |name|
|
18
|
+
r = v[name]
|
19
|
+
next unless r.is_a?(Hash) # Could complain.
|
20
|
+
next if r.key?('$ref')
|
21
|
+
v[name] = { '$ref' => components.reference(r) }
|
22
|
+
end
|
23
|
+
else
|
24
|
+
replace_headers(v, components)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def main
|
30
|
+
input_name = nil
|
31
|
+
output_name = nil
|
32
|
+
path = %w[components headers]
|
33
|
+
components = Components.new(path, 'Header')
|
34
|
+
|
35
|
+
ENV['POSIXLY_CORRECT'] = '1'
|
36
|
+
parser = OptionParser.new do |opts|
|
37
|
+
opts.summary_indent = ' '
|
38
|
+
opts.summary_width = 26
|
39
|
+
opts.banner = 'Usage: openapi-addheaders [options]'
|
40
|
+
opts.separator ''
|
41
|
+
opts.separator 'Options:'
|
42
|
+
opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
|
43
|
+
input_name = f
|
44
|
+
end
|
45
|
+
opts.on('-o', '--output FILE', 'Output to FILE, not stdout .') do |f|
|
46
|
+
output_name = f
|
47
|
+
end
|
48
|
+
components.add_options(opts)
|
49
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
50
|
+
$stdout.puts %(#{opts}
|
51
|
+
|
52
|
+
Loads API document in OpenAPI format and moves headers under components and
|
53
|
+
replaces the original with reference.
|
54
|
+
|
55
|
+
#{components.help}
|
56
|
+
)
|
57
|
+
exit 0
|
58
|
+
end
|
59
|
+
end
|
60
|
+
parser.parse!
|
61
|
+
|
62
|
+
doc = load_source(input_name)
|
63
|
+
return 2 if doc.nil?
|
64
|
+
|
65
|
+
components.items = doc.dig(*path) || {}
|
66
|
+
replace_headers(doc.dig('components', 'responses') || {}, components)
|
67
|
+
replace_headers(doc.fetch('paths', {}), components)
|
68
|
+
bury(doc, path, components.items) unless components.items.empty?
|
69
|
+
|
70
|
+
dump_result(output_name, YAML.dump(doc, line_width: 1_000_000), 3)
|
71
|
+
end
|
72
|
+
|
73
|
+
exit(main) if (defined? $unit_test).nil?
|
@@ -0,0 +1,72 @@
|
|
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/apiobjects'
|
8
|
+
require_relative '../lib/common'
|
9
|
+
require 'optparse'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
|
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
|
+
def main
|
30
|
+
input_name = nil
|
31
|
+
output_name = nil
|
32
|
+
path = %w[components responses]
|
33
|
+
components = Components.new(path, 'Response')
|
34
|
+
|
35
|
+
ENV['POSIXLY_CORRECT'] = '1'
|
36
|
+
parser = OptionParser.new do |opts|
|
37
|
+
opts.summary_indent = ' '
|
38
|
+
opts.summary_width = 26
|
39
|
+
opts.banner = 'Usage: openapi-addresponses [options]'
|
40
|
+
opts.separator ''
|
41
|
+
opts.separator 'Options:'
|
42
|
+
opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
|
43
|
+
input_name = f
|
44
|
+
end
|
45
|
+
opts.on('-o', '--output FILE', 'Output to FILE, not stdout .') do |f|
|
46
|
+
output_name = f
|
47
|
+
end
|
48
|
+
components.add_options(opts)
|
49
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
50
|
+
$stdout.puts %(#{opts}
|
51
|
+
|
52
|
+
Loads API document in OpenAPI format and moves responses under components and
|
53
|
+
replaces the original with reference.
|
54
|
+
|
55
|
+
#{components.help}
|
56
|
+
)
|
57
|
+
exit 0
|
58
|
+
end
|
59
|
+
end
|
60
|
+
parser.parse!
|
61
|
+
|
62
|
+
doc = load_source(input_name)
|
63
|
+
return 2 if doc.nil?
|
64
|
+
|
65
|
+
components.items = doc.dig(*path) || {}
|
66
|
+
replace_responses(doc.fetch('paths', {}), components)
|
67
|
+
bury(doc, path, components.items) unless components.items.empty?
|
68
|
+
|
69
|
+
dump_result(output_name, YAML.dump(doc, line_width: 1_000_000), 3)
|
70
|
+
end
|
71
|
+
|
72
|
+
exit(main) if (defined? $unit_test).nil?
|
@@ -0,0 +1,104 @@
|
|
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/apiobjects'
|
8
|
+
require_relative '../lib/common'
|
9
|
+
require 'optparse'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
|
13
|
+
def remove_subitem(obj, path)
|
14
|
+
return obj.delete(path.first) || {} if path.size == 1
|
15
|
+
parent = obj.dig(*path.take(path.size - 1))
|
16
|
+
return {} if parent.nil?
|
17
|
+
parent.delete(path.last) || {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def replace_inlines(obj, components, top_level = false)
|
21
|
+
if obj.is_a?(Array)
|
22
|
+
obj.each do |o|
|
23
|
+
return false unless replace_inlines(o, components)
|
24
|
+
end
|
25
|
+
return true
|
26
|
+
end
|
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.
|
30
|
+
# Is inlined, process parts recursively.
|
31
|
+
items = obj['allOf']
|
32
|
+
items = obj['anyOf'] if items.nil?
|
33
|
+
items = obj['oneOf'] if items.nil?
|
34
|
+
# All above need special treatment in code generation templates.
|
35
|
+
return replace_inlines(items, components) unless items.nil?
|
36
|
+
t = obj['type']
|
37
|
+
if t.nil? # Some kind of intermediate-level object.
|
38
|
+
(obj.keys.sort! { |a, b| a.to_s <=> b.to_s }).each do |key|
|
39
|
+
return false unless replace_inlines(obj[key], components)
|
40
|
+
end
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
case t
|
44
|
+
when 'array'
|
45
|
+
return false unless replace_inlines(obj['items'], components)
|
46
|
+
when 'object'
|
47
|
+
props = obj.fetch('properties', {})
|
48
|
+
props.keys.sort!.each do |name|
|
49
|
+
return false unless replace_inlines(props[name], components)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
obj.replace({ '$ref' => components.reference(obj) }) unless top_level
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def main
|
57
|
+
input_name = nil
|
58
|
+
output_name = nil
|
59
|
+
path = %w[components schemas]
|
60
|
+
components = Components.new(path, 'Schema')
|
61
|
+
|
62
|
+
ENV['POSIXLY_CORRECT'] = '1'
|
63
|
+
parser = OptionParser.new do |opts|
|
64
|
+
opts.summary_indent = ' '
|
65
|
+
opts.summary_width = 26
|
66
|
+
opts.banner = 'Usage: openapi-addschemas [options]'
|
67
|
+
opts.separator ''
|
68
|
+
opts.separator 'Options:'
|
69
|
+
opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
|
70
|
+
input_name = f
|
71
|
+
end
|
72
|
+
opts.on('-o', '--output FILE', 'Output to FILE, not stdout .') do |f|
|
73
|
+
output_name = f
|
74
|
+
end
|
75
|
+
components.add_options(opts)
|
76
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
77
|
+
$stdout.puts %(#{opts}
|
78
|
+
|
79
|
+
Loads API document in OpenAPI format and adds a schema for each inline type.
|
80
|
+
|
81
|
+
#{components.help}
|
82
|
+
)
|
83
|
+
exit 0
|
84
|
+
end
|
85
|
+
end
|
86
|
+
parser.parse!
|
87
|
+
|
88
|
+
doc = load_source(input_name)
|
89
|
+
return 2 if doc.nil?
|
90
|
+
|
91
|
+
# Find schema object and remove it temporarily to prevent being
|
92
|
+
# processed again.
|
93
|
+
components.items = remove_subitem(doc, path)
|
94
|
+
# Get rid of inlined types within existing schemas first.
|
95
|
+
components.items.keys.sort!.each do |k|
|
96
|
+
return 4 unless replace_inlines(components.items[k], components, true)
|
97
|
+
end
|
98
|
+
return 4 unless replace_inlines(doc, components)
|
99
|
+
bury(doc, path, components.items) unless components.items.empty?
|
100
|
+
|
101
|
+
dump_result(output_name, YAML.dump(doc, line_width: 1_000_000), 3)
|
102
|
+
end
|
103
|
+
|
104
|
+
exit(main) if (defined? $unit_test).nil?
|
@@ -0,0 +1,217 @@
|
|
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/apiobjects'
|
8
|
+
require_relative '../lib/common'
|
9
|
+
require 'optparse'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
|
13
|
+
def typesame(a, b, ignored_keys)
|
14
|
+
return same(a, b, ignored_keys) unless a['type'] == 'object'
|
15
|
+
return same(a, b, ignored_keys) unless b['type'] == 'object'
|
16
|
+
# For each property in a, find out unused mathing property in b that is
|
17
|
+
# also either required or not.
|
18
|
+
# Requiring property affects e.g. C++ by allowing direct membership or
|
19
|
+
# when optional, a pointer that can have nullptr value. Or class instance
|
20
|
+
# or null in TypeScript so I consider required to affect type.
|
21
|
+
pa = a.fetch('properties', {})
|
22
|
+
ra = a.fetch('required', [])
|
23
|
+
pb = b.fetch('properties', {}).merge
|
24
|
+
rb = b.fetch('required', [])
|
25
|
+
pa.each do |aname, aspec|
|
26
|
+
bnames = pb.keys.sort!
|
27
|
+
if ra.include?(aname)
|
28
|
+
bnames.select! { |n| rb.include?(n) }
|
29
|
+
else
|
30
|
+
bnames.reject! { |n| rb.include?(n) }
|
31
|
+
end
|
32
|
+
matched = false
|
33
|
+
bnames.each do |bname|
|
34
|
+
bspec = pb[bname]
|
35
|
+
# Intended use is after openapi-addschemas so same should work as well.
|
36
|
+
if typesame(aspec, bspec, ignored_keys)
|
37
|
+
pb.delete(bname)
|
38
|
+
matched = true
|
39
|
+
break
|
40
|
+
end
|
41
|
+
end
|
42
|
+
return false unless matched
|
43
|
+
end
|
44
|
+
pb.empty? # All matched and nothing left in pb.
|
45
|
+
end
|
46
|
+
|
47
|
+
def keys2array(keys)
|
48
|
+
keys.to_h { |k| [ k, [] ] }
|
49
|
+
end
|
50
|
+
|
51
|
+
def gather_equivalencies(components)
|
52
|
+
order = components.items.keys.sort!
|
53
|
+
result = keys2array(order)
|
54
|
+
(order.size - 1).times do |k|
|
55
|
+
item = components.items[order[k]]
|
56
|
+
((k + 1)...order.size).each do |n|
|
57
|
+
if same(item, components.items[order[n]], components.ignored_keys)
|
58
|
+
result[order[k]].push(order[n])
|
59
|
+
result[order[n]].push(order[k])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
result.delete_if { |_k, v| v.empty? }
|
64
|
+
end
|
65
|
+
|
66
|
+
def gather_typematches(components)
|
67
|
+
order = components.items.keys.sort!
|
68
|
+
result = keys2array(order)
|
69
|
+
(order.size - 1).times do |k|
|
70
|
+
item = components.items[order[k]]
|
71
|
+
((k + 1)...order.size).each do |n|
|
72
|
+
if typesame(item, components.items[order[n]], components.ignored_keys)
|
73
|
+
result[order[k]].push(order[n])
|
74
|
+
result[order[n]].push(order[k])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
result.delete_if { |_k, v| v.empty? }
|
79
|
+
end
|
80
|
+
|
81
|
+
def gather_refs(obj, prefix, past, refs)
|
82
|
+
if obj.is_a?(Hash)
|
83
|
+
obj.each do |k, v|
|
84
|
+
if v.is_a?(String) && v.start_with?(prefix)
|
85
|
+
p = past.join('/')
|
86
|
+
if refs.key?(v)
|
87
|
+
refs[v].push(p)
|
88
|
+
else
|
89
|
+
refs[v] = [ p ]
|
90
|
+
end
|
91
|
+
else
|
92
|
+
past.push(k)
|
93
|
+
gather_refs(v, prefix, past, refs)
|
94
|
+
past.pop
|
95
|
+
end
|
96
|
+
end
|
97
|
+
elsif obj.is_a?(Array)
|
98
|
+
obj.size.times do |n|
|
99
|
+
past.push(n)
|
100
|
+
gather_refs(obj[n], prefix, past, refs)
|
101
|
+
past.pop
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def gather_references(doc, prefix)
|
107
|
+
refs = {}
|
108
|
+
gather_refs(doc, prefix, [], refs)
|
109
|
+
out = {}
|
110
|
+
refs.keys.sort!.each do |k|
|
111
|
+
r = refs[k]
|
112
|
+
next if r.empty?
|
113
|
+
out[k] = r.sort!
|
114
|
+
end
|
115
|
+
out
|
116
|
+
end
|
117
|
+
|
118
|
+
def drop_untouched(refs, report, prefix)
|
119
|
+
touched = Set.new
|
120
|
+
%w[equivalent typematch].each do |s|
|
121
|
+
r = report[s]
|
122
|
+
next if r.nil?
|
123
|
+
r.keys.each { |k| touched.add("#{prefix}#{k}") }
|
124
|
+
end
|
125
|
+
refs.delete_if { |k, _v| !touched.member?(k) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def count_refs(refs)
|
129
|
+
counts = {}
|
130
|
+
refs.each { |k, v| counts[k] = v.size }
|
131
|
+
counts
|
132
|
+
end
|
133
|
+
|
134
|
+
def main
|
135
|
+
input_name = nil
|
136
|
+
output_name = nil
|
137
|
+
path = %w[components schemas]
|
138
|
+
components = Components.new(path, '')
|
139
|
+
equivalent = true
|
140
|
+
typematch = false
|
141
|
+
references = false
|
142
|
+
count = true
|
143
|
+
keep = false
|
144
|
+
|
145
|
+
ENV['POSIXLY_CORRECT'] = '1'
|
146
|
+
parser = OptionParser.new do |opts|
|
147
|
+
opts.summary_indent = ' '
|
148
|
+
opts.summary_width = 26
|
149
|
+
opts.banner = 'Usage: openapi-checkschemas [options]'
|
150
|
+
opts.separator ''
|
151
|
+
opts.separator 'Options:'
|
152
|
+
opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
|
153
|
+
input_name = f
|
154
|
+
end
|
155
|
+
opts.on('-o', '--output FILE', 'Output to FILE, not stdout .') do |f|
|
156
|
+
output_name = f
|
157
|
+
end
|
158
|
+
opts.on('-e', '--[no-]equivalent', "Report equivalent schemas, default #{equivalent}") do |b|
|
159
|
+
equivalent = b
|
160
|
+
end
|
161
|
+
opts.on('-t', '--[no-]typematch', "Report typematch schemas, default #{typematch}") do |b|
|
162
|
+
typematch = b
|
163
|
+
end
|
164
|
+
opts.on('-r', '--[no-]reference', "Report schema references, default #{references}") do |b|
|
165
|
+
references = b
|
166
|
+
end
|
167
|
+
opts.on('-c', '--[no-]count', "Report schema reference counts, default #{count}") do |b|
|
168
|
+
count = b
|
169
|
+
end
|
170
|
+
opts.on('-k', '--[no-]keep', "Keep all schema references/counts, default #{keep}") do |b|
|
171
|
+
keep = b
|
172
|
+
end
|
173
|
+
components.add_options(opts)
|
174
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
175
|
+
$stdout.puts %(#{opts}
|
176
|
+
|
177
|
+
Loads API document in OpenAPI format and checks if any schemas are duplicates
|
178
|
+
of another and to what degree. Equivalency may be an exact match, or property
|
179
|
+
names differ but types match.
|
180
|
+
|
181
|
+
#{components.help}
|
182
|
+
|
183
|
+
Search is performed only at top schema level. References to equivalent types
|
184
|
+
are not considered equivalent when references themselves are not equivalent.
|
185
|
+
Any allOf, anyOf, oneOf checks merely check the refernces. Hence two different
|
186
|
+
allOf schemas may in practice result in equivalent types and that is not
|
187
|
+
detected.
|
188
|
+
|
189
|
+
Implicit expectation is the openapi-addschemas has been used to process the
|
190
|
+
input, as inlined types in requests for example are ignored.
|
191
|
+
)
|
192
|
+
exit 0
|
193
|
+
end
|
194
|
+
end
|
195
|
+
parser.parse!
|
196
|
+
|
197
|
+
doc = load_source(input_name)
|
198
|
+
return 2 if doc.nil?
|
199
|
+
|
200
|
+
components.items = doc.dig(*path) || {}
|
201
|
+
report = {}
|
202
|
+
report['equivalent'] = gather_equivalencies(components) if equivalent
|
203
|
+
report['typematch'] = gather_typematches(components) if typematch
|
204
|
+
if references || count
|
205
|
+
refs = gather_references(doc, components.path)
|
206
|
+
if equivalent || typematch # There is something to limit references to.
|
207
|
+
refs = drop_untouched(refs, report, components.path) unless keep
|
208
|
+
end
|
209
|
+
report['reference'] = refs if references
|
210
|
+
report['count'] = count_refs(refs) if count
|
211
|
+
end
|
212
|
+
# Filter reference counts that are not for type in equivalent or typematch.
|
213
|
+
# Could check for pretty-printed output format here.
|
214
|
+
dump_result(output_name, YAML.dump(report, line_width: 80), 3)
|
215
|
+
end
|
216
|
+
|
217
|
+
exit(main) if (defined? $unit_test).nil?
|
data/bin/openapi-frequencies
CHANGED
@@ -1,105 +1,78 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
# Copyright © 2021 Ismo Kärkkäinen
|
4
|
+
# Copyright © 2021-2024 Ismo Kärkkäinen
|
5
5
|
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
6
|
|
7
|
-
require_relative '../lib/common
|
7
|
+
require_relative '../lib/common'
|
8
8
|
require 'optparse'
|
9
9
|
require 'yaml'
|
10
|
-
require 'json'
|
11
10
|
|
11
|
+
def main
|
12
|
+
input_name = nil
|
13
|
+
output_name = nil
|
14
|
+
paths_name = nil
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
opts.on('-p', '--paths FILE', 'Path log file name (PATHS=FILE).') do |f|
|
31
|
-
env(:paths, f)
|
32
|
-
end
|
33
|
-
opts.on('--yaml', 'Output format is YAML (default) (FORMAT=YAML).') do
|
34
|
-
env(:format, 'YAML')
|
35
|
-
end
|
36
|
-
opts.on('--json', 'Output format is JSON. (FORMAT=JSON)') do
|
37
|
-
env(:format, 'JSON')
|
38
|
-
end
|
39
|
-
opts.on('-h', '--help', 'Print this help and exit.') do
|
40
|
-
$stdout.puts %(#{opts}
|
16
|
+
parser = OptionParser.new do |opts|
|
17
|
+
opts.summary_indent = ' '
|
18
|
+
opts.summary_width = 24
|
19
|
+
opts.banner = 'Usage: openapi-frequencies [options]'
|
20
|
+
opts.separator ''
|
21
|
+
opts.separator 'Options:'
|
22
|
+
opts.on('-i', '--input FILE', 'Read source from FILE, not stdin.') do |f|
|
23
|
+
input_name = f
|
24
|
+
end
|
25
|
+
opts.on('-o', '--output FILE', 'Output result to FILE, not stdout.') do |f|
|
26
|
+
output_name = f
|
27
|
+
end
|
28
|
+
opts.on('-p', '--paths FILE', 'Path log file name.') do |f|
|
29
|
+
paths_name = f
|
30
|
+
end
|
31
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
32
|
+
$stdout.puts %(#{opts}
|
41
33
|
|
42
|
-
Matches given file with paths and adds
|
34
|
+
Matches given OpenAPI document file paths with paths from file and adds
|
35
|
+
into each path object matching count as "freq".
|
43
36
|
)
|
44
|
-
|
37
|
+
exit 0
|
38
|
+
end
|
45
39
|
end
|
46
|
-
|
47
|
-
parser.parse!
|
40
|
+
parser.parse!
|
48
41
|
|
49
|
-
aargh(
|
50
|
-
fn = env(:paths)
|
51
|
-
aargh("Path log file name must be given.") if fn.empty?
|
42
|
+
return aargh('Path log file name must be given.', 1) if paths_name.nil?
|
52
43
|
|
53
|
-
|
54
|
-
|
55
|
-
rescue Errno::ENOENT => e
|
56
|
-
aargh("Could not read #{filename.empty? ? 'stdin' : filename}", 2)
|
57
|
-
rescue StandardError => e
|
58
|
-
aargh(e.to_s, 3)
|
59
|
-
end
|
60
|
-
doc = read_source(env(:in))
|
44
|
+
doc = load_source(input_name)
|
45
|
+
return 2 if doc.nil?
|
61
46
|
|
62
|
-
begin
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
47
|
+
begin
|
48
|
+
# Expect one path per line with nothing else. Query is allowed and ignored.
|
49
|
+
f = File.new(paths_name, 'rt')
|
50
|
+
f.each_line do |line|
|
51
|
+
p = ServerPath.new(split_path(line))
|
52
|
+
doc['paths'].each_value do |info|
|
53
|
+
next unless (p <=> info['parts']).zero?
|
54
|
+
info['freq'] = info.fetch('freq', 0) + 1
|
55
|
+
# Lookalikes are the others that can be matched, since path has no
|
56
|
+
# variables so any change in fixed parts (not a lookalike) will be a
|
57
|
+
# mismatch with path.
|
58
|
+
info['lookalike'].each do |path|
|
59
|
+
cand = doc['paths'][path]
|
60
|
+
next unless (p <=> cand['parts']).zero?
|
61
|
+
cand['freq'] = cand.fetch('freq', 0) + 1
|
62
|
+
end
|
63
|
+
break
|
77
64
|
end
|
78
|
-
break
|
79
65
|
end
|
66
|
+
f.close
|
67
|
+
rescue Errno::ENOENT
|
68
|
+
return aargh("Could not read path log file: #{paths_name}", 4)
|
69
|
+
rescue NoMethodError
|
70
|
+
return aargh('Is input file an output of openapi-processpaths?', 5)
|
71
|
+
rescue StandardError => e
|
72
|
+
return aargh(e.to_s, 6)
|
80
73
|
end
|
81
|
-
f.close
|
82
|
-
rescue Errno::ENOENT => e
|
83
|
-
aargh("Could not read path log file: #{fn}", 4)
|
84
|
-
rescue NoMethodError
|
85
|
-
aargh('Is input file an output of openapi-processpaths?', 5)
|
86
|
-
rescue StandardError => e
|
87
|
-
aargh(e.to_s, 6)
|
88
|
-
end
|
89
74
|
|
90
|
-
|
91
|
-
if output.empty?
|
92
|
-
output = $stdout
|
93
|
-
else
|
94
|
-
begin
|
95
|
-
output = File.open(output, 'w')
|
96
|
-
rescue StandardError
|
97
|
-
aargh("Failed to open for writing: #{output}", 7)
|
98
|
-
end
|
75
|
+
dump_result(output_name, YAML.dump(doc), 3)
|
99
76
|
end
|
100
77
|
|
101
|
-
|
102
|
-
when 'JSON' then output.puts JSON.generate(doc)
|
103
|
-
when 'YAML' then output.puts YAML.dump(doc)
|
104
|
-
end
|
105
|
-
output.close
|
78
|
+
exit(main) if (defined? $unit_test).nil?
|