├── .github └── workflows │ └── main.yml ├── .gitignore ├── .overcommit.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── clsx.gemspec ├── lib ├── clsx.rb └── clsx │ ├── railtie.rb │ └── version.rb ├── sig └── clsx.rbs └── test ├── test_clsx.rb └── test_helper.rb /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | 10 | jobs: 11 | lint: 12 | timeout-minutes: 10 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Ruby 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: 3.3.0 23 | bundler-cache: true 24 | 25 | - name: Lint code for consistent style 26 | run: bundle exec standardrb 27 | 28 | test: 29 | runs-on: ubuntu-latest 30 | name: Ruby ${{ matrix.ruby }} 31 | strategy: 32 | matrix: 33 | ruby: ['3.1', '3.2', '3.3'] 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | - name: Set up Ruby 38 | uses: ruby/setup-ruby@v1 39 | with: 40 | ruby-version: ${{ matrix.ruby }} 41 | bundler-cache: true 42 | - name: Run the default task 43 | run: bundle exec rake 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | # Releases 11 | clsx-*.gem 12 | .ruby-version 13 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | PreCommit: 2 | StandardRB: 3 | enabled: true 4 | required: true 5 | command: ['bundle', 'exec', 'standardrb'] 6 | flags: ['--fix'] 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | ## [0.1.3] - 2024-03-02 3 | PUBLIC RELEASE 4 | remove github actions to publish to rubygems, easier to run rake release 5 | 6 | ## [0.1.2] - 2024-03-02 7 | add github actions to publish to rubygems 8 | 9 | ## [0.1.1] - 2024-03-03 10 | ### Changed 11 | - Changed `clsx` to `CLSX` 12 | 13 | 14 | ## [0.1.0] - 2024-03-02 15 | 16 | - Initial release 17 | 18 | These examples should be working 19 | 20 | Strings (variadic) 21 | ```ruby 22 | clsx("foo", true && "bar", "baz") 23 | #=> "foo bar baz" 24 | ``` 25 | 26 | Objects 27 | ```ruby 28 | clsx({ foo:true, bar:false, baz:is_true? }) 29 | #=> "foo baz" 30 | ``` 31 | 32 | Objects (variadic) 33 | ```ruby 34 | clsx({ foo:true }, { bar:false }, null, { "--foobar":"hello" }) 35 | #=> "foo --foobar" 36 | ``` 37 | 38 | Arrays 39 | ```ruby 40 | clsx(["foo", 0, false, "bar"]) 41 | #=> "foo bar" 42 | ``` 43 | 44 | Arrays (variadic) 45 | ```ruby 46 | clsx(["foo"], ["", 0, false, "bar"], [["baz", [["hello"], "there"]]]) 47 | #=> "foo bar baz hello there" 48 | ``` 49 | 50 | Kitchen sink (with nesting) 51 | ```ruby 52 | clsx("foo", [1 && "bar", { baz:false, bat:null }, ["hello", ["world"]]], "cya") 53 | #=> "foo bar hello world cya" 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in clsx.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.0" 9 | 10 | gem "minitest", "~> 5.16" 11 | 12 | gem "standard" 13 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | clsx (0.1.3) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | ast (2.4.2) 10 | json (2.7.1) 11 | language_server-protocol (3.17.0.3) 12 | lint_roller (1.1.0) 13 | minitest (5.22.2) 14 | parallel (1.24.0) 15 | parser (3.3.0.5) 16 | ast (~> 2.4.1) 17 | racc 18 | racc (1.7.3) 19 | rainbow (3.1.1) 20 | rake (13.1.0) 21 | regexp_parser (2.9.0) 22 | rexml (3.2.6) 23 | rubocop (1.61.0) 24 | json (~> 2.3) 25 | language_server-protocol (>= 3.17.0) 26 | parallel (~> 1.10) 27 | parser (>= 3.3.0.2) 28 | rainbow (>= 2.2.2, < 4.0) 29 | regexp_parser (>= 1.8, < 3.0) 30 | rexml (>= 3.2.5, < 4.0) 31 | rubocop-ast (>= 1.30.0, < 2.0) 32 | ruby-progressbar (~> 1.7) 33 | unicode-display_width (>= 2.4.0, < 3.0) 34 | rubocop-ast (1.31.1) 35 | parser (>= 3.3.0.4) 36 | rubocop-performance (1.20.2) 37 | rubocop (>= 1.48.1, < 2.0) 38 | rubocop-ast (>= 1.30.0, < 2.0) 39 | ruby-progressbar (1.13.0) 40 | standard (1.34.0) 41 | language_server-protocol (~> 3.17.0.2) 42 | lint_roller (~> 1.0) 43 | rubocop (~> 1.60) 44 | standard-custom (~> 1.0.0) 45 | standard-performance (~> 1.3) 46 | standard-custom (1.0.2) 47 | lint_roller (~> 1.0) 48 | rubocop (~> 1.50) 49 | standard-performance (1.3.1) 50 | lint_roller (~> 1.1) 51 | rubocop-performance (~> 1.20.2) 52 | unicode-display_width (2.5.0) 53 | 54 | PLATFORMS 55 | arm64-darwin-23 56 | ruby 57 | 58 | DEPENDENCIES 59 | clsx! 60 | minitest (~> 5.16) 61 | rake (~> 13.0) 62 | standard 63 | 64 | BUNDLED WITH 65 | 2.5.3 66 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Seth Horsley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clsx 2 | ## A gem for constructing HTML class strings conditionally 3 | 4 | Ruby utility for constructing HTML class strings conditionally with the provided syntax, you can define a module named Clsx that includes a method to handle each case: strings, objects (hashes), arrays, and a combination of these with nested structures. This method will recursively process each argument, filter out falsy values, and concatenate the truthy values into a single string. 5 | 6 | ## Installation 7 | 8 | Install the gem and add to the application's Gemfile by executing: 9 | 10 | $ bundle add "clsx" 11 | 12 | 13 | ## Usage 14 | Strings (variadic) 15 | ```ruby 16 | clsx("foo", true && "bar", "baz") 17 | #=> "foo bar baz" 18 | ``` 19 | 20 | Objects 21 | ```ruby 22 | clsx({ foo:true, bar:false, baz:is_true? }) 23 | #=> "foo baz" 24 | ``` 25 | 26 | Objects (variadic) 27 | ```ruby 28 | clsx({ foo:true }, { bar:false }, null, { "--foobar":"hello" }) 29 | #=> "foo --foobar" 30 | ``` 31 | 32 | Arrays 33 | ```ruby 34 | clsx(["foo", 0, false, "bar"]) 35 | #=> "foo bar" 36 | ``` 37 | 38 | Arrays (variadic) 39 | ```ruby 40 | clsx(["foo"], ["", 0, false, "bar"], [["baz", [["hello"], "there"]]]) 41 | #=> "foo bar baz hello there" 42 | ``` 43 | 44 | Kitchen sink (with nesting) 45 | ```ruby 46 | clsx("foo", [1 && "bar", { baz:false, bat:null }, ["hello", ["world"]]], "cya") 47 | #=> "foo bar hello world cya" 48 | ``` 49 | 50 | You can also use clsx in rails views like this: 51 | ```ruby 52 |
"> 53 | 54 |
55 | ``` 56 | 57 | 58 | ## Development 59 | 60 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 61 | 62 | 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). 63 | 64 | ## Contributing 65 | 66 | Bug reports and pull requests are welcome. Just fork this repo make the changes you want and submit a pull request back to this repo :) 67 | 68 | ## License 69 | 70 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 71 | 72 | ## Code of Conduct 73 | 74 | Everyone interacting in the Clsx project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/clsx/blob/main/CODE_OF_CONDUCT.md). 75 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "minitest/test_task" 5 | 6 | Minitest::TestTask.create 7 | 8 | task default: :test 9 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "clsx" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | require "irb" 11 | IRB.start(__FILE__) 12 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /clsx.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/clsx/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "clsx" 7 | spec.version = CLSX::VERSION 8 | spec.authors = ["Seth Horsley"] 9 | spec.email = ["isethi@me.com"] 10 | 11 | spec.summary = "A gem for constructing HTML class strings conditionally" 12 | spec.description = "Ruby utility for constructing HTML class strings conditionally with the provided syntax, you can define a module named Clsx that includes a method to handle each case: strings, objects (hashes), arrays, and a combination of these with nested structures. This method will recursively process each argument, filter out falsy values, and concatenate the truthy values into a single string." 13 | spec.homepage = "https://github.com/iseth/ruby-clsx" 14 | spec.license = "MIT" 15 | spec.required_ruby_version = ">= 2.6.0" 16 | 17 | spec.metadata["homepage_uri"] = spec.homepage 18 | spec.metadata["source_code_uri"] = spec.homepage 19 | spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md" 20 | 21 | # Specify which files should be added to the gem when it is released. 22 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 23 | spec.files = Dir.chdir(__dir__) do 24 | `git ls-files -z`.split("\x0").reject do |f| 25 | (File.expand_path(f) == __FILE__) || 26 | f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) 27 | end 28 | end 29 | spec.bindir = "exe" 30 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 31 | spec.require_paths = ["lib"] 32 | 33 | # Uncomment to register a new dependency of your gem 34 | # spec.add_dependency "example-gem", "~> 1.0" 35 | 36 | # For more information and examples about making a new gem, check out our 37 | # guide at: https://bundler.io/guides/creating_gem.html 38 | end 39 | -------------------------------------------------------------------------------- /lib/clsx.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "clsx/version" 4 | require "clsx/railtie" if defined?(Rails) 5 | 6 | module CLSX 7 | class Error < StandardError; end 8 | 9 | def self.clsx(*args) 10 | args.flat_map { |arg| parse(arg) }.compact.join(" ") 11 | end 12 | 13 | def self.parse(arg) 14 | case arg 15 | when String 16 | arg unless arg.empty? 17 | when Hash 18 | arg.map { |k, v| k if v }.compact 19 | when Array 20 | arg.flat_map { |a| parse(a) } 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/clsx/railtie.rb: -------------------------------------------------------------------------------- 1 | require "clsx" 2 | require "rails" 3 | 4 | module Clsx 5 | class Railtie < Rails::Railtie 6 | initializer "clsx.action_view" do 7 | ActiveSupport.on_load :action_view do 8 | ActionView::Base.send :include, Helper 9 | end 10 | end 11 | end 12 | 13 | module Helper 14 | def clsx(*) 15 | CLSX.clsx(*) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/clsx/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CLSX 4 | VERSION = "0.1.3" 5 | end 6 | -------------------------------------------------------------------------------- /sig/clsx.rbs: -------------------------------------------------------------------------------- 1 | module CLSX 2 | VERSION: String 3 | # See the writing guide of rbs: https://github.com/ruby/rbs#guides 4 | end 5 | -------------------------------------------------------------------------------- /test/test_clsx.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ClsxTest < Minitest::Test 6 | def test_strings 7 | assert_equal "foo bar baz", CLSX.clsx("foo", true && "bar", "baz") 8 | end 9 | 10 | def test_objects 11 | assert_equal "foo baz", CLSX.clsx({foo: true, bar: false, baz: true}) 12 | end 13 | 14 | def test_objects_variadic 15 | assert_equal "foo --foobar", CLSX.clsx({foo: true}, {bar: false}, nil, {"--foobar" => "hello"}) 16 | end 17 | 18 | def test_arrays 19 | assert_equal "foo bar", CLSX.clsx(["foo", 0, false, "bar"]) 20 | end 21 | 22 | def test_arrays_variadic 23 | assert_equal "foo bar baz hello there", CLSX.clsx(["foo"], ["", 0, false, "bar"], [["baz", [["hello"], "there"]]]) 24 | end 25 | 26 | def test_kitchen_sink 27 | assert_equal "foo bar hello world cya", CLSX.clsx("foo", [1 && "bar", {baz: false, bat: nil}, ["hello", ["world"]]], "cya") 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 4 | require "clsx" 5 | 6 | require "minitest/autorun" 7 | --------------------------------------------------------------------------------