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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23e0cd1aefa69559d35171e477d8b749ec61251b67c51040b1130ce48fb57725
4
- data.tar.gz: 1fa4a40b88aa863d3eed153db455aed0bd4d9b9b88a4aa9ca8f92874e07613f5
3
+ metadata.gz: 211083083a2a3c53321234416ffff41bd1d00320a5ebbdab3ccdcd49576653b3
4
+ data.tar.gz: 34535443138a6308869974d62e79321d9ec9dbb3f46434c19d571075044751e1
5
5
  SHA512:
6
- metadata.gz: c56924c74979a016aeb8789bd7071f0d54e3c1de746c7bd92d9166cf0a03001f76f5b7b772d128f3e278bbc7835f52f7f139e4c71fe814032c49a252c24f6ece
7
- data.tar.gz: f7c87b497a0c154d99b7cbd33da06cce91e9a7aa8d6b8d72bc6b64f86a50fa1f93a93a0e96f922dab1fdb0c189989cd197f48d12ee5399254fe78120e286ddd4
6
+ metadata.gz: 3c4a5361e25a1cf7903b6038a32f4a94cf42579c0c4fefb21f885fae996c219a02eef322fd91d694dfe0ff668314685b990d3128ca7e6a98c3f30660639494d0
7
+ data.tar.gz: a563ba480140037e0ef91dbdc89fb62a2175954897726717976760879a74bf8284d673d3a259ce338b7b7493c84fd6c1f5cf4246b90ee42ae1959d3b31c7b21f
@@ -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, YAML.dump(doc, line_width: 1_000_000), 3)
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?
@@ -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, YAML.dump(doc, line_width: 1_000_000), 3)
69
+ dump_result(output_name, doc, 3)
70
70
  end
71
71
 
72
72
  exit(main) if (defined? $unit_test).nil?
@@ -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, YAML.dump(doc, line_width: 1_000_000), 3)
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
- # Should mapping JSON schema types to native types be a separate step?
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 Output
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.5.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-05-17 00:00:00.000000000 Z
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