clsx-rails 1.0.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f910e1aa08bd5f0ed39ae1e8b6472cc5c2c7872fbccdbb11ad8269cfb8ac5ff
4
- data.tar.gz: fb198c1b483216c51529869c66ed7d30b6ea4263ad7c97e9a7d9c9a077c2d8a4
3
+ metadata.gz: 5ac79817d0d2f653dbc76e4f3d044c6d599f342acecc1c6d6c6639dbbdec1ecf
4
+ data.tar.gz: ce38f0509202614f8aeab6f16f0079eb93d686e6c5c42d48170af8d50ccd801b
5
5
  SHA512:
6
- metadata.gz: 7cea9003c893fbc6cd5de38f42767a4b06156176fea6fbf2c5be67aa90452546d9a9c8f53f66db2ea77c69cc98abcdeddd2814311cf5daf834b11f0c11245bf0
7
- data.tar.gz: 74be5e9a20bf2e34d1e295d71ba249a8dfadcb7a9b74704de908e4289a6fc28a31f838053d13be6f29336d69981888fc8bb27c505ca5985ee9a307ac8fd12b4e
6
+ metadata.gz: 0c8e9e1d7100c07f7336b9a8ac4fd699b4a1a90f78bd4c2fa51cdd931583948913d9622e901a17f3fa03104fbebca4f92ac07df50287a1f4209bb531cf4886fe
7
+ data.tar.gz: 155e24fb75550a7a12e050fc20089fc9677e0435d7b08f0412d65b19a727f7fbca2cb86cbb0d7cf6df44b037e4e515efa947c42d2da9f020805ac8c4461d7e58
data/CHANGELOG.md CHANGED
@@ -1,5 +1,57 @@
1
- ## [Unreleased]
1
+ # Changelog
2
2
 
