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 macro_string::MacroString;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use quote::{quote, ToTokens};
|
||||
use std::{
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use syn::{
|
||||
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
|
||||
// - 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) {
|
||||
let asset = match self.asset.as_ref() {
|
||||
Ok(asset) => asset,
|
||||
Err(err) => {
|
||||
let err = err.to_string();
|
||||
tokens.append_all(quote! { compile_error!(#err) });
|
||||
return;
|
||||
}
|
||||
};
|
||||
match self.asset.as_ref() {
|
||||
Ok(asset) => tokens.extend(self.expand_asset_tokens(asset)),
|
||||
Err(err) => tokens.extend(self.error_tokens(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AssetParser {
|
||||
pub(crate) fn expand_asset_tokens(&self, asset: &Path) -> proc_macro2::TokenStream {
|
||||
let asset_string = asset.to_string_lossy();
|
||||
let mut asset_str = proc_macro2::Literal::string(&asset_string);
|
||||
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
|
||||
let constructor = if asset.is_relative() {
|
||||
quote::quote! { create_bundled_asset_relative }
|
||||
quote! { create_bundled_asset_relative }
|
||||
} else {
|
||||
quote::quote! { create_bundled_asset }
|
||||
quote! { create_bundled_asset }
|
||||
};
|
||||
|
||||
let options = if self.options.is_empty() {
|
||||
@@ -102,7 +103,7 @@ impl ToTokens for AssetParser {
|
||||
self.options.clone()
|
||||
};
|
||||
|
||||
tokens.extend(quote! {
|
||||
quote! {
|
||||
{
|
||||
// The source is used by the CLI to copy the asset
|
||||
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;
|
||||
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
|
||||
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
|
||||
use manganis::{ImageFormat, Asset, asset, ImageSize, AssetOptions};
|
||||
// You can collect arbitrary files. Absolute paths are resolved relative to the package root
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#[doc(hidden)]
|
||||
pub mod macro_helpers;
|
||||
pub use manganis_macro::asset;
|
||||
pub use manganis_macro::option_asset;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use manganis_macro::css_module;
|
||||
|
||||
Reference in new issue
Block a user