Skip to main content

mbed/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! # mbed
4//!
5//! Embed and transform assets into your Rust crate.
6//!
7//! [`mbed`](crate) provides macros for turning files and generated outputs into [`Artifact`]
8//! values. Artifacts carry their bytes, MIME type, and [`Id`], and can be grouped
9//! into a [`Manifest`] for lookup by identifier.
10//!
11//! ## Basic usage
12//!
13//! Requires the `macros` feature.
14//!
15//! ```rust
16//! # #[cfg(any())]
17//! # fn __no_run() {
18//! pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../assets/logo.svg"];
19//! pub const INDEX: Artifact<str> = mbed::include_str!["../pages/index.html"];
20//!
21//! let id = LOGO.id();
22//! assert_eq!(LOGO.mime(), "image/svg+xml");
23//! # }
24//! ```
25//!
26//! ## Collections and manifests
27//!
28//! `collect!` groups artifacts inside a module. `manifest!` can then reference
29//! individual artifacts or whole collections with `::*`.
30//!
31//! Requires the `macros` feature.
32//!
33//! ```rust
34//! # #[cfg(any())]
35//! # fn __no_run() {
36//! pub mod pages {
37//!     pub const INDEX: Artifact<str> = mbed::include_str!["../pages/index.html"];
38//!     pub const ABOUT: Artifact<str> = mbed::include_str!["../pages/about.html"];
39//!
40//!     mbed::collect![INDEX, ABOUT];
41//! }
42//!
43//! pub mod images {
44//!     pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../assets/logo.svg"];
45//!
46//!     mbed::collect![LOGO];
47//! }
48//!
49//! pub const ASSETS: Manifest = mbed::manifest![pages::*, images::*];
50//!
51//! let artifact = ASSETS.get(pages::INDEX.id()).unwrap();
52//! assert_eq!(artifact.mime(), pages::INDEX.mime());
53//! # }
54//! ```
55//!
56//! ## Images
57//!
58//! Requires the `image` feature.
59//!
60//! ```rust
61//! # #[cfg(any())]
62//! # fn __no_run() {
63//! pub const LOGO: Artifact<Image> = mbed::image::include! {
64//!     resize: Scale(0.5),
65//!     path: "../assets/logo.png",
66//!     format: WebP,
67//! };
68//!
69//! assert_eq!(LOGO.format(), mbed::image::Format::WebP);
70//! let bytes = LOGO.as_bytes();
71//! # }
72//! ```
73//!
74//! ## JavaScript bundles
75//!
76//! Requires the `bundle` feature.
77//!
78//! ```rust
79//! # #[cfg(any())]
80//! # fn __no_run() {
81//! pub const APP: Artifact<Bundle> = mbed::js::bundle!["../frontend/app.js"];
82//!
83//! let code = APP.code();
84//! let sourcemap = APP.sourcemap();
85//! # }
86//! ```
87//!
88//! ## Tailwind CSS
89//!
90//! Requires the `tailwindcss` feature.
91//!
92//! ```rust
93//! # #[cfg(any())]
94//! # fn __no_run() {
95//! pub const STYLES: Artifact<str> = mbed::css::tailwindcss!("../tailwind.css");
96//! # }
97//! ```
98//!
99//! ## Feature flags
100//!
101//! - `macros` enables [`include_bytes!`], [`include_str!`], [`collect!`], and
102//!   [`manifest!`].
103//! - `image` enables [`image::include!`] and image metadata support.
104//! - `bundle` enables [`js::bundle!`].
105//! - `tailwindcss` enables [`css::tailwindcss!`].
106//! - `serde` enables serialization support for public data types.
107//! - `cargo-progress` shows bundling progress in Cargo build output. This feature
108//!   is only supported on Linux.
109
110use std::{iter::FusedIterator, ops::Deref, str::FromStr};
111
112/// A collection of embedded artifacts indexed by [`Id`].
113///
114/// Manifests are usually generated with [`manifest!`]. They store artifacts as
115/// byte artifacts, regardless of whether the original source artifact was bytes,
116/// text, an image, or a JavaScript bundle.
117///
118/// # Examples
119///
120/// Requires the `macros` feature.
121///
122/// ```rust
123/// # #[cfg(any())]
124/// # fn __no_run() {
125/// pub mod assets {
126///     pub const ROBOTS: Artifact<str> = mbed::include_str!["../public/robots.txt"];
127///     pub const FAVICON: Artifact<[u8]> = mbed::include_bytes!["../public/favicon.ico"];
128///
129///     mbed::collect![ROBOTS, FAVICON];
130/// }
131///
132/// pub const MANIFEST: Manifest = mbed::manifest![assets::*];
133///
134/// let robots = MANIFEST.get(assets::ROBOTS.id()).unwrap();
135/// assert_eq!(robots.as_bytes(), assets::ROBOTS.as_bytes());
136/// # }
137/// ```
138#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
139pub struct Manifest {
140    artifacts: &'static [Artifact<[u8]>],
141    buckets: &'static [Bucket],
142}
143
144impl std::fmt::Debug for Manifest {
145    #[inline]
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        f.debug_tuple("Manifest").field(&self.artifacts).finish()
148    }
149}
150
151impl Manifest {
152    /// Returns the artifact with the given [`Id`].
153    ///
154    /// Returns `None` when the id is not present in this manifest.
155    #[inline]
156    pub const fn get(&self, id: Id) -> Option<Artifact> {
157        let bucket_index = id.bucket_index(self.buckets.len());
158
159        match self.buckets[bucket_index].find(id) {
160            Some(index) => Some(self.artifacts[index]),
161            None => None,
162        }
163    }
164
165    /// Returns all artifacts in this manifest.
166    ///
167    /// The order matches the order generated by [`manifest!`].
168    #[inline]
169    pub const fn artifacts(&self) -> &'static [Artifact] {
170        self.artifacts
171    }
172}
173
174impl IntoIterator for Manifest {
175    type Item = Artifact;
176
177    type IntoIter = Iter<[u8]>;
178
179    #[inline]
180    fn into_iter(self) -> Self::IntoIter {
181        Iter {
182            artifacts: self.artifacts(),
183            index: 0,
184        }
185    }
186}
187
188#[cfg(feature = "serde")]
189impl serde::Serialize for Manifest {
190    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
191    where
192        S: serde::Serializer,
193    {
194        <[_] as serde::Serialize>::serialize(self.artifacts(), serializer)
195    }
196}
197
198/// An iterator over embedded artifacts.
199#[derive(Debug, Clone)]
200pub struct Iter<T>
201where
202    T: ?Sized + 'static,
203{
204    artifacts: &'static [Artifact<T>],
205    index: usize,
206}
207
208impl<T> Iterator for Iter<T>
209where
210    T: ?Sized + 'static,
211{
212    type Item = Artifact<T>;
213
214    #[inline]
215    fn next(&mut self) -> Option<Self::Item> {
216        if self.index < self.artifacts.len() {
217            let artifact = self.artifacts[self.index];
218            self.index += 1;
219            Some(artifact)
220        } else {
221            None
222        }
223    }
224}
225
226impl<T> FusedIterator for Iter<T> where T: ?Sized + 'static {}
227
228/// An embedded asset and its metadata.
229///
230/// `Artifact` values are produced by the crate's embedding macros. The default
231/// artifact type is `Artifact<[u8]>`, but text artifacts use `Artifact<str>`,
232/// image artifacts use `Artifact<image::Image>`, and JavaScript bundles use
233/// `Artifact<js::Bundle>`.
234///
235/// Each artifact has:
236///
237/// - an [`Id`], generated from the artifact contents;
238/// - a MIME type;
239/// - a reference to the embedded value.
240///
241/// # Examples
242///
243/// Requires the `macros` feature.
244///
245/// ```rust
246/// # #[cfg(any())]
247/// # fn __no_run() {
248/// pub const CONFIG: Artifact<str> = mbed::include_str!["../config/default.toml"];
249///
250/// let id = CONFIG.id();
251/// let mime = CONFIG.mime();
252/// let text = CONFIG.as_str();
253/// # }
254/// ```
255#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize))]
257pub struct Artifact<T = [u8]>
258where
259    T: ?Sized + 'static,
260{
261    mime: &'static str,
262    id: Id,
263    value: &'static T,
264}
265
266impl<T> Clone for Artifact<T>
267where
268    T: ?Sized,
269{
270    #[inline]
271    fn clone(&self) -> Self {
272        *self
273    }
274}
275
276impl<T> Copy for Artifact<T> where T: ?Sized {}
277
278impl<T> Artifact<T>
279where
280    T: ?Sized,
281{
282    /// Returns this artifact's SHA-224 identifier.
283    #[inline]
284    pub const fn id(&self) -> Id {
285        self.id
286    }
287
288    /// Returns this artifact's MIME type.
289    #[inline]
290    pub const fn mime(&self) -> &'static str {
291        self.mime
292    }
293}
294
295impl Artifact<[u8]> {
296    /// Returns this artifact's embedded bytes.
297    #[inline]
298    pub const fn as_bytes(&self) -> &'static [u8] {
299        self.value
300    }
301
302    #[doc(hidden)]
303    #[inline]
304    pub const fn __into_bytes_artifact(self) -> Artifact<[u8]> {
305        self
306    }
307}
308
309impl Artifact<str> {
310    /// Returns this artifact's UTF-8 contents as bytes.
311    #[inline]
312    pub const fn as_bytes(&self) -> &'static [u8] {
313        self.value.as_bytes()
314    }
315
316    /// Returns this artifact's UTF-8 contents.
317    #[inline]
318    pub const fn as_str(&self) -> &'static str {
319        self.value
320    }
321
322    /// Converts this text artifact into a byte artifact.
323    #[inline]
324    pub const fn into_bytes_artifact(self) -> Artifact<[u8]> {
325        Artifact {
326            id: self.id,
327            mime: self.mime,
328            value: self.value.as_bytes(),
329        }
330    }
331
332    #[doc(hidden)]
333    #[inline]
334    pub const fn __into_bytes_artifact(self) -> Artifact<[u8]> {
335        self.into_bytes_artifact()
336    }
337}
338
339impl AsRef<[u8]> for Artifact<[u8]> {
340    #[inline]
341    fn as_ref(&self) -> &[u8] {
342        self.as_bytes()
343    }
344}
345
346impl AsRef<[u8]> for Artifact<str> {
347    #[inline]
348    fn as_ref(&self) -> &[u8] {
349        self.as_bytes()
350    }
351}
352
353impl AsRef<str> for Artifact<str> {
354    #[inline]
355    fn as_ref(&self) -> &str {
356        self.as_str()
357    }
358}
359
360impl Deref for Artifact<[u8]> {
361    type Target = [u8];
362
363    #[inline]
364    fn deref(&self) -> &Self::Target {
365        self.as_bytes()
366    }
367}
368
369impl Deref for Artifact<str> {
370    type Target = str;
371
372    #[inline]
373    fn deref(&self) -> &Self::Target {
374        self.as_str()
375    }
376}
377
378/// A SHA-224 artifact identifier.
379///
380/// `Id` is a 28-byte digest. Its [`Display`](std::fmt::Display)
381/// representation is lowercase hexadecimal, and [`FromStr`] parses the same
382/// representation.
383///
384/// # Examples
385///
386/// ```rust
387/// # #[cfg(any())]
388/// # fn __no_run() {
389/// let id = mbed::Id::from_str(
390///     "00000000000000000000000000000000000000000000000000000000",
391/// ).unwrap();
392///
393/// assert_eq!(id.to_string().len(), 56);
394/// assert_eq!(id.as_bytes().len(), 28);
395/// # }
396/// ```
397#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord, Hash)]
398#[repr(transparent)]
399#[allow(clippy::derived_hash_with_manual_eq)]
400pub struct Id([u8; 28]);
401
402impl std::fmt::Debug for Id {
403    #[inline]
404    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405        f.debug_tuple("Id")
406            .field(&const_hex::Buffer::<_, false>::new().const_format(self.as_bytes()))
407            .finish()
408    }
409}
410
411impl std::fmt::Display for Id {
412    #[inline]
413    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414        f.write_str(
415            const_hex::Buffer::<_, false>::new()
416                .const_format(self.as_bytes())
417                .as_str(),
418        )
419    }
420}
421
422impl Id {
423    /// The smallest possible identifier.
424    pub const MIN: Self = Self([u8::MIN; _]);
425
426    /// The largest possible identifier.
427    pub const MAX: Self = Self([u8::MAX; _]);
428
429    /// Compares two identifiers byte by byte.
430    #[inline]
431    pub const fn eq(&self, other: &Self) -> bool {
432        let mut x = true;
433        let mut i = 0;
434
435        while i < self.0.len() {
436            x = x && self.0[i] == other.0[i];
437            i += 1;
438        }
439
440        x
441    }
442
443    /// Returns this identifier as a 28-byte array reference.
444    #[inline]
445    pub const fn as_bytes(&self) -> &[u8; 28] {
446        &self.0
447    }
448
449    /// Returns this identifier as a 28-byte array.
450    #[inline]
451    pub const fn to_bytes(self) -> [u8; 28] {
452        self.0
453    }
454
455    /// Creates an identifier from a 28-byte array.
456    #[inline]
457    pub const fn from_bytes(x: [u8; 28]) -> Self {
458        Self(x)
459    }
460
461    #[inline]
462    const fn bucket_index(&self, n: usize) -> usize {
463        usize::from_ne_bytes(*self.0.first_chunk().unwrap()) & !(usize::MAX << n.ilog2())
464    }
465}
466
467impl PartialEq for Id {
468    #[inline]
469    fn eq(&self, other: &Self) -> bool {
470        self.eq(other)
471    }
472}
473
474impl AsRef<[u8]> for Id {
475    #[inline]
476    fn as_ref(&self) -> &[u8] {
477        self.as_bytes()
478    }
479}
480
481impl Deref for Id {
482    type Target = [u8];
483
484    #[inline]
485    fn deref(&self) -> &Self::Target {
486        self.as_bytes()
487    }
488}
489
490impl FromStr for Id {
491    type Err = const_hex::FromHexError;
492
493    #[inline]
494    fn from_str(s: &str) -> Result<Self, Self::Err> {
495        const_hex::decode_to_array(s).map(Self::from_bytes)
496    }
497}
498
499#[cfg(feature = "serde")]
500impl serde::Serialize for Id {
501    #[inline]
502    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
503    where
504        S: serde::Serializer,
505    {
506        serializer.serialize_str(
507            const_hex::Buffer::<_, false>::new()
508                .const_format(self.as_bytes())
509                .as_str(),
510        )
511    }
512}
513
514#[cfg(feature = "serde")]
515impl<'de> serde::Deserialize<'de> for Id {
516    #[inline]
517    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
518    where
519        D: serde::Deserializer<'de>,
520    {
521        <&str as serde::Deserialize>::deserialize(deserializer)
522            .and_then(|x| Self::from_str(x).map_err(<D::Error as serde::de::Error>::custom))
523    }
524}
525
526#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
527#[doc(hidden)]
528#[repr(transparent)]
529pub struct Bucket(&'static [Entry]);
530
531impl Bucket {
532    const fn find(&self, id: Id) -> Option<usize> {
533        let mut i = 0;
534
535        while i < self.0.len() {
536            if self.0[i].id.eq(&id) {
537                return Some(self.0[i].index as usize);
538            }
539
540            i += 1;
541        }
542
543        None
544    }
545}
546
547#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
548#[doc(hidden)]
549pub struct Entry {
550    id: Id,
551    index: u32,
552}
553
554/// Groups artifacts into a module-local collection.
555///
556/// `collect!` creates a hidden `__ARTIFACTS` item that can be consumed by
557/// [`manifest!`] or by another `collect!` invocation using `module::*`.
558///
559/// This macro accepts artifact names and collection globs.
560///
561/// Requires the `macros` feature.
562///
563/// # Examples
564///
565/// ```rust
566/// # #[cfg(any())]
567/// # fn __no_run() {
568/// pub mod icons {
569///     pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../icons/logo.svg"];
570///     pub const MENU: Artifact<[u8]> = mbed::include_bytes!["../icons/menu.svg"];
571///
572///     mbed::collect![LOGO, MENU];
573/// }
574///
575/// pub mod pages {
576///     pub const INDEX: Artifact<str> = mbed::include_str!["../pages/index.html"];
577///
578///     mbed::collect![INDEX];
579/// }
580///
581/// pub mod public {
582///     mbed::collect![super::icons::*, super::pages::*];
583/// }
584/// # }
585/// ```
586#[cfg(feature = "macros")]
587#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
588#[macro_export]
589macro_rules! collect {
590    (@impl $($x:ident)::+ ::*) => {
591        &$($x)::+ ::__ARTIFACTS
592    };
593
594    (@impl $($x:ident)::+) => {
595        &[($($x)::+).__into_bytes_artifact()]
596    };
597
598
599    ($($($tt:tt)::+),* $(,)?) => {
600        #[allow(unused)]
601        #[doc(hidden)]
602        pub const __ARTIFACTS: &'static [$crate::__macro::Artifact]
603            = $crate::__macro::concat_slices!([$crate::__macro::Artifact]: $($crate::collect!(@impl $($tt)::+)),*);
604    };
605}
606
607/// Builds a [`Manifest`] from artifacts and collections.
608///
609/// `manifest!` accepts the same input shape as [`collect!`]: artifact names and
610/// module globs such as `assets::*`.
611///
612/// Requires the `macros` feature.
613///
614/// # Examples
615///
616/// ```rust
617/// # #[cfg(any())]
618/// # fn __no_run() {
619/// pub mod assets {
620///     pub const README: Artifact<str> = mbed::include_str!["../README.md"];
621///     pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../assets/logo.svg"];
622///
623///     mbed::collect![README, LOGO];
624/// }
625///
626/// pub const MANIFEST: Manifest = mbed::manifest![assets::*];
627///
628/// let artifact = MANIFEST.get(assets::README.id()).unwrap();
629/// assert_eq!(artifact.mime(), assets::README.mime());
630/// # }
631/// ```
632#[cfg(feature = "macros")]
633#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
634#[macro_export]
635macro_rules! manifest {
636    ($($tt:tt)*) => {{
637        $crate::collect!($($tt)*);
638
639        const MANIFEST: $crate::__macro::Manifest = {
640            const BUCKET_LEN: usize = 1 << __ARTIFACTS.len().ilog2() as usize;
641
642            const ENTRIES_LEN: usize = {
643                let mut entries_len = [0usize; BUCKET_LEN];
644                let mut entries_max = 0;
645
646                let mut i = 0;
647
648                while i < __ARTIFACTS.len() {
649                    let x = &mut entries_len
650                        [$crate::__macro::bucket_index(&__ARTIFACTS[i].id(), BUCKET_LEN)];
651
652                    *x += 1;
653
654                    if *x > entries_max {
655                        entries_max = *x;
656                    }
657
658                    i += 1;
659                }
660
661                entries_max
662            };
663
664            const BUCKETS_RAW: [([$crate::__macro::Entry; ENTRIES_LEN], usize); BUCKET_LEN] = {
665                let mut buckets =
666                    [([$crate::__macro::entry($crate::__macro::Id::MIN, 0); ENTRIES_LEN], 0usize); BUCKET_LEN];
667
668                let mut i = 0;
669
670                while i < __ARTIFACTS.len() {
671                    let x =
672                        &mut buckets[$crate::__macro::bucket_index(&__ARTIFACTS[i].id(), BUCKET_LEN)];
673
674                    let k = x.1;
675
676                    x.0[k] = $crate::__macro::entry(__ARTIFACTS[i].id(), i as u32);
677                    x.1 += 1;
678
679                    i += 1;
680                }
681
682                buckets
683            };
684
685            const BUCKETS: [$crate::__macro::Bucket; BUCKET_LEN] = {
686                let mut buckets = [$crate::__macro::bucket(&[]); BUCKET_LEN];
687
688                let mut i = 0;
689
690                while i < BUCKET_LEN {
691                    buckets[i] = $crate::__macro::bucket(BUCKETS_RAW[i].0.split_at(BUCKETS_RAW[i].1).0);
692                    i += 1;
693                }
694
695                buckets
696            };
697
698            $crate::__macro::manifest(__ARTIFACTS, &BUCKETS)
699        };
700
701        MANIFEST
702    }};
703}
704
705/// Embeds a file as an [`Artifact<[u8]>`].
706///
707/// This is the mbed equivalent of Rust's `include_bytes!`, with artifact
708/// metadata attached.
709///
710/// Requires the `macros` feature.
711///
712/// # Examples
713///
714/// ```rust
715/// # #[cfg(any())]
716/// # fn __no_run() {
717/// pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../assets/logo.svg"];
718///
719/// let bytes = LOGO.as_bytes();
720/// let id = LOGO.id();
721/// # }
722/// ```
723#[cfg(feature = "macros")]
724#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
725pub use mbed_core::include_str;
726
727/// Embeds a UTF-8 file as an [`Artifact<str>`].
728///
729/// This is the mbed equivalent of Rust's `include_str!`, with artifact metadata
730/// attached.
731///
732/// Requires the `macros` feature.
733///
734/// # Examples
735///
736/// ```rust
737/// # #[cfg(any())]
738/// # fn __no_run() {
739/// pub const TEMPLATE: Artifact<str> = mbed::include_str!["../templates/page.html"];
740///
741/// let text = TEMPLATE.as_str();
742/// let bytes = TEMPLATE.as_bytes();
743/// # }
744/// ```
745#[cfg(feature = "macros")]
746#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
747pub use mbed_core::include_bytes;
748
749/// CSS asset support.
750#[cfg(feature = "css")]
751#[cfg_attr(docsrs, doc(cfg(feature = "css")))]
752pub mod css {
753    /// Compiles Tailwind CSS into an embedded text artifact.
754    ///
755    /// Requires the `tailwindcss` feature.
756    ///
757    /// # Examples
758    ///
759    /// ```rust
760    /// # #[cfg(any())]
761    /// # fn __no_run() {
762    /// pub const APP_CSS: Artifact<str> = mbed::css::tailwindcss!("../tailwind.css");
763    ///
764    /// let css = APP_CSS.as_str();
765    /// # }
766    /// ```
767    #[cfg(feature = "tailwindcss")]
768    #[cfg_attr(docsrs, doc(cfg(feature = "tailwindcss")))]
769    pub use mbed_css::tailwindcss;
770}
771
772/// Image asset support.
773///
774/// Requires the `image` feature.
775///
776/// Optional image features:
777///
778/// - `image-avif` enables AVIF support and requires `libdav1d`.
779/// - `image-asm` enables assembly optimizations and requires `nasm`.
780#[cfg(feature = "image")]
781#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
782pub mod image {
783    use std::ops::Deref;
784
785    use crate::Artifact;
786
787    /// Embeds an image as an [`Artifact<Image>`].
788    ///
789    /// The embedded artifact includes the encoded image bytes, image dimensions,
790    /// and detected output format.
791    ///
792    /// Requires the `image` feature.
793    ///
794    /// Optional image features:
795    ///
796    /// - `image-avif` enables AVIF support and requires `libdav1d`.
797    /// - `image-asm` enables assembly optimizations and requires `nasm`.
798    ///
799    /// # Syntax
800    ///
801    /// ```rust
802    /// # #[cfg(any())]
803    /// # fn __no_run() {
804    /// mbed::image::include! {
805    ///     path: "../assets/logo.png",
806    ///     format: WebP,
807    ///     compression: Fast,
808    ///     resize: Scale(0.75),
809    /// }
810    /// # }
811    /// ```
812    ///
813    /// The macro accepts a braced list of named fields. The `path` field is
814    /// required; all other fields are optional.
815    ///
816    /// ## Fields
817    ///
818    /// - `path`: Path to the source image file.
819    /// - `format`: Output image format. If omitted, the original image format is
820    ///   preserved.
821    /// - `compression`: Best-effort hint for how the image should be re-encoded.
822    /// - `resize`: Optional resize operation to apply before embedding.
823    ///
824    /// ## Supported formats
825    ///
826    /// `Png`, `Jpeg`, `Gif`, `WebP`, `Pnm`, `Tiff`, `Tga`, `Bmp`, `Ico`, `Hdr`,
827    /// `OpenExr`, `Farbfeld`, `Avif`, and `Qoi`.
828    ///
829    /// AVIF support requires the `image-avif` feature.
830    ///
831    /// ## Supported compression modes
832    ///
833    /// `Fast`, `Best`, `Uncompressed`, and `Balanced`.
834    ///
835    /// Compression is a best-effort hint. Some formats may ignore it or only
836    /// support a subset of compression behavior.
837    ///
838    /// ## Resize syntax
839    ///
840    /// Images can be resized by scale factor:
841    ///
842    /// ```rust
843    /// # #[cfg(any())]
844    /// # fn __no_run() {
845    /// # __ {
846    /// resize: Scale(0.75)
847    /// # }}
848    /// ```
849    ///
850    /// Or to exact dimensions:
851    ///
852    /// ```rust
853    /// # #[cfg(any())]
854    /// # fn __no_run() {
855    /// # __ {
856    /// resize: Exact {
857    ///     width: 1024,
858    ///     height: 1024,
859    ///     keep_aspect_ratio: false,
860    /// }
861    /// # }}
862    /// ```
863    ///
864    /// When `keep_aspect_ratio` is enabled, the image is resized to fit within the
865    /// requested dimensions without changing its aspect ratio.
866    ///
867    /// # Examples
868    ///
869    /// Embed an image using its original format:
870    ///
871    /// ```rust
872    /// # #[cfg(any())]
873    /// # fn __no_run() {
874    /// pub const LOGO: Artifact<Image> = mbed::image::include! {
875    ///     path: "../assets/logo.png",
876    /// };
877    ///
878    /// assert_eq!(LOGO.width(), 512);
879    /// assert_eq!(LOGO.height(), 512);
880    /// let bytes = LOGO.as_bytes();
881    /// # }
882    /// ```
883    ///
884    /// Re-encode, compress, and resize an image:
885    ///
886    /// ```rust
887    /// # #[cfg(any())]
888    /// # fn __no_run() {
889    /// pub const LOGO_WEBP: Artifact<Image> = mbed::image::include! {
890    ///     path: "../assets/logo.png",
891    ///     format: WebP,
892    ///     compression: Fast,
893    ///     resize: Scale(0.75),
894    /// };
895    /// # }
896    /// ```
897    pub use mbed_image::include;
898
899    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
900    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
901    pub struct Image {
902        format: Format,
903        width: u32,
904        height: u32,
905        bytes: &'static [u8],
906    }
907
908    impl Artifact<Image> {
909        /// Returns the encoded image format.
910        #[inline]
911        pub const fn format(&self) -> Format {
912            self.value.format
913        }
914
915        /// Returns the image width in pixels.
916        #[inline]
917        pub const fn width(&self) -> u32 {
918            self.value.width
919        }
920
921        /// Returns the image height in pixels.
922        #[inline]
923        pub const fn height(&self) -> u32 {
924            self.value.height
925        }
926
927        /// Returns the encoded image bytes.
928        #[inline]
929        pub const fn as_bytes(&self) -> &'static [u8] {
930            self.value.bytes
931        }
932
933        #[doc(hidden)]
934        #[inline]
935        pub const fn __into_bytes_artifact(self) -> Artifact<[u8]> {
936            Artifact {
937                mime: self.mime(),
938                id: self.id(),
939                value: self.as_bytes(),
940            }
941        }
942    }
943
944    impl AsRef<[u8]> for Artifact<Image> {
945        #[inline]
946        fn as_ref(&self) -> &[u8] {
947            self.as_bytes()
948        }
949    }
950
951    impl Deref for Artifact<Image> {
952        type Target = [u8];
953
954        #[inline]
955        fn deref(&self) -> &Self::Target {
956            self.as_bytes()
957        }
958    }
959
960    /// An encoded image format.
961    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
962    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
963    pub enum Format {
964        /// PNG image data.
965        Png,
966        /// JPEG image data.
967        Jpeg,
968        /// GIF image data.
969        Gif,
970        /// WebP image data.
971        WebP,
972        /// PNM image data.
973        Pnm,
974        /// TIFF image data.
975        Tiff,
976        /// TGA image data.
977        Tga,
978        /// BMP image data.
979        Bmp,
980        /// ICO image data.
981        Ico,
982        /// Radiance HDR image data.
983        Hdr,
984        /// OpenEXR image data.
985        OpenExr,
986        /// Farbfeld image data.
987        Farbfeld,
988        /// AVIF image data.
989        #[cfg(any(feature = "image-avif", doc, docsrs))]
990        #[cfg_attr(docsrs, doc(cfg(feature = "image-avif")))]
991        Avif,
992        /// QOI image data.
993        Qoi,
994    }
995
996    #[cfg(feature = "macros")]
997    #[doc(hidden)]
998    pub mod __macro {
999        pub use super::{Format, Image};
1000
1001        #[inline]
1002        pub const fn image(format: Format, width: u32, height: u32, bytes: &'static [u8]) -> Image {
1003            Image {
1004                format,
1005                width,
1006                height,
1007                bytes,
1008            }
1009        }
1010    }
1011}
1012
1013/// JavaScript asset support.
1014#[cfg(feature = "js")]
1015#[cfg_attr(docsrs, doc(cfg(feature = "js")))]
1016pub mod js {
1017    use std::ops::Deref;
1018
1019    use crate::Artifact;
1020
1021    /// Bundles and minifies JavaScript into an embedded [`Artifact<Bundle>`].
1022    ///
1023    /// The embedded artifact includes the bundled JavaScript code and source map.
1024    ///
1025    /// Bundling and minification are performed using `oxc`.
1026    ///
1027    /// Requires the `bundle` feature.
1028    ///
1029    /// # Syntax
1030    ///
1031    /// The macro accepts either a single JavaScript file path:
1032    ///
1033    /// ```rust
1034    /// # #[cfg(any())]
1035    /// # fn __no_run() {
1036    /// mbed::js::bundle!("../frontend/app.js")
1037    /// # }
1038    /// ```
1039    ///
1040    /// Or a list of JavaScript file paths:
1041    ///
1042    /// ```rust
1043    /// # #[cfg(any())]
1044    /// # fn __no_run() {
1045    /// mbed::js::bundle![
1046    ///     "../frontend/vendor.js",
1047    ///     "../frontend/app.js",
1048    /// ]
1049    /// # }
1050    /// ```
1051    ///
1052    /// Paths are resolved relative to the source file containing the macro
1053    /// invocation.
1054    ///
1055    /// Only JavaScript files are currently supported. TypeScript files cannot be
1056    /// bundled by this macro at this time.
1057    ///
1058    /// # Examples
1059    ///
1060    /// Bundle a single JavaScript entry point:
1061    ///
1062    /// ```rust
1063    /// # #[cfg(any())]
1064    /// # fn __no_run() {
1065    /// pub const APP: Artifact<Bundle> = mbed::js::bundle!("../frontend/app.js");
1066    ///
1067    /// let code = APP.code();
1068    /// let sourcemap = APP.sourcemap();
1069    /// # }
1070    /// ```
1071    ///
1072    /// Bundle multiple JavaScript files:
1073    ///
1074    /// ```rust
1075    /// # #[cfg(any())]
1076    /// # fn __no_run() {
1077    /// pub const APP: Artifact<Bundle> = mbed::js::bundle![
1078    ///     "../frontend/vendor.js",
1079    ///     "../frontend/app.js",
1080    /// ];
1081    ///
1082    /// let code = APP.code();
1083    /// let sourcemap = APP.sourcemap();
1084    /// # }
1085    /// ```
1086    #[cfg(feature = "bundle")]
1087    #[cfg_attr(docsrs, doc(cfg(feature = "bundle")))]
1088    pub use mbed_js::bundle;
1089
1090    /// Embedded JavaScript bundle output.
1091    ///
1092    /// A bundle contains generated JavaScript code and its source map.
1093    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1094    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1095    pub struct Bundle {
1096        code: &'static str,
1097        sourcemap: &'static str,
1098    }
1099
1100    impl Artifact<Bundle> {
1101        /// Returns the bundle code as UTF-8 bytes.
1102        #[inline]
1103        pub const fn as_bytes(&self) -> &'static [u8] {
1104            self.as_str().as_bytes()
1105        }
1106
1107        /// Returns the bundle code.
1108        #[inline]
1109        pub const fn as_str(&self) -> &'static str {
1110            self.code()
1111        }
1112
1113        /// Returns the generated JavaScript code.
1114        #[inline]
1115        pub const fn code(&self) -> &'static str {
1116            self.value.code
1117        }
1118
1119        /// Returns the generated source map.
1120        #[inline]
1121        pub const fn sourcemap(&self) -> &'static str {
1122            self.value.sourcemap
1123        }
1124
1125        #[doc(hidden)]
1126        #[inline]
1127        pub const fn __into_bytes_artifact(self) -> Artifact<[u8]> {
1128            Artifact {
1129                mime: self.mime(),
1130                id: self.id(),
1131                value: self.code().as_bytes(),
1132            }
1133        }
1134    }
1135
1136    impl AsRef<[u8]> for Artifact<Bundle> {
1137        #[inline]
1138        fn as_ref(&self) -> &[u8] {
1139            self.as_bytes()
1140        }
1141    }
1142
1143    impl AsRef<str> for Artifact<Bundle> {
1144        #[inline]
1145        fn as_ref(&self) -> &str {
1146            self.as_str()
1147        }
1148    }
1149
1150    impl Deref for Artifact<Bundle> {
1151        type Target = str;
1152
1153        #[inline]
1154        fn deref(&self) -> &Self::Target {
1155            self.as_str()
1156        }
1157    }
1158
1159    #[cfg(feature = "macros")]
1160    #[doc(hidden)]
1161    pub mod __macro {
1162        pub use super::Bundle;
1163
1164        #[inline]
1165        pub const fn bundle(code: &'static str, sourcemap: &'static str) -> Bundle {
1166            Bundle { code, sourcemap }
1167        }
1168    }
1169}
1170
1171#[cfg(feature = "macros")]
1172#[doc(hidden)]
1173pub mod __macro {
1174    pub use crate::{Artifact, Bucket, Entry, Id, Manifest};
1175    pub use constcat::concat_slices;
1176
1177    #[inline]
1178    pub const fn manifest(
1179        artifacts: &'static [Artifact<[u8]>],
1180        buckets: &'static [Bucket],
1181    ) -> Manifest {
1182        Manifest { artifacts, buckets }
1183    }
1184
1185    #[inline]
1186    pub const fn artifact<T>(mime: &'static str, id: Id, value: &'static T) -> Artifact<T>
1187    where
1188        T: ?Sized + 'static,
1189    {
1190        Artifact { mime, id, value }
1191    }
1192
1193    #[inline]
1194    pub const fn bucket(x: &'static [Entry]) -> Bucket {
1195        Bucket(x)
1196    }
1197
1198    #[inline]
1199    pub const fn entry(id: Id, index: u32) -> Entry {
1200        Entry { id, index }
1201    }
1202
1203    #[inline]
1204    pub const fn bucket_index(x: &Id, n: usize) -> usize {
1205        x.bucket_index(n)
1206    }
1207}