Skip to content

Commit b1ec7f9

Browse files
authored
Merge pull request #39 from nebulab/elia+francesco/tw-class-sorter
Add support for sorting TailwindCSS class names
2 parents 246816c + 2624861 commit b1ec7f9

File tree

8 files changed

+104
-11
lines changed

8 files changed

+104
-11
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
/spec/reports/
88
/tmp/
99
/Gemfile.lock
10+
/test/fixtures/tailwindcss/class_sorting.css

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
125125

126126
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
127127

128+
In order to run a specific test, use the following command:
129+
130+
```bash
131+
m test/erb/test_formatter.rb:123
132+
```
133+
128134
## Contributing
129135

130136
Bug reports and pull requests are welcome on GitHub at https://github.com/nebulab/erb-formatter.

erb-formatter.gemspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,7 @@ Gem::Specification.new do |spec|
2929
spec.require_paths = ["lib"]
3030

3131
spec.add_dependency "syntax_tree", '~> 6.0'
32+
33+
spec.add_development_dependency "tailwindcss-rails", "~> 2.0"
34+
spec.add_development_dependency "m", "~> 1.0"
3235
end

lib/erb/formatter.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,15 @@ def self.format(source, filename: nil)
7575
new(source, filename: filename).html
7676
end
7777

78-
def initialize(source, line_width: 80, single_class_per_line: false, filename: nil, debug: $DEBUG)
78+
def initialize(source, line_width: 80, single_class_per_line: false, filename: nil, css_class_sorter: nil, debug: $DEBUG)
7979
@original_source = source
8080
@filename = filename || '(erb)'
8181
@line_width = line_width
8282
@source = remove_front_matter source.dup
8383
@html = +""
8484
@debug = debug
8585
@single_class_per_line = single_class_per_line
86+
@css_class_sorter = css_class_sorter
8687

8788
html.extend DebugShovel if @debug
8889

@@ -125,7 +126,9 @@ def format_attributes(tag_name, attrs, tag_closing)
125126
return "" if attrs.strip.empty?
126127

127128
plain_attrs = attrs.tr("\n", " ").squeeze(" ").gsub(erb_tags_regexp, erb_tags)
128-
return " #{plain_attrs}" if "<#{tag_name} #{plain_attrs}#{tag_closing}".size <= line_width
129+
within_line_width = "<#{tag_name} #{plain_attrs}#{tag_closing}".size <= line_width
130+
131+
return " #{plain_attrs}" if within_line_width && !@css_class_sorter && !plain_attrs.match?(/ class=/)
129132

130133
attr_html = ""
131134
tag_stack_push(['attr='], attrs)
@@ -140,8 +143,10 @@ def format_attributes(tag_name, attrs, tag_closing)
140143
end
141144

142145
value_parts = value[1...-1].strip.split(/\s+/)
146+
value_parts.sort_by!(&@css_class_sorter) if name == 'class' && @css_class_sorter
143147

144-
full_attr = indented("#{name}=#{value[0]}#{value_parts.join(" ")}#{value[-1]}")
148+
full_attr = "#{name}=#{value[0]}#{value_parts.join(" ")}#{value[-1]}"
149+
full_attr = within_line_width ? " #{full_attr}" : indented(full_attr)
145150

146151
if full_attr.size > line_width && MULTILINE_ATTR_NAMES.include?(name) && attr.match?(QUOTED_ATTR)
147152
attr_html << indented("#{name}=#{value[0]}")
@@ -165,14 +170,14 @@ def format_attributes(tag_name, attrs, tag_closing)
165170
end
166171

167172
tag_stack_pop('attr"', value)
168-
attr_html << indented(value[-1])
173+
attr_html << (within_line_width ? value[-1] : indented(value[-1]))
169174
else
170175
attr_html << full_attr
171176
end
172177
end
173178

174179
tag_stack_pop(['attr='], attrs)
175-
attr_html << indented("")
180+
attr_html << indented("") unless within_line_width
176181
attr_html
177182
end
178183

lib/erb/formatter/command_line.rb

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
require 'optparse'
44

