A proc macro for creating compile-time checked CSS class sets, in the style of classNames

Related tags

Utilities semester
Overview

semester

Semester is a declarative CSS conditional class name joiner, in the style of React's classnames. It's intended for use in web frameworks (like Yew) and HTML template engines (like horrorshow) as an efficient and compile-time checked way to conditionally activate or deactivate CSS classes on HTML elements.

Semester provides two similar macros, classes and static_classes, for creating sets of classes. Each one has a slightly different mode of operation, but they both use the same simple syntax and perform the same checks and guarantees:

  • The macro takes a list of CSS classes as input, and returns an impl Classes:
use semester::{classes, Classes as _};

let classes = classes!(
    "class1",
    "class2",
    "class3"
);

assert_eq!(classes.render(), "class1 class2 class3");
  • Each class may optionally include a condition:
use semester::{classes, Classes as _};

let classes = classes!(
    "always",
    "yup": 10 == 10,
    "nope": 10 == 15,
);

assert_eq!(classes.render(), "always yup");

semester will render all of the enabled classes in declaration order, separated by a single space. It will do its best to pre-compute parts of the output (for instance, by concatenating all the consecutive unconditional classes), and you can go further and use static_classes, which pre-computes every possible combination of classes at compile time.

Besides render, semester provides several other ways to access the class set, so you can use whichever one makes the most sense for your use case. See the Classes and StaticClasses traits for details. semester is no_std and will generally only allocate on specific methods like render and to_string.

Additionally, semester performs several compile time correctness checks on your classes:

  • Classes must be made up of ascii printable characters:
// This does not compile
use semester::classes;

classes!("null\0class")
  • Classes must not have any whitespace:
// This does not compile
use semester::classes;

classes!("class pair")
  • Classes must not be empty:
// This does not compile
use semester::classes;

classes!("")
  • Classes should exclude the HTML unsafe characters: < > & ' "
// This does not compile
use semester::classes;

classes!("<injected-class>")
  • Classes may not duplicate. Note that semester can't detect mutually exclusive conditions, so it prevents duplicates unconditionally.
// This does not compile
use semester::classes;

let x = 10;
classes!(
    "class1": x == 10,
    "class1": x != 10,
);

License: MPL-2.0

You might also like...
Convert Juniper configurations to 'set-style'

JCC: Juniper Config Converter Convert Juniper configurations. Takes a Juniper configuration as displayed using show configuration and transforms it to

A library to compile USDT probes into a Rust library
A library to compile USDT probes into a Rust library

sonde sonde is a library to compile USDT probes into a Rust library, and to generate a friendly Rust idiomatic API around it. Userland Statically Defi

A rollup plugin that compile Rust code into WebAssembly modules

rollup-plugin-rust tl;dr -- see examples This is a rollup plugin that loads Rust code so it can be interop with Javascript base project. Currently, th

Showcase for pathological compile times when using knuffel / chumsky / VERY LARGE types

netherquote Showcase for pathological compile times when using knuffel / chumsky / VERY LARGE types. How to reproduce The rust toolchain version is pi

 Creating CLI's just got a whole lot better.
Creating CLI's just got a whole lot better.

Staq Creating CLI's just got a whole lot better. Don't worry about CLI colouring, networking, Size of Executables, Speed ever again Have any doubts? R

A playground for creating generative art, buit with Rust🦀 and WASM🕸
A playground for creating generative art, buit with Rust🦀 and WASM🕸

Genny A playground for creating generative art, buit with Rust 🦀 and WASM 🕸 About This is a simple playground that allows me to explore ideas around

🦊 An interactive cli for creating conventional commits.
🦊 An interactive cli for creating conventional commits.

🦊 koji An interactive cli for creating conventional commits, built on cocogitto and inspired by cz-cli. Installation Not yet. 😔 Usage Using koji # C

A swiss army knife for creating binary modules for Garry's Mod in Rust.

A swiss army knife for creating binary modules for Garry's Mod in Rust.

Mononym is a library for creating unique type-level names for each value in Rust.

Mononym is a library for creating unique type-level names for each value in Rust.

