refactor!: remove lepty-scoping feature and make its behaviour the default; deprecate scope members; bump MSRV; bump deps
This commit is contained in:
parent
293de0a1d1
commit
ee1b401f47
15 changed files with 284 additions and 374 deletions
64
.github/workflows/cbt.yml
vendored
64
.github/workflows/cbt.yml
vendored
|
|
@ -1,40 +1,40 @@
|
|||
name: cargo basic build-test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["window", "master"]
|
||||
pull_request:
|
||||
branches: ["window"]
|
||||
push:
|
||||
branches: ["window", "master"]
|
||||
pull_request:
|
||||
branches: ["window"]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Sabry - latest
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
toolchain:
|
||||
- stable
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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"
|
||||
build_and_test:
|
||||
name: Sabry - latest
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
toolchain:
|
||||
- stable
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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"
|
||||
|
||||
build_examples:
|
||||
name: Sabry - latset - examples
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
example:
|
||||
- define-styles
|
||||
- use-styles
|
||||
- leptos-axum
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup update nightly && rustup default nightly
|
||||
- run: cd examples/${{ matrix.example }}
|
||||
- run: cargo build --verbose
|
||||
build_examples:
|
||||
name: Sabry - latset - examples
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
example:
|
||||
- define-styles
|
||||
- use-styles
|
||||
- leptos-axum
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup update nightly && rustup default nightly
|
||||
- run: cd examples/${{ matrix.example }}
|
||||
- run: cargo build --verbose
|
||||
|
|
|
|||
16
Cargo.toml
16
Cargo.toml
|
|
@ -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
178
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
name = "define-styles"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
rust-version = "1.88"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
sabry = {path = "../../", features = ["nightly"]}
|
||||
sabry = { path = "../../", features = ["nightly"] }
|
||||
|
|
|
|||
|
|
@ -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,31 +9,31 @@ 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"}
|
||||
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"}
|
||||
define-styles = { path = "../define-styles" }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
name = "use-styles"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
rust-version = "1.88"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
sabry = {path = "../../", features = ["nightly"]}
|
||||
sabry = { path = "../../", features = ["nightly"] }
|
||||
|
||||
[build-dependencies]
|
||||
sabry = {path = "../../", features = ["build", "nightly"]}
|
||||
define-styles = {path = "../define-styles"}
|
||||
sabry = { path = "../../", features = ["build", "nightly"] }
|
||||
define-styles = { path = "../define-styles" }
|
||||
|
||||
[package.metadata.sabry]
|
||||
css.bundle = "target/static/style.css"
|
||||
|
|
@ -21,4 +21,4 @@ hash.size = 6
|
|||
hash.collision = "error"
|
||||
|
||||
[package.metadata.sabry.lightningcss]
|
||||
targets = {chrome = "120", firefox = "120", ie = "8"}
|
||||
targets = { chrome = "120", firefox = "120", ie = "8" }
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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")] {
|
||||
format!("{}.{}", a.ident, hash.as_str())
|
||||
} else {
|
||||
format!(".{} {}", hash.as_str(), a.ident)
|
||||
}
|
||||
}
|
||||
// Tag scoping is done by composition
|
||||
format!("{}.{}", a.ident, hash.as_str())
|
||||
}
|
||||
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,33 +428,33 @@ 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")]{
|
||||
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;}";
|
||||
}
|
||||
}
|
||||
//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;}";
|
||||
//}
|
||||
//}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "lepty-scoping")] {
|
||||
let expect_selector_htmls = HashSet::from([
|
||||
"cls1".to_string(),
|
||||
"id1".to_string(),
|
||||
"cls3".to_string(),
|
||||
"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
|
||||
]);
|
||||
}
|
||||
}
|
||||
// cfg_if! {
|
||||
// if #[cfg(feature = "lepty-scoping")] {
|
||||
let expect_selector_htmls = HashSet::from([
|
||||
"cls1".to_string(),
|
||||
"id1".to_string(),
|
||||
"cls3".to_string(),
|
||||
"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,
|
||||
|
|
|
|||
|
|
@ -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,21 +117,27 @@ impl<'s> StylesheetAdapter<'s> {
|
|||
// 4 - Danik
|
||||
// 5 - Yiffy
|
||||
// 8 - Danik
|
||||
c.filter_map(|s| s.as_qualified_rule().cloned())
|
||||
.flat_map(|q| {
|
||||
let thissels = q
|
||||
.selector
|
||||
.selectors
|
||||
.iter()
|
||||
.flat_map(|sel| &sel.children)
|
||||
.filter_map(|sc| sc.as_compound_selector())
|
||||
.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| match sc {
|
||||
ComplexSelectorChild::CompoundSelector(sel) => Some(sel),
|
||||
_ => None,
|
||||
})
|
||||
.cloned();
|
||||
|
||||
let blockstmt = q.block.statements.iter().cloned();
|
||||
Self::selectors_of(blockstmt)
|
||||
.chain(thissels)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
let blockstmt = q.block.statements.iter().cloned();
|
||||
Self::selectors_of(blockstmt)
|
||||
.chain(thissels)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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! {
|
||||
|
|
|
|||
Loading…
Reference in a new issue