clsx-rails 1.0.1 → 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: 458496856224ba13decce2fcc1c4a15efd2eb8a44f86639c7eba9ecd5a06027a
4
- data.tar.gz: 10dffbb660e3888a3d1379642d9895cc2cb63acde24e15cce06ae00428517722
3
+ metadata.gz: 5ac79817d0d2f653dbc76e4f3d044c6d599f342acecc1c6d6c6639dbbdec1ecf
4
+ data.tar.gz: ce38f0509202614f8aeab6f16f0079eb93d686e6c5c42d48170af8d50ccd801b
5
5
  SHA512:
6
- metadata.gz: 72edd36ee323a3a8900d949142676f0539e44e1daa965fd2a02c87d7e8ec182bb2caa69f7494468d3bc500e8ad600d320839e70e4d0f3fac41589e3a541981ff
7
- data.tar.gz: b65cf587a989e8ceac0b2b71c1e6ef518e2bd0761143fba3db407e9598d7c3e69de58fe92445e69eb70deb6df77e57c1e25c1f8c61c5601bbedeab4ab417657d
6
+ metadata.gz: 0c8e9e1d7100c07f7336b9a8ac4fd699b4a1a90f78bd4c2fa51cdd931583948913d9622e901a17f3fa03104fbebca4f92ac07df50287a1f4209bb531cf4886fe
7
+ data.tar.gz: 155e24fb75550a7a12e050fc20089fc9677e0435d7b08f0412d65b19a727f7fbca2cb86cbb0d7cf6df44b037e4e515efa947c42d2da9f020805ac8c4461d7e58
data/CHANGELOG.md CHANGED
@@ -6,30 +6,52 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## Unreleased changes
8
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
+
9
30
  ## v1.0.1 (2024-03-04)
10
31
 
11
32
  ### Performance Improvements
12
- - Speeds up the performance by 2x [`32236ed`](git@github.com:svyatov/clsx-rails/commit/32236ed)
33
+ - Speeds up the performance by 2x [`32236ed`](https://github.com/svyatov/clsx-rails/commit/32236ed)
13
34
 
14
35
  ### Chore
15
- - Fixes CI action [`f1b948c`](git@github.com:svyatov/clsx-rails/commit/f1b948c)
16
- - Upload code coverage to CodeCov for the latest combination of Ruby and ActionView only [`4e5d768`](git@github.com:svyatov/clsx-rails/commit/4e5d768)
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)
17
38
 
18
39
  ### Documentation
19
- - Adds information about supported Ruby and Rails version [skip ci] [`2e6483f`](git@github.com:svyatov/clsx-rails/commit/2e6483f)
20
- - Adds link to the CodeCov badge, switch to Conventional Commits [`b48cc84`](git@github.com:svyatov/clsx-rails/commit/b48cc84)
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)
21
43
 
22
44
  ### Other
23
- - Updates CI badge [skip ci] [`a829613`](git@github.com:svyatov/clsx-rails/commit/a829613)
24
- - Adds code coverage tracking [`0c5d34c`](git@github.com:svyatov/clsx-rails/commit/0c5d34c)
25
- - Ignore ruby-head in the CI matrix [`f3ab4df`](git@github.com:svyatov/clsx-rails/commit/f3ab4df)
26
- - Better name for the GitHub Action job [`a28adb7`](git@github.com:svyatov/clsx-rails/commit/a28adb7)
27
- - Adds badges, fixes rubocop configuration for CI [`56fab44`](git@github.com:svyatov/clsx-rails/commit/56fab44)
28
- - Create dependabot.yml [`ed1e0eb`](git@github.com:svyatov/clsx-rails/commit/ed1e0eb)
29
- - Fixes GitHub Actions [`f58a4b2`](git@github.com:svyatov/clsx-rails/commit/f58a4b2)
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)
30
52
 
31
53
  ## v1.0.0 (2024-03-03)
32
54
 
33
55
  ### Other
34
- - Initial commit [`f65b9b8`](git@github.com:svyatov/clsx-rails/commit/f65b9b8)
56
+ - Initial commit [`f65b9b8`](https://github.com/svyatov/clsx-rails/commit/f65b9b8)
35
57
 
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
@@ -8,7 +8,7 @@ It is especially useful when you have a lot of conditional classes and you want
8
8
 
9
9
  ## Supported Ruby and Rails versions
10
10
 
11
- Ruby 3.1+ and Rails 7.0+ are supported.
11
+ Ruby 3.1+ and Rails 7.1+ are supported.
12
12
 
13
13
  ## Installation
14
14
 
data/lib/clsx/helper.rb CHANGED
@@ -1,15 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
-
5
3
  # :nodoc:
6
4
  module Clsx
7
5
  # :nodoc:
8
6
  module Helper
9
7
  # The clsx function can take any number of arguments,
10
- # each of which can be an Hash, Array, Boolean, String, or Symbol.
8
+ # each of which can be Hash, Array, Boolean, String, or Symbol.
11
9
  #
12
- # **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.
13
12
  #
14
13
  # @param [Mixed] args
15
14
  #
@@ -25,33 +24,97 @@ module Clsx
25
24
  # <div class="<%= clsx('foo', 'bar') %>">
26
25
  # <div class="<%= clsx('foo', active: @is_active, 'another-class' => @condition) %>">
27
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
28
33
  def clsx(*args)
29
- result = clsx_args_processor(*args)
30
- result.uniq!
31
- result.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(' ')
32
57
  end
58
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
59
+
33
60
  alias cn clsx
34
61
 
35
62
  private
36
63
 
37
- # @param [Mixed] args
38
- #
39
- # @return [Array]
40
- def clsx_args_processor(*args) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
41
- result = []
42
- complex_keys = []
64
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
65
+ def clsx_simple_hash(hash)
66
+ return nil if hash.empty?
43
67
 
44
- args.flatten!
45
- args.each do |arg|
46
- next if arg.blank? || arg.is_a?(TrueClass) || arg.is_a?(Proc)
47
- next result << arg.to_s unless arg.is_a?(Hash)
68
+ seen = {}
69
+ hash.each do |key, value|
70
+ next unless value
71
+
72
+ klass = key.class
48
73
 
49
- arg.each { |key, value| complex_keys << key if value }
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
50
84
  end
51
85
 
52
- return result if complex_keys.empty?
86
+ seen.empty? ? nil : seen.keys.join(' ')
87
+ end
88
+
89
+ # rubocop:disable Style/MultipleComparison
90
+ def clsx_process(args, seen)
91
+ deferred = nil
92
+
93
+ args.each do |arg|
94
+ klass = arg.class
95
+
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
114
+ end
53
115
 
54
- result + clsx_args_processor(*complex_keys)
116
+ clsx_process(deferred, seen) if deferred
55
117
  end
118
+ # rubocop:enable Style/MultipleComparison, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
56
119
  end
57
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.1'
4
+ VERSION = '2.0.0'
5
5
  end
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.1
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-04 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: []