1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! # Icons
//!
//! All icons are available as a constant, thanks to the build-executed script:
//! ```
//! # use yew::html;
//! # use yew_bootstrap::icons::BI;
//! let icon = BI::HEART;
//! # let result =
//! html!{
//!     <h1>{"I"} {icon} {BI::GEAR}</h1>
//! }
//! # ;
//! ```
//!
//! # Required CSS
//!
//! These icons require the inclusion of a CSS file from Bootstrap (`bootstrap-icons.css`). This file can be added by:
//!
//! 1. Using [`BIFiles::cdn()`](crate::icons::BIFiles::cdn) (easiest, see below)
//! 2. Using [`BIFiles::copy()`](crate::icons::BIFiles::copy) - in build-executed code (recommended, see below)
//! 3. Copying it yourself from the Bootstrap website
//! 4. Accessing the data via [`BIFiles::FILES`](crate::icons::BIFiles::FILES) and delivering it yourself
//!
//! # 1. Quickstart - Using CDN
//!
//! Call `BIFiles::cdn()` inside the `html!{}` returned by your application.
//!
//! # 2. Recommended - Automatically Copying Files
//!
//! This is copies the files to the `dist` directory, which is recommended.
//!
//! It is shown in `/examples/icons`.
//!
//! A copy of `bootstrap-icons` is included and should change only rarely. `trunk` does not add a hash to generated files,
//! and thus a change in those files won't be detected by `trunk`.
//!
//! 1. `Cargo.toml`
//!
//!    Add the build-executed binary.
//!
//!    ```toml
//!    [[bin]]
//!    name = "copy-bootstrap-icons"
//!    ```
//!
//! 2. `src/bin/copy-bootstrap-icons.rs`
//!
//!    Create the program to copy the files.
//!
//!    ```no_run
//!    use std::path::PathBuf;
//!    use yew_bootstrap::icons::BIFiles;
//!
//!    fn main() -> Result<(), std::io::Error> {
//!        let path = PathBuf::from(
//!            std::env::var("TRUNK_STAGING_DIR").expect("Environment variable TRUNK_STAGING_DIR"),
//!        )
//!        .join(BIFiles::NAME);
//!        if !path.is_dir() {
//!            std::fs::create_dir(&path)?;
//!        }
//!        BIFiles::copy(&path)
//!    }
//!    ```
//!
//! 3. `index.html`
//!
//!    Set base reference, link the required CSS and specify your WASM program[^1].
//!
//!    [^1]: Since we'll be writing a build-executed program, there are now two binaries and trunk needs to know which is your WASM binary.
//!
//!    ```html
//!    <base data-trunk-public-url />
//!    <link rel="stylesheet" href="bootstrap-icons-v1.10.5/bootstrap-icons.css" />
//!    <link data-trunk rel="rust" data-bin="name-of-app" />
//!    ```
//!
//! 4. `Trunk.toml`
//!
//!    Add a hook to run the build-executed program.
//!
//!    ```toml
//!    [[hooks]]
//!    stage = "build"
//!    command = "cargo"
//!    command_arguments = ["run", "--bin", "copy-bootstrap-icons"]
//!    ```
//!
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![warn(clippy::pedantic)]

use core::hash::{Hash, Hasher};
use yew::html::{ChildrenRenderer, IntoPropValue};
use yew::virtual_dom::{VNode, VRaw};
use yew::{AttrValue, Html, ToHtml};

/// Represents an bootstrap-icon.
///
/// This is a transparent wrapper of a `&'static str`, so `Copy` is cheap.
///
/// Use [`BI::html`] or the `From` or `IntoPropValue` implementation to generate the actual html.
///
/// Search for an icon at <https://icons.getbootstrap.com/#search>.
// Invariant: All possible strings are different and thus `(ptr,len)` must me different as well.
// Invariant: No two strings at different pointers are equal,
// Invariant: this is guaranteed due to the fact that it's not possible to create new.
#[derive(Clone, Copy, Ord, PartialOrd, Eq)]
#[repr(transparent)]
pub struct BI(pub(crate) &'static str);

impl BI {
    /// Returns the `Html` of this icon.
    #[inline]
    pub const fn html(self) -> Html {
        VNode::VRaw(VRaw {
            html: AttrValue::Static(self.0),
        })
    }

    /// Returns the raw html as a str of this icon.
    #[inline]
    #[must_use]
    pub const fn raw_html(self) -> &'static str {
        self.0
    }
}

