ruby_ui_converter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +73 -0
- data/LICENSE.txt +21 -0
- data/README.md +487 -0
- data/exe/ruby_ui_converter +7 -0
- data/lib/ruby_ui_converter/cli.rb +179 -0
- data/lib/ruby_ui_converter/code_builder.rb +33 -0
- data/lib/ruby_ui_converter/component_map.rb +125 -0
- data/lib/ruby_ui_converter/configuration.rb +53 -0
- data/lib/ruby_ui_converter/converter.rb +76 -0
- data/lib/ruby_ui_converter/doctor.rb +190 -0
- data/lib/ruby_ui_converter/file_walker.rb +22 -0
- data/lib/ruby_ui_converter/form_builder.rb +252 -0
- data/lib/ruby_ui_converter/html_tokenizer.rb +109 -0
- data/lib/ruby_ui_converter/lexer.rb +58 -0
- data/lib/ruby_ui_converter/locals_detector.rb +111 -0
- data/lib/ruby_ui_converter/naming.rb +45 -0
- data/lib/ruby_ui_converter/nodes.rb +128 -0
- data/lib/ruby_ui_converter/parser.rb +179 -0
- data/lib/ruby_ui_converter/rails_helpers.rb +230 -0
- data/lib/ruby_ui_converter/template.rb +170 -0
- data/lib/ruby_ui_converter/transformer.rb +401 -0
- data/lib/ruby_ui_converter/version.rb +5 -0
- data/lib/ruby_ui_converter.rb +54 -0
- metadata +114 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ripper"
|
|
4
|
+
|
|
5
|
+
module RubyUIConverter
|
|
6
|
+
# Walks the AST and writes Phlex/RubyUI Ruby source into a CodeBuilder.
|
|
7
|
+
class Transformer
|
|
8
|
+
attr_reader :config, :template
|
|
9
|
+
|
|
10
|
+
def initialize(config:, template: nil)
|
|
11
|
+
@config = config
|
|
12
|
+
@template = template
|
|
13
|
+
@form_stack = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The form scope ({var:, model:, param:}) currently being emitted, if any.
|
|
17
|
+
# Set while inside a mapped form_with/form_for block; used by FormBuilder.
|
|
18
|
+
def current_form
|
|
19
|
+
@form_stack.last
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Entry point: emits the body of the view_template method.
|
|
23
|
+
def emit(document, builder)
|
|
24
|
+
emit_children(meaningful(document.children), builder)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# --- public helpers (also used by ComponentMap emitters) ---------------
|
|
28
|
+
|
|
29
|
+
def emit_children(nodes, builder)
|
|
30
|
+
nodes.each { |node| emit_node(node, builder) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def meaningful(nodes)
|
|
34
|
+
nodes.reject do |node|
|
|
35
|
+
(node.is_a?(Nodes::Text) && node.content.strip.empty?) ||
|
|
36
|
+
(node.is_a?(Nodes::RawText) && node.content.strip.empty?)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def render_attrs(attributes, except: [])
|
|
41
|
+
attributes.reject { |name, _| except.include?(name) }
|
|
42
|
+
.map { |name, parts| attr_pair(name, parts) }
|
|
43
|
+
.join(", ")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def base_namespace
|
|
47
|
+
config.base_namespace
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def current_namespace_parts
|
|
51
|
+
template ? template.namespace_parts : []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Convenience for component emitters: render a component wrapping children.
|
|
55
|
+
def wrap_component(const, element, builder)
|
|
56
|
+
attrs = render_attrs(element.attributes)
|
|
57
|
+
call = attrs.empty? ? "#{const}.new" : "#{const}.new(#{attrs})"
|
|
58
|
+
kids = meaningful(element.children)
|
|
59
|
+
|
|
60
|
+
if kids.empty?
|
|
61
|
+
builder.line("render #{call}")
|
|
62
|
+
else
|
|
63
|
+
builder.line("render #{call} do")
|
|
64
|
+
builder.indent
|
|
65
|
+
emit_children(kids, builder)
|
|
66
|
+
builder.dedent
|
|
67
|
+
builder.line("end")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Emits a Phlex::Kit-style component call (`Link(href: x) { "Home" }`).
|
|
72
|
+
# Parens are always kept — a bare capitalized name would be a constant.
|
|
73
|
+
# `void: true` components (Input, Checkbox...) never take a block.
|
|
74
|
+
# `extra:` prepends literal arguments (e.g. "variant: :destructive").
|
|
75
|
+
def kit_component(name, element, builder, except: [], void: false, extra: nil)
|
|
76
|
+
attrs = [extra, render_attrs(element.attributes, except: except)]
|
|
77
|
+
.compact.reject(&:empty?).join(", ")
|
|
78
|
+
call = "#{name}(#{attrs})"
|
|
79
|
+
kids = void ? [] : meaningful(element.children)
|
|
80
|
+
|
|
81
|
+
if kids.empty?
|
|
82
|
+
builder.line(call)
|
|
83
|
+
elsif kids.length == 1 && inlineable?(kids.first)
|
|
84
|
+
builder.line("#{call} { #{inline_value(kids.first)} }")
|
|
85
|
+
else
|
|
86
|
+
builder.line("#{call} do")
|
|
87
|
+
builder.indent
|
|
88
|
+
emit_children(kids, builder)
|
|
89
|
+
builder.dedent
|
|
90
|
+
builder.line("end")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Emits a component that wraps the given children with no attributes:
|
|
95
|
+
# `Name { inline }` for a single inlineable child, else a do/end block.
|
|
96
|
+
# Handy for ComponentMap emitters that nest content components (e.g.
|
|
97
|
+
# `AlertDescription { notice }`).
|
|
98
|
+
def component_block(name, children, builder)
|
|
99
|
+
kids = meaningful(children)
|
|
100
|
+
|
|
101
|
+
if kids.empty?
|
|
102
|
+
builder.line(name)
|
|
103
|
+
elsif kids.length == 1 && inlineable?(kids.first)
|
|
104
|
+
builder.line("#{name} { #{inline_value(kids.first)} }")
|
|
105
|
+
else
|
|
106
|
+
builder.line("#{name} do")
|
|
107
|
+
builder.indent
|
|
108
|
+
emit_children(kids, builder)
|
|
109
|
+
builder.dedent
|
|
110
|
+
builder.line("end")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def emit_node(node, builder)
|
|
117
|
+
case node
|
|
118
|
+
when Nodes::Element then emit_element_or_component(node, builder)
|
|
119
|
+
when Nodes::Text then emit_text(node, builder)
|
|
120
|
+
when Nodes::RawText then emit_raw_text(node, builder)
|
|
121
|
+
when Nodes::Output then emit_output(node, builder)
|
|
122
|
+
when Nodes::Statement then emit_statement(node, builder)
|
|
123
|
+
when Nodes::Control then emit_control(node, builder)
|
|
124
|
+
when Nodes::Comment then emit_comment(node, builder)
|
|
125
|
+
when Nodes::Doctype then emit_doctype(node, builder)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def emit_element_or_component(node, builder)
|
|
130
|
+
emitter = config.component_map.lookup(node)
|
|
131
|
+
return emitter.call(node, self, builder) if emitter
|
|
132
|
+
|
|
133
|
+
emit_element(node, builder)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def emit_element(node, builder)
|
|
137
|
+
method = element_method(node.name)
|
|
138
|
+
attrs = render_attrs(node.attributes)
|
|
139
|
+
call = attrs.empty? ? method : "#{method}(#{attrs})"
|
|
140
|
+
kids = meaningful(node.children)
|
|
141
|
+
|
|
142
|
+
if kids.empty?
|
|
143
|
+
builder.line(call)
|
|
144
|
+
elsif kids.length == 1 && inlineable?(kids.first)
|
|
145
|
+
builder.line("#{call} { #{inline_value(kids.first)} }")
|
|
146
|
+
else
|
|
147
|
+
builder.line("#{call} do")
|
|
148
|
+
builder.indent
|
|
149
|
+
emit_children(kids, builder)
|
|
150
|
+
builder.dedent
|
|
151
|
+
builder.line("end")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def emit_text(node, builder)
|
|
156
|
+
# Collapse runs of whitespace to a single space but keep meaningful
|
|
157
|
+
# leading/trailing spaces so inline text ("Hello, ") stays readable.
|
|
158
|
+
content = node.content.gsub(/\s+/, " ")
|
|
159
|
+
return if content.strip.empty?
|
|
160
|
+
|
|
161
|
+
builder.line("plain #{ruby_string(content)}")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def emit_raw_text(node, builder)
|
|
165
|
+
content = node.content.strip
|
|
166
|
+
return if content.empty?
|
|
167
|
+
|
|
168
|
+
builder.line("# TODO: move inline script/style to an asset or helper")
|
|
169
|
+
builder.line(config.raw_call(ruby_string(node.content)))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def emit_output(node, builder)
|
|
173
|
+
code = sanitize_code(node.code)
|
|
174
|
+
return if FormBuilder.transform(code, self, builder)
|
|
175
|
+
return if RailsHelpers.transform(code, node, self, builder)
|
|
176
|
+
|
|
177
|
+
if node.raw
|
|
178
|
+
builder.line(config.raw_call(code))
|
|
179
|
+
else
|
|
180
|
+
builder.line("plain(#{code})")
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def emit_statement(node, builder)
|
|
185
|
+
builder.line(sanitize_code(node.code))
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def emit_control(node, builder)
|
|
189
|
+
if config.ruby_ui? && node.branches.length == 1 &&
|
|
190
|
+
(form = FormBuilder.form_scope(sanitize_code(node.branches.first.header)))
|
|
191
|
+
return emit_form_control(node, form, builder)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
node.branches.each do |branch|
|
|
195
|
+
builder.line(sanitize_code(branch.header))
|
|
196
|
+
builder.indent
|
|
197
|
+
emit_children(meaningful(branch.children), builder)
|
|
198
|
+
builder.dedent
|
|
199
|
+
end
|
|
200
|
+
builder.line("end")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# A form_with/form_for block whose fields map to RubyUI components. The
|
|
204
|
+
# block variable (`|form|`) is dropped unless an unmapped `form.*` call needs it.
|
|
205
|
+
def emit_form_control(node, form, builder)
|
|
206
|
+
branch = node.branches.first
|
|
207
|
+
header = sanitize_code(branch.header)
|
|
208
|
+
header = strip_block_var(header) unless FormBuilder.needs_block_var?(form[:var], collect_codes(branch.children))
|
|
209
|
+
|
|
210
|
+
@form_stack.push(form)
|
|
211
|
+
builder.line(header)
|
|
212
|
+
builder.indent
|
|
213
|
+
emit_children(meaningful(branch.children), builder)
|
|
214
|
+
builder.dedent
|
|
215
|
+
builder.line("end")
|
|
216
|
+
ensure
|
|
217
|
+
@form_stack.pop
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def strip_block_var(header)
|
|
221
|
+
header.sub(/(\bdo\b)\s*\|[^|]*\|/, '\1')
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# All Output/Statement codes anywhere under these nodes (used to decide
|
|
225
|
+
# whether the form block variable is still referenced).
|
|
226
|
+
def collect_codes(nodes, acc = [])
|
|
227
|
+
nodes.each do |node|
|
|
228
|
+
case node
|
|
229
|
+
when Nodes::Output, Nodes::Statement then acc << node.code
|
|
230
|
+
when Nodes::Control then node.branches.each { |b| collect_codes(b.children, acc) }
|
|
231
|
+
when Nodes::Element then collect_codes(node.children, acc)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
acc
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def emit_comment(node, builder)
|
|
238
|
+
if node.html
|
|
239
|
+
text = node.text.strip
|
|
240
|
+
builder.line("comment { #{ruby_string(text)} }") unless text.empty?
|
|
241
|
+
else
|
|
242
|
+
node.text.to_s.each_line { |line| builder.line("# #{line.chomp}") }
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def emit_doctype(node, builder)
|
|
247
|
+
builder.line("doctype") if node.value =~ /doctype/i
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# --- attribute helpers -------------------------------------------------
|
|
251
|
+
|
|
252
|
+
def attr_pair(name, parts)
|
|
253
|
+
if name == :__splat__
|
|
254
|
+
code = parts.find { |kind, _| kind == :erb }&.dig(1)&.value
|
|
255
|
+
return "**(#{code})"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
"#{attr_key(name)}: #{attr_value(parts)}"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def attr_key(name)
|
|
262
|
+
name =~ /\A[a-zA-Z_][a-zA-Z0-9_]*\z/ ? name : name.inspect
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def attr_value(parts)
|
|
266
|
+
return "true" if parts.nil?
|
|
267
|
+
|
|
268
|
+
if parts.length == 1 && parts[0][0] == :erb && parts[0][1].type == :output
|
|
269
|
+
bare_expression(sanitize_code(parts[0][1].value))
|
|
270
|
+
elsif parts.all? { |kind, _| kind == :text }
|
|
271
|
+
ruby_string(parts.map { |_, value| value }.join)
|
|
272
|
+
else
|
|
273
|
+
interpolated(parts)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Expressions with whitespace (`dom_id user`, `a ? b : c`) would parse
|
|
278
|
+
# incorrectly inside the attribute argument list, so wrap them in parens.
|
|
279
|
+
def bare_expression(code)
|
|
280
|
+
code =~ /\s/ ? "(#{code})" : code
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def interpolated(parts)
|
|
284
|
+
buffer = +'"'
|
|
285
|
+
parts.each do |kind, value|
|
|
286
|
+
if kind == :text
|
|
287
|
+
buffer << escape_inner(value)
|
|
288
|
+
else
|
|
289
|
+
buffer << "\#{#{sanitize_code(value.value)}}"
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
buffer << '"'
|
|
293
|
+
buffer
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# --- misc helpers ------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
def element_method(name)
|
|
299
|
+
name.to_s.downcase
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def inlineable?(node)
|
|
303
|
+
node.is_a?(Nodes::Text) ||
|
|
304
|
+
(node.is_a?(Nodes::Output) && !node.raw && !special_output?(node.code))
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def inline_value(node)
|
|
308
|
+
if node.is_a?(Nodes::Text)
|
|
309
|
+
ruby_string(node.content.strip)
|
|
310
|
+
else
|
|
311
|
+
sanitize_code(node.code)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def special_output?(code)
|
|
316
|
+
stripped = code.strip
|
|
317
|
+
return true if stripped.start_with?("render", "yield")
|
|
318
|
+
return true if FormBuilder.form_field?(stripped, current_form)
|
|
319
|
+
|
|
320
|
+
RailsHelpers.html_helper?(stripped)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def sanitize_code(code)
|
|
324
|
+
code = code.to_s
|
|
325
|
+
.gsub(/local_assigns\.fetch\(:(\w+)[^)]*\)/, '\1')
|
|
326
|
+
.gsub(/local_assigns\[:(\w+)\]/, '\1')
|
|
327
|
+
.strip
|
|
328
|
+
code = rewrite_locals_to_ivars(code) if literal_locals.any?
|
|
329
|
+
code
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# With --literal, props set ivars and generate no readers, so every bare
|
|
333
|
+
# reference to a detected local must become `@local`. Safe by design:
|
|
334
|
+
# LocalsDetector only reports names that are never assigned or shadowed by
|
|
335
|
+
# block params anywhere in the template (read-only identifiers).
|
|
336
|
+
def literal_locals
|
|
337
|
+
@literal_locals ||=
|
|
338
|
+
if config.literal? && template&.partial?
|
|
339
|
+
template.locals
|
|
340
|
+
else
|
|
341
|
+
[]
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Token-level rewrite via Ripper (lossless lexer): hash keys (`user:`) lex
|
|
346
|
+
# as :on_label, symbols are preceded by :on_symbeg, string contents are
|
|
347
|
+
# :on_tstring_content — none are :on_ident, so they're naturally skipped.
|
|
348
|
+
# Interpolated code inside strings lexes as regular idents and is rewritten.
|
|
349
|
+
def rewrite_locals_to_ivars(code)
|
|
350
|
+
tokens = Ripper.lex(code)
|
|
351
|
+
return code if tokens.nil? || tokens.empty?
|
|
352
|
+
|
|
353
|
+
tokens.each_with_index.map do |(_, type, tok, _), index|
|
|
354
|
+
next tok unless type == :on_ident && literal_locals.include?(tok)
|
|
355
|
+
next tok if method_call_token?(tokens, index)
|
|
356
|
+
|
|
357
|
+
"@#{tok}"
|
|
358
|
+
end.join
|
|
359
|
+
rescue StandardError
|
|
360
|
+
code
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# True when the ident is a method call (`x.user`, `x&.user`, `user(...)`)
|
|
364
|
+
# or a symbol (`:user`) rather than a bare local reference.
|
|
365
|
+
def method_call_token?(tokens, index)
|
|
366
|
+
prev = significant_token(tokens, index, -1)
|
|
367
|
+
return true if prev && (prev[1] == :on_period || prev[1] == :on_symbeg ||
|
|
368
|
+
(prev[1] == :on_op && ["&.", "::"].include?(prev[2])))
|
|
369
|
+
|
|
370
|
+
following = significant_token(tokens, index, 1)
|
|
371
|
+
following && following[1] == :on_lparen
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def significant_token(tokens, index, step)
|
|
375
|
+
index += step
|
|
376
|
+
while index >= 0 && index < tokens.length
|
|
377
|
+
return tokens[index] unless %i[on_sp on_ignored_nl on_nl].include?(tokens[index][1])
|
|
378
|
+
|
|
379
|
+
index += step
|
|
380
|
+
end
|
|
381
|
+
nil
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def ruby_string(string)
|
|
385
|
+
"\"#{escape_inner(string)}\""
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def escape_inner(string)
|
|
389
|
+
string.to_s.gsub(/[\\"\n\t\r]|\#\{/) do |match|
|
|
390
|
+
{
|
|
391
|
+
"\\" => "\\\\",
|
|
392
|
+
"\"" => "\\\"",
|
|
393
|
+
"\n" => "\\n",
|
|
394
|
+
"\t" => "\\t",
|
|
395
|
+
"\r" => "\\r",
|
|
396
|
+
"\#{" => "\\\#{"
|
|
397
|
+
}[match]
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
require "strscan"
|
|
5
|
+
|
|
6
|
+
require_relative "ruby_ui_converter/version"
|
|
7
|
+
require_relative "ruby_ui_converter/nodes"
|
|
8
|
+
require_relative "ruby_ui_converter/lexer"
|
|
9
|
+
require_relative "ruby_ui_converter/html_tokenizer"
|
|
10
|
+
require_relative "ruby_ui_converter/parser"
|
|
11
|
+
require_relative "ruby_ui_converter/code_builder"
|
|
12
|
+
require_relative "ruby_ui_converter/naming"
|
|
13
|
+
require_relative "ruby_ui_converter/rails_helpers"
|
|
14
|
+
require_relative "ruby_ui_converter/form_builder"
|
|
15
|
+
require_relative "ruby_ui_converter/locals_detector"
|
|
16
|
+
require_relative "ruby_ui_converter/component_map"
|
|
17
|
+
require_relative "ruby_ui_converter/configuration"
|
|
18
|
+
require_relative "ruby_ui_converter/transformer"
|
|
19
|
+
require_relative "ruby_ui_converter/template"
|
|
20
|
+
require_relative "ruby_ui_converter/file_walker"
|
|
21
|
+
require_relative "ruby_ui_converter/converter"
|
|
22
|
+
require_relative "ruby_ui_converter/doctor"
|
|
23
|
+
|
|
24
|
+
module RubyUIConverter
|
|
25
|
+
class Error < StandardError; end
|
|
26
|
+
|
|
27
|
+
# Convert a single .erb string into Ruby/Phlex source (no file IO).
|
|
28
|
+
#
|
|
29
|
+
# RubyUIConverter.convert_string("<h1><%= @title %></h1>")
|
|
30
|
+
def self.convert_string(source, class_name: "Component", base_namespace: "",
|
|
31
|
+
base_class: "Phlex::HTML", **opts)
|
|
32
|
+
config = Configuration.new(base_namespace: base_namespace, base_class: base_class, **opts)
|
|
33
|
+
document = Parser.new(source).parse
|
|
34
|
+
builder = CodeBuilder.new(indent: config.indent)
|
|
35
|
+
builder.line("class #{class_name} < #{config.base_class}")
|
|
36
|
+
builder.indent
|
|
37
|
+
builder.line("def #{config.template_method}")
|
|
38
|
+
builder.indent
|
|
39
|
+
Transformer.new(config: config).emit(document, builder)
|
|
40
|
+
builder.dedent
|
|
41
|
+
builder.line("end")
|
|
42
|
+
builder.dedent
|
|
43
|
+
builder.line("end")
|
|
44
|
+
builder.to_s
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Convert files under a path, writing .rb files. Returns Converter::Result[].
|
|
48
|
+
#
|
|
49
|
+
# RubyUIConverter.convert("app/views/users")
|
|
50
|
+
def self.convert(path, **opts)
|
|
51
|
+
config = Configuration.new(**opts)
|
|
52
|
+
Converter.new(path, config: config).run
|
|
53
|
+
end
|
|
54
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby_ui_converter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jackson Pires
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: thor
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: minitest
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '5.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '5.0'
|
|
54
|
+
description: |
|
|
55
|
+
ruby_ui_converter walks a Rails views directory recursively and converts each
|
|
56
|
+
.erb template into an equivalent .rb file written with Phlex (and RubyUI when
|
|
57
|
+
configured). Traditional Rails partials (_partial.html.erb) are converted into
|
|
58
|
+
their own Phlex component classes.
|
|
59
|
+
email:
|
|
60
|
+
- jackson@linkana.com
|
|
61
|
+
executables:
|
|
62
|
+
- ruby_ui_converter
|
|
63
|
+
extensions: []
|
|
64
|
+
extra_rdoc_files: []
|
|
65
|
+
files:
|
|
66
|
+
- CHANGELOG.md
|
|
67
|
+
- LICENSE.txt
|
|
68
|
+
- README.md
|
|
69
|
+
- exe/ruby_ui_converter
|
|
70
|
+
- lib/ruby_ui_converter.rb
|
|
71
|
+
- lib/ruby_ui_converter/cli.rb
|
|
72
|
+
- lib/ruby_ui_converter/code_builder.rb
|
|
73
|
+
- lib/ruby_ui_converter/component_map.rb
|
|
74
|
+
- lib/ruby_ui_converter/configuration.rb
|
|
75
|
+
- lib/ruby_ui_converter/converter.rb
|
|
76
|
+
- lib/ruby_ui_converter/doctor.rb
|
|
77
|
+
- lib/ruby_ui_converter/file_walker.rb
|
|
78
|
+
- lib/ruby_ui_converter/form_builder.rb
|
|
79
|
+
- lib/ruby_ui_converter/html_tokenizer.rb
|
|
80
|
+
- lib/ruby_ui_converter/lexer.rb
|
|
81
|
+
- lib/ruby_ui_converter/locals_detector.rb
|
|
82
|
+
- lib/ruby_ui_converter/naming.rb
|
|
83
|
+
- lib/ruby_ui_converter/nodes.rb
|
|
84
|
+
- lib/ruby_ui_converter/parser.rb
|
|
85
|
+
- lib/ruby_ui_converter/rails_helpers.rb
|
|
86
|
+
- lib/ruby_ui_converter/template.rb
|
|
87
|
+
- lib/ruby_ui_converter/transformer.rb
|
|
88
|
+
- lib/ruby_ui_converter/version.rb
|
|
89
|
+
homepage: https://github.com/jacksonpires/ruby_ui_converter
|
|
90
|
+
licenses:
|
|
91
|
+
- MIT
|
|
92
|
+
metadata:
|
|
93
|
+
homepage_uri: https://github.com/jacksonpires/ruby_ui_converter
|
|
94
|
+
source_code_uri: https://github.com/jacksonpires/ruby_ui_converter
|
|
95
|
+
changelog_uri: https://github.com/jacksonpires/ruby_ui_converter/blob/main/CHANGELOG.md
|
|
96
|
+
rubygems_mfa_required: 'true'
|
|
97
|
+
rdoc_options: []
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: 3.0.0
|
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
requirements: []
|
|
111
|
+
rubygems_version: 4.0.10
|
|
112
|
+
specification_version: 4
|
|
113
|
+
summary: Convert Rails .erb views and partials into RubyUI/Phlex Ruby components.
|
|
114
|
+
test_files: []
|