55
class ERB::Formatter::CommandLine
6+
def self.tailwindcss_class_sorter(css_path)
7+
css = File.read(css_path)
8+
9+
css = css.tr("\n", " ").gsub(%r{\/\*.*?\*\/},"") # remove comments
10+
css = css.gsub(%r<@media.*?\{>, "") # strip media queries
11+
css = css.scan(%r<(?:^|\}|\{) *(\S.*?) *\{>).join(" ") # extract selectors
12+
classes = css.tr(","," ").split(" ").grep(/\./).uniq.map { _1.split('.').last.gsub("\\", "") }
13+
indexed_classes = Hash[classes.zip((0...classes.size).to_a)]
14+
15+
->(class_name) do
16+
indexed_classes[class_name] || classes.index { _1.start_with?(class_name) } || -1
17+
end
18+
end
619

720
attr_reader :write, :filename, :read_stdin
821

@@ -41,6 +54,10 @@ def initialize(argv, stdin: $stdin)
4154
@single_class_per_line = value
4255
end
4356

57+
parser.on("--tailwind-output-path PATH", "Set the path to the tailwind output file") do |value|
58+
@tailwind_output_path = value
59+
end
60+
4461
parser.on("--[no-]debug", "Enable debug mode") do |value|
4562
$DEBUG = value
4663
end
@@ -61,10 +78,6 @@ def ignore_list
6178
@ignore_list ||= ERB::Formatter::IgnoreList.new
6279
end
6380

64-
def ignore?(filename)
65-
66-
end
67-
6881
def run
6982
if read_stdin
7083
abort "Can't read both stdin and a list of files" unless @argv.empty?
@@ -77,11 +90,21 @@ def run
7790
end
7891
end
7992

93+
if @tailwind_output_path
94+
css_class_sorter = self.class.tailwindcss_class_sorter(@tailwind_output_path)
95+
end
96+
8097
files.each do |(filename, code)|
8198
if ignore_list.should_ignore_file? filename
8299
print code unless write
83100
else
84-
html = ERB::Formatter.new(code, filename: filename, line_width: @width || 80, single_class_per_line: @single_class_per_line)
101+
html = ERB::Formatter.new(
102+
code,
103+
filename: filename,
104+
line_width: @width || 80,
105+
single_class_per_line: @single_class_per_line,
106+
css_class_sorter: css_class_sorter
107+
)
85108

86109
if write
87110
File.write(filename, html)

test/erb/test_formatter.rb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def test_fixtures
1616
expected_path = erb_path.chomp('.erb') + '.expected.erb'
1717

1818
# File.write expected_path, ERB::Formatter.format(File.read(erb_path))
19-
assert_equal(File.read(expected_path), ERB::Formatter.format(File.read(erb_path)), "Formatting of #{erb_path} failed")
19+
assert_equal(File.read(expected_path), ERB::Formatter.new(File.read(erb_path)).to_s, "Formatting of #{erb_path} failed")
2020
end
2121
end
2222

@@ -156,4 +156,29 @@ def test_format_ruby_with_long_lines_and_larger_line_width
156156
).to_s,
157157
)
158158
end
159+
160+
def test_tailwindcss_class_sorting
161+
require 'tailwindcss-rails'
162+
require 'erb/formatter/command_line'
163+
164+
error_log = "#{__dir__}/../../tmp/tailwindcss.err.log"
165+
Dir.mkdir(File.dirname(error_log)) unless File.exist?(File.dirname(error_log))
166+
167+
system(
168+
Tailwindcss::Commands.executable,
169+
"--content", "#{__dir__}/../fixtures/tailwindcss/class_sorting.html.erb",
170+
"--output", "#{__dir__}/../fixtures/tailwindcss/class_sorting.css",
171+
err: error_log,
172+
) || raise("Failed to generate tailwindcss output:\n#{File.read(error_log)}")
173+
174+
css_class_sorter = ERB::Formatter::CommandLine.tailwindcss_class_sorter("#{__dir__}/../fixtures/tailwindcss/class_sorting.css")
175+
176+
assert_equal(
177+
File.read("#{__dir__}/../fixtures/tailwindcss/class_sorting.html.expected.erb"),
178+
ERB::Formatter.new(
179+
File.read("#{__dir__}/../fixtures/tailwindcss/class_sorting.html.erb"),
180+
css_class_sorter: css_class_sorter,
181+
).to_s,
182+
)
183+
end
159184
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<div class="pt-2 p-4">
2+
</div>
3+
4+
<div class="text-gray-700 shadow-md p-3 border-gray-300 ml-4 h-24 flex border-2">
5+
</div>
6+
7+
<div class="hover:opacity-75 opacity-50 hover:scale-150 scale-125">
8+
</div>
9+
10+
<div class="lg:grid-cols-4 grid sm:grid-cols-3 grid-cols-2">
11+
</div>
12+
13+
<div class="p-3 shadow-xl select2-dropdown">
14+
</div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<div class="p-4 pt-2">
2+
</div>
3+
4+
<div
5+
class="ml-4 flex h-24 border-2 border-gray-300 p-3 text-gray-700 shadow-md"
6+
>
7+
</div>
8+
9+
<div class="scale-125 opacity-50 hover:scale-150 hover:opacity-75">
10+
</div>
11+
12+
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4">
13+
</div>
14+
15+
<div class="select2-dropdown p-3 shadow-xl">
16+
</div>

0 commit comments

Comments
 (0)