3
- ## [1.0.0] - 2024-03-03
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
6
+
7
+ ## Unreleased changes
8
+
9
+ ## v2.0.0 (2025-01-11)
10
+
11
+ ### Breaking Changes
12
+ - Drop Rails 6.1 and 7.0 support, require Rails 7.1+
13
+
14
+ ### Features
15
+ - Add Ruby 3.4 support
16
+ - Add Rails 8.0, 8.1, and edge support
17
+
18
+ ### Performance Improvements
19
+ - Rewrite algorithm for 2-5x performance improvement
20
+ - Add fast-paths for single string, string array, and simple hash
21
+ - Use Hash-based deduplication instead of Array + `uniq!`
22
+ - Use `Symbol#name` instead of `to_s` for faster symbol conversion
23
+ - Use direct class comparison for type checking
24
+ - Remove unused `require 'set'`
25
+
26
+ ### Chore
27
+ - Refactor benchmark infrastructure (data.rb, original.rb, quick.rb, run.rb)
28
+ - Add CLAUDE.md for AI coding assistants
29
+
30
+ ## v1.0.1 (2024-03-04)
31
+
32
+ ### Performance Improvements
33
+ - Speeds up the performance by 2x [`32236ed`](https://github.com/svyatov/clsx-rails/commit/32236ed)
34
+
35
+ ### Chore
36
+ - Fixes CI action [`f1b948c`](https://github.com/svyatov/clsx-rails/commit/f1b948c)
37
+ - Upload code coverage to CodeCov for the latest combination of Ruby and ActionView only [`4e5d768`](https://github.com/svyatov/clsx-rails/commit/4e5d768)
38
+
39
+ ### Documentation
40
+ - Changelog update + version bump [`13b408d`](https://github.com/svyatov/clsx-rails/commit/13b408d)
41
+ - Adds information about supported Ruby and Rails version [skip ci] [`2e6483f`](https://github.com/svyatov/clsx-rails/commit/2e6483f)
42
+ - Adds link to the CodeCov badge, switch to Conventional Commits [`b48cc84`](https://github.com/svyatov/clsx-rails/commit/b48cc84)
43
+
44
+ ### Other
45
+ - Updates CI badge [skip ci] [`a829613`](https://github.com/svyatov/clsx-rails/commit/a829613)
46
+ - Adds code coverage tracking [`0c5d34c`](https://github.com/svyatov/clsx-rails/commit/0c5d34c)
47
+ - Ignore ruby-head in the CI matrix [`f3ab4df`](https://github.com/svyatov/clsx-rails/commit/f3ab4df)
48
+ - Better name for the GitHub Action job [`a28adb7`](https://github.com/svyatov/clsx-rails/commit/a28adb7)
49
+ - Adds badges, fixes rubocop configuration for CI [`56fab44`](https://github.com/svyatov/clsx-rails/commit/56fab44)
50
+ - Create dependabot.yml [`ed1e0eb`](https://github.com/svyatov/clsx-rails/commit/ed1e0eb)
51
+ - Fixes GitHub Actions [`f58a4b2`](https://github.com/svyatov/clsx-rails/commit/f58a4b2)
52
+
53
+ ## v1.0.0 (2024-03-03)
54
+
55
+ ### Other
56
+ - Initial commit [`f65b9b8`](https://github.com/svyatov/clsx-rails/commit/f65b9b8)
4
57
 
5
- - Initial release
data/CLAUDE.md ADDED
@@ -0,0 +1,54 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ clsx-rails is a Ruby gem that provides a Rails view helper (`clsx`/`cn`) for constructing CSS class strings conditionally. It's a Ruby port of the JavaScript [clsx](https://github.com/lukeed/clsx) package, adapted for Rails conventions.
8
+
9
+ ## Common Commands
10
+
11
+ ```bash
12
+ # Run all tests and linting (default rake task)
13
+ bundle exec rake
14
+
15
+ # Run tests only
16
+ bundle exec rake test
17
+
18
+ # Run a single test file
19
+ bundle exec ruby -Itest test/clsx/helper_test.rb
20
+
21
+ # Run a specific test method
22
+ bundle exec ruby -Itest test/clsx/helper_test.rb -n test_with_strings
23
+
24
+ # Run linter
25
+ bundle exec rake rubocop
26
+
27
+ # Run benchmark
28
+ bundle exec ruby benchmark/run.rb
29
+
30
+ # Install dependencies
31
+ bin/setup
32
+ ```
33
+
34
+ ## Architecture
35
+
36
+ The gem has a minimal structure:
37
+
38
+ - `lib/clsx-rails.rb` - Entry point that auto-includes the helper into ActionView via `ActiveSupport.on_load`
39
+ - `lib/clsx/helper.rb` - Core implementation with `clsx` method and `cn` alias
40
+ - `lib/clsx/version.rb` - Version constant
41
+
42
+ The helper uses an optimized algorithm with fast-paths for common cases (single string, string array, simple hash) and Hash-based deduplication for complex inputs.
43
+
44
+ ## Key Behaviors
45
+
46
+ - Returns `nil` (not empty string) when no classes apply - this prevents Rails from rendering empty `class=""` attributes
47
+ - Eliminates duplicate classes automatically
48
+ - Ruby falsy values are only `false` and `nil` (unlike JS, `0`, `''`, `[]`, `{}` are truthy)
49
+ - Ignores `Proc`/lambda objects and boolean `true` values
50
+ - Supports complex hash keys like `{ %w[foo bar] => true }` which resolve recursively
51
+
52
+ ## Commit Convention
53
+
54
+ Uses [Conventional Commits](https://www.conventionalcommits.org/): `feat`, `fix`, `perf`, `chore`, `docs`, `refactor`
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # clsx-rails
1
+ # clsx-rails [![Gem Version](https://img.shields.io/gem/v/clsx-rails)](https://rubygems.org/gems/clsx-rails) [![Codecov](https://img.shields.io/codecov/c/github/svyatov/clsx-rails)](https://app.codecov.io/gh/svyatov/clsx-rails) [![CI](https://github.com/svyatov/clsx-rails/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/svyatov/clsx-rails/actions?query=workflow%3ACI) [![GitHub License](https://img.shields.io/github/license/svyatov/clsx-rails)](LICENSE.txt)
2
2
 
3
3
  > A tiny Rails view helper for constructing CSS class strings conditionally.
4
4
 
@@ -6,21 +6,29 @@ This gem is essentially a clone if the [clsx](https://github.com/lukeed/clsx) np
6
6
  It provides a simple way to conditionally apply classes to HTML elements in Rails views.
7
7
  It is especially useful when you have a lot of conditional classes and you want to keep your views clean and readable.
8
8
 
9
+ ## Supported Ruby and Rails versions
10
+
11
+ Ruby 3.1+ and Rails 7.1+ are supported.
12
+
9
13
  ## Installation
10
14
 
11
15
  Install the gem and add to the application's Gemfile by executing:
12
16
 
13
- $ bundle add clsx-rails
17
+ ```bash
18
+ bundle add clsx-rails
19
+ ```
14
20
 
15
- If bundler is not being used to manage dependencies, install the gem by executing:
21
+ Or add it manually to the Gemfile:
16
22
 
17
- $ gem install clsx-rails
23
+ ```ruby
24
+ gem 'clsx-rails', '~> 1.0'
25
+ ```
18
26
 
19
27
  ## Usage
20
28
 
21
- The `clsx` method can be used in views to conditionally apply classes to HTML elements.
29
+ The `clsx` helper method can be used in views to conditionally apply classes to HTML elements.
22
30
  You can also use a slightly more conise `cn` alias.
23
- It accepts a variety of arguments and returns a string of classes.
31
+ It accepts a variety of arguments and returns a string of unique classes.
24
32
 
25
33
  ```ruby
26
34
  # Strings (variadic)
@@ -151,6 +159,18 @@ There is a simple benchmark script in the `benchmark` directory.
151
159
  You can run it with `bundle exec ruby benchmark/run.rb`.
152
160
  I've added it for easier performance testing when making changes to the gem.
153
161
 
162
+ ## Conventional Commits
163
+
164
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages.
165
+
166
+ Types of commits are:
167
+ - `feat`: a new feature
168
+ - `fix`: a bug fix
169
+ - `perf`: code that improves performance
170
+ - `chore`: updating build tasks, configs, formatting etc; no code change
171
+ - `docs`: changes to documentation
172
+ - `refactor`: refactoring code
173
+
154
174
  ## Contributing
155
175
 
156
176
  Bug reports and pull requests are welcome on GitHub at https://github.com/svyatov/clsx-rails.
data/lib/clsx/helper.rb CHANGED
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
- require 'action_view'
5
-
6
3
  # :nodoc:
7
4
  module Clsx
8
5
  # :nodoc:
9
6
  module Helper
10
7
  # The clsx function can take any number of arguments,
11
- # each of which can be an Hash, Array, Boolean, String, or Symbol.
8
+ # each of which can be Hash, Array, Boolean, String, or Symbol.
12
9
  #
13
- # **Important**: Any falsy values are discarded! Standalone Boolean values are discarded as well.
10
+ # **Important**
11
+ # Any falsy values are discarded! Standalone Boolean values are discarded as well.
14
12
  #
15
13
  # @param [Mixed] args
16
14
  #
@@ -26,28 +24,97 @@ module Clsx
26
24
  # <div class="<%= clsx('foo', 'bar') %>">
27
25
  # <div class="<%= clsx('foo', active: @is_active, 'another-class' => @condition) %>">
28
26
  # <%= tag.div class: clsx(%w[foo bar], hidden: @condition) do ... end %>
27
+ #
28
+ # @note Implementation prioritizes performance over readability.
29
+ # Direct class comparisons and explicit conditionals are used
30
+ # instead of more idiomatic Ruby patterns for speed.
31
+
32
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
29
33
  def clsx(*args)
30
- clsx_args_processor(*args).join(' ').presence
34
+ return nil if args.empty?
35
+
36
+ # Fast path: single argument (most common cases)
37
+ if args.size == 1
38
+ arg = args[0]
39
+ klass = arg.class
40
+
41
+ if klass == String
42
+ return arg.empty? ? nil : arg
43
+ elsif klass == Symbol
44
+ return arg.name
45
+ elsif klass == Array && arg.all?(String)
46
+ seen = {}
47
+ arg.each { |s| seen[s] = true unless s.empty? || seen.key?(s) }
48
+ return seen.empty? ? nil : seen.keys.join(' ')
49
+ elsif klass == Hash
50
+ return clsx_simple_hash(arg)
51
+ end
52
+ end
53
+
54
+ seen = {}
55
+ clsx_process(args, seen)
56
+ seen.empty? ? nil : seen.keys.join(' ')
31
57
  end
58
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
59
+
32
60
  alias cn clsx
33
61
 
34
62
  private
35
63
 
36
- # @param [Mixed] args
37
- #
38
- # @return [Set]
39
- def clsx_args_processor(*args) # rubocop:disable Metrics/CyclomaticComplexity
40
- result = Set.new
41
- args.flatten!
64
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
65
+ def clsx_simple_hash(hash)
66
+ return nil if hash.empty?
67
+
68
+ seen = {}
69
+ hash.each do |key, value|
70
+ next unless value
71
+
72
+ klass = key.class
73
+
74
+ if klass == Symbol
75
+ seen[key.name] = true
76
+ elsif klass == String
77
+ seen[key] = true unless key.empty?
78
+ else
79
+ # Complex key - fall back to full processing
80
+ seen = {}
81
+ clsx_process([hash], seen)
82
+ return seen.empty? ? nil : seen.keys.join(' ')
83
+ end
84
+ end
85
+
86
+ seen.empty? ? nil : seen.keys.join(' ')
87
+ end
88
+
89
+ # rubocop:disable Style/MultipleComparison
90
+ def clsx_process(args, seen)
91
+ deferred = nil
42
92
 
43
93
  args.each do |arg|
44
- next if arg.blank? || arg.is_a?(TrueClass) || arg.is_a?(Proc)
45
- next result << arg.to_s unless arg.is_a?(Hash)
94
+ klass = arg.class
46
95
 
47
- arg.each { |k, v| result += clsx_args_processor(k) if v }
96
+ if klass == String
97
+ seen[arg] = true unless arg.empty? || seen.key?(arg)
98
+ elsif klass == Symbol
99
+ str = arg.name
100
+ seen[str] = true unless seen.key?(str)
101
+ elsif klass == Array
102
+ clsx_process(arg, seen)
103
+ elsif klass == Hash
104
+ arg.each { |key, value| (deferred ||= []) << key if value }
105
+ elsif klass == Integer || klass == Float
106
+ str = arg.to_s
107
+ seen[str] = true unless seen.key?(str)
108
+ elsif klass == NilClass || klass == FalseClass || klass == TrueClass || klass == Proc
109
+ next
110
+ else
111
+ str = arg.to_s
112
+ seen[str] = true unless str.empty? || seen.key?(str)
113
+ end
48
114
  end
49
115
 
50
- result
116
+ clsx_process(deferred, seen) if deferred
51
117
  end
118
+ # rubocop:enable Style/MultipleComparison, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
52
119
  end
53
120
  end
data/lib/clsx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clsx
4
- VERSION = '1.0.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/clsx-rails.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support'
4
+ require 'action_view'
4
5
 
5
6
  require_relative 'clsx/version'
6
7
  require_relative 'clsx/helper'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clsx-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-03-03 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: actionview
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '6.1'
18
+ version: '7.1'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: '6.1'
25
+ version: '7.1'
27
26
  description: A tiny Rails view helper for constructing CSS class strings conditionally
28
27
  email:
29
28
  - leonid@svyatov.com
@@ -32,6 +31,7 @@ extensions: []
32
31
  extra_rdoc_files: []
33
32
  files:
34
33
  - CHANGELOG.md
34
+ - CLAUDE.md
35
35
  - LICENSE.txt
36
36
  - README.md
37
37
  - lib/clsx-rails.rb
@@ -45,7 +45,6 @@ metadata:
45
45
  source_code_uri: https://github.com/svyatov/clsx-rails
46
46
  changelog_uri: https://github.com/svyatov/clsx-rails/blob/main/CHANGELOG.md
47
47
  rubygems_mfa_required: 'true'
48
- post_install_message:
49
48
  rdoc_options: []
50
49
  require_paths:
51
50
  - lib
@@ -60,8 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
59
  - !ruby/object:Gem::Version
61
60
  version: '0'
62
61
  requirements: []
63
- rubygems_version: 3.5.6
64
- signing_key:
62
+ rubygems_version: 4.0.3
65
63
  specification_version: 4
66
64
  summary: clsx / classnames for Rails views
67
65
  test_files: []