blueprint-html2slim 1.1.0 → 1.3.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.
@@ -0,0 +1,254 @@
1
+ require_relative 'slim_manipulator'
2
+ require 'json'
3
+
4
+ module Blueprint
5
+ module Html2Slim
6
+ class SlimRailsifier < SlimManipulator
7
+ def railsify_file(file_path)
8
+ content = read_file(file_path)
9
+ original_content = content.dup
10
+ conversions = []
11
+
12
+ # Load custom mappings if provided
13
+ @custom_mappings = load_custom_mappings if options[:mappings]
14
+
15
+ if options[:add_helpers] != false
16
+ content, link_conversions = convert_links_to_helpers(content)
17
+ conversions.concat(link_conversions)
18
+ end
19
+
20
+ if options[:use_assets] != false
21
+ content, asset_conversions = convert_assets_to_pipeline(content)
22
+ conversions.concat(asset_conversions)
23
+ end
24
+
25
+ if options[:add_csrf]
26
+ content, csrf_additions = add_csrf_protection(content)
27
+ conversions.concat(csrf_additions)
28
+ end
29
+
30
+ if options[:dry_run]
31
+ if conversions.any?
32
+ puts "\nChanges that would be made to #{file_path}:"
33
+ puts " Conversions: #{conversions.join(", ")}"
34
+ show_diff(original_content, content) if options[:verbose]
35
+ else
36
+ puts "No Rails conversions needed for #{file_path}"
37
+ end
38
+ elsif content != original_content
39
+ write_file(file_path, content)
40
+ end
41
+
42
+ { success: true, conversions: conversions }
43
+ rescue StandardError => e
44
+ { success: false, error: e.message }
45
+ end
46
+
47
+ private
48
+
49
+ def load_custom_mappings
50
+ return {} unless options[:mappings] && File.exist?(options[:mappings])
51
+
52
+ begin
53
+ content = File.read(options[:mappings])
54
+ if options[:mappings].end_with?('.json')
55
+ JSON.parse(content)
56
+ elsif options[:mappings].end_with?('.yml', '.yaml')
57
+ require 'yaml'
58
+ YAML.load_file(options[:mappings])
59
+ else
60
+ # Try to parse as JSON by default
61
+ JSON.parse(content)
62
+ end
63
+ rescue StandardError => e
64
+ puts "Warning: Failed to load mappings from #{options[:mappings]}: #{e.message}"
65
+ {}
66
+ end
67
+ end
68
+
69
+ def convert_links_to_helpers(content)
70
+ conversions = []
71
+ lines = content.split("\n")
72
+
73
+ lines.map!.with_index do |line, index|
74
+ if line =~ /^(\s*)a\[href="([^"]+)"([^\]]*)\](.*)/
75
+ indent = ::Regexp.last_match(1)
76
+ href = ::Regexp.last_match(2)
77
+ attrs = ::Regexp.last_match(3)
78
+ text = ::Regexp.last_match(4).strip
79
+
80
+ # Convert known static paths to Rails helpers (only if mappings provided)
81
+ rails_path = path_to_rails_helper(href)
82
+ if rails_path
83
+ conversions << "link at line #{index + 1}"
84
+ "#{indent}= link_to \"#{text}\", #{rails_path}#{format_link_options(attrs)}"
85
+ else
86
+ line
87
+ end
88
+ else
89
+ line
90
+ end
91
+ end
92
+
93
+ [lines.join("\n"), conversions]
94
+ end
95
+
96
+ def convert_assets_to_pipeline(content)
97
+ conversions = []
98
+ lines = content.split("\n")
99
+
100
+ lines.map!.with_index do |line, index|
101
+ # Convert stylesheet links
102
+ if line =~ /^(\s*)link\[.*href="([^"]+\.css[^"]*)"/
103
+ indent = ::Regexp.last_match(1)
104
+ href = ::Regexp.last_match(2)
105
+
106
+ if !href.start_with?('http') && !href.start_with?('//')
107
+ asset_name = File.basename(href, '.*')
108
+ conversions << "stylesheet at line #{index + 1}"
109
+ "#{indent}= stylesheet_link_tag '#{asset_name}'"
110
+ else
111
+ line
112
+ end
113
+ # Convert script tags
114
+ elsif line =~ /^(\s*)script\[.*src="([^"]+\.js[^"]*)"/
115
+ indent = ::Regexp.last_match(1)
116
+ src = ::Regexp.last_match(2)
117
+
118
+ if !src.start_with?('http') && !src.start_with?('//')
119
+ asset_name = File.basename(src, '.*')
120
+ conversions << "javascript at line #{index + 1}"
121
+ "#{indent}= javascript_include_tag '#{asset_name}'"
122
+ else
123
+ line
124
+ end
125
+ # Convert image tags
126
+ elsif line =~ /^(\s*)img\[src="([^"]+)"([^\]]*)\]/
127
+ indent = ::Regexp.last_match(1)
128
+ src = ::Regexp.last_match(2)
129
+ attrs = ::Regexp.last_match(3)
130
+
131
+ if !src.start_with?('http') && !src.start_with?('//')
132
+ conversions << "image at line #{index + 1}"
133
+ "#{indent}= image_tag '#{src}'#{format_image_options(attrs)}"
134
+ else
135
+ line
136
+ end
137
+ else
138
+ line
139
+ end
140
+ end
141
+
142
+ [lines.join("\n"), conversions]
143
+ end
144
+
145
+ def add_csrf_protection(content)
146
+ conversions = []
147
+ lines = content.split("\n")
148
+
149
+ # Find head section and add CSRF meta tags
150
+ head_index = lines.index { |line| line.strip.start_with?('head') }
151
+
152
+ if head_index
153
+ indent = lines[head_index][/\A */]
154
+ csrf_tags = [
155
+ "#{indent} = csrf_meta_tags"
156
+ ]
157
+
158
+ # Insert after head tag
159
+ lines.insert(head_index + 1, *csrf_tags)
160
+ conversions << 'CSRF meta tags'
161
+ end
162
+
163
+ # Add CSRF token to forms
164
+ lines.map!.with_index do |line, index|
165
+ if line =~ /^(\s*)form\[.*method="(post|patch|put|delete)"/i
166
+ # Mark that this form needs CSRF token
167
+ conversions << "form CSRF at line #{index + 1}"
168
+ line
169
+ else
170
+ line
171
+ end
172
+ end
173
+
174
+ [lines.join("\n"), conversions]
175
+ end
176
+
177
+ def path_to_rails_helper(href)
178
+ # Only use custom mappings if provided, no fallbacks
179
+ return nil unless @custom_mappings
180
+
181
+ # Remove leading slash if present for comparison
182
+ clean_href = href.sub(%r{^/}, '')
183
+
184
+ # Try exact match
185
+ return @custom_mappings[href] if @custom_mappings[href]
186
+ return @custom_mappings[clean_href] if @custom_mappings[clean_href]
187
+
188
+ # Try pattern matching for custom mappings with wildcards
189
+ @custom_mappings.each do |pattern, helper|
190
+ if pattern.include?('*')
191
+ regex_pattern = pattern.gsub('*', '.*')
192
+ return helper if href.match?(/^#{regex_pattern}$/)
193
+ end
194
+ end
195
+
196
+ nil # No mapping found
197
+ end
198
+
199
+ def format_link_options(attrs)
200
+ return '' if attrs.nil? || attrs.strip.empty?
201
+
202
+ # Parse remaining attributes
203
+ options = []
204
+ attrs.scan(/(\w+)="([^"]+)"/).each do |key, value|
205
+ next if key == 'href'
206
+
207
+ if key == 'class'
208
+ options << "class: '#{value}'"
209
+ elsif key == 'id'
210
+ options << "id: '#{value}'"
211
+ elsif key.start_with?('data-')
212
+ data_key = key.sub('data-', '').gsub('-', '_')
213
+ options << "data: { #{data_key}: '#{value}' }"
214
+ else
215
+ options << "#{key}: '#{value}'"
216
+ end
217
+ end
218
+
219
+ options.empty? ? '' : ", #{options.join(", ")}"
220
+ end
221
+
222
+ def format_image_options(attrs)
223
+ return '' if attrs.nil? || attrs.strip.empty?
224
+
225
+ options = []
226
+ attrs.scan(/(\w+)="([^"]+)"/).each do |key, value|
227
+ next if key == 'src'
228
+
229
+ options << if key == 'alt'
230
+ "alt: '#{value}'"
231
+ elsif key == 'class'
232
+ "class: '#{value}'"
233
+ elsif %w[width height].include?(key)
234
+ "#{key}: #{value}"
235
+ else
236
+ "#{key}: '#{value}'"
237
+ end
238
+ end
239
+
240
+ options.empty? ? '' : ", #{options.join(", ")}"
241
+ end
242
+
243
+ def show_diff(original, modified)
244
+ puts "\n--- Original ---"
245
+ puts original[0..500]
246
+ puts '...' if original.length > 500
247
+ puts "\n+++ Modified +++"
248
+ puts modified[0..500]
249
+ puts '...' if modified.length > 500
250
+ puts
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,170 @@
1
+ require_relative 'slim_manipulator'
2
+
3
+ module Blueprint
4
+ module Html2Slim
5
+ class SlimValidator < SlimManipulator
6
+ def validate_file(file_path)
7
+ content = read_file(file_path)
8
+ structure = parse_slim_structure(content)
9
+
10
+ errors = []
11
+ warnings = []
12
+
13
+ # Check for syntax errors
14
+ syntax_errors = check_syntax_errors(structure)
15
+ errors.concat(syntax_errors)
16
+
17
+ # Check for common issues that might cause problems
18
+ potential_issues = check_potential_issues(structure)
19
+ warnings.concat(potential_issues)
20
+
21
+ # Rails-specific checks if requested
22
+ if options[:check_rails]
23
+ rails_issues = check_rails_conventions(structure)
24
+ warnings.concat(rails_issues)
25
+ end
26
+
27
+ # Try to parse with Slim if available
28
+ if defined?(Slim)
29
+ begin
30
+ Slim::Template.new { content }
31
+ rescue StandardError => e
32
+ errors << "Slim parsing error: #{e.message}"
33
+ end
34
+ end
35
+
36
+ {
37
+ valid: errors.empty?,
38
+ errors: errors,
39
+ warnings: warnings
40
+ }
41
+ rescue StandardError => e
42
+ {
43
+ valid: false,
44
+ errors: ["Failed to read or validate file: #{e.message}"],
45
+ warnings: []
46
+ }
47
+ end
48
+
49
+ private
50
+
51
+ def check_syntax_errors(structure)
52
+ errors = []
53
+ indent_stack = [-1]
54
+
55
+ structure.each_with_index do |item, _index|
56
+ line = item[:stripped]
57
+ indent = item[:indent_level]
58
+ line_num = item[:line_number]
59
+
60
+ # Check indentation consistency
61
+ if indent > indent_stack.last + 1
62
+ errors << "Line #{line_num}: Invalid indentation jump (expected #{indent_stack.last + 1}, got #{indent})"
63
+ end
64
+
65
+ # Update indent stack
66
+ indent_stack.pop while indent_stack.last > indent
67
+ indent_stack << indent if indent > indent_stack.last
68
+
69
+ # Check for invalid syntax patterns
70
+ if line.start_with?('/') && !line.match?(%r{^/[!|\s]})
71
+ msg = "Line #{line_num}: Forward slash will be interpreted as comment. "
72
+ errors << "#{msg}Use pipe notation for text: | #{line}"
73
+ end
74
+
75
+ # Check for text after element that starts with slash
76
+ if line =~ %r{^([a-z#.][^\s]*)\s+(/[^/].*)$}i
77
+ errors << "Line #{line_num}: Forward slash will be interpreted as comment. Use pipe notation for text"
78
+ end
79
+
80
+ # Check for unclosed brackets in attributes
81
+ if line.include?('[') && !line.include?(']')
82
+ errors << "Line #{line_num}: Unclosed attribute bracket"
83
+ elsif line.include?(']') && !line.include?('[')
84
+ errors << "Line #{line_num}: Unexpected closing bracket"
85
+ end
86
+
87
+ # Check for invalid Ruby code markers
88
+ errors << "Line #{line_num}: Ruby code marker without code" if line =~ /^[=-]\s*$/
89
+
90
+ # Check for mixed tabs and spaces (if strict mode)
91
+ if options[:strict] && item[:line].match?(/\t/)
92
+ errors << "Line #{line_num}: Contains tabs (use spaces for indentation)"
93
+ end
94
+ end
95
+
96
+ errors
97
+ end
98
+
99
+ def check_potential_issues(structure)
100
+ warnings = []
101
+
102
+ structure.each do |item|
103
+ line = item[:stripped]
104
+ line_num = item[:line_number]
105
+
106
+ # Warn about text that might be misinterpreted
107
+ warnings << "Line #{line_num}: Text containing '/' might need pipe notation" if line =~ %r{^\w+.*\s/\w+}
108
+
109
+ # Warn about very long lines
110
+ warnings << "Line #{line_num}: Line exceeds 120 characters" if item[:line].length > 120
111
+
112
+ # Warn about deprecated Slim syntax
113
+ warnings << "Line #{line_num}: Single quote for text interpolation is deprecated" if line.start_with?("'")
114
+
115
+ # Warn about potential multiline text issues
116
+ if line =~ /^[a-z#.][^\s]*\s+[^=\-|].{50,}/i
117
+ warnings << "Line #{line_num}: Long inline text might be better as multiline with pipe notation"
118
+ end
119
+
120
+ # Warn about inline styles (if strict)
121
+ if options[:strict] && line.include?('style=')
122
+ warnings << "Line #{line_num}: Inline styles detected (consider using classes)"
123
+ end
124
+ end
125
+
126
+ warnings
127
+ end
128
+
129
+ def check_rails_conventions(structure)
130
+ warnings = []
131
+
132
+ structure.each do |item|
133
+ line = item[:stripped]
134
+ line_num = item[:line_number]
135
+
136
+ # Check for static asset links that should use Rails helpers
137
+ if line =~ /link.*href=["'].*\.(css|scss)/
138
+ warnings << "Line #{line_num}: Consider using stylesheet_link_tag for CSS files"
139
+ end
140
+
141
+ if line =~ /script.*src=["'].*\.js/
142
+ warnings << "Line #{line_num}: Consider using javascript_include_tag for JS files"
143
+ end
144
+
145
+ # Check for static image paths
146
+ if line =~ %r{img.*src=["'](?!http|//)}
147
+ warnings << "Line #{line_num}: Consider using image_tag helper for images"
148
+ end
149
+
150
+ # Check for forms without Rails helpers
151
+ if line.start_with?('form') && !line.include?('form_for') && !line.include?('form_with')
152
+ warnings << "Line #{line_num}: Consider using Rails form helpers (form_for, form_with)"
153
+ end
154
+
155
+ # Check for missing CSRF token in forms
156
+ if line =~ /^form\[.*method=["'](post|patch|put|delete)/i
157
+ warnings << "Line #{line_num}: Ensure CSRF token is included in form"
158
+ end
159
+
160
+ # Check for hardcoded URLs
161
+ if line =~ %r{href=["']/(users|posts|articles|products)}
162
+ warnings << "Line #{line_num}: Consider using Rails path helpers instead of hardcoded URLs"
163
+ end
164
+ end
165
+
166
+ warnings
167
+ end
168
+ end
169
+ end
170
+ end
@@ -1,5 +1,5 @@
1
1
  module Blueprint
2
2
  module Html2Slim
3
- VERSION = '1.1.0'.freeze
3
+ VERSION = '1.3.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blueprint-html2slim
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Elchinov
@@ -127,6 +127,7 @@ email:
127
127
  - info@railsblueprint.com
128
128
  executables:
129
129
  - html2slim
130
+ - slimtool
130
131
  extensions: []
131
132
  extra_rdoc_files: []
132
133
  files:
@@ -135,8 +136,15 @@ files:
135
136
  - README.md
136
137
  - bin/html2slim
137
138
  - bin/index.html
139
+ - bin/slimtool
138
140
  - lib/blueprint/html2slim.rb
139
141
  - lib/blueprint/html2slim/converter.rb
142
+ - lib/blueprint/html2slim/link_extractor.rb
143
+ - lib/blueprint/html2slim/slim_extractor.rb
144
+ - lib/blueprint/html2slim/slim_fixer.rb
145
+ - lib/blueprint/html2slim/slim_manipulator.rb
146
+ - lib/blueprint/html2slim/slim_railsifier.rb
147
+ - lib/blueprint/html2slim/slim_validator.rb
140
148
  - lib/blueprint/html2slim/version.rb
141
149
  homepage: https://github.com/railsblueprint/html2slim
142
150
  licenses: