effects_preview/
preview.rs1use std::{io::Write as _, path::PathBuf};
20
21use solium_effects::{Axis, Deform};
22
23fn main() {
24 let Some(wasm) = std::env::args_os().nth(1).map(PathBuf::from) else {
25 eprintln!("usage: effects-preview <effects.wasm>");
26 eprintln!(" run dev/preview instead, which builds it first");
27 std::process::exit(2);
28 };
29
30 let bytes = match std::fs::read(&wasm) {
31 Ok(bytes) => bytes,
32 Err(err) => {
33 eprintln!("error: could not read {}: {err}", wasm.display());
34 std::process::exit(1);
35 }
36 };
37
38 let names = |list: Vec<&str>| {
43 list.into_iter()
44 .map(|name| format!("{name:?}"))
45 .collect::<Vec<_>>()
46 .join(", ")
47 };
48 let effects = names(Deform::all().iter().map(|(name, _)| *name).collect());
49 let axes = names(Axis::all().iter().map(|(name, _)| *name).collect());
50
51 const PAGE: &str = include_str!("../../preview/index.html");
52 let page = PAGE
53 .replace("/*EFFECTS*/ null", &format!("[{effects}]"))
54 .replace("/*AXES*/ null", &format!("[{axes}]"))
55 .replace("/*WASM*/ \"\"", &format!("{:?}", base64(&bytes)));
56
57 let mut stdout = std::io::stdout().lock();
58 if let Err(err) = stdout.write_all(page.as_bytes()) {
59 eprintln!("error: could not write the page: {err}");
60 std::process::exit(1);
61 }
62}
63
64fn base64(bytes: &[u8]) -> String {
72 const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
73
74 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
75 for chunk in bytes.chunks(3) {
76 let mut block = [0_u8; 3];
77 block[..chunk.len()].copy_from_slice(chunk);
78 let packed = (u32::from(block[0]) << 16) | (u32::from(block[1]) << 8) | u32::from(block[2]);
79
80 for slot in 0..4 {
81 if slot <= chunk.len() {
82 let index = ((packed >> (18 - slot * 6)) & 0x3F) as usize;
83 out.push(char::from(ALPHABET[index]));
84 } else {
85 out.push('=');
86 }
87 }
88 }
89 out
90}
91
92#[cfg(test)]
93mod tests {
94 use super::base64;
95
96 #[test]
97 fn base64_matches_the_standard_including_padding() {
98 assert_eq!(base64(b""), "");
101 assert_eq!(base64(b"f"), "Zg==");
102 assert_eq!(base64(b"fo"), "Zm8=");
103 assert_eq!(base64(b"foo"), "Zm9v");
104 assert_eq!(base64(b"foob"), "Zm9vYg==");
105 assert_eq!(base64(b"fooba"), "Zm9vYmE=");
106 assert_eq!(base64(b"foobar"), "Zm9vYmFy");
107 }
108
109 #[test]
110 fn high_bytes_survive() {
111 assert_eq!(base64(&[0x00, 0x10, 0x83, 0xFF]), "ABCD/w==");
112 }
113
114 #[test]
119 fn the_page_has_somewhere_to_put_everything() {
120 const PAGE: &str = include_str!("../../preview/index.html");
121 for marker in ["/*EFFECTS*/ null", "/*AXES*/ null", "/*WASM*/ \"\""] {
122 assert!(PAGE.contains(marker), "the page has no {marker} to fill in");
123 }
124 }
125}