openapi-sourcetools 0.5.0 → 0.7.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/bin/openapi-addheaders +1 -1
- data/bin/openapi-addparameters +72 -0
- data/bin/openapi-addresponses +1 -1
- data/bin/openapi-addschemas +1 -1
- data/bin/openapi-generate +58 -0
- data/bin/openapi-modifypaths +123 -0
- data/lib/apiobjects.rb +34 -5
- data/lib/common.rb +2 -1
- data/lib/docs.rb +33 -0
- data/lib/gen.rb +104 -0
- data/lib/generate.rb +90 -0
- data/lib/helper.rb +94 -0
- data/lib/loaders.rb +96 -0
- data/lib/output.rb +58 -0
- data/lib/task.rb +101 -0
- metadata +16 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 211083083a2a3c53321234416ffff41bd1d00320a5ebbdab3ccdcd49576653b3
|
4
|
+
data.tar.gz: 34535443138a6308869974d62e79321d9ec9dbb3f46434c19d571075044751e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c4a5361e25a1cf7903b6038a32f4a94cf42579c0c4fefb21f885fae996c219a02eef322fd91d694dfe0ff668314685b990d3128ca7e6a98c3f30660639494d0
|
7
|
+
data.tar.gz: a563ba480140037e0ef91dbdc89fb62a2175954897726717976760879a74bf8284d673d3a259ce338b7b7493c84fd6c1f5cf4246b90ee42ae1959d3b31c7b21f
|
data/bin/openapi-addheaders
CHANGED
@@ -67,7 +67,7 @@ replaces the original with reference.
|
|
67
67
|
replace_headers(doc.fetch('paths', {}), components)
|
68
68
|
bury(doc, path, components.items) unless components.items.empty?
|
69
69
|
|
70
|
-
dump_result(output_name,
|
70
|
+
dump_result(output_name, doc, 3)
|
71
71
|
end
|
72
72
|
|
73
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
|
+
def replace_parameter(p, components)
|
13
|
+
return p unless p.is_a?(Hash) # Could complain.
|
14
|
+
return p if p.key?('$ref')
|
15
|
+
{ '$ref' => components.reference(p) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def replace_parameters(obj, components)
|
19
|
+
return unless obj.is_a?(Hash)
|
20
|
+
obj.each do |k, v|
|
21
|
+
if k == 'parameters'
|
22
|
+
obj[k] = v.map { |p| replace_parameter(p, components) }
|
23
|
+
else
|
24
|
+
replace_parameters(v, components)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def main
|
30
|
+
input_name = nil
|
31
|
+
output_name = nil
|
32
|
+
path = %w[components parameters]
|
33
|
+
components = Components.new(path, 'Parameter')
|
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-addparameters [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 parameters 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_parameters(doc.fetch('paths', {}), components)
|
67
|
+
bury(doc, path, components.items) unless components.items.empty?
|
68
|
+
|
69
|
+
dump_result(output_name, doc, 3)
|
70
|
+
end
|
71
|
+
|
72
|
+
exit(main) if defined?($unit_test).nil?
|
data/bin/openapi-addresponses
CHANGED
@@ -66,7 +66,7 @@ replaces the original with reference.
|
|
66
66
|
replace_responses(doc.fetch('paths', {}), components)
|
67
67
|
bury(doc, path, components.items) unless components.items.empty?
|
68
68
|
|
69
|
-
dump_result(output_name,
|
69
|
+
dump_result(output_name, doc, 3)
|
70
70
|
end
|
71
71
|
|
72
72
|
exit(main) if (defined? $unit_test).nil?
|
data/bin/openapi-addschemas
CHANGED
@@ -98,7 +98,7 @@ Loads API document in OpenAPI format and adds a schema for each inline type.
|
|
98
98
|
return 4 unless replace_inlines(doc, components)
|
99
99
|
bury(doc, path, components.items) unless components.items.empty?
|
100
100
|
|
101
|
-
dump_result(output_name,
|
101
|
+
dump_result(output_name, doc, 3)
|
102
102
|
end
|
103
103
|
|
104
104
|
exit(main) if (defined? $unit_test).nil?
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2021-2024 Ismo Kärkkäinen
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
|
+
|
7
|
+
require_relative '../lib/common'
|
8
|
+
require_relative '../lib/loaders'
|
9
|
+
require_relative '../lib/gen'
|
10
|
+
require_relative '../lib/generate'
|
11
|
+
require 'optparse'
|
12
|
+
require 'yaml'
|
13
|
+
|
14
|
+
|
15
|
+
def main
|
16
|
+
input_name = nil
|
17
|
+
output_dir = nil
|
18
|
+
|
19
|
+
parser = OptionParser.new do |opts|
|
20
|
+
opts.summary_indent = ' '
|
21
|
+
opts.summary_width = 26
|
22
|
+
opts.banner = 'Usage: openapi-generate [options] generator-names...'
|
23
|
+
opts.separator ''
|
24
|
+
opts.separator 'Options:'
|
25
|
+
opts.on('-i', '--input FILE', 'Read processed API from FILE, not stdin.') do |f|
|
26
|
+
input_name = f
|
27
|
+
end
|
28
|
+
opts.on('-o', '--outdir DIR', 'Output directory.') do |d|
|
29
|
+
output_dir = d
|
30
|
+
end
|
31
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
32
|
+
$stdout.puts %(#{opts}
|
33
|
+
Loads API document in OpenAPI format and generator names. Built-in generator
|
34
|
+
or additional document loaders accept the following:
|
35
|
+
#{Loaders.document.strip}
|
36
|
+
|
37
|
+
During load each generator can add and modify tasks via Gen module:
|
38
|
+
#{Gen.document.strip}
|
39
|
+
|
40
|
+
After all generators have loaded succesfully, tasks are run.
|
41
|
+
)
|
42
|
+
exit 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
parser.order!
|
46
|
+
|
47
|
+
return aargh('Generator names must be given.', 1) if ARGV.empty?
|
48
|
+
|
49
|
+
a = load_source(input_name)
|
50
|
+
return 2 if a.nil?
|
51
|
+
return aargh("Not a directory: #{output_dir}", 3) unless File.directory?(output_dir)
|
52
|
+
gen = Generator.new(a, input_name, output_dir)
|
53
|
+
ec = gen.load(ARGV)
|
54
|
+
return ec unless ec.zero?
|
55
|
+
gen.run
|
56
|
+
end
|
57
|
+
|
58
|
+
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
|
+
main if defined?($unit_test).nil?
|
data/lib/apiobjects.rb
CHANGED
@@ -85,11 +85,7 @@ class PathOperation
|
|
85
85
|
attr_accessor :summary, :description
|
86
86
|
end
|
87
87
|
|
88
|
-
#
|
89
|
-
# Just add x-openapi-sourcetools-native: nativetype
|
90
|
-
# Separate program that can pipe modified spec out to code generation?
|
91
|
-
|
92
|
-
# JSON schema is way too complex to simplify here. One could have a convenience
|
88
|
+
# One could have a convenience
|
93
89
|
# method that determines how many bytes the value needs, and if it needs to be
|
94
90
|
# signed.
|
95
91
|
|
@@ -302,3 +298,36 @@ class SchemaOrderer
|
|
302
298
|
chosen
|
303
299
|
end
|
304
300
|
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
@@ -21,7 +21,7 @@ def bury(doc, path, value)
|
|
21
21
|
doc[path.last] = value
|
22
22
|
end
|
23
23
|
|
24
|
-
module
|
24
|
+
module Out
|
25
25
|
attr_reader :count
|
26
26
|
|
27
27
|
def put(message)
|
@@ -54,6 +54,7 @@ rescue StandardError => e
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def dump_result(output, doc, error_return)
|
57
|
+
doc = YAML.dump(doc, line_width: 1_000_000) unless doc.is_a?(String)
|
57
58
|
if output.nil?
|
58
59
|
$stdout.puts doc
|
59
60
|
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
ADDED
@@ -0,0 +1,104 @@
|
|
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
|
+
require_relative 'helper'
|
8
|
+
require_relative 'docs'
|
9
|
+
require_relative 'output'
|
10
|
+
|
11
|
+
|
12
|
+
module Gen
|
13
|
+
def self.add_doc(symbol, docstr)
|
14
|
+
return if docstr.nil?
|
15
|
+
@docsrc = [] unless instance_variable_defined?('@docsrc')
|
16
|
+
@docsrc.push("- #{symbol.to_s} : #{docstr}")
|
17
|
+
end
|
18
|
+
|
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)
|
37
|
+
attr_accessor(symbol)
|
38
|
+
module_function(symbol)
|
39
|
+
s = symbol.to_s
|
40
|
+
module_function((s + '=').to_sym)
|
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)
|
52
|
+
end
|
53
|
+
|
54
|
+
mod_attr_reader :doc, 'OpenAPI document.'
|
55
|
+
mod_attr_reader :outdir, 'Output directory name.'
|
56
|
+
mod_attr_reader :d, 'Other documents object.', Docs.new
|
57
|
+
mod_attr_accessor :in_name, 'OpenAPI document name, nil if stdin.'
|
58
|
+
mod_attr_accessor :in_basename, 'OpenAPI document basename, nil if stdin.'
|
59
|
+
mod_attr_accessor :tasks, 'Tasks array.', []
|
60
|
+
mod_attr_accessor :g, 'Hash for storing values visible to all tasks.', {}
|
61
|
+
mod_attr_accessor :a, 'Intended for instance with defined attributes.'
|
62
|
+
mod_attr_accessor :h, 'Instance of class with helper methods.'
|
63
|
+
mod_attr2_accessor :task, :t, 'Current task instance.'
|
64
|
+
mod_attr_accessor :task_index, 'Current task index.'
|
65
|
+
mod_attr_accessor :loaders, 'Array of generator loader methods.', []
|
66
|
+
mod_attr2_accessor :output, :o, 'Output-related methods.', Output.new
|
67
|
+
|
68
|
+
def self.setup(document_content, input_name, output_directory)
|
69
|
+
@doc = document_content
|
70
|
+
@outdir = output_directory
|
71
|
+
unless input_name.nil?
|
72
|
+
@in_name = File.basename(input_name)
|
73
|
+
@in_basename = File.basename(input_name, '.*')
|
74
|
+
end
|
75
|
+
add_task(task: HelperTask.new)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.add_task(task:, name: nil, executable: false, x: nil)
|
79
|
+
@tasks.push(task)
|
80
|
+
# Since this method allows the user to pass their own task type instance,
|
81
|
+
# assign optional values with defaults only when clearly given.
|
82
|
+
@tasks.last.name = name unless name.nil?
|
83
|
+
@tasks.last.executable = executable unless executable == false
|
84
|
+
@tasks.last.x = x unless x.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.add_write_content(name:, content:, executable: false)
|
88
|
+
add_task(task: WriteTask.new(name, content, executable))
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.add(source:, template: nil, template_name: nil, name: nil, executable: false, x: nil)
|
92
|
+
add_task(task: Task.new(source, template, template_name), name: name, executable: executable, x: x)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.document
|
96
|
+
@docsrc.join("\n") + %(
|
97
|
+
- add_task(task:, name: nil, executable: false, x: nil) : Adds task object.
|
98
|
+
- add_write_content(name:, content:, executable: false) : Add file write task.
|
99
|
+
- add(source:, template: nil, template_name: nil, name: nil,
|
100
|
+
executable: false, x: nil) :
|
101
|
+
Adds template task with source as object to process.
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
data/lib/generate.rb
ADDED
@@ -0,0 +1,90 @@
|
|
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 'common'
|
8
|
+
require_relative 'loaders'
|
9
|
+
require_relative 'gen'
|
10
|
+
|
11
|
+
|
12
|
+
def executable_bits_on(mode)
|
13
|
+
mode = mode.to_s(8).split('')
|
14
|
+
mode.size.times do |k|
|
15
|
+
m = mode[k].to_i(8)
|
16
|
+
# Applies to Unix-likes. Other system, check and handle.
|
17
|
+
m += 1 unless 3 < mode.size - k || m.zero? || m % 2 == 1
|
18
|
+
mode[k] = m
|
19
|
+
end
|
20
|
+
m = 0
|
21
|
+
mode.each do |v|
|
22
|
+
m = 8 * m + v
|
23
|
+
end
|
24
|
+
m
|
25
|
+
end
|
26
|
+
|
27
|
+
class Generator
|
28
|
+
def initialize(document_content, input_name, output_directory)
|
29
|
+
Gen.setup(document_content, input_name, output_directory)
|
30
|
+
Gen.loaders = Loaders.loaders
|
31
|
+
end
|
32
|
+
|
33
|
+
def context_binding
|
34
|
+
binding
|
35
|
+
end
|
36
|
+
|
37
|
+
def load(generator_names)
|
38
|
+
generator_names.each do |name|
|
39
|
+
idx = Gen.loaders.index { |loader| loader.call(name) }
|
40
|
+
return aargh("No loader could handle #{name}", 2) if idx.nil?
|
41
|
+
end
|
42
|
+
0
|
43
|
+
rescue StandardError => e
|
44
|
+
aargh(e.to_s, 2)
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate(t)
|
48
|
+
t.generate(context_binding)
|
49
|
+
rescue Exception => e
|
50
|
+
aargh(e.to_s, 4)
|
51
|
+
end
|
52
|
+
|
53
|
+
def output_name(t, index)
|
54
|
+
name = t.output_name
|
55
|
+
name = "#{index}.txt" if name.nil?
|
56
|
+
File.join(Gen.outdir, name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def save(name, contents, executable)
|
60
|
+
f = File.new(name, File::WRONLY | File::CREAT | File::TRUNC)
|
61
|
+
s = executable ? f.stat : nil
|
62
|
+
f.write(contents)
|
63
|
+
f.close
|
64
|
+
return unless executable
|
65
|
+
mode = executable_bits_on(s.mode)
|
66
|
+
File.chmod(mode, name) unless mode == s.mode
|
67
|
+
end
|
68
|
+
|
69
|
+
def run
|
70
|
+
# This allows tasks to be added while processing.
|
71
|
+
# Not intended to be done but might prove handy.
|
72
|
+
# Also exposes current task index in case new task is added in the middle.
|
73
|
+
Gen.task_index = 0
|
74
|
+
while Gen.task_index < Gen.tasks.size
|
75
|
+
Gen.t = Gen.tasks[Gen.task_index]
|
76
|
+
out = generate(Gen.t)
|
77
|
+
Gen.task_index += 1
|
78
|
+
next if Gen.t.discard # Check first to ignore result if no output.
|
79
|
+
return out if out.is_a?(Integer)
|
80
|
+
next if out.empty? # Allows no output but return value still checked.
|
81
|
+
name = output_name(Gen.t, Gen.task_index - 1)
|
82
|
+
begin
|
83
|
+
save(name, out, Gen.t.executable)
|
84
|
+
rescue StandardError => e
|
85
|
+
return aargh("Error writing output file: #{name}\n#{e}", 3)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
0
|
89
|
+
end
|
90
|
+
end
|
data/lib/helper.rb
ADDED
@@ -0,0 +1,94 @@
|
|
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 Helper
|
10
|
+
attr_reader :doc, :parents
|
11
|
+
attr_accessor :parent_parameters
|
12
|
+
|
13
|
+
# Stores the nearest Hash for each Hash.
|
14
|
+
def store_parents(obj, parent = nil)
|
15
|
+
if obj.is_a?(Hash)
|
16
|
+
@parents[obj.object_id] = parent
|
17
|
+
obj.each do |k, v|
|
18
|
+
store_parents(v, obj)
|
19
|
+
end
|
20
|
+
elsif obj.is_a?(Array)
|
21
|
+
obj.each do |v|
|
22
|
+
store_parents(v, parent)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(doc)
|
28
|
+
@doc = doc
|
29
|
+
@parents = {}
|
30
|
+
store_parents(@doc)
|
31
|
+
end
|
32
|
+
|
33
|
+
def parent(object)
|
34
|
+
@parents[object.object_id]
|
35
|
+
end
|
36
|
+
|
37
|
+
COMPONENTS = '#/components/'
|
38
|
+
|
39
|
+
def category_and_name(ref_or_obj)
|
40
|
+
ref = ref_or_obj.is_a?(Hash) ? ref_or_obj['$ref'] : ref_or_obj
|
41
|
+
return nil unless ref.is_a?(String)
|
42
|
+
return nil unless ref.start_with?(Helper::COMPONENTS)
|
43
|
+
idx = ref.index('/', Helper::COMPONENTS.size)
|
44
|
+
return nil if idx.nil?
|
45
|
+
category = ref[Helper::COMPONENTS.size...idx]
|
46
|
+
[ category, ref[(idx + 1)...ref.size] ]
|
47
|
+
end
|
48
|
+
|
49
|
+
def dereference(ref_or_obj)
|
50
|
+
cn = category_and_name(ref_or_obj)
|
51
|
+
return nil if cn.nil?
|
52
|
+
cs = @doc.dig('components', cn.first) || {}
|
53
|
+
cs[cn.last]
|
54
|
+
end
|
55
|
+
|
56
|
+
def basename(ref_or_obj)
|
57
|
+
cn = category_and_name(ref_or_obj)
|
58
|
+
return nil if cn.nil?
|
59
|
+
cn.last
|
60
|
+
end
|
61
|
+
|
62
|
+
def parameters(operation_object, empty_unless_local = false)
|
63
|
+
return [] if empty_unless_local && !operation_object.key?('parameters')
|
64
|
+
cps = @doc.dig('components', 'parameters') || {}
|
65
|
+
uniqs = {}
|
66
|
+
path_item_object = parent(operation_object)
|
67
|
+
[path_item_object, operation_object].each do |p|
|
68
|
+
p.fetch('parameters', []).each do |param|
|
69
|
+
r = basename(param)
|
70
|
+
r = cps[r] if r.is_a?(String)
|
71
|
+
uniqs["#{r['name']}:#{r['in']}"] = param
|
72
|
+
end
|
73
|
+
end
|
74
|
+
uniqs.keys.sort!.map { |k| uniqs[k] }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
class HelperTask
|
80
|
+
include TaskInterface
|
81
|
+
|
82
|
+
def generate(context_binding)
|
83
|
+
Gen.h = Helper.new(Gen.doc)
|
84
|
+
end
|
85
|
+
|
86
|
+
def output_name
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def discard
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/lib/loaders.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2024 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
|
7
|
+
# Original loader functions. These are accessible via Gen.loaders. New loaders
|
8
|
+
# should be added there.
|
9
|
+
module Loaders
|
10
|
+
|
11
|
+
GEM_PREFIX = 'gem:'
|
12
|
+
|
13
|
+
def self.gem_loader(name)
|
14
|
+
return false unless name.downcase.start_with?(GEM_PREFIX)
|
15
|
+
begin
|
16
|
+
require(name.slice(GEM_PREFIX.size...name.size))
|
17
|
+
rescue LoadError => e
|
18
|
+
raise StandardError, "Failed to require #{name}\n#{e.to_s}"
|
19
|
+
rescue Exception => e
|
20
|
+
raise StandardError, "Problem with #{name}\n#{e.to_s}"
|
21
|
+
end
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
RUBY_EXT = '.rb'
|
26
|
+
|
27
|
+
def self.ruby_loader(name)
|
28
|
+
return false unless name.downcase.end_with?(RUBY_EXT)
|
29
|
+
origwd = Dir.pwd
|
30
|
+
d = File.dirname(name)
|
31
|
+
Dir.chdir(d) unless d == '.'
|
32
|
+
begin
|
33
|
+
require(File.join(Dir.pwd, File.basename(name)))
|
34
|
+
rescue LoadError => e
|
35
|
+
raise StandardError, "Failed to require #{name}\n#{e.to_s}"
|
36
|
+
rescue Exception => e
|
37
|
+
raise StandardError, "Problem with #{name}\n#{e.to_s}"
|
38
|
+
end
|
39
|
+
Dir.chdir(origwd) unless d == '.'
|
40
|
+
true
|
41
|
+
end
|
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
|
+
|
81
|
+
def self.loaders
|
82
|
+
pre = @preloaders
|
83
|
+
[ method(:gem_loader), method(:ruby_loader), method(:yaml_loader), method(:bin_loader) ]
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.document
|
87
|
+
%(
|
88
|
+
- #{Loaders::GEM_PREFIX}gem_name : requires the gem.
|
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.
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
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
|
data/lib/task.rb
ADDED
@@ -0,0 +1,101 @@
|
|
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 'erb'
|
7
|
+
require_relative 'common'
|
8
|
+
|
9
|
+
|
10
|
+
module TaskInterface
|
11
|
+
def generate(context_binding)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def output_name
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def discard
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def executable
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Task
|
29
|
+
include TaskInterface
|
30
|
+
|
31
|
+
attr_reader :src, :template, :template_name
|
32
|
+
attr_accessor :name, :executable, :discard, :x
|
33
|
+
|
34
|
+
def initialize(src, template, template_name)
|
35
|
+
@src = src
|
36
|
+
@template = template
|
37
|
+
@template_name = template_name
|
38
|
+
if @template.nil?
|
39
|
+
raise ArgumentError, "template_name or template must be given" if @template_name.nil?
|
40
|
+
begin
|
41
|
+
@template = File.read(@template_name)
|
42
|
+
rescue Errno::ENOENT
|
43
|
+
raise StandardError, "Could not load #{@template_name}"
|
44
|
+
rescue StandardError => e
|
45
|
+
raise StandardError, "#{e}\nFailed to read #{@template_name}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@name = nil
|
49
|
+
@executable = false
|
50
|
+
@discard = false
|
51
|
+
@x = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
# If this is overridden to perform some processing but not to produce output,
|
55
|
+
# set @discard = true and return value will be ignored. No other methods are
|
56
|
+
# called in that case.
|
57
|
+
def internal_generate(context_binding)
|
58
|
+
ERB.new(@template).result(context_binding)
|
59
|
+
end
|
60
|
+
|
61
|
+
# You can override this instead of internal_generate if you do not need the
|
62
|
+
# exception handling.
|
63
|
+
def generate(context_binding)
|
64
|
+
n = @template_name.nil? ? '' : "#{@template_name} "
|
65
|
+
internal_generate(context_binding)
|
66
|
+
rescue SyntaxError => e
|
67
|
+
aargh("Template #{n}syntax error: #{e.full_message}", 5)
|
68
|
+
rescue Exception => e
|
69
|
+
aargh("Template #{n}error: #{e.full_message}", 6)
|
70
|
+
end
|
71
|
+
|
72
|
+
# This is only called when generate produced output that is not discarded.
|
73
|
+
def output_name
|
74
|
+
return @name unless @name.nil?
|
75
|
+
# Using template name may show where name assignment is missing.
|
76
|
+
# Name assignment may also be missing in the task creation stage.
|
77
|
+
return File.basename(@template_name) unless @template_name.nil?
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class WriteTask
|
83
|
+
include TaskInterface
|
84
|
+
|
85
|
+
attr_reader :name, :contents, :executable
|
86
|
+
|
87
|
+
def initialize(name, contents, executable = false)
|
88
|
+
raise ArgumentError, "name and contents must be given" if name.nil? || contents.nil?
|
89
|
+
@name = name
|
90
|
+
@contents = contents
|
91
|
+
@executable = executable
|
92
|
+
end
|
93
|
+
|
94
|
+
def generate(context_binding)
|
95
|
+
@contents
|
96
|
+
end
|
97
|
+
|
98
|
+
def output_name
|
99
|
+
@name
|
100
|
+
end
|
101
|
+
end
|
metadata
CHANGED
@@ -1,42 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi-sourcetools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
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-
|
11
|
+
date: 2024-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
|
15
15
|
Tools for handling API specification in OpenAPI format. Replacement of
|
16
16
|
duplicate definitions with references. Other checks. Does not validate
|
17
|
-
the document against specification.
|
17
|
+
the document against OpenAPI format specification.
|
18
18
|
email: ismokarkkainen@icloud.com
|
19
19
|
executables:
|
20
20
|
- openapi-addheaders
|
21
|
+
- openapi-addparameters
|
21
22
|
- openapi-addresponses
|
22
23
|
- openapi-addschemas
|
23
24
|
- openapi-checkschemas
|
24
25
|
- openapi-frequencies
|
26
|
+
- openapi-generate
|
25
27
|
- openapi-merge
|
28
|
+
- openapi-modifypaths
|
26
29
|
- openapi-processpaths
|
27
30
|
extensions: []
|
28
31
|
extra_rdoc_files: []
|
29
32
|
files:
|
30
33
|
- LICENSE.txt
|
31
34
|
- bin/openapi-addheaders
|
35
|
+
- bin/openapi-addparameters
|
32
36
|
- bin/openapi-addresponses
|
33
37
|
- bin/openapi-addschemas
|
34
38
|
- bin/openapi-checkschemas
|
35
39
|
- bin/openapi-frequencies
|
40
|
+
- bin/openapi-generate
|
36
41
|
- bin/openapi-merge
|
42
|
+
- bin/openapi-modifypaths
|
37
43
|
- bin/openapi-processpaths
|
38
44
|
- lib/apiobjects.rb
|
39
45
|
- lib/common.rb
|
46
|
+
- lib/docs.rb
|
47
|
+
- lib/gen.rb
|
48
|
+
- lib/generate.rb
|
49
|
+
- lib/helper.rb
|
50
|
+
- lib/loaders.rb
|
51
|
+
- lib/output.rb
|
52
|
+
- lib/task.rb
|
40
53
|
homepage: https://xn--ismo-krkkinen-gfbd.fi/openapi-sourcetools/index.html
|
41
54
|
licenses:
|
42
55
|
- UPL-1.0
|