openapi-sourcetools 0.7.1 → 0.8.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 +4 -4
- data/bin/openapi-addheaders +8 -7
- data/bin/openapi-addparameters +50 -9
- data/bin/openapi-addresponses +8 -8
- data/bin/openapi-addschemas +10 -9
- data/bin/openapi-addsecurityschemes +108 -0
- data/bin/openapi-checkschemas +16 -15
- data/bin/openapi-frequencies +23 -25
- data/bin/openapi-generate +17 -14
- data/bin/openapi-merge +6 -6
- data/bin/openapi-modifypaths +16 -15
- data/bin/openapi-processpaths +15 -26
- data/lib/openapi/sourcetools/apiobjects.rb +191 -0
- data/lib/openapi/sourcetools/common.rb +82 -0
- data/lib/openapi/sourcetools/config.rb +158 -0
- data/lib/openapi/sourcetools/docs.rb +41 -0
- data/lib/openapi/sourcetools/gen.rb +121 -0
- data/lib/openapi/sourcetools/generate.rb +95 -0
- data/lib/openapi/sourcetools/helper.rb +93 -0
- data/lib/openapi/sourcetools/loaders.rb +163 -0
- data/lib/openapi/sourcetools/output.rb +83 -0
- data/lib/openapi/sourcetools/task.rb +137 -0
- data/lib/openapi/sourcetools/version.rb +13 -0
- data/lib/openapi/sourcetools.rb +16 -0
- metadata +43 -18
- data/lib/apiobjects.rb +0 -306
- data/lib/common.rb +0 -114
- data/lib/docs.rb +0 -33
- data/lib/gen.rb +0 -104
- data/lib/generate.rb +0 -90
- data/lib/helper.rb +0 -94
- data/lib/loaders.rb +0 -96
- data/lib/output.rb +0 -58
- data/lib/task.rb +0 -101
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2024-2025 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
|
+
require_relative 'config'
|
11
|
+
require 'deep_merge'
|
12
|
+
require 'singleton'
|
13
|
+
|
14
|
+
# The generation module that contains things visible to tasks.
|
15
|
+
class Gen
|
16
|
+
include Singleton
|
17
|
+
|
18
|
+
@docsrc = []
|
19
|
+
def self.add_doc(symbol, docstr)
|
20
|
+
@docsrc.push("- #{symbol} : #{docstr}")
|
21
|
+
end
|
22
|
+
private_class_method :add_doc
|
23
|
+
|
24
|
+
def self.attrib_reader(symbol, docstr, initial_value = nil)
|
25
|
+
add_doc(symbol, docstr)
|
26
|
+
attr_reader(symbol)
|
27
|
+
instance_eval("def #{symbol}; Gen.instance.#{symbol}; end")
|
28
|
+
Gen.instance.instance_variable_set("@#{symbol}", initial_value) unless initial_value.nil?
|
29
|
+
end
|
30
|
+
private_class_method :attrib_reader
|
31
|
+
|
32
|
+
def self.attrib_accessor(symbol, docstr, initial_value = nil)
|
33
|
+
add_doc(symbol, docstr)
|
34
|
+
attr_accessor(symbol)
|
35
|
+
instance_eval("def #{symbol}; Gen.instance.#{symbol}; end")
|
36
|
+
instance_eval("def #{symbol}=(v); Gen.instance.#{symbol} = v; end")
|
37
|
+
Gen.instance.instance_variable_set("@#{symbol}", initial_value) unless initial_value.nil?
|
38
|
+
end
|
39
|
+
private_class_method :attrib_accessor
|
40
|
+
|
41
|
+
attrib_reader :doc, 'OpenAPI document.'
|
42
|
+
attrib_reader :outdir, 'Output directory name.'
|
43
|
+
attrib_reader :d, 'Other documents object.', OpenAPISourceTools::Docs.new
|
44
|
+
attrib_reader :wd, 'Original working directory', Dir.pwd
|
45
|
+
attrib_reader :configuration, 'Generator internal configuration'
|
46
|
+
attrib_accessor :config, 'Configuration file name for next gem or Ruby file.'
|
47
|
+
attrib_accessor :separator, 'Key separator in config file names.'
|
48
|
+
attrib_accessor :in_name, 'OpenAPI document name, nil if stdin.'
|
49
|
+
attrib_accessor :in_basename, 'OpenAPI document basename, nil if stdin.'
|
50
|
+
attrib_reader :g, 'Hash for storing values visible to all tasks.', {}
|
51
|
+
attrib_accessor :x, 'Hash for storing values visible to tasks from processor.', {}
|
52
|
+
attrib_accessor :h, 'Instance of class with helper methods.'
|
53
|
+
attrib_accessor :tasks, 'Tasks array.', []
|
54
|
+
attrib_accessor :t, 'Current task instance.'
|
55
|
+
attrib_accessor :task_index, 'Current task index.'
|
56
|
+
attrib_accessor :loaders, 'Array of processor loader methods.', []
|
57
|
+
attrib_accessor :output, 'Output-formatting helper.', OpenAPISourceTools::Output.new
|
58
|
+
|
59
|
+
def self.load_config(name_prefix, extensions = [ '.*' ])
|
60
|
+
cfg = {}
|
61
|
+
cfgs = OpenAPISourceTools::ConfigLoader.find_files(name_prefix:, extensions:)
|
62
|
+
cfgs = OpenAPISourceTools::ConfigLoader.read_contents(cfgs)
|
63
|
+
cfgs.each { |c| cfg.deep_merge!(c) }
|
64
|
+
cfg
|
65
|
+
end
|
66
|
+
|
67
|
+
def setup(document_content, input_name, output_directory, config_prefix)
|
68
|
+
@doc = document_content
|
69
|
+
@outdir = output_directory
|
70
|
+
unless input_name.nil?
|
71
|
+
@in_name = File.basename(input_name)
|
72
|
+
@in_basename = File.basename(input_name, '.*')
|
73
|
+
end
|
74
|
+
@configuration = Gen.load_config(config_prefix)
|
75
|
+
add_task(task: OpenAPISourceTools::HelperTask.new)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.setup(document_content, input_name, output_directory, config_prefix)
|
79
|
+
Gen.instance.setup(document_content, input_name, output_directory, config_prefix)
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_task(task:, name: nil, executable: false, x: nil)
|
83
|
+
@tasks.push(task)
|
84
|
+
# Since this method allows the user to pass their own task type instance,
|
85
|
+
# assign optional values with defaults only when clearly given.
|
86
|
+
@tasks.last.name = name unless name.nil?
|
87
|
+
@tasks.last.executable = executable unless executable == false
|
88
|
+
@tasks.last.x = x unless x.nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.add_task(task:, name: nil, executable: false, x: nil)
|
92
|
+
Gen.instance.add_task(task:, name:, executable:, x:)
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_write_content(name:, content:, executable: false)
|
96
|
+
add_task(task: OpenAPISourceTools::WriteTask.new(name, content, executable))
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.add_write_content(name:, content:, executable: false)
|
100
|
+
Gen.instance.add_write_content(name:, content:, executable:)
|
101
|
+
end
|
102
|
+
|
103
|
+
def add(source:, template: nil, template_name: nil, name: nil, executable: false, x: nil)
|
104
|
+
add_task(task: OpenAPISourceTools::Task.new(source, template, template_name),
|
105
|
+
name:, executable:, x:)
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.add(source:, template: nil, template_name: nil, name: nil, executable: false, x: nil)
|
109
|
+
Gen.instance.add(source:, template:, template_name:, name:, executable:, x:)
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.document
|
113
|
+
@docsrc.join("\n") + %(
|
114
|
+
- add_task(task:, name: nil, executable: false, x: nil) : Adds task object.
|
115
|
+
- add_write_content(name:, content:, executable: false) : Add file write task.
|
116
|
+
- add(source:, template: nil, template_name: nil, name: nil,
|
117
|
+
executable: false, x: nil) :
|
118
|
+
Adds template task with source as object to process.
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2024-2025 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
|
+
module OpenAPISourceTools
|
13
|
+
def self.executable_bits_on(mode)
|
14
|
+
mode = mode.to_s(8).chars
|
15
|
+
mode.size.times do |k|
|
16
|
+
m = mode[k].to_i(8)
|
17
|
+
# Applies to Unix-likes. Other system, check and handle.
|
18
|
+
m += 1 unless 3 < mode.size - k || m.zero? || m.odd?
|
19
|
+
mode[k] = m
|
20
|
+
end
|
21
|
+
m = 0
|
22
|
+
mode.each do |v|
|
23
|
+
m = 8 * m + v
|
24
|
+
end
|
25
|
+
m
|
26
|
+
end
|
27
|
+
|
28
|
+
# Runs all tasks that generate the results.
|
29
|
+
# Used internally by openapi-generate.
|
30
|
+
class Generator
|
31
|
+
def initialize(document_content, input_name, output_directory, config_prefix)
|
32
|
+
Gen.setup(document_content, input_name, output_directory, config_prefix)
|
33
|
+
Gen.loaders = Loaders.loaders
|
34
|
+
end
|
35
|
+
|
36
|
+
def context_binding
|
37
|
+
binding
|
38
|
+
end
|
39
|
+
|
40
|
+
def load(generator_names)
|
41
|
+
generator_names.each do |name|
|
42
|
+
idx = Gen.loaders.index { |loader| loader.call(name) }
|
43
|
+
return Common.aargh("No loader could handle #{name}", 2) if idx.nil?
|
44
|
+
end
|
45
|
+
0
|
46
|
+
rescue StandardError => e
|
47
|
+
Common.aargh(e.to_s, 2)
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate(t)
|
51
|
+
t.generate(context_binding)
|
52
|
+
rescue Exception => e
|
53
|
+
Common.aargh(e.to_s, 4)
|
54
|
+
end
|
55
|
+
|
56
|
+
def output_name(t, index)
|
57
|
+
name = t.output_name
|
58
|
+
name = "#{index}.txt" if name.nil?
|
59
|
+
File.join(Gen.outdir, name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def save(name, contents, executable)
|
63
|
+
d = File.dirname(name)
|
64
|
+
FileUtils.mkdir_p(d) unless File.directory?(d)
|
65
|
+
f = File.new(name, File::WRONLY | File::CREAT | File::TRUNC)
|
66
|
+
s = executable ? f.stat : nil
|
67
|
+
f.write(contents)
|
68
|
+
f.close
|
69
|
+
return unless executable
|
70
|
+
mode = OpenAPISourceTools.executable_bits_on(s.mode)
|
71
|
+
File.chmod(mode, name) unless mode == s.mode
|
72
|
+
end
|
73
|
+
|
74
|
+
def run
|
75
|
+
# This allows tasks to be added while processing.
|
76
|
+
# Not intended to be done but might prove handy.
|
77
|
+
# Also exposes current task index in case new task is added in the middle.
|
78
|
+
Gen.task_index = 0
|
79
|
+
while Gen.task_index < Gen.tasks.size
|
80
|
+
Gen.t = Gen.tasks[Gen.task_index]
|
81
|
+
out = generate(Gen.t)
|
82
|
+
Gen.task_index += 1
|
83
|
+
return out if out.is_a?(Integer)
|
84
|
+
next if Gen.t.discard || out.empty?
|
85
|
+
name = output_name(Gen.t, Gen.task_index - 1)
|
86
|
+
begin
|
87
|
+
save(name, out, Gen.t.executable)
|
88
|
+
rescue StandardError => e
|
89
|
+
return Common.aargh("Error writing output file: #{name}\n#{e}", 3)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
0
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2024-2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
require_relative 'task'
|
7
|
+
|
8
|
+
module OpenAPISourceTools
|
9
|
+
# Helper class supposed to contain helpful methods.
|
10
|
+
# Exposed as Gen.h if HelperTask has been run. It is automatically
|
11
|
+
# added as the first task but later tasks can remove it.
|
12
|
+
class Helper
|
13
|
+
attr_reader :doc, :parents
|
14
|
+
attr_accessor :parent_parameters
|
15
|
+
|
16
|
+
# Stores the nearest Hash for each Hash.
|
17
|
+
def store_parents(obj, parent = nil)
|
18
|
+
if obj.is_a?(Hash)
|
19
|
+
@parents[obj] = parent
|
20
|
+
obj.each_value do |v|
|
21
|
+
store_parents(v, obj)
|
22
|
+
end
|
23
|
+
elsif obj.is_a?(Array)
|
24
|
+
obj.each do |v|
|
25
|
+
store_parents(v, parent)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(doc)
|
31
|
+
@doc = doc
|
32
|
+
@parents = {}.compare_by_identity
|
33
|
+
store_parents(@doc)
|
34
|
+
end
|
35
|
+
|
36
|
+
def parent(object)
|
37
|
+
@parents[object]
|
38
|
+
end
|
39
|
+
|
40
|
+
COMPONENTS = '#/components/'
|
41
|
+
|
42
|
+
def category_and_name(ref_or_obj)
|
43
|
+
ref = ref_or_obj.is_a?(Hash) ? ref_or_obj['$ref'] : ref_or_obj
|
44
|
+
return nil unless ref.is_a?(String)
|
45
|
+
return nil unless ref.start_with?(Helper::COMPONENTS)
|
46
|
+
idx = ref.index('/', Helper::COMPONENTS.size)
|
47
|
+
return nil if idx.nil?
|
48
|
+
category = ref[Helper::COMPONENTS.size...idx]
|
49
|
+
[ category, ref[(idx + 1)...ref.size] ]
|
50
|
+
end
|
51
|
+
|
52
|
+
def dereference(ref_or_obj)
|
53
|
+
cn = category_and_name(ref_or_obj)
|
54
|
+
return nil if cn.nil?
|
55
|
+
cs = @doc.dig('components', cn.first) || {}
|
56
|
+
cs[cn.last]
|
57
|
+
end
|
58
|
+
|
59
|
+
def basename(ref_or_obj)
|
60
|
+
cn = category_and_name(ref_or_obj)
|
61
|
+
return nil if cn.nil?
|
62
|
+
cn.last
|
63
|
+
end
|
64
|
+
|
65
|
+
def parameters(operation_object, empty_unless_local = false)
|
66
|
+
return [] if empty_unless_local && !operation_object.key?('parameters')
|
67
|
+
cps = @doc.dig('components', 'parameters') || {}
|
68
|
+
uniqs = {}
|
69
|
+
path_item_object = parent(operation_object)
|
70
|
+
[path_item_object, operation_object].each do |p|
|
71
|
+
p.fetch('parameters', []).each do |param|
|
72
|
+
r = basename(param)
|
73
|
+
r = cps[r] if r.is_a?(String)
|
74
|
+
uniqs["#{r['name']}:#{r['in']}"] = param
|
75
|
+
end
|
76
|
+
end
|
77
|
+
uniqs.keys.sort!.map { |k| uniqs[k] }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Task class to add an Helper instance to Gen.h, for convenience.
|
82
|
+
class HelperTask
|
83
|
+
include OpenAPISourceTools::TaskInterface
|
84
|
+
|
85
|
+
def generate(_context_binding)
|
86
|
+
Gen.h = Helper.new(Gen.doc) if Gen.h.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
def discard
|
90
|
+
true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2024-2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
require_relative 'task'
|
7
|
+
require_relative 'gen'
|
8
|
+
|
9
|
+
|
10
|
+
# Original loader functions. These are accessible via Gen.loaders. New loaders
|
11
|
+
# should be added there.
|
12
|
+
module OpenAPISourceTools
|
13
|
+
# Loaders used to load gems and files and set config etc.
|
14
|
+
# Exposed as Gen.loaders if you need to modify the array.
|
15
|
+
module Loaders
|
16
|
+
# Prefix etc. and loader pairs for all loaders.
|
17
|
+
|
18
|
+
REQ_PREFIX = 'req:'
|
19
|
+
|
20
|
+
def self.req_loader(name)
|
21
|
+
return false unless name.downcase.start_with?(REQ_PREFIX)
|
22
|
+
begin
|
23
|
+
t = OpenAPISourceTools::RestoreProcessorStorage.new({})
|
24
|
+
Gen.tasks.push(t)
|
25
|
+
base = name.slice(REQ_PREFIX.size...name.size)
|
26
|
+
require(base)
|
27
|
+
Gen.config = nil
|
28
|
+
t.x = Gen.x # In case setup code replaced the object.
|
29
|
+
rescue LoadError => e
|
30
|
+
raise StandardError, "Failed to require #{name}\n#{e}"
|
31
|
+
rescue Exception => e
|
32
|
+
raise StandardError, "Problem with #{name}\n#{e}\n#{e.backtrace.join("\n")}"
|
33
|
+
end
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
EVAL_PREFIX = 'eval:'
|
38
|
+
|
39
|
+
def self.eval_loader(name)
|
40
|
+
return false unless name.downcase.start_with?(EVAL_PREFIX)
|
41
|
+
begin
|
42
|
+
t = OpenAPISourceTools::RestoreProcessorStorage.new({})
|
43
|
+
Gen.tasks.push(t)
|
44
|
+
code = name.slice(EVAL_PREFIX.size...name.size)
|
45
|
+
eval(code)
|
46
|
+
Gen.config = nil
|
47
|
+
t.x = Gen.x # In case setup code replaced the object.
|
48
|
+
rescue Exception => e
|
49
|
+
raise StandardError, "Problem with #{name}\n#{e}\n#{e.backtrace.join("\n")}"
|
50
|
+
end
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
RUBY_EXT = '.rb'
|
55
|
+
|
56
|
+
def self.ruby_loader(name)
|
57
|
+
return false unless name.downcase.end_with?(RUBY_EXT)
|
58
|
+
origwd = Dir.pwd
|
59
|
+
d = File.dirname(name)
|
60
|
+
Dir.chdir(d) unless d == '.'
|
61
|
+
begin
|
62
|
+
t = OpenAPISourceTools::RestoreProcessorStorage.new({})
|
63
|
+
Gen.tasks.push(t)
|
64
|
+
base = File.basename(name)
|
65
|
+
Gen.config = base[0..-4] if Gen.config.nil?
|
66
|
+
load(File.join(Dir.pwd, base))
|
67
|
+
Gen.config = nil
|
68
|
+
t.x = Gen.x # In case setup code replaced the object.
|
69
|
+
rescue LoadError => e
|
70
|
+
raise StandardError, "Failed to load #{name}\n#{e}"
|
71
|
+
rescue Exception => e
|
72
|
+
raise StandardError, "Problem with #{name}\n#{e}\n#{e.backtrace.join("\n")}"
|
73
|
+
end
|
74
|
+
Dir.chdir(origwd) unless d == '.'
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
YAML_PREFIX = 'yaml:'
|
79
|
+
YAML_EXTS = [ '.yaml', '.yml' ].freeze
|
80
|
+
|
81
|
+
def self.yaml_loader(name)
|
82
|
+
d = name.downcase
|
83
|
+
if d.start_with?(YAML_PREFIX)
|
84
|
+
name = name.slice(YAML_PREFIX.size...name.size)
|
85
|
+
elsif (YAML_EXTS.index { |s| d.end_with?(s) }).nil?
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
n, _sep, f = name.partition(':')
|
89
|
+
raise StandardError, 'No name given.' if n.empty?
|
90
|
+
raise StandardError, 'No filename given.' if f.empty?
|
91
|
+
doc = YAML.safe_load_file(f)
|
92
|
+
raise StandardError, "#{name} #{n} exists already." unless Gen.d.add(n, doc)
|
93
|
+
true
|
94
|
+
rescue Errno::ENOENT
|
95
|
+
raise StandardError, "Not found: #{f}\n#{e}"
|
96
|
+
rescue Exception => e # Whatever was raised, we want it.
|
97
|
+
raise StandardError, "Failed to read as YAML: #{f}\n#{e}"
|
98
|
+
end
|
99
|
+
|
100
|
+
BIN_PREFIX = 'bin:'
|
101
|
+
|
102
|
+
def self.bin_loader(name)
|
103
|
+
return false unless name.downcase.start_with?(BIN_PREFIX)
|
104
|
+
n, _sep, f = name.slice(BIN_PREFIX.size...name.size).partition(':')
|
105
|
+
raise StandardError, 'No name given.' if n.empty?
|
106
|
+
raise StandardError, 'No filename given.' if f.empty?
|
107
|
+
doc = File.binread(f)
|
108
|
+
raise StandardError, "#{name} #{n} exists already." unless Gen.d.add(n, doc)
|
109
|
+
true
|
110
|
+
rescue Errno::ENOENT
|
111
|
+
raise StandardError, "Not found: #{f}\n#{e}"
|
112
|
+
rescue Exception => e # Whatever was raised, we want it.
|
113
|
+
raise StandardError, "Failed to read #{f}\n#{e}"
|
114
|
+
end
|
115
|
+
|
116
|
+
CONFIG_PREFIX = 'config:'
|
117
|
+
|
118
|
+
def self.config_loader(name)
|
119
|
+
return false unless name.downcase.start_with?(CONFIG_PREFIX)
|
120
|
+
raise StandardError, "Config name remains: #{Gen.config}" unless Gen.config.nil?
|
121
|
+
n = name.slice(CONFIG_PREFIX.size...name.size)
|
122
|
+
raise StandardError, 'No name given.' if n.empty?
|
123
|
+
# Interpretation left completely to config loading.
|
124
|
+
Gen.config = n
|
125
|
+
true
|
126
|
+
end
|
127
|
+
|
128
|
+
SEPARATOR_PREFIX = 'separator:'
|
129
|
+
|
130
|
+
def self.separator_loader(name)
|
131
|
+
return false unless name.downcase.start_with?(SEPARATOR_PREFIX)
|
132
|
+
n = name.slice(SEPARATOR_PREFIX.size...name.size)
|
133
|
+
n = nil if n.empty?
|
134
|
+
Gen.separator = n
|
135
|
+
true
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.loaders
|
139
|
+
[
|
140
|
+
method(:req_loader),
|
141
|
+
method(:eval_loader),
|
142
|
+
method(:ruby_loader),
|
143
|
+
method(:yaml_loader),
|
144
|
+
method(:bin_loader),
|
145
|
+
method(:config_loader),
|
146
|
+
method(:separator_loader)
|
147
|
+
]
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.document
|
151
|
+
<<EOB
|
152
|
+
- #{Loaders::REQ_PREFIX}req_name : requires the gem.
|
153
|
+
- #{Loaders::EVAL_PREFIX}code : runs code to add gem tasks again.
|
154
|
+
- ruby_file#{Loaders::RUBY_EXT} : changes to Ruby file directory and requires the file.
|
155
|
+
- #{Loaders::YAML_PREFIX}name:filename : Loads YAML file into Gen.d.name.
|
156
|
+
- name:filename.{#{(Loaders::YAML_EXTS.map { |s| s[1...s.size] }).join('|')}} : Loads YAML file into Gen.d.name.
|
157
|
+
- #{Loaders::BIN_PREFIX}name:filename : Loads binary file into Gen.d.name.
|
158
|
+
- #{Loaders::CONFIG_PREFIX}name : Sets Gen.config for next gem/Ruby file configuration loading.
|
159
|
+
- #{Loaders::SEPARATOR_PREFIX}string : Sets Gen.separator to string.
|
160
|
+
EOB
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2024-2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
|
7
|
+
module OpenAPISourceTools
|
8
|
+
# Output configuration settings for easy storage.
|
9
|
+
# You can have it in configuration and pass hash to initialize.
|
10
|
+
class OutputConfiguration
|
11
|
+
attr_reader :indent_character, :indent_step
|
12
|
+
attr_reader :tab, :tab_replaces_count
|
13
|
+
|
14
|
+
def initialize(cfg = {})
|
15
|
+
@indent_character = cfg['indent_character'] || ' '
|
16
|
+
@indent_step = cfg['indent_step'] || 4
|
17
|
+
@tab = cfg['tab'] || "\t"
|
18
|
+
@tab_replaces_count = cfg['tab_replaces_count'] || 0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Output indentation helper class.
|
23
|
+
# Exposed as Gen.output for use from templates.
|
24
|
+
class Output
|
25
|
+
attr_reader :config
|
26
|
+
attr_accessor :last_indent
|
27
|
+
|
28
|
+
def initialize(cfg = OutputConfiguration.new)
|
29
|
+
@config = cfg
|
30
|
+
@last_indent = 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def config=(cfg)
|
34
|
+
cfg = OutputConfiguration.new(cfg) if cfg.is_a?(Hash)
|
35
|
+
raise ArgumentError, "Expected OutputConfiguration or Hash, got #{cfg.class}" unless cfg.is_a?(OutputConfiguration)
|
36
|
+
@config = cfg
|
37
|
+
@last_indent = 0
|
38
|
+
end
|
39
|
+
|
40
|
+
# Takes an array of code blocks/lines or integers/booleans and produces
|
41
|
+
# indented output using the separator character.
|
42
|
+
# Set class attributes to obtain desired outcome.
|
43
|
+
def join(blocks, separator = "\n")
|
44
|
+
indented = []
|
45
|
+
blocks.flatten!
|
46
|
+
indent = 0
|
47
|
+
blocks.each do |block|
|
48
|
+
if block.nil?
|
49
|
+
indent = 0
|
50
|
+
elsif block.is_a?(Integer)
|
51
|
+
indent += block
|
52
|
+
elsif block.is_a?(TrueClass)
|
53
|
+
indent += @config.indent_step
|
54
|
+
elsif block.is_a?(FalseClass)
|
55
|
+
indent -= @config.indent_step
|
56
|
+
else
|
57
|
+
block = block.to_s unless block.is_a?(String)
|
58
|
+
if block.empty?
|
59
|
+
indented.push('')
|
60
|
+
next
|
61
|
+
end
|
62
|
+
if indent.zero?
|
63
|
+
indented.push(block)
|
64
|
+
next
|
65
|
+
end
|
66
|
+
if @config.tab_replaces_count.positive?
|
67
|
+
tabs = @config.tab * (indent / @config.tab_replaces_count)
|
68
|
+
chars = @config.indent_character * (indent % @config.tab_replaces_count)
|
69
|
+
else
|
70
|
+
tabs = ''
|
71
|
+
chars = @config.indent_character * indent
|
72
|
+
end
|
73
|
+
lines = block.lines(chomp: true)
|
74
|
+
lines.each do |line|
|
75
|
+
indented.push("#{tabs}#{chars}#{line}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
@last_indent = indent
|
80
|
+
indented.join(separator)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2024-2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
require_relative 'common'
|
7
|
+
require 'erb'
|
8
|
+
|
9
|
+
|
10
|
+
module OpenAPISourceTools
|
11
|
+
|
12
|
+
# Required interface for tasks, with default implementation for some methods.
|
13
|
+
module TaskInterface
|
14
|
+
def generate(context_binding)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def output_name
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def discard
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def executable
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def system
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Loads template or takes it as argument. Renders template using ERB.
|
36
|
+
class Task
|
37
|
+
include TaskInterface
|
38
|
+
|
39
|
+
attr_reader :src, :template, :template_name
|
40
|
+
attr_accessor :name, :executable, :discard, :x
|
41
|
+
|
42
|
+
def initialize(src, template, template_name)
|
43
|
+
@src = src
|
44
|
+
@template = template
|
45
|
+
@template_name = template_name
|
46
|
+
if @template.nil?
|
47
|
+
raise ArgumentError, 'template_name or template must be given' if @template_name.nil?
|
48
|
+
begin
|
49
|
+
@template = File.read(@template_name)
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
raise StandardError, "Could not load #{@template_name}"
|
52
|
+
rescue StandardError => e
|
53
|
+
raise StandardError, "#{e}\nFailed to read #{@template_name}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
@name = nil
|
57
|
+
@executable = false
|
58
|
+
@discard = false
|
59
|
+
@x = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# If this is overridden to perform some processing but not to produce output,
|
63
|
+
# set @discard = true and return value will be ignored. No other methods are
|
64
|
+
# called in that case.
|
65
|
+
def internal_generate(context_binding)
|
66
|
+
ERB.new(@template).result(context_binding)
|
67
|
+
end
|
68
|
+
|
69
|
+
# You can override this instead of internal_generate if you do not need the
|
70
|
+
# exception handling.
|
71
|
+
def generate(context_binding)
|
72
|
+
n = @template_name.nil? ? '' : "#{@template_name} "
|
73
|
+
internal_generate(context_binding)
|
74
|
+
rescue SyntaxError => e
|
75
|
+
OpenAPISourceTools::Common.aargh("Template #{n}syntax error: #{e.full_message}", 5)
|
76
|
+
rescue Exception => e # Some unexpected error.
|
77
|
+
OpenAPISourceTools::Common.aargh("Template #{n}error: #{e.full_message}", 6)
|
78
|
+
end
|
79
|
+
|
80
|
+
# This is only called when generate produced output that is not discarded.
|
81
|
+
def output_name
|
82
|
+
return @name unless @name.nil?
|
83
|
+
# Using template name may show where name assignment is missing.
|
84
|
+
# Name assignment may also be missing in the task creation stage.
|
85
|
+
return File.basename(@template_name) unless @template_name.nil?
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# A task that provides contents to be written to a named file.
|
91
|
+
# Optionaly the file may be made executable.
|
92
|
+
class WriteTask
|
93
|
+
include TaskInterface
|
94
|
+
|
95
|
+
attr_reader :name, :contents, :executable
|
96
|
+
|
97
|
+
def initialize(name, contents, executable = false)
|
98
|
+
raise ArgumentError, 'name and contents must be given' if name.nil? || contents.nil?
|
99
|
+
@name = name
|
100
|
+
@contents = contents
|
101
|
+
@executable = executable
|
102
|
+
end
|
103
|
+
|
104
|
+
def generate(_context_binding)
|
105
|
+
@contents
|
106
|
+
end
|
107
|
+
|
108
|
+
def output_name
|
109
|
+
@name
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Sets Gen.x to empty hash. Inserted after gem tasks.
|
114
|
+
class RestoreProcessorStorage
|
115
|
+
include TaskInterface
|
116
|
+
|
117
|
+
attr_accessor :x # Allows setting the current value when setup code finishes.
|
118
|
+
|
119
|
+
# Sets initial default value.
|
120
|
+
def initialize(x = {})
|
121
|
+
@x = x
|
122
|
+
Gen.x = @x
|
123
|
+
end
|
124
|
+
|
125
|
+
def generate(_context_binding)
|
126
|
+
Gen.x = @x # Restore whatever was current when setup code finished.
|
127
|
+
end
|
128
|
+
|
129
|
+
def discard
|
130
|
+
true
|
131
|
+
end
|
132
|
+
|
133
|
+
def system
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
module OpenAPISourceTools
|
7
|
+
NAME = 'openapi-sourcetools'
|
8
|
+
VERSION = '0.8.1'
|
9
|
+
|
10
|
+
def self.info(separator = ': ')
|
11
|
+
"#{NAME}#{separator}#{VERSION}"
|
12
|
+
end
|
13
|
+
end
|