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.
@@ -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