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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +16 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +62 -0
- data/CHANGELOG.md +6 -0
- data/CLAUDE.md +209 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +173 -0
- data/lex-codegen.gemspec +37 -0
- data/lib/legion/extensions/codegen/client.rb +17 -0
- data/lib/legion/extensions/codegen/helpers/constants.rb +304 -0
- data/lib/legion/extensions/codegen/helpers/file_writer.rb +28 -0
- data/lib/legion/extensions/codegen/helpers/spec_generator.rb +112 -0
- data/lib/legion/extensions/codegen/helpers/template_engine.rb +39 -0
- data/lib/legion/extensions/codegen/runners/generate.rb +142 -0
- data/lib/legion/extensions/codegen/runners/template.rb +48 -0
- data/lib/legion/extensions/codegen/runners/validate.rb +123 -0
- data/lib/legion/extensions/codegen/version.rb +9 -0
- data/lib/legion/extensions/codegen.rb +20 -0
- metadata +164 -0
|
@@ -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
|