Comments
  • Support for dynamic classes

    Support for dynamic classes

    Currently, the classes macro only accepts string literals with conditions. This is already great (thank you very much for this crate, btw).

    In my codebase, however, I regularly have components that are “styled” by using enums with Display implementation, e.g.

    enum ComponentSize { Normal, Large, ExtraLarge }
    impl fmt::Display for Structure { … }
    

    where display would output my CSS framework's class names. The nice thing about this is the encapsulation of concerns and not doing the pattern matching on the enum every time I need the (always same) classes out of it.

    There are basically two approaches I have found with semester:

    1. performing matching at the call-site (which is definitely far from ideal because I lose encapsulation and swapping out the CSS framework would result in me having to change all call-sites):

      classes!(
          "size-md": matches!(size, ComponentSize::Normal),
          "size-lg": matches!(size, ComponentSize::Large),
          "size-xl": matches!(size, ComponentSize::ExtraLarge),
      )
      
    2. not implementing Display but instead having something like

      impl ComponentSize {
          fn to_classes() -> impl Classes { … classes as above … }
      }
      
      // call-site
      size.to_classes().to_string()
      

    Now the 2nd approach looks like it would be nice. However, usually these classes from components are not used in isolation, i.e. I would love to use something like

    classes!(
        "my-component",
        "another-class",
        "highlight": required,
        size.to_classes()
    )
    

    which will not work at the moment. For my purposes, it would be irrelevant if I would have size.to_classes() (have something return impl Classes) or if I would put some String or &str there, the only important part would be that it is not required to be a string literal (&'static str).

    There is, of course, a workaround, but that gets ugly pretty quickly:

    size.to_classes().iter().chain(classes!("my-component", …).iter()).collect()
    

    Hence, would it be possible to add some kind of support for dynamic classes to classes or even a new macro dynamic_classes which lets me use variables instead of literals?

    opened by stopbystudent 0
Owner
Nathan West
Neophile. #rustlang at @1Password. #ADHD. 31. He/him/his. Mercator projection apologist. Pronounced "LUKE-reh-TEAL".
Nathan West
Proc. macro to generate C-like `enum` tags.

Continuous Integration Documentation Crates.io #[derive(EnumTag)] This crate provides a proc. macro to derive the EnumTag trait for the given Rust enu

Robin Freyler 5 Mar 27, 2023
Monadic checked arithmetic for Rust

Monadic checked arithmetic for Rust This library provides types wrapping raw numeric types, and tracking possibility of an overflow, enforcing correct

Dawid Ciężarkiewicz 18 Jun 10, 2022
Cakecutter - a utility tool that quickly sets up a project from a pre-built template

Cakecutter Create projects from pre-built cakes (templates)! Supports files, packages, content, running commands and more! Cakecutter is a utility too

Dhravya Shah 10 Jun 22, 2022
An experimental programming language for exploring first class iterators.

An experimental programming language for exploring first class iterators.

Miccah 4 Nov 23, 2021
Lightweight compile-time UUID parser.

compiled-uuid Anywhere you're building Uuids from a string literal, you should use uuid. Motivation If you want to use a fixed Uuid throughout your pr

Quinn 10 Dec 8, 2022
Like jq, but for HTML. Uses CSS selectors to extract bits content from HTML files.

Like jq, but for HTML. Uses CSS selectors to extract bits content from HTML files. Mozilla's MDN has a good reference for CSS selector syntax.

Michael Maclean 6.3k Jan 3, 2023
List of Persian Colors and hex colors for CSS, SCSS, PHP, JS, Python, and Ruby.

Persian Colors (Iranian colors) List of Persian Colors and hex colors for CSS, SCSS, PHP, C++, QML, JS, Python, Ruby and CSharp. Persian colors Name H

Max Base 12 Sep 3, 2022
Swift-style keypaths in Rust

keypath Strongly typed references to arbitrarily nested fields. This is an early experiment in implementing Swift-style keypaths in Rust.

Colin Rofls 58 Dec 16, 2022
Managing schema for AWS Athena in GitOps-style

athena-rs Managing AWS Athena Schemas Installation $ cargo install --git https://github.com/duyet/athena-rs $ athena --help athena 0.1.0 Duyet <me@du

Duyet Le 3 Sep 25, 2022
Command palette-style Git client for blazing-fast commits.

?? About Commit Commit is the world's simplest Git client. Open it with a keyboard shortcut, write your commit, and you're done! Commit will automatic

Miguel Piedrafita 190 Aug 18, 2023