ruby_ui_converter 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +146 -9
- data/lib/ruby_ui_converter/cli.rb +22 -6
- data/lib/ruby_ui_converter/converter.rb +15 -2
- data/lib/ruby_ui_converter/file_walker.rb +12 -5
- data/lib/ruby_ui_converter/form_builder.rb +4 -0
- data/lib/ruby_ui_converter/haml_attributes.rb +186 -0
- data/lib/ruby_ui_converter/haml_parser.rb +326 -0
- data/lib/ruby_ui_converter/html_entities.rb +89 -0
- data/lib/ruby_ui_converter/locals_detector.rb +2 -0
- data/lib/ruby_ui_converter/nodes.rb +5 -1
- data/lib/ruby_ui_converter/parser.rb +8 -2
- data/lib/ruby_ui_converter/ruby_literal.rb +100 -0
- data/lib/ruby_ui_converter/template.rb +9 -3
- data/lib/ruby_ui_converter/transformer.rb +76 -15
- data/lib/ruby_ui_converter/version.rb +1 -1
- data/lib/ruby_ui_converter.rb +29 -3
- metadata +25 -6
|
@@ -31,10 +31,10 @@ module RubyUIConverter
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def meaningful(nodes)
|
|
34
|
-
nodes.reject do |node|
|
|
35
|
-
(node.is_a?(Nodes::Text) && node.content.strip.empty?) ||
|
|
34
|
+
nodes.each_with_index.reject do |node, index|
|
|
35
|
+
(node.is_a?(Nodes::Text) && node.content.strip.empty? && !inline_space?(nodes, index)) ||
|
|
36
36
|
(node.is_a?(Nodes::RawText) && node.content.strip.empty?)
|
|
37
|
-
end
|
|
37
|
+
end.map(&:first)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def render_attrs(attributes, except: [])
|
|
@@ -156,6 +156,7 @@ module RubyUIConverter
|
|
|
156
156
|
# Collapse runs of whitespace to a single space but keep meaningful
|
|
157
157
|
# leading/trailing spaces so inline text ("Hello, ") stays readable.
|
|
158
158
|
content = node.content.gsub(/\s+/, " ")
|
|
159
|
+
return builder.line("whitespace") if content == " " # see #inline_space?
|
|
159
160
|
return if content.strip.empty?
|
|
160
161
|
|
|
161
162
|
builder.line("plain #{ruby_string(content)}")
|
|
@@ -228,14 +229,22 @@ module RubyUIConverter
|
|
|
228
229
|
case node
|
|
229
230
|
when Nodes::Output, Nodes::Statement then acc << node.code
|
|
230
231
|
when Nodes::Control then node.branches.each { |b| collect_codes(b.children, acc) }
|
|
231
|
-
when Nodes::Element then collect_codes(node.children, acc)
|
|
232
|
+
when Nodes::Element, Nodes::Comment then collect_codes(node.children, acc)
|
|
232
233
|
end
|
|
233
234
|
end
|
|
234
235
|
acc
|
|
235
236
|
end
|
|
236
237
|
|
|
237
238
|
def emit_comment(node, builder)
|
|
238
|
-
if node.html
|
|
239
|
+
if node.html && meaningful(node.children).any?
|
|
240
|
+
builder.line("comment do")
|
|
241
|
+
builder.indent
|
|
242
|
+
text = node.text.strip
|
|
243
|
+
builder.line("plain #{ruby_string(text)}") unless text.empty?
|
|
244
|
+
emit_children(meaningful(node.children), builder)
|
|
245
|
+
builder.dedent
|
|
246
|
+
builder.line("end")
|
|
247
|
+
elsif node.html
|
|
239
248
|
text = node.text.strip
|
|
240
249
|
builder.line("comment { #{ruby_string(text)} }") unless text.empty?
|
|
241
250
|
else
|
|
@@ -276,8 +285,43 @@ module RubyUIConverter
|
|
|
276
285
|
|
|
277
286
|
# Expressions with whitespace (`dom_id user`, `a ? b : c`) would parse
|
|
278
287
|
# incorrectly inside the attribute argument list, so wrap them in parens.
|
|
288
|
+
# Self-delimited expressions (`[a, b]`, `{a: 1}`, `dom_id(user, :x)`) are
|
|
289
|
+
# safe as-is.
|
|
279
290
|
def bare_expression(code)
|
|
280
|
-
code =~ /\s/
|
|
291
|
+
return code unless code =~ /\s/
|
|
292
|
+
|
|
293
|
+
self_delimited?(code) ? code : "(#{code})"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
GROUP_TOKENS = {
|
|
297
|
+
on_lparen: 1, on_lbracket: 1, on_lbrace: 1,
|
|
298
|
+
on_rparen: -1, on_rbracket: -1, on_rbrace: -1
|
|
299
|
+
}.freeze
|
|
300
|
+
CALL_PREFIX_TOKENS = %i[on_ident on_const on_ivar on_cvar on_gvar on_period].freeze
|
|
301
|
+
|
|
302
|
+
# True when the code is a bracketed literal or a parenthesized call
|
|
303
|
+
# (optionally on a receiver chain written without spaces) whose opening
|
|
304
|
+
# bracket closes exactly at the end of the code.
|
|
305
|
+
def self_delimited?(code)
|
|
306
|
+
tokens = Ripper.lex(code)
|
|
307
|
+
index = 0
|
|
308
|
+
while index < tokens.length &&
|
|
309
|
+
(CALL_PREFIX_TOKENS.include?(tokens[index][1]) ||
|
|
310
|
+
(tokens[index][1] == :on_op && ["::", "&."].include?(tokens[index][2])))
|
|
311
|
+
index += 1
|
|
312
|
+
end
|
|
313
|
+
return false if index.positive? && !%i[on_ident on_const].include?(tokens[index - 1][1])
|
|
314
|
+
return false unless tokens[index] && GROUP_TOKENS[tokens[index][1]] == 1
|
|
315
|
+
return false if index.positive? && tokens[index][1] != :on_lparen
|
|
316
|
+
|
|
317
|
+
depth = 0
|
|
318
|
+
tokens[index..].each_with_index do |token, offset|
|
|
319
|
+
depth += GROUP_TOKENS.fetch(token[1], 0)
|
|
320
|
+
return index + offset == tokens.length - 1 if depth.zero?
|
|
321
|
+
end
|
|
322
|
+
false
|
|
323
|
+
rescue StandardError
|
|
324
|
+
false
|
|
281
325
|
end
|
|
282
326
|
|
|
283
327
|
def interpolated(parts)
|
|
@@ -299,6 +343,15 @@ module RubyUIConverter
|
|
|
299
343
|
name.to_s.downcase
|
|
300
344
|
end
|
|
301
345
|
|
|
346
|
+
# A whitespace-only text node on a single line between two siblings
|
|
347
|
+
# (`<em>a</em> <%= b %>`) separates inline content and is kept (emitted as
|
|
348
|
+
# Phlex's `whitespace`); whitespace spanning lines between tags is layout
|
|
349
|
+
# and is dropped.
|
|
350
|
+
def inline_space?(nodes, index)
|
|
351
|
+
content = nodes[index].content
|
|
352
|
+
!content.empty? && !content.include?("\n") && index.positive? && index < nodes.length - 1
|
|
353
|
+
end
|
|
354
|
+
|
|
302
355
|
def inlineable?(node)
|
|
303
356
|
node.is_a?(Nodes::Text) ||
|
|
304
357
|
(node.is_a?(Nodes::Output) && !node.raw && !special_output?(node.code))
|
|
@@ -385,16 +438,24 @@ module RubyUIConverter
|
|
|
385
438
|
"\"#{escape_inner(string)}\""
|
|
386
439
|
end
|
|
387
440
|
|
|
441
|
+
STRING_ESCAPES = {
|
|
442
|
+
"\\" => "\\\\",
|
|
443
|
+
"\"" => "\\\"",
|
|
444
|
+
"\n" => "\\n",
|
|
445
|
+
"\t" => "\\t",
|
|
446
|
+
"\r" => "\\r",
|
|
447
|
+
"\#{" => "\\\#{"
|
|
448
|
+
}.freeze
|
|
449
|
+
|
|
450
|
+
# Invisible / easily-confused characters (no-break and other Unicode
|
|
451
|
+
# spaces, soft hyphen, zero-width characters, BOM) — typically from a
|
|
452
|
+
# decoded ` ` — are written as \u escapes so the generated source
|
|
453
|
+
# stays readable.
|
|
454
|
+
INVISIBLE_CHARS = /[ -
- -]/
|
|
455
|
+
|
|
388
456
|
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]
|
|
457
|
+
string.to_s.gsub(/[\\"\n\t\r]|\#\{|#{INVISIBLE_CHARS}/o) do |match|
|
|
458
|
+
STRING_ESCAPES.fetch(match) { format("\\u%04X", match.ord) }
|
|
398
459
|
end
|
|
399
460
|
end
|
|
400
461
|
end
|
data/lib/ruby_ui_converter.rb
CHANGED
|
@@ -5,9 +5,13 @@ require "strscan"
|
|
|
5
5
|
|
|
6
6
|
require_relative "ruby_ui_converter/version"
|
|
7
7
|
require_relative "ruby_ui_converter/nodes"
|
|
8
|
+
require_relative "ruby_ui_converter/html_entities"
|
|
8
9
|
require_relative "ruby_ui_converter/lexer"
|
|
9
10
|
require_relative "ruby_ui_converter/html_tokenizer"
|
|
10
11
|
require_relative "ruby_ui_converter/parser"
|
|
12
|
+
require_relative "ruby_ui_converter/ruby_literal"
|
|
13
|
+
require_relative "ruby_ui_converter/haml_attributes"
|
|
14
|
+
require_relative "ruby_ui_converter/haml_parser"
|
|
11
15
|
require_relative "ruby_ui_converter/code_builder"
|
|
12
16
|
require_relative "ruby_ui_converter/naming"
|
|
13
17
|
require_relative "ruby_ui_converter/rails_helpers"
|
|
@@ -24,13 +28,35 @@ require_relative "ruby_ui_converter/doctor"
|
|
|
24
28
|
module RubyUIConverter
|
|
25
29
|
class Error < StandardError; end
|
|
26
30
|
|
|
27
|
-
#
|
|
31
|
+
# Template file extension -> source format (front-end parser).
|
|
32
|
+
FORMATS = { ".erb" => :erb, ".haml" => :haml }.freeze
|
|
33
|
+
|
|
34
|
+
# The source format of a template path ("index.html.haml" -> :haml), or
|
|
35
|
+
# nil when it isn't a supported template.
|
|
36
|
+
def self.format_for(path)
|
|
37
|
+
FORMATS[File.extname(path.to_s)]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Parse template source into the shared AST (Nodes::Document). This is the
|
|
41
|
+
# single dispatch point between the front-ends: ERB (Lexer + HtmlTokenizer
|
|
42
|
+
# + Parser) and HAML (HamlParser, backed by the optional haml gem).
|
|
43
|
+
def self.parse(source, format: :erb)
|
|
44
|
+
case format.to_sym
|
|
45
|
+
when :erb then Parser.new(source).parse
|
|
46
|
+
when :haml then HamlParser.new(source).parse
|
|
47
|
+
else raise Error, "unsupported template format: #{format.inspect}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Convert a single .erb (or, with format: :haml, .haml) string into
|
|
52
|
+
# Ruby/Phlex source (no file IO).
|
|
28
53
|
#
|
|
29
54
|
# RubyUIConverter.convert_string("<h1><%= @title %></h1>")
|
|
55
|
+
# RubyUIConverter.convert_string("%h1= @title", format: :haml)
|
|
30
56
|
def self.convert_string(source, class_name: "Component", base_namespace: "",
|
|
31
|
-
base_class: "Phlex::HTML", **opts)
|
|
57
|
+
base_class: "Phlex::HTML", format: :erb, **opts)
|
|
32
58
|
config = Configuration.new(base_namespace: base_namespace, base_class: base_class, **opts)
|
|
33
|
-
document =
|
|
59
|
+
document = parse(source, format: format)
|
|
34
60
|
builder = CodeBuilder.new(indent: config.indent)
|
|
35
61
|
builder.line("class #{class_name} < #{config.base_class}")
|
|
36
62
|
builder.indent
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_ui_converter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jackson Pires
|
|
@@ -51,11 +51,26 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '5.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: haml
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.2'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.2'
|
|
54
68
|
description: |
|
|
55
69
|
ruby_ui_converter walks a Rails views directory recursively and converts each
|
|
56
|
-
.erb template into an equivalent .rb file written with Phlex (and
|
|
57
|
-
configured). Traditional Rails partials (_partial.html.erb) are
|
|
58
|
-
their own Phlex component classes.
|
|
70
|
+
.erb or .haml template into an equivalent .rb file written with Phlex (and
|
|
71
|
+
RubyUI when configured). Traditional Rails partials (_partial.html.erb) are
|
|
72
|
+
converted into their own Phlex component classes. HAML conversion uses the
|
|
73
|
+
haml gem when it is available (optional dependency).
|
|
59
74
|
email:
|
|
60
75
|
- jackson@linkana.com
|
|
61
76
|
executables:
|
|
@@ -76,6 +91,9 @@ files:
|
|
|
76
91
|
- lib/ruby_ui_converter/doctor.rb
|
|
77
92
|
- lib/ruby_ui_converter/file_walker.rb
|
|
78
93
|
- lib/ruby_ui_converter/form_builder.rb
|
|
94
|
+
- lib/ruby_ui_converter/haml_attributes.rb
|
|
95
|
+
- lib/ruby_ui_converter/haml_parser.rb
|
|
96
|
+
- lib/ruby_ui_converter/html_entities.rb
|
|
79
97
|
- lib/ruby_ui_converter/html_tokenizer.rb
|
|
80
98
|
- lib/ruby_ui_converter/lexer.rb
|
|
81
99
|
- lib/ruby_ui_converter/locals_detector.rb
|
|
@@ -83,6 +101,7 @@ files:
|
|
|
83
101
|
- lib/ruby_ui_converter/nodes.rb
|
|
84
102
|
- lib/ruby_ui_converter/parser.rb
|
|
85
103
|
- lib/ruby_ui_converter/rails_helpers.rb
|
|
104
|
+
- lib/ruby_ui_converter/ruby_literal.rb
|
|
86
105
|
- lib/ruby_ui_converter/template.rb
|
|
87
106
|
- lib/ruby_ui_converter/transformer.rb
|
|
88
107
|
- lib/ruby_ui_converter/version.rb
|
|
@@ -108,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
108
127
|
- !ruby/object:Gem::Version
|
|
109
128
|
version: '0'
|
|
110
129
|
requirements: []
|
|
111
|
-
rubygems_version: 4.0.
|
|
130
|
+
rubygems_version: 4.0.3
|
|
112
131
|
specification_version: 4
|
|
113
|
-
summary: Convert Rails .erb views and partials into RubyUI/Phlex Ruby components.
|
|
132
|
+
summary: Convert Rails .erb and .haml views and partials into RubyUI/Phlex Ruby components.
|
|
114
133
|
test_files: []
|