Add option_asset! to match asset! (#4791)
This commit is contained in:
1 parent
e656bace5c
commit
8adea41efa
5 files changed
+77
-16
No files matched your search
@@ -1,10 +1,10 @@
|
|||||||
use crate::{resolve_path, AssetParseError};
|
use crate::{resolve_path, AssetParseError};
|
||||||
use macro_string::MacroString;
|
use macro_string::MacroString;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{quote, ToTokens, TokenStreamExt};
|
use quote::{quote, ToTokens};
|
||||||
use std::{
|
use std::{
|
||||||
hash::{DefaultHasher, Hash, Hasher},
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
path::PathBuf,
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
@@ -66,14 +66,15 @@ impl ToTokens for AssetParser {
|
|||||||
// - The macro needs to output the absolute path to the asset for the bundler to find later
|
// - The macro needs to output the absolute path to the asset for the bundler to find later
|
||||||
// - It also needs to serialize the bundled asset along with the asset options for the bundler to use later
|
// - It also needs to serialize the bundled asset along with the asset options for the bundler to use later
|
||||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
let asset = match self.asset.as_ref() {
|
match self.asset.as_ref() {
|
||||||
Ok(asset) => asset,
|
Ok(asset) => tokens.extend(self.expand_asset_tokens(asset)),
|
||||||
Err(err) => {
|
Err(err) => tokens.extend(self.error_tokens(err)),
|
||||||
let err = err.to_string();
|
}
|
||||||
tokens.append_all(quote! { compile_error!(#err) });
|
}
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
};
|
impl AssetParser {
|
||||||
|
pub(crate) fn expand_asset_tokens(&self, asset: &Path) -> proc_macro2::TokenStream {
|
||||||
let asset_string = asset.to_string_lossy();
|
let asset_string = asset.to_string_lossy();
|
||||||
let mut asset_str = proc_macro2::Literal::string(&asset_string);
|
let mut asset_str = proc_macro2::Literal::string(&asset_string);
|
||||||
asset_str.set_span(self.path_expr.span());
|
asset_str.set_span(self.path_expr.span());
|
||||||
@@ -91,9 +92,9 @@ impl ToTokens for AssetParser {
|
|||||||
|
|
||||||
// generate the asset::new method to deprecate the `./assets/blah.css` syntax
|
// generate the asset::new method to deprecate the `./assets/blah.css` syntax
|
||||||
let constructor = if asset.is_relative() {
|
let constructor = if asset.is_relative() {
|
||||||
quote::quote! { create_bundled_asset_relative }
|
quote! { create_bundled_asset_relative }
|
||||||
} else {
|
} else {
|
||||||
quote::quote! { create_bundled_asset }
|
quote! { create_bundled_asset }
|
||||||
};
|
};
|
||||||
|
|
||||||
let options = if self.options.is_empty() {
|
let options = if self.options.is_empty() {
|
||||||
@@ -102,7 +103,7 @@ impl ToTokens for AssetParser {
|
|||||||
self.options.clone()
|
self.options.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
tokens.extend(quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
// The source is used by the CLI to copy the asset
|
// The source is used by the CLI to copy the asset
|
||||||
const __ASSET_SOURCE_PATH: &'static str = #asset_str;
|
const __ASSET_SOURCE_PATH: &'static str = #asset_str;
|
||||||
@@ -120,10 +121,26 @@ impl ToTokens for AssetParser {
|
|||||||
|
|
||||||
static __REFERENCE_TO_LINK_SECTION: &'static [u8] = &__LINK_SECTION;
|
static __REFERENCE_TO_LINK_SECTION: &'static [u8] = &__LINK_SECTION;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
manganis::Asset::new(|| unsafe { std::ptr::read_volatile(&__REFERENCE_TO_LINK_SECTION) })
|
manganis::Asset::new(|| unsafe { std::ptr::read_volatile(&__REFERENCE_TO_LINK_SECTION) })
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expand_option_tokens(&self) -> proc_macro2::TokenStream {
|
||||||
|
match self.asset.as_ref() {
|
||||||
|
Ok(asset) => {
|
||||||
|
let asset_tokens = self.expand_asset_tokens(asset);
|
||||||
|
quote! { ::core::option::Option::Some(#asset_tokens) }
|
||||||
|
}
|
||||||
|
Err(AssetParseError::AssetDoesntExist { .. }) => {
|
||||||
|
quote! { ::core::option::Option::<manganis::Asset>::None }
|
||||||
|
}
|
||||||
|
Err(err) => self.error_tokens(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_tokens(&self, err: &AssetParseError) -> proc_macro2::TokenStream {
|
||||||
|
let err = err.to_string();
|
||||||
|
quote! { compile_error!(#err) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +66,24 @@ pub fn asset(input: TokenStream) -> TokenStream {
|
|||||||
quote! { #asset }.into_token_stream().into()
|
quote! { #asset }.into_token_stream().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve an asset at compile time, returning `None` if the asset does not exist.
|
||||||
|
///
|
||||||
|
/// This behaves like the `asset!` macro when the asset can be resolved, but mirrors
|
||||||
|
/// [`option_env!`](core::option_env) by returning an `Option` instead of emitting a compile error
|
||||||
|
/// when the asset is missing.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use manganis::{asset, option_asset, Asset};
|
||||||
|
/// const REQUIRED: Asset = asset!("/assets/style.css");
|
||||||
|
/// const OPTIONAL: Option<Asset> = option_asset!("/assets/maybe.css");
|
||||||
|
/// ```
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn option_asset(input: TokenStream) -> TokenStream {
|
||||||
|
let asset = parse_macro_input!(input as asset::AssetParser);
|
||||||
|
|
||||||
|
asset.expand_option_tokens().into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate type-safe and globally-unique CSS identifiers from a CSS module.
|
/// Generate type-safe and globally-unique CSS identifiers from a CSS module.
|
||||||
///
|
///
|
||||||
/// CSS modules allow you to have unique, scoped and type-safe CSS identifiers. A CSS module is a CSS file with the `.module.css` file extension.
|
/// CSS modules allow you to have unique, scoped and type-safe CSS identifiers. A CSS module is a CSS file with the `.module.css` file extension.
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use manganis::{asset, option_asset, Asset};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolves_existing_asset() {
|
||||||
|
const REQUIRED: Asset = asset!("/assets/asset.txt");
|
||||||
|
const OPTIONAL: Option<Asset> = option_asset!("/assets/asset.txt");
|
||||||
|
|
||||||
|
let optional = OPTIONAL.expect("option_asset! should return Some for existing assets");
|
||||||
|
assert_eq!(optional.to_string(), REQUIRED.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_asset_returns_none() {
|
||||||
|
const OPTIONAL: Option<Asset> = option_asset!("/assets/does_not_exist.txt");
|
||||||
|
|
||||||
|
assert!(OPTIONAL.is_none());
|
||||||
|
}
|
||||||
@@ -11,6 +11,14 @@ const AVIF_ASSET: Asset = manganis::asset!("/assets/image.png");
|
|||||||
|
|
||||||
AVIF_ASSET will be set to a new file name that will be served by some CLI. That file can be collected by any package that depends on the component library.
|
AVIF_ASSET will be set to a new file name that will be served by some CLI. That file can be collected by any package that depends on the component library.
|
||||||
|
|
||||||
|
If you have assets that may not always be bundled, you can fall back gracefully with `option_asset!`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use manganis::{Asset, asset, option_asset};
|
||||||
|
const REQUIRED: Asset = asset!("/assets/style.css");
|
||||||
|
const OPTIONAL: Option<Asset> = option_asset!("/assets/missing.css");
|
||||||
|
```
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use manganis::{ImageFormat, Asset, asset, ImageSize, AssetOptions};
|
use manganis::{ImageFormat, Asset, asset, ImageSize, AssetOptions};
|
||||||
// You can collect arbitrary files. Absolute paths are resolved relative to the package root
|
// You can collect arbitrary files. Absolute paths are resolved relative to the package root
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod macro_helpers;
|
pub mod macro_helpers;
|
||||||
pub use manganis_macro::asset;
|
pub use manganis_macro::asset;
|
||||||
|
pub use manganis_macro::option_asset;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use manganis_macro::css_module;
|
pub use manganis_macro::css_module;
|
||||||
|
|||||||
Reference in new issue
Block a user