lex-codegen 0.1.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,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Codegen
6
+ module Helpers
7
+ module Constants
8
+ TEMPLATE_TYPES = %i[gemspec gemfile rubocop ci rspec gitignore license version entry_point spec_helper runner client].freeze
9
+ DEFAULT_RUBY_VERSION = '3.4'
10
+ DEFAULT_LICENSE = 'MIT'
11
+ DEFAULT_AUTHOR = 'Esity'
12
+ DEFAULT_EMAIL = 'matthewdiverson@gmail.com'
13
+ DEFAULT_GEM_VERSION = '0.1.0'
14
+
15
+ GEMSPEC_TEMPLATE = <<~'ERB'
16
+ # frozen_string_literal: true
17
+
18
+ require_relative 'lib/legion/extensions/<%= gem_name_underscored %>/version'
19
+
20
+ Gem::Specification.new do |spec|
21
+ spec.name = '<%= gem_name %>'
22
+ spec.version = Legion::Extensions::<%= module_name %>::VERSION
23
+ spec.authors = ['<%= author %>']
24
+ spec.email = ['<%= email %>']
25
+
26
+ spec.summary = 'Legion::Extensions::<%= module_name %>'
27
+ spec.description = '<%= description %>'
28
+ spec.homepage = 'https://github.com/LegionIO/<%= gem_name %>'
29
+ spec.license = '<%= license %>'
30
+ spec.required_ruby_version = '>= <%= ruby_version %>'
31
+
32
+ spec.metadata['homepage_uri'] = spec.homepage
33
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/<%= gem_name %>'
34
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/<%= gem_name %>'
35
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/<%= gem_name %>'
36
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/<%= gem_name %>/issues'
37
+ spec.metadata['rubygems_mfa_required'] = 'true'
38
+
39
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
40
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
41
+ end
42
+ spec.require_paths = ['lib']
43
+
44
+ spec.add_dependency 'legionio'
45
+ <% extra_deps.each do |dep| -%>
46
+ spec.add_dependency '<%= dep %>'
47
+ <% end -%>
48
+
49
+ spec.add_development_dependency 'rake'
50
+ spec.add_development_dependency 'rspec', '~> 3.13'
51
+ spec.add_development_dependency 'rspec_junit_formatter'
52
+ spec.add_development_dependency 'rubocop', '~> 1.75'
53
+ spec.add_development_dependency 'rubocop-rspec'
54
+ spec.add_development_dependency 'simplecov'
55
+ end
56
+ ERB
57
+
58
+ GEMFILE_TEMPLATE = <<~ERB
59
+ # frozen_string_literal: true
60
+
61
+ source 'https://rubygems.org'
62
+ gemspec
63
+
64
+ group :test do
65
+ gem 'rake'
66
+ gem 'rspec', '~> 3.13'
67
+ gem 'rspec_junit_formatter'
68
+ gem 'rubocop', '~> 1.75'
69
+ gem 'rubocop-rspec'
70
+ gem 'simplecov'
71
+ end
72
+ ERB
73
+
74
+ RUBOCOP_TEMPLATE = <<~ERB
75
+ AllCops:
76
+ TargetRubyVersion: <%= ruby_version %>
77
+ NewCops: enable
78
+ SuggestExtensions: false
79
+
80
+ Layout/LineLength:
81
+ Max: 160
82
+
83
+ Layout/SpaceAroundEqualsInParameterDefault:
84
+ EnforcedStyle: space
85
+
86
+ Layout/HashAlignment:
87
+ EnforcedHashRocketStyle: table
88
+ EnforcedColonStyle: table
89
+
90
+ Metrics/MethodLength:
91
+ Max: 50
92
+
93
+ Metrics/ClassLength:
94
+ Max: 1500
95
+
96
+ Metrics/ModuleLength:
97
+ Max: 1500
98
+
99
+ Metrics/BlockLength:
100
+ Max: 40
101
+
102
+ Metrics/AbcSize:
103
+ Max: 60
104
+
105
+ Metrics/CyclomaticComplexity:
106
+ Max: 15
107
+
108
+ Metrics/PerceivedComplexity:
109
+ Max: 17
110
+
111
+ Style/Documentation:
112
+ Enabled: false
113
+
114
+ Style/SymbolArray:
115
+ Enabled: true
116
+
117
+ Style/FrozenStringLiteralComment:
118
+ Enabled: true
119
+ EnforcedStyle: always
120
+
121
+ Naming/FileName:
122
+ Enabled: false
123
+
124
+ Naming/PredicateMethod:
125
+ Enabled: false
126
+
127
+ Naming/PredicatePrefix:
128
+ Enabled: false
129
+ ERB
130
+
131
+ CI_TEMPLATE = <<~ERB
132
+ name: CI
133
+ on: [push, pull_request]
134
+ jobs:
135
+ ci:
136
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
137
+ ERB
138
+
139
+ RSPEC_TEMPLATE = <<~ERB
140
+ --format documentation
141
+ --color
142
+ --require spec_helper
143
+ ERB
144
+
145
+ GITIGNORE_TEMPLATE = <<~ERB
146
+ /.bundle/
147
+ /.yardoc
148
+ /_yardoc/
149
+ /coverage/
150
+ /doc/
151
+ /pkg/
152
+ /spec/reports/
153
+ /tmp/
154
+
155
+ # rspec failure tracking
156
+ .rspec_status
157
+ ERB
158
+
159
+ LICENSE_TEMPLATE = <<~ERB
160
+ MIT License
161
+
162
+ Copyright (c) 2024 <%= author %>
163
+
164
+ Permission is hereby granted, free of charge, to any person obtaining a copy
165
+ of this software and associated documentation files (the "Software"), to deal
166
+ in the Software without restriction, including without limitation the rights
167
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
168
+ copies of the Software, and to permit persons to whom the Software is
169
+ furnished to do so, subject to the following conditions:
170
+
171
+ The above copyright notice and this permission notice shall be included in all
172
+ copies or substantial portions of the Software.
173
+
174
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
175
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
176
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
177
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
178
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
179
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
180
+ SOFTWARE.
181
+ ERB
182
+
183
+ VERSION_TEMPLATE = <<~ERB
184
+ # frozen_string_literal: true
185
+
186
+ module Legion
187
+ module Extensions
188
+ module <%= module_name %>
189
+ VERSION = '<%= gem_version %>'
190
+ end
191
+ end
192
+ end
193
+ ERB
194
+
195
+ ENTRY_POINT_TEMPLATE = <<~ERB
196
+ # frozen_string_literal: true
197
+
198
+ require 'securerandom'
199
+ require_relative '<%= gem_name_underscored %>/version'
200
+ require_relative '<%= gem_name_underscored %>/helpers/constants'
201
+ <% helpers.each do |h| -%>
202
+ require_relative '<%= gem_name_underscored %>/helpers/<%= h %>'
203
+ <% end -%>
204
+ <% runner_names.each do |r| -%>
205
+ require_relative '<%= gem_name_underscored %>/runners/<%= r %>'
206
+ <% end -%>
207
+ require_relative '<%= gem_name_underscored %>/client'
208
+
209
+ module Legion
210
+ module Extensions
211
+ module <%= module_name %>
212
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
213
+ end
214
+ end
215
+ end
216
+ ERB
217
+
218
+ SPEC_HELPER_TEMPLATE = <<~ERB
219
+ # frozen_string_literal: true
220
+
221
+ require 'bundler/setup'
222
+ require 'legion/extensions/<%= gem_name_underscored %>'
223
+
224
+ # Stub Legion::Logging when running standalone
225
+ unless defined?(Legion::Logging)
226
+ module Legion
227
+ module Logging
228
+ def self.info(*); end
229
+ def self.debug(*); end
230
+ def self.warn(*); end
231
+ def self.error(*); end
232
+ end
233
+ end
234
+ end
235
+
236
+ RSpec.configure do |config|
237
+ config.example_status_persistence_file_path = '.rspec_status'
238
+ config.disable_monkey_patching!
239
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
240
+ end
241
+ ERB
242
+
243
+ RUNNER_TEMPLATE = <<~ERB
244
+ # frozen_string_literal: true
245
+
246
+ module Legion
247
+ module Extensions
248
+ module <%= module_name %>
249
+ module Runners
250
+ module <%= runner_class %>
251
+ extend self
252
+ <% methods.each do |m| -%>
253
+
254
+ def <%= m[:name] %>(<%= m[:params].join(', ') %><%= m[:params].empty? ? '**' : ', **' %>)
255
+ Legion::Logging.debug "[<%= gem_name_underscored %>] <%= m[:name] %> called"
256
+ { success: true }
257
+ end
258
+ <% end -%>
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
264
+ ERB
265
+
266
+ CLIENT_TEMPLATE = <<~ERB
267
+ # frozen_string_literal: true
268
+
269
+ module Legion
270
+ module Extensions
271
+ module <%= module_name %>
272
+ class Client
273
+ <% runner_names.each do |r| -%>
274
+ include Runners::<%= r.split('_').map(&:capitalize).join %>
275
+ <% end -%>
276
+
277
+ def initialize(base_path: Dir.pwd)
278
+ @base_path = base_path
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
284
+ ERB
285
+
286
+ TEMPLATE_MAP = {
287
+ gemspec: GEMSPEC_TEMPLATE,
288
+ gemfile: GEMFILE_TEMPLATE,
289
+ rubocop: RUBOCOP_TEMPLATE,
290
+ ci: CI_TEMPLATE,
291
+ rspec: RSPEC_TEMPLATE,
292
+ gitignore: GITIGNORE_TEMPLATE,
293
+ license: LICENSE_TEMPLATE,
294
+ version: VERSION_TEMPLATE,
295
+ entry_point: ENTRY_POINT_TEMPLATE,
296
+ spec_helper: SPEC_HELPER_TEMPLATE,
297
+ runner: RUNNER_TEMPLATE,
298
+ client: CLIENT_TEMPLATE
299
+ }.freeze
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Codegen
8
+ module Helpers
9
+ class FileWriter
10
+ def initialize(base_path:)
11
+ @base_path = base_path
12
+ end
13
+
14
+ def write(relative_path, content)
15
+ full_path = ::File.join(@base_path, relative_path)
16
+ ::FileUtils.mkdir_p(::File.dirname(full_path))
17
+ ::File.write(full_path, content)
18
+ { path: full_path, bytes: content.bytesize }
19
+ end
20
+
21
+ def write_all(files)
22
+ files.map { |path, content| write(path, content) }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Codegen
6
+ module Helpers
7
+ class SpecGenerator
8
+ def initialize
9
+ # no state needed
10
+ end
11
+
12
+ def generate_runner_spec(module_name:, runner_name:, methods: [])
13
+ runner_class = runner_name.split('_').map(&:capitalize).join
14
+ lines = [
15
+ '# frozen_string_literal: true',
16
+ '',
17
+ "RSpec.describe Legion::Extensions::#{module_name}::Runners::#{runner_class} do",
18
+ ' subject(:runner) { described_class }',
19
+ ''
20
+ ]
21
+
22
+ methods.each do |m|
23
+ method_name = m[:name]
24
+ lines << " describe '.#{method_name}' do"
25
+ lines << " it 'responds to #{method_name}' do"
26
+ lines << " expect(runner).to respond_to(:#{method_name})"
27
+ lines << ' end'
28
+ lines << ''
29
+ lines << " it 'returns a hash with success key' do"
30
+
31
+ call_args = build_call_args(m[:params])
32
+ lines << " result = runner.#{method_name}(#{call_args})"
33
+ lines << ' expect(result).to be_a(Hash)'
34
+ lines << ' expect(result).to have_key(:success)'
35
+ lines << ' end'
36
+ lines << ' end'
37
+ lines << ''
38
+ end
39
+
40
+ lines << 'end'
41
+ "#{lines.join("\n")}\n"
42
+ end
43
+
44
+ def generate_client_spec(module_name:, runner_name: nil, methods: []) # rubocop:disable Lint/UnusedMethodArgument
45
+ lines = [
46
+ '# frozen_string_literal: true',
47
+ '',
48
+ "RSpec.describe Legion::Extensions::#{module_name}::Client do",
49
+ ' subject(:client) { described_class.new }',
50
+ ''
51
+ ]
52
+
53
+ lines << " it 'instantiates successfully' do"
54
+ lines << ' expect(client).to be_a(described_class)'
55
+ lines << ' end'
56
+ lines << ''
57
+
58
+ methods.each do |m|
59
+ lines << " it 'responds to #{m[:name]}' do"
60
+ lines << " expect(client).to respond_to(:#{m[:name]})"
61
+ lines << ' end'
62
+ lines << ''
63
+ end
64
+
65
+ lines << 'end'
66
+ "#{lines.join("\n")}\n"
67
+ end
68
+
69
+ def generate_helper_spec(module_name:, helper_name:, methods: [])
70
+ helper_class = helper_name.split('_').map(&:capitalize).join
71
+ lines = [
72
+ '# frozen_string_literal: true',
73
+ '',
74
+ "RSpec.describe Legion::Extensions::#{module_name}::Helpers::#{helper_class} do",
75
+ ' subject(:helper) { described_class.new }',
76
+ ''
77
+ ]
78
+
79
+ lines << " it 'instantiates successfully' do"
80
+ lines << ' expect(helper).to be_a(described_class)'
81
+ lines << ' end'
82
+ lines << ''
83
+
84
+ methods.each do |m|
85
+ lines << " it 'responds to #{m[:name]}' do"
86
+ lines << " expect(helper).to respond_to(:#{m[:name]})"
87
+ lines << ' end'
88
+ lines << ''
89
+ end
90
+
91
+ lines << 'end'
92
+ "#{lines.join("\n")}\n"
93
+ end
94
+
95
+ private
96
+
97
+ def build_call_args(params)
98
+ return '' if params.nil? || params.empty?
99
+
100
+ keyword_params = params.reject { |p| ['**', '*args'].include?(p) }
101
+ return '' if keyword_params.empty?
102
+
103
+ keyword_params.map do |p|
104
+ name = p.to_s.gsub(/[:\s].*/, '').chomp(':')
105
+ "#{name}: nil"
106
+ end.join(', ')
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Codegen
8
+ module Helpers
9
+ class TemplateEngine
10
+ def initialize
11
+ # no state needed
12
+ end
13
+
14
+ def render(template_type, variables = {})
15
+ key = template_type.to_sym
16
+ raise ArgumentError, "Unknown template type: #{template_type}" unless Constants::TEMPLATE_MAP.key?(key)
17
+
18
+ render_string(Constants::TEMPLATE_MAP[key], variables)
19
+ end
20
+
21
+ def render_string(template_string, variables = {})
22
+ ERB.new(template_string, trim_mode: '-').result(binding_with(variables))
23
+ end
24
+
25
+ private
26
+
27
+ def binding_with(variables)
28
+ ctx = Object.new
29
+ variables.each do |key, value|
30
+ ctx.instance_variable_set(:"@#{key}", value)
31
+ ctx.define_singleton_method(key) { instance_variable_get(:"@#{key}") }
32
+ end
33
+ ctx.instance_eval { binding }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Codegen
6
+ module Runners
7
+ module Generate
8
+ extend self
9
+
10
+ def scaffold_extension(name:, module_name:, description:, category: :cognition, # rubocop:disable Lint/UnusedMethodArgument
11
+ helpers: [], runner_methods: [], base_path: nil, **)
12
+ base_path ||= ::Dir.pwd
13
+ ext_path = ::File.join(base_path, "lex-#{name}")
14
+ gem_name = "lex-#{name}"
15
+ underscored = name.to_s.gsub('-', '_')
16
+ runner_names = runner_methods.map { |r| r[:name] }
17
+
18
+ engine = Helpers::TemplateEngine.new
19
+ writer = Helpers::FileWriter.new(base_path: ext_path)
20
+ spec_gen = Helpers::SpecGenerator.new
21
+
22
+ variables = build_variables(
23
+ gem_name: gem_name,
24
+ underscored: underscored,
25
+ module_name: module_name,
26
+ description: description,
27
+ helpers: helpers.map { |h| h.is_a?(Hash) ? h[:name] : h.to_s },
28
+ runner_names: runner_names,
29
+ extra_deps: []
30
+ )
31
+
32
+ files = build_scaffold_files(engine, variables, helpers, runner_methods, spec_gen, module_name, underscored, runner_names)
33
+ writer.write_all(files)
34
+
35
+ Legion::Logging.info "[codegen] scaffolded #{gem_name} with #{files.size} files at #{ext_path}"
36
+ { success: true, path: ext_path, files_created: files.size, name: gem_name }
37
+ rescue ArgumentError => e
38
+ { success: false, error: e.message }
39
+ end
40
+
41
+ def generate_file(template_type:, output_path:, variables: {}, **)
42
+ engine = Helpers::TemplateEngine.new
43
+ content = engine.render(template_type, variables)
44
+ ::FileUtils.mkdir_p(::File.dirname(output_path))
45
+ ::File.write(output_path, content)
46
+ { success: true, path: output_path, bytes: content.bytesize }
47
+ rescue ArgumentError => e
48
+ { success: false, error: e.message }
49
+ end
50
+
51
+ private
52
+
53
+ def build_variables(gem_name:, underscored:, module_name:, description:, helpers:, runner_names:, extra_deps:)
54
+ {
55
+ gem_name: gem_name,
56
+ gem_name_underscored: underscored,
57
+ module_name: module_name,
58
+ description: description,
59
+ author: Helpers::Constants::DEFAULT_AUTHOR,
60
+ email: Helpers::Constants::DEFAULT_EMAIL,
61
+ ruby_version: Helpers::Constants::DEFAULT_RUBY_VERSION,
62
+ gem_version: Helpers::Constants::DEFAULT_GEM_VERSION,
63
+ license: Helpers::Constants::DEFAULT_LICENSE,
64
+ helpers: helpers,
65
+ runner_names: runner_names,
66
+ extra_deps: extra_deps
67
+ }
68
+ end
69
+
70
+ def build_scaffold_files(engine, variables, helpers, runner_methods, spec_gen, module_name, underscored, runner_names)
71
+ files = {}
72
+
73
+ files["#{variables[:gem_name]}.gemspec"] = engine.render(:gemspec, variables)
74
+ files['Gemfile'] = engine.render(:gemfile, variables)
75
+ files['.rubocop.yml'] = engine.render(:rubocop, variables)
76
+ files['.github/workflows/ci.yml'] = engine.render(:ci, variables)
77
+ files['.rspec'] = engine.render(:rspec, variables)
78
+ files['.gitignore'] = engine.render(:gitignore, variables)
79
+ files['LICENSE'] = engine.render(:license, variables)
80
+ files["lib/legion/extensions/#{underscored}/version.rb"] = engine.render(:version, variables)
81
+ files["lib/legion/extensions/#{underscored}.rb"] = engine.render(:entry_point, variables)
82
+ files['spec/spec_helper.rb'] = engine.render(:spec_helper, variables)
83
+ files["lib/legion/extensions/#{underscored}/client.rb"] = engine.render(:client, variables.merge(runner_names: runner_names))
84
+
85
+ helpers.each do |helper|
86
+ helper_name = helper.is_a?(Hash) ? helper[:name] : helper.to_s
87
+ helper_methods = helper.is_a?(Hash) ? (helper[:methods] || []) : []
88
+ files["lib/legion/extensions/#{underscored}/helpers/#{helper_name}.rb"] =
89
+ generate_helper_stub(module_name, helper_name, helper_methods)
90
+ files["spec/legion/extensions/#{underscored}/helpers/#{helper_name}_spec.rb"] =
91
+ spec_gen.generate_helper_spec(module_name: module_name, helper_name: helper_name, methods: helper_methods)
92
+ end
93
+
94
+ runner_methods.each do |runner|
95
+ r_name = runner[:name]
96
+ r_methods = runner.is_a?(Hash) ? [runner] : []
97
+ r_class = r_name.split('_').map(&:capitalize).join
98
+ files["lib/legion/extensions/#{underscored}/runners/#{r_name}.rb"] =
99
+ engine.render(:runner, variables.merge(runner_class: r_class, methods: [runner]))
100
+ files["spec/legion/extensions/#{underscored}/runners/#{r_name}_spec.rb"] =
101
+ spec_gen.generate_runner_spec(module_name: module_name, runner_name: r_name, methods: r_methods)
102
+ end
103
+
104
+ files["spec/legion/extensions/#{underscored}/client_spec.rb"] =
105
+ spec_gen.generate_client_spec(module_name: module_name, runner_name: 'client', methods: [])
106
+
107
+ files
108
+ end
109
+
110
+ def generate_helper_stub(module_name, helper_name, methods)
111
+ helper_class = helper_name.split('_').map(&:capitalize).join
112
+ method_lines = methods.map do |m|
113
+ params = Array(m[:params])
114
+ param_str = params.empty? ? '**' : "#{params.join(', ')}, **"
115
+ " def #{m[:name]}(#{param_str})\n { success: true }\n end"
116
+ end.join("\n\n")
117
+
118
+ <<~RUBY
119
+ # frozen_string_literal: true
120
+
121
+ module Legion
122
+ module Extensions
123
+ module #{module_name}
124
+ module Helpers
125
+ class #{helper_class}
126
+ def initialize
127
+ # no state needed
128
+ end
129
+
130
+ #{method_lines}
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ RUBY
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Codegen
6
+ module Runners
7
+ module Template
8
+ extend self # rubocop:disable Style/ModuleFunction
9
+
10
+ TEMPLATE_REQUIRED_VARS = {
11
+ gemspec: %i[gem_name module_name description author email ruby_version license extra_deps],
12
+ gemfile: [],
13
+ rubocop: %i[ruby_version],
14
+ ci: [],
15
+ rspec: [],
16
+ gitignore: [],
17
+ license: %i[author],
18
+ version: %i[module_name gem_version],
19
+ entry_point: %i[gem_name_underscored module_name helpers runner_names],
20
+ spec_helper: %i[gem_name_underscored],
21
+ runner: %i[module_name runner_class methods gem_name_underscored],
22
+ client: %i[module_name runner_names]
23
+ }.freeze
24
+
25
+ def list_templates(**)
26
+ { success: true, templates: Helpers::Constants::TEMPLATE_TYPES }
27
+ end
28
+
29
+ def render_template(template_type:, variables: {}, **)
30
+ engine = Helpers::TemplateEngine.new
31
+ content = engine.render(template_type, variables)
32
+ { success: true, content: content, template_type: template_type }
33
+ rescue ArgumentError => e
34
+ { success: false, error: e.message }
35
+ end
36
+
37
+ def template_variables(template_type:, **)
38
+ key = template_type.to_sym
39
+ return { success: false, error: "Unknown template type: #{template_type}" } unless Helpers::Constants::TEMPLATE_TYPES.include?(key)
40
+
41
+ required = TEMPLATE_REQUIRED_VARS.fetch(key, [])
42
+ { success: true, template_type: key, required_variables: required }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end