impl PartialEq for BI {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        // Invariant: All possible strings are different and thus `(ptr,len)` must me different as well.
        // Invariant: No two strings at different pointers are equal,
        // Invariant: this is guaranteed due to the fact that it's not possible to create new.
        // Performance hack: Only check those.
        self.0.as_ptr() as usize == other.0.as_ptr() as usize && self.0.len() == other.0.len()
    }
}

impl Hash for BI {
    #[inline]
    fn hash<H: Hasher>(&self, state: &mut H) {
        // Invariant: All possible strings are different and thus `(ptr,len)` must me different as well.
        // Invariant: No two strings at different pointers are equal,
        // Invariant: this is guaranteed due to the fact that it's not possible to create new.
        // Performance hack: Only hash the ptr to the middle of the string.
        (self.0.as_ptr() as usize + (self.0.len() >> 1)).hash(state);
    }
}

impl From<BI> for Html {
    #[inline]
    fn from(value: BI) -> Self {
        value.html()
    }
}

impl From<&BI> for Html {
    #[allow(clippy::inline_always)]
    #[inline(always)]
    fn from(value: &BI) -> Self {
        Html::from(*value)
    }
}

impl IntoPropValue<ChildrenRenderer<VNode>> for BI {
    #[inline]
    fn into_prop_value(self) -> ChildrenRenderer<VNode> {
        self.html().into_prop_value()
    }
}

impl IntoPropValue<ChildrenRenderer<VNode>> for &BI {
    #[allow(clippy::inline_always)]
    #[inline(always)]
    fn into_prop_value(self) -> ChildrenRenderer<VNode> {
        (*self).into_prop_value()
    }
}

impl ToHtml for BI {
    #[allow(clippy::inline_always)]
    #[inline(always)]
    fn to_html(&self) -> Html {
        self.html()
    }
}

/// Holds all bootstrap-icons data.
///
/// Intended use:
/// ```
/// # use yew_bootstrap::icons::BIFiles;
/// let BIFiles {css, font_woff, font_woff2, license} = BIFiles::FILES;
/// ```
/// (That way it will be an error if a file is added/removed.)
pub struct BIFiles {
    /// Contents of the file `bootstrap-icons.css`.
    pub css: &'static str,
    /// Contents of the file `fonts/bootstrap-icons.woff`.
    pub font_woff: &'static [u8],
    /// Contents of the file `fonts/bootstrap-icons.woff2`.
    pub font_woff2: &'static [u8],
    /// Contents of the file `fonts/LICENSE`.
    pub license: &'static str,
}

impl BIFiles {
    /// Version of the package.
    pub const VERSION: &'static str = "v1.10.5";

    /// Name of the package.
    pub const NAME: &'static str = "bootstrap-icons-v1.10.5";

    /// All bootstrap-icons files.
    ///
    /// Intended use:
    /// ```
    /// # use yew_bootstrap::icons::BIFiles;
    /// let BIFiles {css, font_woff, font_woff2, license} = BIFiles::FILES;
    /// ```
    /// (That way it will be an error if a file is added/removed.)
    pub const FILES: Self = Self {
        css: include_str!("../../bootstrap-icons-v1.10.5/bootstrap-icons.css"),
        font_woff: include_bytes!("../../bootstrap-icons-v1.10.5/fonts/bootstrap-icons.woff"),
        font_woff2: include_bytes!("../../bootstrap-icons-v1.10.5/fonts/bootstrap-icons.woff2"),
        license: include_str!("../../bootstrap-icons-v1.10.5/fonts/LICENSE"),
    };

    /// Load the bootstrap-icons files from the official cdn.
    ///
    /// Call `BIFiles::cdn()` inside the `html!{}` returned by your application.
    pub const fn cdn() -> VNode {
        VNode::VRaw(VRaw {
            html: AttrValue::Static(
                r#"<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">"#,
            ),
        })
    }

    /// Copy all bootstrap icons files to the specified directory.
    ///
    /// # Errors
    ///
    /// Will return an error when there is a problem with creating the directories or writing the files.
    pub fn copy(to: &std::path::Path) -> Result<(), std::io::Error> {
        let BIFiles {
            css,
            font_woff,
            font_woff2,
            license,
        } = Self::FILES;

        let fonts = to.join("fonts");
        if !fonts.is_dir() {
            std::fs::create_dir(&fonts)?;
        }
        std::fs::write(to.join("bootstrap-icons.css"), css)?;
        std::fs::write(fonts.join("bootstrap-icons.woff"), font_woff)?;
        std::fs::write(fonts.join("bootstrap-icons.woff2"), font_woff2)?;
        std::fs::write(fonts.join("LICENSE"), license)?;

        Ok(())
    }
}

include!(concat!(env!("OUT_DIR"), "/bootstrap_icons_generated.rs"));