refactor!: remove lepty-scoping feature and make its behaviour the default; deprecate scope members; bump MSRV; bump deps

This commit is contained in:
yiffyrusdev 2025-09-12 14:15:47 +05:00
commit ee1b401f47
No known key found for this signature in database
GPG key ID: FB83DFEAFB3F4709
15 changed files with 284 additions and 374 deletions

View file

@ -22,7 +22,7 @@ jobs:
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose
- run: cargo test --verbose --all
- run: cargo test --verbose --all --features "const-scoping lepty-scoping"
- run: cargo test --verbose --all --features "const-scoping"
build_examples:
name: Sabry - latset - examples

View file

@ -9,16 +9,15 @@ sabry_intrnl = { path = "sabry_intrnl", version = "0.0.6-rc.1" }
sabry_procmacro = { path = "sabry_procmacro", version = "0.0.6-rc.1" }
sabry_procmacro_impl = { path = "sabry_procmacro_impl", version = "0.0.6-rc.1" }
sabry_build = { path = "sabry_build", version = "0.0.6-rc.1" }
## Package
## Package
lightningcss.version = "1.0.0-alpha.65"
#/
lightningcss.version = "1.0.0-alpha.67"
lightningcss.default-features = false
grass.version = "^0.13"
grass.default-features = false
raffia.version = "^0.9"
raffia.version = "^0.11"
#/
serde.version = "^1"
toml.version = "^0.8"
toml.version = "^0.9"
blake3.version = "^1"
base64.version = "^0.22"
regex.version = "^1"
@ -35,7 +34,7 @@ syn.version = "^2"
[workspace.package]
version = "0.0.6-rc.1"
edition = "2021"
rust-version = "1.78"
rust-version = "1.88"
authors = ["Svyatoslav Pushkarev <yiffyrusdev@yandex.ru>"]
repository = "https://github.com/yiffyrusdev/sabry"
license = "MIT"
@ -60,12 +59,11 @@ sabry_build = { workspace = true, optional = true }
sabry_intrnl = { workspace = true }
[dev-dependencies]
leptos = { version = "0.7.0-gamma3" }
insta = { version = "^1.41", features = ["glob"] }
leptos = { version = "^0.8" }
insta = { version = "^1", features = ["glob"] }
[features]
default = ["procmacro"]
lepty-scoping = ["sabry_intrnl/lepty-scoping"]
const-scoping = ["sabry_procmacro?/const-compile"]
procmacro = ["dep:sabry_procmacro"]
build = ["dep:sabry_build"]

178
README.md
View file

