blueprint-html2slim 1.0.0 → 1.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +2 -0
- data/bin/html2slim +10 -13
- data/lib/blueprint/html2slim/converter.rb +88 -48
- data/lib/blueprint/html2slim/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcd9f4a5d346b4edee89ff58f86e84501a642530ce563be68daca7ccaa4747e4
|
4
|
+
data.tar.gz: b8d50860a181f7b4077be2aba82c520a5c3c0e498fed7b3c069e6e40077447ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f53a93b08cb863037eadc43536bbdf5a551a58586d495254cfbfff181d2e0db1512f8a2533453a4c60b914b78ef5e7adfa3ce9fe3780c8ff3bd76c07ad0f388d
|
7
|
+
data.tar.gz: d0206ced006d3113f391387223c77b96e958d1e6f6b5bafb44882badbd93eba8f75d786ba5a03d60787b873bb8bcb36ef05bf932f2b6c8c7a804d21e1f670382
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,23 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
+
## [1.1.0] - 2025-01-16
|
6
|
+
|
7
|
+
### Added
|
8
|
+
- Unicode/UTF-8 character support (CJK, emoji, European accents, Cyrillic, Arabic, Hebrew)
|
9
|
+
- Multiline ERB code block handling using `ruby:` blocks for better Slim syntax
|
10
|
+
- Support for inline arrays with chained methods in ERB blocks
|
11
|
+
|
12
|
+
### Fixed
|
13
|
+
- Text starting with "/" now uses pipe notation to avoid being interpreted as Slim comments
|
14
|
+
- Multiline Ruby code (hashes, arrays, method calls) now generates valid Slim syntax
|
15
|
+
- UTF-8 encoding issues in CLI tool when reading and writing files
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
- Improved handling of complex ERB structures with proper indentation
|
19
|
+
- Better detection and formatting of multiline ERB blocks
|
20
|
+
- Updated RuboCop configuration to disable unnecessary cops
|
21
|
+
|
5
22
|
## [1.0.0] - 2024-01-01
|
6
23
|
|
7
24
|
### Added
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
A Ruby gem providing a command-line tool to convert HTML and ERB files to Slim format.
|
4
4
|
|
5
|
+
**Online Converter**: Try the web-based version at [https://railsblueprint.com/html2slim](https://railsblueprint.com/html2slim)
|
6
|
+
|
5
7
|
## Requirements
|
6
8
|
|
7
9
|
- Ruby 2.7 - 3.4 (compatible with latest Ruby versions)
|
data/bin/html2slim
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
2
|
require 'thor'
|
4
3
|
require 'pathname'
|
5
4
|
require_relative '../lib/blueprint/html2slim'
|
@@ -42,7 +41,7 @@ class Html2SlimCLI < Thor
|
|
42
41
|
puts ' html2slim -d old_views/*.erb # Convert and delete sources'
|
43
42
|
exit 0
|
44
43
|
elsif given_args.first == 'version' || given_args.include?('-v') || given_args.include?('--version')
|
45
|
-
puts 'html2slim 1.
|
44
|
+
puts 'html2slim 1.1.0'
|
46
45
|
exit 0
|
47
46
|
else
|
48
47
|
args = given_args.dup
|
@@ -71,15 +70,13 @@ class Html2SlimCLI < Thor
|
|
71
70
|
puts 'Error: -o/--output can only be used with a single input file'
|
72
71
|
exit 1
|
73
72
|
end
|
74
|
-
|
73
|
+
|
75
74
|
if options[:output] && options[:target_dir]
|
76
75
|
puts 'Error: Cannot use both -o/--output and -t/--target-dir together'
|
77
76
|
exit 1
|
78
77
|
end
|
79
|
-
|
80
|
-
if options[:backup] && options[:delete]
|
81
|
-
puts 'Warning: -b/--backup takes precedence over -d/--delete'
|
82
|
-
end
|
78
|
+
|
79
|
+
puts 'Warning: -b/--backup takes precedence over -d/--delete' if options[:backup] && options[:delete]
|
83
80
|
|
84
81
|
converter = Blueprint::Html2Slim::Converter.new(indent_size: options[:indent])
|
85
82
|
processed_count = 0
|
@@ -136,7 +133,7 @@ class Html2SlimCLI < Thor
|
|
136
133
|
else
|
137
134
|
input_path.basename
|
138
135
|
end
|
139
|
-
|
136
|
+
|
140
137
|
# Apply smart naming convention to the filename
|
141
138
|
output_name = case relative_path.to_s
|
142
139
|
when /\.html\.erb$/
|
@@ -148,7 +145,7 @@ class Html2SlimCLI < Thor
|
|
148
145
|
else
|
149
146
|
"#{relative_path}.slim"
|
150
147
|
end
|
151
|
-
|
148
|
+
|
152
149
|
Pathname.new(options[:target_dir]).join(output_name)
|
153
150
|
else
|
154
151
|
# Smart naming convention in place
|
@@ -182,7 +179,7 @@ class Html2SlimCLI < Thor
|
|
182
179
|
end
|
183
180
|
|
184
181
|
begin
|
185
|
-
html_content = File.read(input_path)
|
182
|
+
html_content = File.read(input_path, encoding: 'UTF-8')
|
186
183
|
slim_content = converter.convert(html_content)
|
187
184
|
|
188
185
|
# Create backup if requested
|
@@ -195,16 +192,16 @@ class Html2SlimCLI < Thor
|
|
195
192
|
output_path.dirname.mkpath
|
196
193
|
# Ensure content ends with newline when writing to file
|
197
194
|
slim_content += "\n" unless slim_content.end_with?("\n")
|
198
|
-
File.write(output_path, slim_content)
|
195
|
+
File.write(output_path, slim_content, encoding: 'UTF-8')
|
199
196
|
|
200
197
|
puts "Converted: #{input_path} -> #{output_path}"
|
201
|
-
|
198
|
+
|
202
199
|
# Delete source file if requested (and not backing up)
|
203
200
|
if options[:delete] && !options[:backup]
|
204
201
|
File.delete(input_path)
|
205
202
|
puts "Deleted: #{input_path}"
|
206
203
|
end
|
207
|
-
|
204
|
+
|
208
205
|
true
|
209
206
|
rescue StandardError => e
|
210
207
|
puts "Error converting #{input_path}: #{e.message}"
|
@@ -17,25 +17,25 @@ module Blueprint
|
|
17
17
|
|
18
18
|
def convert(html_content)
|
19
19
|
lines = []
|
20
|
-
|
20
|
+
|
21
21
|
# Handle DOCTYPE declaration
|
22
22
|
if html_content =~ /<!DOCTYPE\s+(.+?)>/i
|
23
23
|
doctype_content = ::Regexp.last_match(1)
|
24
|
-
if doctype_content =~ /strict/i
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
24
|
+
lines << if doctype_content =~ /strict/i
|
25
|
+
'doctype strict'
|
26
|
+
elsif doctype_content =~ /transitional/i
|
27
|
+
'doctype transitional'
|
28
|
+
elsif doctype_content =~ /frameset/i
|
29
|
+
'doctype frameset'
|
30
|
+
elsif doctype_content =~ /html$/i
|
31
|
+
'doctype html'
|
32
|
+
else
|
33
|
+
'doctype'
|
34
|
+
end
|
35
35
|
# Remove DOCTYPE from content for further processing
|
36
36
|
html_content = html_content.sub(/<!DOCTYPE\s+.+?>/i, '')
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
html_content = preprocess_erb(html_content)
|
40
40
|
# Use HTML.parse for full documents, DocumentFragment for fragments
|
41
41
|
if html_content =~ /<html/i
|
@@ -68,7 +68,7 @@ module Blueprint
|
|
68
68
|
def preprocess_erb(content)
|
69
69
|
# Convert ERB blocks to span elements to preserve hierarchy
|
70
70
|
# This approach is inspired by the original html2slim gem
|
71
|
-
|
71
|
+
|
72
72
|
# Keep ERB tags in attributes unchanged by temporarily replacing them
|
73
73
|
erb_in_attrs = []
|
74
74
|
content = content.gsub(/(<[^>]*)(<%=?.+?%>)([^>]*>)/) do
|
@@ -79,65 +79,79 @@ module Blueprint
|
|
79
79
|
erb_in_attrs << erb
|
80
80
|
"#{before}#{placeholder}#{after}"
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
# Handle multi-line ERB blocks first (with m flag for multiline)
|
84
84
|
content = content.gsub(/<%\s*\n(.*?)\n\s*-?%>/m) do
|
85
|
-
|
86
|
-
#
|
87
|
-
|
85
|
+
code_block = ::Regexp.last_match(1).strip
|
86
|
+
# Mark as multiline block for special handling
|
87
|
+
"<!--ERB_MULTILINE_CODE:#{code_block.gsub("-->", "__ARROW__")}-->"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Handle multiline ERB output blocks (e.g., <%= form_with(...) spanning multiple lines %>)
|
91
|
+
content = content.gsub(/<%=\s*\n(.*?)\n\s*-?%>/m) do
|
92
|
+
code_block = ::Regexp.last_match(1).strip
|
93
|
+
# Check if it ends with do block
|
94
|
+
if code_block =~ /\bdo\s*(\|[^|]*\|)?\s*$/
|
95
|
+
# Convert to single line for block processing
|
96
|
+
single_line = code_block.gsub(/\s+/, ' ')
|
97
|
+
"<%= #{single_line} %>" # Keep for block processing
|
98
|
+
else
|
99
|
+
# Mark as multiline output for special handling
|
100
|
+
"<!--ERB_MULTILINE_OUTPUT:#{code_block.gsub("-->", "__ARROW__")}-->"
|
101
|
+
end
|
88
102
|
end
|
89
|
-
|
103
|
+
|
90
104
|
# Convert simple ERB output tags that don't create blocks
|
91
105
|
# This prevents them from being caught by the block regex
|
92
106
|
content = content.gsub(/<%=\s*([^%]+?)\s*%>/) do
|
93
107
|
code = ::Regexp.last_match(1).strip
|
94
108
|
# Skip if it's a do block
|
95
109
|
if code =~ /\bdo\s*(\|[^|]*\|)?\s*$/
|
96
|
-
"<%= #{code} %>"
|
110
|
+
"<%= #{code} %>" # Keep original, will be processed later
|
97
111
|
else
|
98
112
|
%(<!--ERB_OUTPUT:#{code}-->)
|
99
113
|
end
|
100
114
|
end
|
101
|
-
|
115
|
+
|
102
116
|
# Convert ERB blocks that create structure (do...end, if...end, etc.)
|
103
117
|
# to span elements so their content becomes proper children
|
104
|
-
content = content.gsub(/<%(-|=)?\s*((\s*(case|if|for|unless|until|while) .+?)|.+?do\s*(\|[^|]*\|)?\s*)-?%>/) do
|
118
|
+
content = content.gsub(/<%(-|=)?\s*((\s*(case|if|for|unless|until|while) .+?)|.+?do\s*(\|[^|]*\|)?\s*)-?%>/m) do
|
105
119
|
type = ::Regexp.last_match(1)
|
106
120
|
code = ::Regexp.last_match(2).strip
|
107
121
|
# Preserve whether it was = or - in the code attribute
|
108
122
|
prefix = type == '=' ? '=' : ''
|
109
|
-
%(<span erb-code="#{prefix}#{code.gsub('"',
|
123
|
+
%(<span erb-code="#{prefix}#{code.gsub('"', """)}">)
|
110
124
|
end
|
111
|
-
|
125
|
+
|
112
126
|
# Handle else
|
113
127
|
content = content.gsub(/<%-?\s*else\s*-?%>/, %(</span><span erb-code="else">))
|
114
|
-
|
128
|
+
|
115
129
|
# Handle elsif
|
116
130
|
content = content.gsub(/<%-?\s*(elsif .+?)\s*-?%>/) do
|
117
131
|
code = ::Regexp.last_match(1).strip
|
118
|
-
%(</span><span erb-code="#{code.gsub('"',
|
132
|
+
%(</span><span erb-code="#{code.gsub('"', """)}">)
|
119
133
|
end
|
120
|
-
|
134
|
+
|
121
135
|
# Handle when
|
122
136
|
content = content.gsub(/<%-?\s*(when .+?)\s*-?%>/) do
|
123
137
|
code = ::Regexp.last_match(1).strip
|
124
|
-
%(</span><span erb-code="#{code.gsub('"',
|
138
|
+
%(</span><span erb-code="#{code.gsub('"', """)}">)
|
125
139
|
end
|
126
|
-
|
140
|
+
|
127
141
|
# Handle end statements - close the span
|
128
142
|
content = content.gsub(/<%\s*(end|}|end\s+-)\s*%>/, %(</span>))
|
129
|
-
|
143
|
+
|
130
144
|
# Convert any remaining ERB code tags to comments
|
131
145
|
content = content.gsub(/<%-?\s*(.+?)\s*%>/) do
|
132
146
|
code = ::Regexp.last_match(1).strip
|
133
147
|
%(<!--ERB_CODE:#{code}-->)
|
134
148
|
end
|
135
|
-
|
149
|
+
|
136
150
|
# Restore ERB tags in attributes
|
137
151
|
erb_in_attrs.each_with_index do |erb, i|
|
138
152
|
content = content.gsub("ERB_IN_ATTR_#{i}", erb)
|
139
153
|
end
|
140
|
-
|
154
|
+
|
141
155
|
content
|
142
156
|
end
|
143
157
|
|
@@ -161,24 +175,24 @@ module Blueprint
|
|
161
175
|
# Check if this is an ERB span element
|
162
176
|
if node.name == 'span' && node['erb-code']
|
163
177
|
erb_code = node['erb-code'].gsub('"', '"')
|
164
|
-
|
178
|
+
|
165
179
|
# Determine if it's output (=) or code (-)
|
166
|
-
if erb_code =~ /^(if|unless|case|for|while|elsif|else|when)\b/
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
180
|
+
lines << if erb_code =~ /^(if|unless|case|for|while|elsif|else|when)\b/
|
181
|
+
"#{indent}- #{erb_code}"
|
182
|
+
elsif erb_code.start_with?('=')
|
183
|
+
# It was originally <%= ... %>, use = prefix
|
184
|
+
"#{indent}= #{erb_code[1..-1].strip}"
|
185
|
+
else
|
186
|
+
# It was originally <% ... %>, use - prefix
|
187
|
+
"#{indent}- #{erb_code}"
|
188
|
+
end
|
189
|
+
|
176
190
|
# Process children with increased depth
|
177
191
|
node.children.each do |child|
|
178
192
|
child_lines = process_node(child, depth + 1)
|
179
193
|
lines.concat(child_lines) unless child_lines.empty?
|
180
194
|
end
|
181
|
-
|
195
|
+
|
182
196
|
return lines
|
183
197
|
end
|
184
198
|
|
@@ -206,6 +220,10 @@ module Blueprint
|
|
206
220
|
text = process_inline_text(text.strip)
|
207
221
|
if text.empty?
|
208
222
|
lines << "#{indent}#{tag_line}"
|
223
|
+
elsif text.start_with?('/')
|
224
|
+
# Text starting with / needs pipe notation to avoid being treated as comment
|
225
|
+
lines << "#{indent}#{tag_line}"
|
226
|
+
lines << "#{" " * ((depth + 1) * @indent_size)}| #{text}"
|
209
227
|
else
|
210
228
|
lines << "#{indent}#{tag_line} #{text}"
|
211
229
|
end
|
@@ -227,7 +245,7 @@ module Blueprint
|
|
227
245
|
# Strip and split classes, filtering out empty strings
|
228
246
|
classes = node['class']&.strip&.split(/\s+/)&.reject(&:empty?) || []
|
229
247
|
attributes = collect_attributes(node)
|
230
|
-
|
248
|
+
|
231
249
|
# Treat empty id as no id
|
232
250
|
id = nil if id && id.strip.empty?
|
233
251
|
|
@@ -299,14 +317,36 @@ module Blueprint
|
|
299
317
|
# Extract indentation level if present
|
300
318
|
extra_indent = 0
|
301
319
|
if comment_text =~ /:INDENT:(\d+)$/
|
302
|
-
extra_indent =
|
320
|
+
extra_indent = ::Regexp.last_match(1).to_i
|
303
321
|
comment_text = comment_text.sub(/:INDENT:\d+$/, '')
|
304
322
|
end
|
305
|
-
|
323
|
+
|
306
324
|
total_depth = depth + extra_indent
|
307
325
|
indent = ' ' * (total_depth * @indent_size)
|
308
326
|
|
309
|
-
if comment_text.start_with?('
|
327
|
+
if comment_text.start_with?('ERB_MULTILINE_CODE:')
|
328
|
+
erb_content = comment_text.sub('ERB_MULTILINE_CODE:', '').gsub('__ARROW__', '-->')
|
329
|
+
# Use ruby: block for multiline code
|
330
|
+
lines = ["#{indent}ruby:"]
|
331
|
+
erb_content.lines.each do |line|
|
332
|
+
lines << "#{indent} #{line.rstrip}"
|
333
|
+
end
|
334
|
+
lines
|
335
|
+
elsif comment_text.start_with?('ERB_MULTILINE_OUTPUT:')
|
336
|
+
erb_content = comment_text.sub('ERB_MULTILINE_OUTPUT:', '').gsub('__ARROW__', '-->')
|
337
|
+
# For multiline output, use line continuation
|
338
|
+
lines = erb_content.lines.map(&:rstrip)
|
339
|
+
if lines.length == 1
|
340
|
+
["#{indent}= #{lines[0]}"]
|
341
|
+
else
|
342
|
+
result = ["#{indent}= #{lines[0]} \\"]
|
343
|
+
lines[1..-2].each do |line|
|
344
|
+
result << "#{indent} #{line} \\"
|
345
|
+
end
|
346
|
+
result << "#{indent} #{lines[-1]}" if lines.length > 1
|
347
|
+
result
|
348
|
+
end
|
349
|
+
elsif comment_text.start_with?('ERB_OUTPUT_BLOCK:')
|
310
350
|
erb_content = comment_text.sub('ERB_OUTPUT_BLOCK:', '')
|
311
351
|
["#{indent}= #{erb_content}"]
|
312
352
|
elsif comment_text.start_with?('ERB_OUTPUT:')
|