Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for sorting TailwindCSS class names #39

Merged
merged 4 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
/spec/reports/
/tmp/
/Gemfile.lock
/test/fixtures/tailwindcss/class_sorting.css
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run

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).

In order to run a specific test, use the following command:

```bash
m test/erb/test_formatter.rb:123
```

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/nebulab/erb-formatter.
Expand Down
3 changes: 3 additions & 0 deletions erb-formatter.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

spec.add_dependency "syntax_tree", '~> 6.0'

spec.add_development_dependency "tailwindcss-rails", "~> 2.0"
spec.add_development_dependency "m", "~> 1.0"
end
15 changes: 10 additions & 5 deletions lib/erb/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,15 @@ def self.format(source, filename: nil)
new(source, filename: filename).html
end

def initialize(source, line_width: 80, single_class_per_line: false, filename: nil, debug: $DEBUG)
def initialize(source, line_width: 80, single_class_per_line: false, filename: nil, css_class_sorter: nil, debug: $DEBUG)
@original_source = source
@filename = filename || '(erb)'
@line_width = line_width
@source = remove_front_matter source.dup
@html = +""
@debug = debug
@single_class_per_line = single_class_per_line
@css_class_sorter = css_class_sorter

html.extend DebugShovel if @debug

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

plain_attrs = attrs.tr("\n", " ").squeeze(" ").gsub(erb_tags_regexp, erb_tags)
return " #{plain_attrs}" if "<#{tag_name} #{plain_attrs}#{tag_closing}".size <= line_width
within_line_width = "<#{tag_name} #{plain_attrs}#{tag_closing}".size <= line_width

return " #{plain_attrs}" if within_line_width && !@css_class_sorter && !plain_attrs.match?(/ class=/)

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

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

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

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

tag_stack_pop('attr"', value)
attr_html << indented(value[-1])
attr_html << (within_line_width ? value[-1] : indented(value[-1]))
else
attr_html << full_attr
end
end

tag_stack_pop(['attr='], attrs)
attr_html << indented("")
attr_html << indented("") unless within_line_width
attr_html
end

Expand Down
33 changes: 28 additions & 5 deletions lib/erb/formatter/command_line.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
require 'optparse'

class ERB::Formatter::CommandLine
def self.tailwindcss_class_sorter(css_path)
css = File.read(css_path)

css = css.tr("\n", " ").gsub(%r{\/\*.*?\*\/},"") # remove comments
css = css.gsub(%r<@media.*?\{>, "") # strip media queries
css = css.scan(%r<(?:^|\}|\{) *(\S.*?) *\{>).join(" ") # extract selectors
classes = css.tr(","," ").split(" ").grep(/\./).uniq.map { _1.split('.').last.gsub("\\", "") }
indexed_classes = Hash[classes.zip((0...classes.size).to_a)]

->(class_name) do
indexed_classes[class_name] || classes.index { _1.start_with?(class_name) } || -1
end
end

attr_reader :write, :filename, :read_stdin

Expand Down Expand Up @@ -41,6 +54,10 @@ def initialize(argv, stdin: $stdin)
@single_class_per_line = value
end

parser.on("--tailwind-output-path PATH", "Set the path to the tailwind output file") do |value|
@tailwind_output_path = value
end

parser.on("--[no-]debug", "Enable debug mode") do |value|
$DEBUG = value
end
Expand All @@ -61,10 +78,6 @@ def ignore_list
@ignore_list ||= ERB::Formatter::IgnoreList.new
end

def ignore?(filename)

end

def run
if read_stdin
abort "Can't read both stdin and a list of files" unless @argv.empty?
Expand All @@ -77,11 +90,21 @@ def run
end
end

if @tailwind_output_path
css_class_sorter = self.class.tailwindcss_class_sorter(@tailwind_output_path)
end

files.each do |(filename, code)|
if ignore_list.should_ignore_file? filename
print code unless write
else
html = ERB::Formatter.new(code, filename: filename, line_width: @width || 80, single_class_per_line: @single_class_per_line)
html = ERB::Formatter.new(
code,
filename: filename,
line_width: @width || 80,
single_class_per_line: @single_class_per_line,
css_class_sorter: css_class_sorter
)

if write
File.write(filename, html)
Expand Down
27 changes: 26 additions & 1 deletion test/erb/test_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_fixtures
expected_path = erb_path.chomp('.erb') + '.expected.erb'

# File.write expected_path, ERB::Formatter.format(File.read(erb_path))
assert_equal(File.read(expected_path), ERB::Formatter.format(File.read(erb_path)), "Formatting of #{erb_path} failed")
assert_equal(File.read(expected_path), ERB::Formatter.new(File.read(erb_path)).to_s, "Formatting of #{erb_path} failed")
end
end

Expand Down Expand Up @@ -156,4 +156,29 @@ def test_format_ruby_with_long_lines_and_larger_line_width
).to_s,
)
end

def test_tailwindcss_class_sorting
require 'tailwindcss-rails'
require 'erb/formatter/command_line'

error_log = "#{__dir__}/../../tmp/tailwindcss.err.log"
Dir.mkdir(File.dirname(error_log)) unless File.exist?(File.dirname(error_log))

system(
Tailwindcss::Commands.executable,
"--content", "#{__dir__}/../fixtures/tailwindcss/class_sorting.html.erb",
"--output", "#{__dir__}/../fixtures/tailwindcss/class_sorting.css",
err: error_log,
) || raise("Failed to generate tailwindcss output:\n#{File.read(error_log)}")

css_class_sorter = ERB::Formatter::CommandLine.tailwindcss_class_sorter("#{__dir__}/../fixtures/tailwindcss/class_sorting.css")

assert_equal(
File.read("#{__dir__}/../fixtures/tailwindcss/class_sorting.html.expected.erb"),
ERB::Formatter.new(
File.read("#{__dir__}/../fixtures/tailwindcss/class_sorting.html.erb"),
css_class_sorter: css_class_sorter,
).to_s,
)
end
end
14 changes: 14 additions & 0 deletions test/fixtures/tailwindcss/class_sorting.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div class="pt-2 p-4">
</div>

<div class="text-gray-700 shadow-md p-3 border-gray-300 ml-4 h-24 flex border-2">
</div>

<div class="hover:opacity-75 opacity-50 hover:scale-150 scale-125">
</div>

<div class="lg:grid-cols-4 grid sm:grid-cols-3 grid-cols-2">
</div>

<div class="p-3 shadow-xl select2-dropdown">
</div>
16 changes: 16 additions & 0 deletions test/fixtures/tailwindcss/class_sorting.html.expected.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="p-4 pt-2">
</div>

<div
class="ml-4 flex h-24 border-2 border-gray-300 p-3 text-gray-700 shadow-md"
>
</div>

<div class="scale-125 opacity-50 hover:scale-150 hover:opacity-75">
</div>

<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4">
</div>

<div class="select2-dropdown p-3 shadow-xl">
</div>
Loading