tailwind yay
This commit is contained in:
parent
91eaf8f6e3
commit
4939d5a58f
15 changed files with 1314 additions and 12 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
.devenv
|
.devenv
|
||||||
.direnv
|
.direnv
|
||||||
|
/node_modules
|
||||||
|
|
259
Cargo.lock
generated
259
Cargo.lock
generated
|
@ -17,6 +17,12 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
@ -26,6 +32,37 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alloc-no-stdlib"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alloc-stdlib"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
|
||||||
|
dependencies = [
|
||||||
|
"alloc-no-stdlib",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-compression"
|
||||||
|
version = "0.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522"
|
||||||
|
dependencies = [
|
||||||
|
"brotli",
|
||||||
|
"flate2",
|
||||||
|
"futures-core",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"zstd",
|
||||||
|
"zstd-safe",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.83"
|
version = "0.1.83"
|
||||||
|
@ -108,17 +145,44 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"miniz_oxide",
|
"miniz_oxide 0.7.4",
|
||||||
"object",
|
"object",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "brotli"
|
||||||
|
version = "7.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
|
||||||
|
dependencies = [
|
||||||
|
"alloc-no-stdlib",
|
||||||
|
"alloc-stdlib",
|
||||||
|
"brotli-decompressor",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "brotli-decompressor"
|
||||||
|
version = "4.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
|
||||||
|
dependencies = [
|
||||||
|
"alloc-no-stdlib",
|
||||||
|
"alloc-stdlib",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -178,6 +242,15 @@ dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crusto"
|
name = "crusto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -186,7 +259,9 @@ dependencies = [
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"eyre",
|
"eyre",
|
||||||
"git2",
|
"git2",
|
||||||
|
"icondata",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
@ -225,6 +300,16 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide 0.8.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
@ -292,6 +377,17 @@ dependencies = [
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
|
@ -362,6 +458,12 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range-header"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.9.5"
|
version = "1.9.5"
|
||||||
|
@ -409,6 +511,31 @@ dependencies = [
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icondata"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "805a2a031c06a768407b774240192b0ad82ddb94554bb5b52053453f2f6b0bf1"
|
||||||
|
dependencies = [
|
||||||
|
"icondata_core",
|
||||||
|
"icondata_lu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icondata_core"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c97be924215abd5e630d84e95a47c710138a6559b4c55039f4f33aa897fa859"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icondata_lu"
|
||||||
|
version = "0.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d552c45cc3ab1d1bf88cc0201004eb92418141e5454e9e0e46c4b4a4faf66248"
|
||||||
|
dependencies = [
|
||||||
|
"icondata_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -554,6 +681,16 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iri-string"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc0f0a572e8ffe56e2ff4f769f32ffe919282c3916799f8b68688b6030063bea"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
|
@ -670,6 +807,16 @@ version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
@ -679,6 +826,15 @@ dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
@ -933,7 +1089,7 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
"syn_derive",
|
"syn_derive",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1121,7 +1277,16 @@ version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 2.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1135,6 +1300,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.8"
|
version = "1.1.8"
|
||||||
|
@ -1215,6 +1391,37 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
|
||||||
|
dependencies = [
|
||||||
|
"async-compression",
|
||||||
|
"base64",
|
||||||
|
"bitflags",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"http-range-header",
|
||||||
|
"httpdate",
|
||||||
|
"iri-string",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -1319,6 +1526,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
|
@ -1360,6 +1573,15 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1384,6 +1606,9 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
|
"icondata_core",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.6",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"typed-builder",
|
"typed-builder",
|
||||||
|
@ -1589,3 +1814,31 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-safe",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-safe"
|
||||||
|
version = "7.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-sys"
|
||||||
|
version = "2.0.13+zstd.1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
|
@ -16,7 +16,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vespid.workspace = true
|
vespid = { workspace = true, features = ["icons"] }
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
eyre = "0.6.12"
|
eyre = "0.6.12"
|
||||||
|
@ -25,3 +25,5 @@ tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
tracing-error = "0.2.1"
|
tracing-error = "0.2.1"
|
||||||
git2 = "0.19.0"
|
git2 = "0.19.0"
|
||||||
|
icondata = { version = "0.5", default-features = false, features = ["lucide"] }
|
||||||
|
tower-http = { version = "0.6.2", features = ["full"] }
|
||||||
|
|
BIN
bun.lockb
Executable file
BIN
bun.lockb
Executable file
Binary file not shown.
11
package.json
Normal file
11
package.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"name": "crusto",
|
||||||
|
"devDependencies": {
|
||||||
|
"@catppuccin/tailwindcss": "^0.1.6",
|
||||||
|
"@tailwindcss/cli": "^4.0.0-beta.6",
|
||||||
|
"tailwindcss": "^4.0.0-beta.6"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"tailwind:build": "tailwindcss -i ./src/app.css -o ./target/app.css"
|
||||||
|
}
|
||||||
|
}
|
101
src/app.css
Normal file
101
src/app.css
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
@config "../tailwind.config.ts";
|
||||||
|
|
||||||
|
html {
|
||||||
|
@apply w-full h-full;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
select,
|
||||||
|
textarea,
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground shadow-accent w-full h-full;
|
||||||
|
cursor: auto !important;
|
||||||
|
font-feature-settings:
|
||||||
|
"rlig" 1,
|
||||||
|
"calt" 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.hstack {
|
||||||
|
@apply flex flex-row items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vstack {
|
||||||
|
@apply flex flex-col;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullcenter {
|
||||||
|
@apply items-center justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ty ol,
|
||||||
|
.ty ul {
|
||||||
|
@apply my-6 ml-6 list-disc [&>li]:mt-2;
|
||||||
|
}
|
||||||
|
.ty h1 {
|
||||||
|
@apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl;
|
||||||
|
}
|
||||||
|
.ty h2 {
|
||||||
|
@apply mt-10 scroll-m-20 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0;
|
||||||
|
}
|
||||||
|
.ty h3 {
|
||||||
|
@apply scroll-m-20 text-2xl font-semibold tracking-tight;
|
||||||
|
}
|
||||||
|
.ty h4 {
|
||||||
|
@apply scroll-m-20 text-xl font-semibold tracking-tight mt-2;
|
||||||
|
}
|
||||||
|
.ty p {
|
||||||
|
@apply leading-7 [&:not(:first-child)]:mt-4;
|
||||||
|
}
|
||||||
|
.ty blockquote {
|
||||||
|
@apply mt-6 border-l-2 pl-6 italic;
|
||||||
|
}
|
||||||
|
.ty code {
|
||||||
|
@apply relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold;
|
||||||
|
}
|
||||||
|
.ty hr {
|
||||||
|
@apply my-6 border-border/60 rounded-lg;
|
||||||
|
}
|
||||||
|
.ty a:not([m-button]) {
|
||||||
|
@apply text-info underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lead {
|
||||||
|
@apply text-xl text-muted-foreground;
|
||||||
|
}
|
||||||
|
.large {
|
||||||
|
@apply text-lg font-semibold;
|
||||||
|
}
|
||||||
|
.small {
|
||||||
|
@apply text-sm font-medium leading-none;
|
||||||
|
}
|
||||||
|
.muted {
|
||||||
|
@apply text-sm text-muted-foreground;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
/* Hide scrollbar for IE, Edge and Firefox */
|
||||||
|
.no-scrollbar {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
}
|
14
src/main.rs
14
src/main.rs
|
@ -1,4 +1,5 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
|
||||||
|
@ -7,7 +8,7 @@ use std::sync::{atomic::AtomicU64, Arc};
|
||||||
use axum::{response::Html, routing::get};
|
use axum::{response::Html, routing::get};
|
||||||
use tracing::level_filters::LevelFilter;
|
use tracing::level_filters::LevelFilter;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use vespid::{axum_compat::render, *};
|
use vespid::{axum::render, prelude::*};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Shell(children: String) -> String {
|
fn Shell(children: String) -> String {
|
||||||
|
@ -19,6 +20,7 @@ fn Shell(children: String) -> String {
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Crusto</title>
|
<title>Crusto</title>
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.3" integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq" crossorigin="anonymous"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.3" integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq" crossorigin="anonymous"></script>
|
||||||
|
<style>{include_str!("../target/app.css")}</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{children}
|
{children}
|
||||||
|
@ -34,9 +36,12 @@ async fn index() -> Html<String> {
|
||||||
<h1>"Hello to Crusto!"</h1>
|
<h1>"Hello to Crusto!"</h1>
|
||||||
<p>"Index"</p>
|
<p>"Index"</p>
|
||||||
|
|
||||||
<button hx-get="/widget" hx-swap="outerHTML" hx-target="#widget">"Get widget"</button>
|
<button hx-get="/widget" hx-swap="outerHTML" hx-target="#widget" hx-trigger="load delay:1s, click" hx-indicator="#widget_container > .spinner">"Get widget"</button>
|
||||||
|
|
||||||
<div id="widget">
|
<div id="widget_container" class="flex flex-col items-center justify-center">
|
||||||
|
<Icon icon=icondata::LuLoader2 class="animate-spin spinner htmx-indicator" />
|
||||||
|
<div id="widget">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Shell>
|
</Shell>
|
||||||
}
|
}
|
||||||
|
@ -59,6 +64,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let amount_of_refreshes = Arc::new(AtomicU64::new(0));
|
let amount_of_refreshes = Arc::new(AtomicU64::new(0));
|
||||||
|
|
||||||
let app = axum::Router::new().route("/", get(index)).route("/widget", get(|| async move {
|
let app = axum::Router::new().route("/", get(index)).route("/widget", get(|| async move {
|
||||||
render(async move {
|
render(async move {
|
||||||
view! {
|
view! {
|
||||||
|
@ -69,8 +75,10 @@ async fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
}).await
|
}).await
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
|
||||||
info!("listening on {}", listener.local_addr()?);
|
info!("listening on {}", listener.local_addr()?);
|
||||||
axum::serve(listener, app).await?;
|
axum::serve(listener, app).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
125
tailwind.config.ts
Normal file
125
tailwind.config.ts
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import catppuccin from '@catppuccin/tailwindcss';
|
||||||
|
import type { Config } from 'tailwindcss';
|
||||||
|
import { fontFamily } from 'tailwindcss/defaultTheme';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
'./src/**/*.{html,ts,scss,css}',
|
||||||
|
'../magic/src/**/*.{html,ts,scss,css}',
|
||||||
|
],
|
||||||
|
darkMode: ['selector', ':not(.latte)'],
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: '2rem',
|
||||||
|
screens: {
|
||||||
|
'2xl': '1400px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
/**
|
||||||
|
--background: var(--ctp-base);
|
||||||
|
--foreground: var(--ctp-text);
|
||||||
|
|
||||||
|
--muted: var(--ctp-overlay0);
|
||||||
|
--muted-foreground: var(--ctp-overlay1);
|
||||||
|
|
||||||
|
--popover: var(--ctp-mantle);
|
||||||
|
--popover-foreground: var(--ctp-text);
|
||||||
|
|
||||||
|
--border: var(--ctp-surface1);
|
||||||
|
--input: var(--ctp-surface1);
|
||||||
|
|
||||||
|
--card: var(--ctp-mantle);
|
||||||
|
--card-foreground: var(--ctp-text);
|
||||||
|
|
||||||
|
--primary: var(--ctp-mauve);
|
||||||
|
--primary-foreground: var(--ctp-base);
|
||||||
|
|
||||||
|
--secondary: var(--ctp-surface0);
|
||||||
|
--secondary-foreground: var(--ctp-text);
|
||||||
|
|
||||||
|
--accent: var(--ctp-base);
|
||||||
|
--accent-foreground: var(--ctp-text);
|
||||||
|
|
||||||
|
--success: var(--ctp-green);
|
||||||
|
--success-foreground: var(--ctp-crust);
|
||||||
|
|
||||||
|
--info: var(--ctp-blue);
|
||||||
|
--info-foreground: var(--ctp-crust);
|
||||||
|
|
||||||
|
--warning: var(--ctp-peach);
|
||||||
|
--warning-foreground: var(--ctp-crust);
|
||||||
|
|
||||||
|
--destructive: var(--ctp-red);
|
||||||
|
--destructive-foreground: var(--ctp-crust);
|
||||||
|
|
||||||
|
--ring: var(--ctp-text);
|
||||||
|
*/
|
||||||
|
colors: {
|
||||||
|
border: 'rgba(var(--ctp-overlay1), <alpha-value>)',
|
||||||
|
input: 'rgba(var(--ctp-surface2), <alpha-value>)',
|
||||||
|
ring: 'rgba(var(--ctp-text), <alpha-value>)',
|
||||||
|
background: 'rgba(var(--ctp-base), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-text), <alpha-value>)',
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-lavender), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-base), <alpha-value>)',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-surface0), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-text), <alpha-value>)',
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-red), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-crust), <alpha-value>)',
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-peach), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-crust), <alpha-value>)',
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-blue), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--info-foreground), <alpha-value>)',
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-green), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-crust), <alpha-value>)',
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-overlay0), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-overlay1), <alpha-value>)',
|
||||||
|
},
|
||||||
|
invariant: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-subtext0), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-mantle), <alpha-value>)',
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-base), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-text), <alpha-value>)',
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-mantle), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-text), <alpha-value>)',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'rgba(var(--ctp-mantle), <alpha-value>)',
|
||||||
|
foreground: 'rgba(var(--ctp-text), <alpha-value>)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: 'var(--radius)',
|
||||||
|
md: 'calc(var(--radius) - 2px)',
|
||||||
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Inter', ...fontFamily.sans],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
catppuccin({
|
||||||
|
defaultFlavour: 'mocha',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
} satisfies Config;
|
|
@ -10,3 +10,10 @@ tokio-util = { version = "0.7.13", features = ["rt"] }
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
vespid_macros.path = "macros"
|
vespid_macros.path = "macros"
|
||||||
typed-builder = "0.20.0"
|
typed-builder = "0.20.0"
|
||||||
|
|
||||||
|
icondata_core = { version = "0.1", optional = true }
|
||||||
|
serde = "1.0.215"
|
||||||
|
thiserror = "2.0.6"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
icons = ["dep:icondata_core"]
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::collections::HashSet;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2_diagnostics::Diagnostic;
|
use proc_macro2_diagnostics::Diagnostic;
|
||||||
|
use proc_macro_error2::{abort, OptionExt};
|
||||||
use quote::{quote, quote_spanned, ToTokens};
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
use rstml::{
|
use rstml::{
|
||||||
node::{KeyedAttribute, Node, NodeAttribute, NodeElement, NodeName},
|
node::{KeyedAttribute, Node, NodeAttribute, NodeElement, NodeName},
|
||||||
|
@ -287,7 +288,6 @@ fn walk_attribute(attribute: &KeyedAttribute) -> (String, Option<proc_macro2::To
|
||||||
static_format.push_str(r#"="{}""#);
|
static_format.push_str(r#"="{}""#);
|
||||||
format_value = Some(
|
format_value = Some(
|
||||||
quote! {{
|
quote! {{
|
||||||
// (#value).escape_attribute()
|
|
||||||
::vespid::EscapeAttribute::escape_attribute(&#value)
|
::vespid::EscapeAttribute::escape_attribute(&#value)
|
||||||
}}
|
}}
|
||||||
.into_token_stream(),
|
.into_token_stream(),
|
||||||
|
@ -400,7 +400,7 @@ impl ToTokens for PropsStruct {
|
||||||
let has_attributes = item
|
let has_attributes = item
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.any(|field| field.ident.as_ref().unwrap().to_string() == "attributes");
|
.any(|field| field.ident.as_ref().expect_or_abort("field.ident == None").to_string() == "attributes");
|
||||||
|
|
||||||
if has_attributes {
|
if has_attributes {
|
||||||
tokens.extend(quote! {
|
tokens.extend(quote! {
|
||||||
|
@ -463,7 +463,7 @@ impl ToTokens for ComponentFn {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|i| match i {
|
.map(|i| match i {
|
||||||
FnArg::Receiver(_) => {
|
FnArg::Receiver(_) => {
|
||||||
panic!("receiver arguments unsupported");
|
abort!(i, "builders do not support self");
|
||||||
}
|
}
|
||||||
FnArg::Typed(mut t) => {
|
FnArg::Typed(mut t) => {
|
||||||
if t.attrs.is_empty() {
|
if t.attrs.is_empty() {
|
||||||
|
@ -480,7 +480,7 @@ impl ToTokens for ComponentFn {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| match i {
|
.map(|i| match i {
|
||||||
FnArg::Receiver(_) => {
|
FnArg::Receiver(_) => {
|
||||||
panic!("receiver arguments unsupported");
|
abort!(i, "builders do not support self");
|
||||||
}
|
}
|
||||||
FnArg::Typed(t) => &t.pat,
|
FnArg::Typed(t) => &t.pat,
|
||||||
})
|
})
|
||||||
|
|
28
vespid/src/icons.rs
Normal file
28
vespid/src/icons.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
pub use icondata_core::Icon;
|
||||||
|
|
||||||
|
#[vespid_macros::component]
|
||||||
|
pub fn Icon(
|
||||||
|
icon: Icon,
|
||||||
|
#[builder(default, setter(into))] width: MaybeText,
|
||||||
|
#[builder(default, setter(into))] height: MaybeText,
|
||||||
|
#[builder(default, setter(into))] class: MaybeText,
|
||||||
|
#[builder(default, setter(into))] style: MaybeText,
|
||||||
|
) -> String {
|
||||||
|
let width = format!("width=\"{}\"", width.get().unwrap_or("1em"));
|
||||||
|
let height = format!("height=\"{}\"", height.get().unwrap_or("1em"));
|
||||||
|
let class = class.get().map_or_else(String::new, |class| format!("class=\"{}\"", class));
|
||||||
|
let style = style.get().map_or_else(String::new, |style| format!("style=\"{}\"", style));
|
||||||
|
let x = icon.x.map_or_else(String::new, |x| format!("x=\"{}\"", x));
|
||||||
|
let y = icon.y.map_or_else(String::new, |y| format!("y=\"{}\"", y));
|
||||||
|
let viewbox = icon.view_box.map_or_else(String::new, |viewbox| format!("viewBox=\"{}\"", viewbox));
|
||||||
|
let stroke_linecap = icon.stroke_linecap.map_or_else(String::new, |stroke_linecap| format!("stroke-linecap=\"{}\"", stroke_linecap));
|
||||||
|
let stroke_linejoin = icon.stroke_linejoin.map_or_else(String::new, |stroke_linejoin| format!("stroke-linejoin=\"{}\"", stroke_linejoin));
|
||||||
|
let stroke_width = icon.stroke_width.map_or_else(String::new, |stroke_width| format!("stroke-width=\"{}\"", stroke_width));
|
||||||
|
let stroke = icon.stroke.map_or_else(String::new, |stroke| format!("stroke=\"{}\"", stroke));
|
||||||
|
let fill = format!("fill=\"{}\"", icon.fill.unwrap_or("currentColor"));
|
||||||
|
let data = icon.data;
|
||||||
|
format!("<svg {width} {height} {class} {style} {x} {y} {viewbox} {stroke_linecap} {stroke_linejoin} {stroke_width} {stroke} {fill}>{data}</svg>")
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
extern crate self as vespid;
|
||||||
|
|
||||||
mod escape_attribute;
|
mod escape_attribute;
|
||||||
pub use escape_attribute::*;
|
pub use escape_attribute::*;
|
||||||
|
|
||||||
|
@ -14,7 +16,9 @@ pub use render_adapter::*;
|
||||||
mod context;
|
mod context;
|
||||||
pub use context::*;
|
pub use context::*;
|
||||||
|
|
||||||
pub mod axum_compat;
|
mod text;
|
||||||
|
pub use text::*;
|
||||||
|
pub mod axum;
|
||||||
|
|
||||||
pub use vespid_macros::*;
|
pub use vespid_macros::*;
|
||||||
|
|
||||||
|
@ -22,3 +26,14 @@ pub use vespid_macros::*;
|
||||||
pub extern crate html_escape;
|
pub extern crate html_escape;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub extern crate typed_builder;
|
pub extern crate typed_builder;
|
||||||
|
|
||||||
|
#[cfg(feature = "icons")]
|
||||||
|
pub mod icons;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use {html_escape, typed_builder};
|
||||||
|
pub use vespid_macros::*;
|
||||||
|
pub use crate::{context::*, render::Render, render_adapter::*, text::*};
|
||||||
|
#[cfg(feature = "icons")]
|
||||||
|
pub use crate::icons::*;
|
||||||
|
}
|
||||||
|
|
748
vespid/src/text.rs
Normal file
748
vespid/src/text.rs
Normal file
|
@ -0,0 +1,748 @@
|
||||||
|
//! This module contains the `Oco` (Owned Clones Once) smart pointer,
|
||||||
|
//! which is used to store immutable references to values.
|
||||||
|
//! This is useful for storing, for example, strings.
|
||||||
|
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
borrow::{Borrow, Cow},
|
||||||
|
ffi::{CStr, OsStr},
|
||||||
|
fmt,
|
||||||
|
hash::Hash,
|
||||||
|
ops::{Add, Deref},
|
||||||
|
path::Path,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type MaybeText = OptionText<'static>;
|
||||||
|
|
||||||
|
pub enum OptionText<'a> {
|
||||||
|
Text(Oco<'a, str>),
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> OptionText<'a> {
|
||||||
|
pub fn get(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
OptionText::Text(text) => Some(text.as_ref()),
|
||||||
|
OptionText::None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for OptionText<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
OptionText::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Oco<'a, str>> for OptionText<'a> {
|
||||||
|
fn from(text: Oco<'a, str>) -> Self {
|
||||||
|
OptionText::Text(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for OptionText<'a> {
|
||||||
|
fn from(text: &'a str) -> Self {
|
||||||
|
OptionText::Text(Oco::Borrowed(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<String> for OptionText<'a> {
|
||||||
|
fn from(text: String) -> Self {
|
||||||
|
OptionText::Text(Oco::Owned(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Text = Oco<'static, str>;
|
||||||
|
|
||||||
|
/// "Owned Clones Once" - a smart pointer that can be either a reference,
|
||||||
|
/// an owned value, or a reference counted pointer. This is useful for
|
||||||
|
/// storing immutable values, such as strings, in a way that is cheap to
|
||||||
|
/// clone and pass around.
|
||||||
|
///
|
||||||
|
/// The `Clone` implementation is amortized `O(1)`. Cloning the [`Oco::Borrowed`]
|
||||||
|
/// variant simply copies the references (`O(1)`). Cloning the [`Oco::Counted`]
|
||||||
|
/// variant increments a reference count (`O(1)`). Cloning the [`Oco::Owned`]
|
||||||
|
/// variant upgrades it to [`Oco::Counted`], which requires an `O(n)` clone of the
|
||||||
|
/// data, but all subsequent clones will be `O(1)`.
|
||||||
|
pub enum Oco<'a, T: ?Sized + ToOwned + 'a> {
|
||||||
|
/// A static reference to a value.
|
||||||
|
Borrowed(&'a T),
|
||||||
|
/// A reference counted pointer to a value.
|
||||||
|
Counted(Arc<T>),
|
||||||
|
/// An owned value.
|
||||||
|
Owned(<T as ToOwned>::Owned),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized + ToOwned> Oco<'a, T> {
|
||||||
|
/// Converts the value into an owned value.
|
||||||
|
pub fn into_owned(self) -> <T as ToOwned>::Owned {
|
||||||
|
match self {
|
||||||
|
Oco::Borrowed(v) => v.to_owned(),
|
||||||
|
Oco::Counted(v) => v.as_ref().to_owned(),
|
||||||
|
Oco::Owned(v) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the value is [`Oco::Borrowed`].
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use std::sync::Arc;
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// assert!(Oco::<str>::Borrowed("Hello").is_borrowed());
|
||||||
|
/// assert!(!Oco::<str>::Counted(Arc::from("Hello")).is_borrowed());
|
||||||
|
/// assert!(!Oco::<str>::Owned("Hello".to_string()).is_borrowed());
|
||||||
|
/// ```
|
||||||
|
pub const fn is_borrowed(&self) -> bool {
|
||||||
|
matches!(self, Oco::Borrowed(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the value is [`Oco::Counted`].
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use std::sync::Arc;
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// assert!(Oco::<str>::Counted(Arc::from("Hello")).is_counted());
|
||||||
|
/// assert!(!Oco::<str>::Borrowed("Hello").is_counted());
|
||||||
|
/// assert!(!Oco::<str>::Owned("Hello".to_string()).is_counted());
|
||||||
|
/// ```
|
||||||
|
pub const fn is_counted(&self) -> bool {
|
||||||
|
matches!(self, Oco::Counted(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the value is [`Oco::Owned`].
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use std::sync::Arc;
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// assert!(Oco::<str>::Owned("Hello".to_string()).is_owned());
|
||||||
|
/// assert!(!Oco::<str>::Borrowed("Hello").is_owned());
|
||||||
|
/// assert!(!Oco::<str>::Counted(Arc::from("Hello")).is_owned());
|
||||||
|
/// ```
|
||||||
|
pub const fn is_owned(&self) -> bool {
|
||||||
|
matches!(self, Oco::Owned(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + ToOwned> Deref for Oco<'_, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
match self {
|
||||||
|
Oco::Borrowed(v) => v,
|
||||||
|
Oco::Owned(v) => v.borrow(),
|
||||||
|
Oco::Counted(v) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + ToOwned> Borrow<T> for Oco<'_, T> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn borrow(&self) -> &T {
|
||||||
|
self.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + ToOwned> AsRef<T> for Oco<'_, T> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn as_ref(&self) -> &T {
|
||||||
|
self.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<Path> for Oco<'_, str> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn as_ref(&self) -> &Path {
|
||||||
|
self.as_str().as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<Path> for Oco<'_, OsStr> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn as_ref(&self) -> &Path {
|
||||||
|
self.as_os_str().as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
// pub fn as_{slice}(&self) -> &{slice}
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
impl Oco<'_, str> {
|
||||||
|
/// Returns a `&str` slice of this [`Oco`].
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// let oco = Oco::<str>::Borrowed("Hello");
|
||||||
|
/// let s: &str = oco.as_str();
|
||||||
|
/// assert_eq!(s, "Hello");
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oco<'_, CStr> {
|
||||||
|
/// Returns a `&CStr` slice of this [`Oco`].
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// use std::ffi::CStr;
|
||||||
|
///
|
||||||
|
/// let oco =
|
||||||
|
/// Oco::<CStr>::Borrowed(CStr::from_bytes_with_nul(b"Hello\0").unwrap());
|
||||||
|
/// let s: &CStr = oco.as_c_str();
|
||||||
|
/// assert_eq!(s, CStr::from_bytes_with_nul(b"Hello\0").unwrap());
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_c_str(&self) -> &CStr {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oco<'_, OsStr> {
|
||||||
|
/// Returns a `&OsStr` slice of this [`Oco`].
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// use std::ffi::OsStr;
|
||||||
|
///
|
||||||
|
/// let oco = Oco::<OsStr>::Borrowed(OsStr::new("Hello"));
|
||||||
|
/// let s: &OsStr = oco.as_os_str();
|
||||||
|
/// assert_eq!(s, OsStr::new("Hello"));
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_os_str(&self) -> &OsStr {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oco<'_, Path> {
|
||||||
|
/// Returns a `&Path` slice of this [`Oco`].
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// use std::path::Path;
|
||||||
|
///
|
||||||
|
/// let oco = Oco::<Path>::Borrowed(Path::new("Hello"));
|
||||||
|
/// let s: &Path = oco.as_path();
|
||||||
|
/// assert_eq!(s, Path::new("Hello"));
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_path(&self) -> &Path {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Oco<'_, [T]>
|
||||||
|
where
|
||||||
|
[T]: ToOwned,
|
||||||
|
{
|
||||||
|
/// Returns a `&[T]` slice of this [`Oco`].
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// let oco = Oco::<[u8]>::Borrowed(b"Hello");
|
||||||
|
/// let s: &[u8] = oco.as_slice();
|
||||||
|
/// assert_eq!(s, b"Hello");
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_slice(&self) -> &[T] {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Clone for Oco<'a, T>
|
||||||
|
where
|
||||||
|
T: ?Sized + ToOwned + 'a,
|
||||||
|
for<'b> Arc<T>: From<&'b T>,
|
||||||
|
{
|
||||||
|
/// Returns a new [`Oco`] with the same value as this one.
|
||||||
|
/// If the value is [`Oco::Owned`], this will convert it into
|
||||||
|
/// [`Oco::Counted`], so that the next clone will be O(1).
|
||||||
|
/// # Examples
|
||||||
|
/// [`String`] :
|
||||||
|
/// ```
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// let oco = Oco::<str>::Owned("Hello".to_string());
|
||||||
|
/// let oco2 = oco.clone();
|
||||||
|
/// assert_eq!(oco, oco2);
|
||||||
|
/// assert!(oco2.is_counted());
|
||||||
|
/// ```
|
||||||
|
/// [`Vec`] :
|
||||||
|
/// ```
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// let oco = Oco::<[u8]>::Owned(b"Hello".to_vec());
|
||||||
|
/// let oco2 = oco.clone();
|
||||||
|
/// assert_eq!(oco, oco2);
|
||||||
|
/// assert!(oco2.is_counted());
|
||||||
|
/// ```
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Borrowed(v) => Self::Borrowed(v),
|
||||||
|
Self::Counted(v) => Self::Counted(Arc::clone(v)),
|
||||||
|
Self::Owned(v) => Self::Counted(Arc::from(v.borrow())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Oco<'a, T>
|
||||||
|
where
|
||||||
|
T: ?Sized + ToOwned + 'a,
|
||||||
|
for<'b> Arc<T>: From<&'b T>,
|
||||||
|
{
|
||||||
|
/// Clones the value with inplace conversion into [`Oco::Counted`] if it
|
||||||
|
/// was previously [`Oco::Owned`].
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// let mut oco1 = Oco::<str>::Owned("Hello".to_string());
|
||||||
|
/// let oco2 = oco1.clone_inplace();
|
||||||
|
/// assert_eq!(oco1, oco2);
|
||||||
|
/// assert!(oco1.is_counted());
|
||||||
|
/// assert!(oco2.is_counted());
|
||||||
|
/// ```
|
||||||
|
pub fn clone_inplace(&mut self) -> Self {
|
||||||
|
match &*self {
|
||||||
|
Self::Borrowed(v) => Self::Borrowed(v),
|
||||||
|
Self::Counted(v) => Self::Counted(Arc::clone(v)),
|
||||||
|
Self::Owned(v) => {
|
||||||
|
let rc = Arc::from(v.borrow());
|
||||||
|
*self = Self::Counted(rc.clone());
|
||||||
|
Self::Counted(rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the value into its cheaply-clonable form in place.
|
||||||
|
/// In other words, if it is currently [`Oco::Owned`], converts into [`Oco::Counted`]
|
||||||
|
/// in an `O(n)` operation, so that all future clones are `O(1)`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use oco_ref::Oco;
|
||||||
|
/// let mut oco = Oco::<str>::Owned("Hello".to_string());
|
||||||
|
/// oco.upgrade_inplace();
|
||||||
|
/// assert!(oco.is_counted());
|
||||||
|
/// ```
|
||||||
|
pub fn upgrade_inplace(&mut self) {
|
||||||
|
if let Self::Owned(v) = &*self {
|
||||||
|
let rc = Arc::from(v.borrow());
|
||||||
|
*self = Self::Counted(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Default for Oco<'_, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
T::Owned: Default,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Oco::Owned(T::Owned::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, A: ?Sized, B: ?Sized> PartialEq<Oco<'b, B>> for Oco<'a, A>
|
||||||
|
where
|
||||||
|
A: PartialEq<B>,
|
||||||
|
A: ToOwned,
|
||||||
|
B: ToOwned,
|
||||||
|
{
|
||||||
|
fn eq(&self, other: &Oco<'b, B>) -> bool {
|
||||||
|
**self == **other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + ToOwned + Eq> Eq for Oco<'_, T> {}
|
||||||
|
|
||||||
|
impl<'a, 'b, A: ?Sized, B: ?Sized> PartialOrd<Oco<'b, B>> for Oco<'a, A>
|
||||||
|
where
|
||||||
|
A: PartialOrd<B>,
|
||||||
|
A: ToOwned,
|
||||||
|
B: ToOwned,
|
||||||
|
{
|
||||||
|
fn partial_cmp(&self, other: &Oco<'b, B>) -> Option<std::cmp::Ordering> {
|
||||||
|
(**self).partial_cmp(&**other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Ord> Ord for Oco<'_, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
{
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
(**self).cmp(&**other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Hash> Hash for Oco<'_, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
{
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
(**self).hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + fmt::Debug> fmt::Debug for Oco<'_, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
(**self).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + fmt::Display> fmt::Display for Oco<'_, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
(**self).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized> From<&'a T> for Oco<'a, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
{
|
||||||
|
fn from(v: &'a T) -> Self {
|
||||||
|
Oco::Borrowed(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized> From<Cow<'a, T>> for Oco<'a, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
{
|
||||||
|
fn from(v: Cow<'a, T>) -> Self {
|
||||||
|
match v {
|
||||||
|
Cow::Borrowed(v) => Oco::Borrowed(v),
|
||||||
|
Cow::Owned(v) => Oco::Owned(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized> From<Oco<'a, T>> for Cow<'a, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
{
|
||||||
|
fn from(value: Oco<'a, T>) -> Self {
|
||||||
|
match value {
|
||||||
|
Oco::Borrowed(v) => Cow::Borrowed(v),
|
||||||
|
Oco::Owned(v) => Cow::Owned(v),
|
||||||
|
Oco::Counted(v) => Cow::Owned(v.as_ref().to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> From<Arc<T>> for Oco<'_, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
{
|
||||||
|
fn from(v: Arc<T>) -> Self {
|
||||||
|
Oco::Counted(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> From<Box<T>> for Oco<'_, T>
|
||||||
|
where
|
||||||
|
T: ToOwned,
|
||||||
|
{
|
||||||
|
fn from(v: Box<T>) -> Self {
|
||||||
|
Oco::Counted(v.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Oco<'_, str> {
|
||||||
|
fn from(v: String) -> Self {
|
||||||
|
Oco::Owned(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Oco<'_, str>> for String {
|
||||||
|
fn from(v: Oco<'_, str>) -> Self {
|
||||||
|
match v {
|
||||||
|
Oco::Borrowed(v) => v.to_owned(),
|
||||||
|
Oco::Counted(v) => v.as_ref().to_owned(),
|
||||||
|
Oco::Owned(v) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Vec<T>> for Oco<'_, [T]>
|
||||||
|
where
|
||||||
|
[T]: ToOwned<Owned = Vec<T>>,
|
||||||
|
{
|
||||||
|
fn from(v: Vec<T>) -> Self {
|
||||||
|
Oco::Owned(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, const N: usize> From<&'a [T; N]> for Oco<'a, [T]>
|
||||||
|
where
|
||||||
|
[T]: ToOwned,
|
||||||
|
{
|
||||||
|
fn from(v: &'a [T; N]) -> Self {
|
||||||
|
Oco::Borrowed(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Oco<'a, str>> for Oco<'a, [u8]> {
|
||||||
|
fn from(v: Oco<'a, str>) -> Self {
|
||||||
|
match v {
|
||||||
|
Oco::Borrowed(v) => Oco::Borrowed(v.as_bytes()),
|
||||||
|
Oco::Owned(v) => Oco::Owned(v.into_bytes()),
|
||||||
|
Oco::Counted(v) => Oco::Counted(v.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error returned from `Oco::try_from` for unsuccessful
|
||||||
|
/// conversion from `Oco<'_, [u8]>` to `Oco<'_, str>`.
|
||||||
|
#[derive(Debug, Clone, thiserror::Error)]
|
||||||
|
#[error("invalid utf-8 sequence: {_0}")]
|
||||||
|
pub enum FromUtf8Error {
|
||||||
|
/// Error for conversion of [`Oco::Borrowed`] and [`Oco::Counted`] variants
|
||||||
|
/// (`&[u8]` to `&str`).
|
||||||
|
#[error("{_0}")]
|
||||||
|
StrFromBytes(
|
||||||
|
#[source]
|
||||||
|
#[from]
|
||||||
|
std::str::Utf8Error,
|
||||||
|
),
|
||||||
|
/// Error for conversion of [`Oco::Owned`] variant (`Vec<u8>` to `String`).
|
||||||
|
#[error("{_0}")]
|
||||||
|
StringFromBytes(
|
||||||
|
#[source]
|
||||||
|
#[from]
|
||||||
|
std::string::FromUtf8Error,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_slice_eq {
|
||||||
|
([$($g:tt)*] $((where $($w:tt)+))?, $lhs:ty, $rhs: ty) => {
|
||||||
|
impl<$($g)*> PartialEq<$rhs> for $lhs
|
||||||
|
$(where
|
||||||
|
$($w)*)?
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &$rhs) -> bool {
|
||||||
|
PartialEq::eq(&self[..], &other[..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<$($g)*> PartialEq<$lhs> for $rhs
|
||||||
|
$(where
|
||||||
|
$($w)*)?
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &$lhs) -> bool {
|
||||||
|
PartialEq::eq(&self[..], &other[..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_slice_eq!([], Oco<'_, str>, str);
|
||||||
|
impl_slice_eq!(['a, 'b], Oco<'a, str>, &'b str);
|
||||||
|
impl_slice_eq!([], Oco<'_, str>, String);
|
||||||
|
impl_slice_eq!(['a, 'b], Oco<'a, str>, Cow<'b, str>);
|
||||||
|
|
||||||
|
impl_slice_eq!([T: PartialEq] (where [T]: ToOwned), Oco<'_, [T]>, [T]);
|
||||||
|
impl_slice_eq!(['a, 'b, T: PartialEq] (where [T]: ToOwned), Oco<'a, [T]>, &'b [T]);
|
||||||
|
impl_slice_eq!([T: PartialEq] (where [T]: ToOwned), Oco<'_, [T]>, Vec<T>);
|
||||||
|
impl_slice_eq!(['a, 'b, T: PartialEq] (where [T]: ToOwned), Oco<'a, [T]>, Cow<'b, [T]>);
|
||||||
|
|
||||||
|
impl<'a, 'b> Add<&'b str> for Oco<'a, str> {
|
||||||
|
type Output = Oco<'static, str>;
|
||||||
|
|
||||||
|
fn add(self, rhs: &'b str) -> Self::Output {
|
||||||
|
Oco::Owned(String::from(self) + rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> Add<Cow<'b, str>> for Oco<'a, str> {
|
||||||
|
type Output = Oco<'static, str>;
|
||||||
|
|
||||||
|
fn add(self, rhs: Cow<'b, str>) -> Self::Output {
|
||||||
|
Oco::Owned(String::from(self) + rhs.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> Add<Oco<'b, str>> for Oco<'a, str> {
|
||||||
|
type Output = Oco<'static, str>;
|
||||||
|
|
||||||
|
fn add(self, rhs: Oco<'b, str>) -> Self::Output {
|
||||||
|
Oco::Owned(String::from(self) + rhs.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromIterator<Oco<'a, str>> for String {
|
||||||
|
fn from_iter<T: IntoIterator<Item = Oco<'a, str>>>(iter: T) -> Self {
|
||||||
|
iter.into_iter().fold(String::new(), |mut acc, item| {
|
||||||
|
acc.push_str(item.as_ref());
|
||||||
|
acc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Deserialize<'a> for Oco<'static, T>
|
||||||
|
where
|
||||||
|
T: ?Sized + ToOwned + 'a,
|
||||||
|
T::Owned: DeserializeOwned,
|
||||||
|
{
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'a>,
|
||||||
|
{
|
||||||
|
<T::Owned>::deserialize(deserializer).map(Oco::Owned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Serialize for Oco<'a, T>
|
||||||
|
where
|
||||||
|
T: ?Sized + ToOwned + 'a,
|
||||||
|
for<'b> &'b T: Serialize,
|
||||||
|
{
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
self.as_ref().serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn debug_fmt_should_display_quotes_for_strings() {
|
||||||
|
let s: Oco<str> = Oco::Borrowed("hello");
|
||||||
|
assert_eq!(format!("{:?}", s), "\"hello\"");
|
||||||
|
let s: Oco<str> = Oco::Counted(Arc::from("hello"));
|
||||||
|
assert_eq!(format!("{:?}", s), "\"hello\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn partial_eq_should_compare_str_to_str() {
|
||||||
|
let s: Oco<str> = Oco::Borrowed("hello");
|
||||||
|
assert_eq!(s, "hello");
|
||||||
|
assert_eq!("hello", s);
|
||||||
|
assert_eq!(s, String::from("hello"));
|
||||||
|
assert_eq!(String::from("hello"), s);
|
||||||
|
assert_eq!(s, Cow::from("hello"));
|
||||||
|
assert_eq!(Cow::from("hello"), s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn partial_eq_should_compare_slice_to_slice() {
|
||||||
|
let s: Oco<[i32]> = Oco::Borrowed([1, 2, 3].as_slice());
|
||||||
|
assert_eq!(s, [1, 2, 3].as_slice());
|
||||||
|
assert_eq!([1, 2, 3].as_slice(), s);
|
||||||
|
assert_eq!(s, vec![1, 2, 3]);
|
||||||
|
assert_eq!(vec![1, 2, 3], s);
|
||||||
|
assert_eq!(s, Cow::<'_, [i32]>::Borrowed(&[1, 2, 3]));
|
||||||
|
assert_eq!(Cow::<'_, [i32]>::Borrowed(&[1, 2, 3]), s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_should_concatenate_strings() {
|
||||||
|
let s: Oco<str> = Oco::Borrowed("hello");
|
||||||
|
assert_eq!(s.clone() + " world", "hello world");
|
||||||
|
assert_eq!(s.clone() + Cow::from(" world"), "hello world");
|
||||||
|
assert_eq!(s + Oco::from(" world"), "hello world");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn as_str_should_return_a_str() {
|
||||||
|
let s: Oco<str> = Oco::Borrowed("hello");
|
||||||
|
assert_eq!(s.as_str(), "hello");
|
||||||
|
let s: Oco<str> = Oco::Counted(Arc::from("hello"));
|
||||||
|
assert_eq!(s.as_str(), "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn as_slice_should_return_a_slice() {
|
||||||
|
let s: Oco<[i32]> = Oco::Borrowed([1, 2, 3].as_slice());
|
||||||
|
assert_eq!(s.as_slice(), [1, 2, 3].as_slice());
|
||||||
|
let s: Oco<[i32]> = Oco::Counted(Arc::from([1, 2, 3]));
|
||||||
|
assert_eq!(s.as_slice(), [1, 2, 3].as_slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_for_str_should_return_an_empty_string() {
|
||||||
|
let s: Oco<str> = Default::default();
|
||||||
|
assert!(s.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_for_slice_should_return_an_empty_slice() {
|
||||||
|
let s: Oco<[i32]> = Default::default();
|
||||||
|
assert!(s.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_for_any_option_should_return_none() {
|
||||||
|
let s: Oco<Option<i32>> = Default::default();
|
||||||
|
assert!(s.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cloned_owned_string_should_make_counted_str() {
|
||||||
|
let s: Oco<str> = Oco::Owned(String::from("hello"));
|
||||||
|
assert!(s.clone().is_counted());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cloned_borrowed_str_should_make_borrowed_str() {
|
||||||
|
let s: Oco<str> = Oco::Borrowed("hello");
|
||||||
|
assert!(s.clone().is_borrowed());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cloned_counted_str_should_make_counted_str() {
|
||||||
|
let s: Oco<str> = Oco::Counted(Arc::from("hello"));
|
||||||
|
assert!(s.clone().is_counted());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cloned_inplace_owned_string_should_make_counted_str_and_become_counted()
|
||||||
|
{
|
||||||
|
let mut s: Oco<str> = Oco::Owned(String::from("hello"));
|
||||||
|
assert!(s.clone_inplace().is_counted());
|
||||||
|
assert!(s.is_counted());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cloned_inplace_borrowed_str_should_make_borrowed_str_and_remain_borrowed(
|
||||||
|
) {
|
||||||
|
let mut s: Oco<str> = Oco::Borrowed("hello");
|
||||||
|
assert!(s.clone_inplace().is_borrowed());
|
||||||
|
assert!(s.is_borrowed());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cloned_inplace_counted_str_should_make_counted_str_and_remain_counted() {
|
||||||
|
let mut s: Oco<str> = Oco::Counted(Arc::from("hello"));
|
||||||
|
assert!(s.clone_inplace().is_counted());
|
||||||
|
assert!(s.is_counted());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialization_works() {
|
||||||
|
let s = serde_json::to_string(&Oco::Borrowed("foo"))
|
||||||
|
.expect("should serialize string");
|
||||||
|
assert_eq!(s, "\"foo\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialization_works() {
|
||||||
|
let s: Oco<str> = serde_json::from_str("\"bar\"")
|
||||||
|
.expect("should deserialize from string");
|
||||||
|
assert_eq!(s, Oco::from(String::from("bar")));
|
||||||
|
}
|
||||||
|
}
|
3
watch.sh
Executable file
3
watch.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
cargo watch -s "bun tailwind:build; cargo run"
|
Loading…
Reference in a new issue