@ -25,17 +25,6 @@ sabry::scssy!(tokens {"@mixin colored{color: red;}"});
<hr/>
Get the *(almost)* proper autocompletion for your scope from rust-analyzer
```rust
sabry::styly!(scope {".cls1{} #id1{}"});
/* autocompleted for you */
scope::cls1;
scope::theid1;
```
<hr/>
Code your SASS in separate files to get proper syntax highlighting, or do sass-in-the-rust to keep things in one place
```rust
@ -71,13 +60,23 @@ Keep things as private and modular as you wish
```rust
sabry::styly!(pub cats "tests/assets/mixin-module.scss");
sabry::styly!(dogs {"#howl {border: none;}"});
sabry::styly!(dogs {".howl {border: none;}"});
```
```rust,ignore
```html
/// something like render function
{
<div class={cats::meow}>
<ul id={dogs::thehowl}></ul>
<div class={concatcp!("meow ", CATS)}>
<ul class={concatcp!("howl ", DOGS)}></ul>
</div>
}
```
```html
/// or even better with leptos
view!{class=CATS
<div class="meow">
{move || view!{class=DOGS,
<ul class="howl"></ul>
}}
</div>
}
```
@ -122,7 +121,7 @@ The only need is the dependency
#Cargo.toml
[dependencies]
sabry = {version = "0.0.5"}
sabry = {version = "0.0.6"}
```
And a proc-macro
```rust
@ -155,7 +154,7 @@ Depend on sabry
# Cargo.toml
[dependencies]
sabry = {version = "0.0.5"}
sabry = {version = "0.0.6"}
```
And create a style scope wherever you want:
```rust
@ -173,18 +172,18 @@ styly!(styles {"
color: white;
}
"});
/* Now you can use all scoped selectors: */
let badges_scoped_class = styles::badges;
let wolf_scoped_id = styles::thewolf;
```
Every selector, if that does make sense, now available for you as a member of `styles` scope. In this example - `styles::badges`, `styles::thewolf` and `styles::_list()`. More about scoping and member names you can read [here](#styly-scopes).
```html
<ul class={concatcp!("badges ", STYLES)}>
<li class={STYLES} id="wolf">
"HOOOOWL!"
</li>
</ul>
```
### Use styles earlier created in another crate
> **Tip** | If you use [leptos](https://github.com/leptos-rs/leptos) - check [this](#leptos-specials) section as well
The combination of previous two, with some additional work to do and some extra sugar to enjoy.
Sabry is needed as both dependency and build-dependency.
@ -193,10 +192,10 @@ To be able to compile all styles sabry also needs the *build* feature flag:
# Cargo.toml
[dependencies]
sabry = {version = "0.0.5"}
sabry = {version = "0.0.6"}
[build-dependencies]
sabry = {version = "0.0.5", features = ["build"]}
sabry = {version = "0.0.6", features = ["build"]}
```
> If you do use some non-default feature flags make sure to keep them in sync between sabry-dependency and sabry-build-dependency.
@ -318,81 +317,6 @@ So I'd say - do the const function back-propogation and let the consumer decide
</details>
### Leptos specials
With [leptos](https://github.com/leptos-rs/leptos) framework you can do the trick to apply some class to most of HtmlElements for the entire component/island:
```rust
use leptos::prelude::*;
#[component]
fn component() -> impl IntoView {
view! {class="cls1", /* <- here */
<h1>"Head"</h1>
<p>"text"</p>
}
}
```
... and have "cls1" on both `h1` and `p` auto-assigned. Isn't that cool?
So, if you're on leptos, and don't mind to take this approach, I'd highly recommend turning the *lepty-scoping* feature on:
```toml
# Cargo.toml
[dependencies]
sabry = {version = "0.0.5", features = ["lepty-scoping"]}
[build-dependencies]
sabry = {version = "0.0.5", features = ["build", "lepty-scoping"]}
```
```rust
use leptos::prelude::*;
use sabry::styly;
styly!(scope {"
// assuming we have tokens!() macro generated by `scssy!`, which is handled by `buildy`
@use 'tokens';
h1 {
@include tokens.sectionhead();
img {
@include tokens.sectionimg();
}
}
.breadcumbs {
@include tokens.badgelist();
&__item {
@include tokens.badge(secondary);
}
}
"});
#[component]
fn component() -> impl IntoView {
view! {class=SCOPE,
<h1>
"Head"
<img src="whatever"/>
</h1>
<ul class="breadcumbs">
<li class="breadcumbs__item">
"Home page"
</li>
</ul>
}
}
```
That will perform *much* better:
- All scope members - like `scope::breadcumbs` - wont contain repeating scope hash, just the original class/id selector
- So instead of scope members (which [aren't](https://github.com/yiffyrusdev/sabry/issues/2) autocompleted for sass-in-the-rust yet) you could just write bare selectors, as you do, for example, with svelte: `<li class="breadcumbs__item">`
- You'll still have the rusty mod generated: its just the `scope::breadcumbs` is the `"breadcumbs"` without the hash
- You will not encounter [the catch](#scoping) with nested tagname selectors
Also, this isn't really exclusive leptos-supporting feature. It just changes scoping behavour.
## Configuration
Sabry configuration lives in `[package.metadata.sabry]` table of the manifest file.
@ -562,8 +486,8 @@ In general the scope does look like this:
```rust,ignore
const FOX: &str = "J9k_s9";
mod fox {
pub const fur: &str = "J9k_s9 fur";
pub fn _dark(c: &str) -> String {format!("{c}-dark")}
#[deprecated(since = "0.0.6", note = "will remove")]
pub const fur: &str = "fur J9k_s9";
}
```
@ -572,9 +496,6 @@ mod fox {
Styly macro itself does not generate the scope. It is done in the `sabry_intrnl::scoper`. However, as a result, you will have the following:
- `const` with the UPPER_CASE name of the scope, which contains its hash
- `mod` with exact scope name which is the scope you're going to use
- for simple selectors, like `.fur` you will have a const members with hashed original selectors to use wherever you need a class/id/etc.
- for parent-selectors like `&-dark` you will have a non-const function, which will create matching the selector from any other.
You can read more about scoping and hashing in the [scoping](#scoping) section.
@ -600,7 +521,6 @@ Which results in following:
```rust,ignore
const SCOPE: &str = /* scope hash */;
const SCOPE_CSS: &str = /* compiled from src/assets/scope.scss */;
mod scope {/* selector collection */}
```
> **There is a catch** | with the `const` modifier macro must compile CSS at compile-time. That results in several game changers:
@ -666,15 +586,15 @@ Exactly this kind of macros is produced by [`scssy!`](#style-definition-with-scs
### Scoping
Sabry handles scoping by restriction or mutation of existing selectors
Sabry handles scoping by restriction of existing selectors
with the hash. Hash is calculated for the entire scope by the [`styly!`](#scoping-with-styly) macro.
Currently the following types of selectors are scoped:
- class
- id
- tagname
- SASS parent selectors (see below, there is a catch)
- tagname (see below, there is a catch)
Sabry does not make difference between top-level and nested selectors,
also selector complexity isn't taken into account: sabry simply walks through all compound selectors
@ -683,40 +603,8 @@ and apply scoping for supported ones.
Different selector types are scoped differently:
- class selectors are restricted with scope hash: `.class` -> `.HASH.class`
- id selector are mutated with scope hash: `#id` -> `#id-HASH`
- tagname selectors are changed into indirect descendance: `div` -> `.HASH div`
> **There is a catch** | currently tagname selectors are scoped with indirect descendancy
> hash restriction. That means - if you do use tagnames in scoped stylesheets, you have to wrap your markdown
> with `SCOPE` class generated by `styly!` macro.
>
> Also if there are nested tagname selectors - sabry won't take this into account and you'll
> have to wrap them with `SCOPE` class as well
>
> **However** If you are able to apply some class to all html elements you have - like `view!{class=CLASS...}` with Leptos - you could use *lepty-scoping* feature flag for sabry and get rid of this catch! See more [here](#leptos-specials).
As for **SASS parent selectors**: they are currently handled in different way. Instead of
walking up the syntax tree sabry just creates function member for the scope and leave the rest to grass:
```rust
sabry::styly!(scope {"
.cls1 {
&-dark {}
}
"});
```
is something like
```rust,ignore
const SCOPE: &str = "Ut8CskJ";
mod scope {
pub const cls1: &str = "Ut8CskJ cls1";
pub fn _dark(c: &str) -> String {format!("{c}-dark")}
}
```
So you still can get the scoped variant of `cls1-dark` class: `scope::_dark(scope::cls1)`.
This isn't very handy, also isn't strict enough, and is a high-priority subject to change. wip.
- id selector are mutated with scope hash: `#id` -> `.HASH#id`
- tagname selectors are changed into indirect descendance: `div` -> `div.HASH`
#### Scope member naming rules
@ -763,9 +651,9 @@ If you'd like to lend a paw - feel free to check the [WIP](#wip) section out, or
## MSRV
Sabry passes its own tests on 1.78 nightly/stable.
Sabry passes its own tests on 1.88 nightly/stable.
Examples are on 1.82.
Examples are on 1.88.
## License

View file

@ -2,7 +2,7 @@
name = "define-styles"
version = "0.0.0"
edition = "2021"
rust-version = "1.82"
rust-version = "1.88"
publish = false
[dependencies]

View file

@ -1,7 +1,7 @@
[package]
name = "leptos-axum-sabry"
version = "0.1.0"
rust-version = "1.82"
rust-version = "1.88"
edition = "2021"
publish = false
@ -9,29 +9,29 @@ publish = false
crate-type = ["cdylib", "rlib"]
[dependencies]
leptos = { version = "^0.7" }
leptos_axum = { version = "^0.7", optional = true }
leptos_meta = { version = "^0.7" }
leptos_router = { version = "^0.7" }
axum = { version = "0.7", optional = true }
leptos = { version = "^0.8" }
leptos_axum = { version = "^0.8", optional = true }
leptos_meta = { version = "^0.8" }
leptos_router = { version = "^0.8" }
axum = { version = "0.8", optional = true }
console_error_panic_hook = "0.1"
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }
tower = { version = "0.5", optional = true }
tower-http = { version = "0.6", features = ["fs"], optional = true }
wasm-bindgen = "=0.2.96"
thiserror = "1"
thiserror = "^1"
tracing = { version = "0.1", optional = true }
http = "1"
http = "^1"
# sabry with sass
sabry = {path = "../../", features = ["lepty-scoping"]}
sabry = { path = "../../" }
# our component library
leptos-components = { path = "../leptos-components" }
[build-dependencies]
# sabry for build with sass
sabry = {path = "../../", features = ["build", "lepty-scoping"]}
sabry = { path = "../../", features = ["build"] }
# lets depend on our styles
define-styles = { path = "../define-styles" }

View file

@ -1,7 +1,7 @@
[package]
name = "leptos-components"
version = "0.1.0"
rust-version = "1.82"
rust-version = "1.88"
edition = "2021"
publish = false
@ -13,14 +13,10 @@ leptos = { version = "^0.7" }
leptos_meta = { version = "^0.7" }
leptos_router = { version = "^0.7" }
sabry = { path = "../../", features = ["lepty-scoping", "const-scoping"] }
sabry = { path = "../../", features = ["const-scoping"] }
[build-dependencies]
sabry = { path = "../../", features = [
"build",
"lepty-scoping",
"const-scoping",
] }
sabry = { path = "../../", features = ["build", "const-scoping"] }
define-styles = { path = "../define-styles" }
[features]

View file

@ -2,7 +2,7 @@
name = "use-styles"
version = "0.0.0"
edition = "2021"
rust-version = "1.82"
rust-version = "1.88"
publish = false
[dependencies]

View file

@ -16,7 +16,6 @@ regex.optional = true
grass.workspace = true
grass.optional = true
lightningcss.workspace = true
lightningcss.features = ["grid"]
lightningcss.optional = true
blake3.workspace = true
blake3.optional = true
@ -30,7 +29,6 @@ toml.workspace = true
cfg-if.workspace = true
[features]
lepty-scoping = []
scope = ["dep:blake3", "dep:base64", "dep:regex"]
compile = ["dep:lightningcss", "dep:grass"]

View file

@ -217,31 +217,21 @@ impl From<SabryLightTargets> for lightningcss::targets::Browsers {
}
/// \[package.metadata.sabry.sass\].module_name_collision option
#[derive(Deserialize, Clone, Copy)]
#[derive(Default, Deserialize, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum BehavSassModCollision {
#[default]
Merge,
Error,
}
impl Default for BehavSassModCollision {
fn default() -> Self {
Self::Merge
}
}
/// \[package.metadata.sabry.hash\].collision option
#[derive(Deserialize, Clone, Copy)]
#[derive(Default, Deserialize, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum BehavHashCollision {
Error,
#[default]
Ignore,
}
impl Default for BehavHashCollision {
fn default() -> Self {
Self::Ignore
}
Error,
}
#[cfg(test)]

View file

@ -1,3 +1,5 @@
use raffia::ast::InterpolableIdent;
/// Convenience wrapper for String-being-a-hash
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ScopeHash(String);
@ -30,7 +32,10 @@ impl ScopeHash {
.iter()
.map(|s| &s.name)
.chain(ids.iter().map(|i| &i.name))
.map(|ident| ident.as_literal())
.map(|ident| match ident {
InterpolableIdent::Literal(lit) => Some(lit),
_ => None,
})
.filter_map(|mbl| mbl.map(|l| l.raw))
.collect::<String>();

View file

@ -1,8 +1,10 @@
use std::fmt::Debug;
use cfg_if::cfg_if;
use hash::ScopeHash;
use raffia::{Span, Spanned};
use raffia::{
ast::{InterpolableIdent, TypeSelector},
Span, Spanned,
};
use crate::syntax::{ostrta::OneSyntaxToRuleThemAll, StylesheetAdapter};
@ -106,7 +108,10 @@ impl<'s> HashedScope<'s> {
let tags = scope.adapter().type_selectors();
let tags = tags
.iter()
.filter_map(|t| t.as_tag_name())
.filter_map(|t| match t {
TypeSelector::TagName(tag) => Some(tag),
_ => None,
})
.map(|c| HashedSelector::from_tag(&hash, c));
// get global selectors
@ -234,29 +239,17 @@ impl HashedSelector {
/// whats this function returns will be usable in HTML with to_hashed_html_def()
pub fn make_hashed_css(value: &ScopedSelector, hash: &ScopeHash) -> String {
match value {
// Class scoping is done with class composition
ScopedSelector::Class(a) => {
// Class scoping is done by composition
format!("{}.{}", hash.as_str(), a.ident)
}
// As we dont want to use two HTML props for a single ID,
// ID scoping is done with id modification
ScopedSelector::Id(a) => {
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
format!("{}.{}", a.ident, hash.as_str())
} else {
format!("{}-{}", hash.as_str(), a.ident)
}
}
}
ScopedSelector::Tag(a) => {
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
// Tag scoping is done by composition
format!("{}.{}", a.ident, hash.as_str())
} else {
format!(".{} {}", hash.as_str(), a.ident)
}
}
ScopedSelector::Id(a) => {
// ID scoping is done by class composition
format!("{}.{}", a.ident, hash.as_str())
}
ScopedSelector::Glob { raw, .. } => raw.clone(),
}
@ -267,31 +260,15 @@ impl HashedSelector {
/// Cooperates with the [HashedSelector::make_hashed_css], so
/// whats this function returns will be usable in CSS with to_hashed_code()
///
/// Not every hashed selector is presentable for HTML-ish use: like `div`, in that case
/// returns [None]
pub fn make_hashed_html(value: &ScopedSelector, _hash: &ScopeHash) -> Option<String> {
/// Since 0.0.6 all scoping is done via classes, so this function
/// persists for bw-compat for everything except for class.
///
/// Global and Tag selectors are never transformed;
pub fn make_hashed_html(value: &ScopedSelector, hash: &ScopeHash) -> Option<String> {
match value {
// Class scoping is done with class composition
ScopedSelector::Class(a) => {
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
Some(a.ident.to_string())
} else {
Some(format!("{} {}", _hash.as_str(), a.ident))
}
}
}
// As we dont want to use two HTML props for a single ID,
// ID scoping is done with id modification
ScopedSelector::Id(a) => {
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
Some(a.ident.to_string())
} else {
Some(format!("{}-{}", _hash.as_str(), a.ident))
}
}
}
ScopedSelector::Class(a) => Some(format!("{} {}", a.ident, hash.as_str())),
ScopedSelector::Id(id) => Some(id.ident.clone()),
ScopedSelector::Tag(_) => None,
ScopedSelector::Glob { .. } => None,
}
@ -366,10 +343,9 @@ impl ScopedSelector {
/// Construct this from class selector
pub fn from_class(s: &raffia::ast::ClassSelector) -> Self {
let lit = s
.name
.as_literal()
.expect("BUG: class selector is not a literal");
let InterpolableIdent::Literal(lit) = &s.name else {
panic!("BUG: class selector was not a literal")
};
Self::Class(ArbitrarySelector {
ident: lit.raw.to_string(),
span: lit.span().clone(),
@ -378,10 +354,9 @@ impl ScopedSelector {
/// Construct this from id selector
pub fn from_id(s: &raffia::ast::IdSelector) -> Self {
let lit = s
.name
.as_literal()
.expect("BUG: id selector is not a literal");
let InterpolableIdent::Literal(lit) = &s.name else {
panic!("BUG: id selector was not a literal")
};
Self::Id(ArbitrarySelector {
ident: lit.raw.to_string(),
span: lit.span().clone(),
@ -390,11 +365,9 @@ impl ScopedSelector {
/// Construct this from tagname selector
pub fn from_tag(s: &raffia::ast::TagNameSelector) -> Self {
let lit = s
.name
.name
.as_literal()
.expect("BUG: tag selector is not a literal");
let InterpolableIdent::Literal(lit) = &s.name.name else {
panic!("BUG: tag selector was not a literal")
};
Self::Tag(ArbitrarySelector {
ident: lit.raw.to_string(),
span: lit.span().clone(),
@ -439,7 +412,6 @@ pub fn apply_basic_rusty_member_gen_rules(source: &str) -> String {
mod test {
use std::collections::HashSet;
use cfg_if::cfg_if;
use syn::Ident;
use crate::{
@ -456,16 +428,16 @@ mod test {
".cls1{color:red; &-dark{color: black;} #id1 {color:green;} div {color:blue;}} .cls3#id2{color: black;}";
let hash = ScopeHash::test_init("F2kf8nMs".into());
cfg_if! {
if #[cfg(feature = "lepty-scoping")]{
//cfg_if! {
//if #[cfg(feature = "lepty-scoping")]{
let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #id1.F2kf8nMs {color:green;} div.F2kf8nMs {color:blue;}} .F2kf8nMs.cls3#id2.F2kf8nMs{color: black;}";
} else {
let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #F2kf8nMs-id1 {color:green;} .F2kf8nMs div {color:blue;}} .F2kf8nMs.cls3#F2kf8nMs-id2{color: black;}";
}
}
//} else {
// let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #F2kf8nMs-id1 {color:green;} .F2kf8nMs div {color:blue;}} .F2kf8nMs.cls3#F2kf8nMs-id2{color: black;}";
//}
//}
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
// cfg_if! {
// if #[cfg(feature = "lepty-scoping")] {
let expect_selector_htmls = HashSet::from([
"cls1".to_string(),
"id1".to_string(),
@ -473,16 +445,16 @@ mod test {
"id2".to_string(),
"".to_string(), // comes from `div` which has no html hashed code, still want to check presense
]);
} else {
let expect_selector_htmls = HashSet::from([
"F2kf8nMs cls1".to_string(),
"F2kf8nMs-id1".to_string(),
"F2kf8nMs cls3".to_string(),
"F2kf8nMs-id2".to_string(),
"".to_string(), // comes from `div` which has no html hashed code, still want to check presense
]);
}
}
// } else {
// let expect_selector_htmls = HashSet::from([
// "F2kf8nMs cls1".to_string(),
// "F2kf8nMs-id1".to_string(),
// "F2kf8nMs cls3".to_string(),
// "F2kf8nMs-id2".to_string(),
// "".to_string(), // comes from `div` which has no html hashed code, still want to check presense
// ]);
// }
//}
let scope = ArbitraryScope::from_source(
OneSyntaxToRuleThemAll::Scss,

View file

@ -1,8 +1,8 @@
use ostrta::OneSyntaxToRuleThemAll;
use raffia::{
ast::{
ClassSelector, CompoundSelector, IdSelector, NestingSelector, PseudoClassSelector,
SimpleSelector, Statement, Stylesheet, TypeSelector,
ClassSelector, ComplexSelectorChild, CompoundSelector, IdSelector, InterpolableIdent,
NestingSelector, PseudoClassSelector, SimpleSelector, Statement, Stylesheet, TypeSelector,
},
ParserBuilder,
};
@ -41,38 +41,49 @@ impl<'s> StylesheetAdapter<'s> {
}
pub fn class_selectors(&self) -> Vec<ClassSelector<'s>> {
self.selectors_by(|c| c.as_class().cloned())
self.selectors_by(|c| match c {
SimpleSelector::Class(sel) => Some(sel),
_ => None,
})
}
pub fn id_selectors(&self) -> Vec<IdSelector<'s>> {
self.selectors_by(|c| c.as_id().cloned())
self.selectors_by(|c| match c {
SimpleSelector::Id(sel) => Some(sel),
_ => None,
})
}
pub fn type_selectors(&self) -> Vec<TypeSelector<'s>> {
self.selectors_by(|c| c.as_type().cloned())
self.selectors_by(|c| match c {
SimpleSelector::Type(sel) => Some(sel),
_ => None,
})
}
pub fn nesting_selectors(&self) -> Vec<NestingSelector<'s>> {
self.selectors_by(|c| c.as_nesting().cloned())
self.selectors_by(|c| match c {
SimpleSelector::Nesting(sel) => Some(sel),
_ => None,
})
}
/// special selector case searching for :g(whatever), :glob(whatever), :global(whatever)
pub fn glob_modified_selectors(&self) -> Vec<PseudoClassSelector<'s>> {
self.selectors_by(|c| {
let cps = match c.as_pseudo_class() {
Some(s) => s.clone(),
let cps = match c {
SimpleSelector::PseudoClass(sel) => sel,
_ => return None,
};
let ident = match cps.name.as_literal().map(|r| r.raw) {
Some(i) => i,
_ => return None,
};
if ident == "global" {
Some(cps)
} else {
None
match &cps.name {
InterpolableIdent::Literal(lit) => match lit.raw {
"g" => Some(cps),
"glob" => Some(cps),
"global" => Some(cps),
_ => None,
},
_ => None,
}
})
}
@ -106,14 +117,20 @@ impl<'s> StylesheetAdapter<'s> {
// 4 - Danik
// 5 - Yiffy
// 8 - Danik
c.filter_map(|s| s.as_qualified_rule().cloned())
c.filter_map(|s| match s {
Statement::QualifiedRule(stmt) => Some(stmt),
_ => None,
})
.flat_map(|q| {
let thissels = q
.selector
.selectors
.iter()
.flat_map(|sel| &sel.children)
.filter_map(|sc| sc.as_compound_selector())
.filter_map(|sc| match sc {
ComplexSelectorChild::CompoundSelector(sel) => Some(sel),
_ => None,
})
.cloned();
let blockstmt = q.block.statements.iter().cloned();
@ -128,7 +145,10 @@ impl<'s> StylesheetAdapter<'s> {
mod test {
use std::collections::HashSet;
use raffia::Spanned;
use raffia::{
ast::{InterpolableIdent, TypeSelector},
Spanned,
};
use crate::syntax::ostrta::OneSyntaxToRuleThemAll;
@ -191,20 +211,32 @@ div#id2
let classes = adp
.class_selectors()
.iter()
.filter_map(|c| c.name.as_literal().map(|l| l.raw))
.filter_map(|c| match &c.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
let ids = adp
.id_selectors()
.iter()
.filter_map(|i| i.name.as_literal().map(|l| l.raw))
.filter_map(|i| match &i.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
let tags = adp
.type_selectors()
.iter()
.filter_map(|t| t.as_tag_name())
.filter_map(|t| t.name.name.as_literal().map(|l| l.raw))
.filter_map(|t| match t {
TypeSelector::TagName(tag) => Some(tag),
_ => None,
})
.filter_map(|t| match &t.name.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
assert_eq!(expect_classes, classes);
@ -217,20 +249,32 @@ div#id2
let classes = adp
.class_selectors()
.iter()
.filter_map(|c| c.name.as_literal().map(|l| l.raw))
.filter_map(|c| match &c.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
let ids = adp
.id_selectors()
.iter()
.filter_map(|i| i.name.as_literal().map(|l| l.raw))
.filter_map(|i| match &i.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
let tags = adp
.type_selectors()
.iter()
.filter_map(|t| t.as_tag_name())
.filter_map(|t| t.name.name.as_literal().map(|l| l.raw))
.filter_map(|t| match t {
TypeSelector::TagName(tag) => Some(tag),
_ => None,
})
.filter_map(|t| match &t.name.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
assert_eq!(expect_classes, classes);

View file

@ -1,15 +1,10 @@
/// Convenience unification entrypoint
/// to make friends with all the different syntax-consumers
#[derive(Debug, Clone, Copy)]
#[derive(Default, Debug, Clone, Copy)]
pub enum OneSyntaxToRuleThemAll {
Sass,
#[default]
Scss,
}
impl Default for OneSyntaxToRuleThemAll {
fn default() -> Self {
Self::Scss
}
Sass,
}
impl TryFrom<&str> for OneSyntaxToRuleThemAll {

View file

@ -11,6 +11,7 @@ license.workspace = true
[dependencies]
sabry_intrnl.workspace = true
sabry_intrnl.features = ["scope"]
raffia.workspace = true
syn.workspace = true
syn.features = ["parsing"]
quote.workspace = true

View file

@ -84,10 +84,17 @@ pub fn styly_macro_impl(input: TokenStream, source_path: Option<PathBuf>) -> Tok
.iter()
.filter_map(|ns| {
ns.suffix.clone().and_then(|s| {
let interp = s
.as_sass_interpolated()
.and_then(|i| i.elements.first())
.and_then(|e| e.as_static().map(|s| s.raw.to_string()));
let interp = match s {
raffia::ast::InterpolableIdent::SassInterpolated(int) => Some(int),
_ => None,
};
let interp = interp.and_then(|i| match i.elements.first() {
Some(raffia::ast::SassInterpolatedIdentElement::Static(i)) => {
Some(i.raw.to_string())
}
_ => None,
});
interp
})
@ -102,6 +109,10 @@ pub fn styly_macro_impl(input: TokenStream, source_path: Option<PathBuf>) -> Tok
quote! {
#[doc = #doc]
#[deprecated(
since = "0.0.6",
note = "All scoping is done by directly applying hash classes to elements now - this is useless for everything except of classes"
)]
pub fn #fnident(c: &str) -> String {format!(#formatstr)}
}
});
@ -120,23 +131,35 @@ pub fn styly_macro_impl(input: TokenStream, source_path: Option<PathBuf>) -> Tok
ScopedSelector::Glob { .. } => "glob",
};
(name, hs.sel.gen_rusty_ident(), html, hs.css_ident.clone())
(
name,
hs.sel.gen_rusty_ident(),
html,
hs.css_ident.clone(),
)
})
})
.map(|(name, ident, html, css)| {
let docs = format!("'{ident:?}' {name}. CSS selector '{css}'");
quote! {
#[doc = #docs]
#[deprecated(
since = "0.0.6",
note = "All scoping is done by directly applying hash classes to elements now - this is useless for everything except of classes"
)]
#[allow(non_upper_case_globals)]
pub const #ident : &str = #html ;
}
});
let mod_docs = format!(
"'{}' style scope. The wrapper class for scoped tagnames is {}",
"'{}' style scope. The hash class is `{}`",
&scope_ident, &scope_wrapper_ident
);
let wrp_docs = format!("wrapper class for '{}' scope. If you have any tagname selectors - they should live as children of element with this class applied.", &scope_ident);
let wrp_docs = format!(
"hash class for '{}' scope. All HTML elements should have this class applied",
&scope_ident
);
if constant {
cfg_if! {