From a33f0ee3419ec05f915ab359f3bebf3552e1f845 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 27 Sep 2024 17:38:42 +0200 Subject: [PATCH] use quote to generate font-awesome code --- Cargo.lock | 16 +++ crates/font-awesome-as-a-crate/Cargo.toml | 5 + crates/font-awesome-as-a-crate/build.rs | 114 ++++++++++++---------- 3 files changed, 86 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76fa0da28..2b64992fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1981,6 +1981,12 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "font-awesome-as-a-crate" version = "0.3.0" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.77", +] [[package]] name = "foreign-types" @@ -4896,6 +4902,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.77", +] + [[package]] name = "proc-macro-error" version = "1.0.4" diff --git a/crates/font-awesome-as-a-crate/Cargo.toml b/crates/font-awesome-as-a-crate/Cargo.toml index 33494158c..d82e70d46 100644 --- a/crates/font-awesome-as-a-crate/Cargo.toml +++ b/crates/font-awesome-as-a-crate/Cargo.toml @@ -6,3 +6,8 @@ license = "CC-BY-4.0 AND MIT" description = "Font Awesome Free, packaged as a crate" repository = "https://github.com/rust-lang/docs.rs" +[build-dependencies] +quote = "1.0.37" +proc-macro2 = "1.0.86" +syn = "2.0.77" +prettyplease = "0.2.22" diff --git a/crates/font-awesome-as-a-crate/build.rs b/crates/font-awesome-as-a-crate/build.rs index 0a811a09e..3c56e4a10 100644 --- a/crates/font-awesome-as-a-crate/build.rs +++ b/crates/font-awesome-as-a-crate/build.rs @@ -1,11 +1,6 @@ -use std::{ - collections::HashMap, - env, - fmt::Write as FmtWrite, - fs::{read_dir, File}, - io::{Read, Write}, - path::Path, -}; +use proc_macro2::Literal; +use quote::{format_ident, quote}; +use std::{collections::HashMap, env, fs, path::Path}; fn main() { println!("cargo::rustc-check-cfg=cfg(font_awesome_out_dir)"); @@ -22,37 +17,33 @@ fn capitalize_first_letter(s: &str) -> String { } fn write_fontawesome_sprite() { + let mut match_arms = vec![]; let mut types = HashMap::new(); - let dest_path = Path::new(&env::var("OUT_DIR").unwrap()).join("fontawesome.rs"); - let mut dest_file = File::create(dest_path).unwrap(); - dest_file - .write_all(b"const fn fontawesome_svg(dir:&str,file:&str)->&'static str{match(dir.as_bytes(),file.as_bytes()){") - .expect("fontawesome fn write"); + for (dirname, trait_name) in &[ ("brands", "Brands"), ("regular", "Regular"), ("solid", "Solid"), ] { - let dir = read_dir(Path::new("fontawesome-free-6.2.0-desktop/svgs").join(dirname)).unwrap(); - let mut data = String::new(); + let dir = + fs::read_dir(Path::new("fontawesome-free-6.2.0-desktop/svgs").join(dirname)).unwrap(); + for file in dir { let file = file.expect("fontawesome directory access"); + let data = fs::read_to_string(file.path()).expect("fontawesome file read"); + let filename = file .file_name() .into_string() - .expect("fontawesome filenames are unicode"); - let mut file = File::open(file.path()).expect("fontawesome file access"); - data.clear(); - file.read_to_string(&mut data) - .expect("fontawesome file read"); - // if this assert goes off, add more hashes here and in the format! below - assert!(!data.contains("###"), "file {filename} breaks raw string"); - let filename = filename.replace(".svg", ""); - dest_file - .write_all( - format!(r####"(b"{dirname}",b"{filename}")=>r#"{data}"#,"####).as_bytes(), - ) - .expect("write fontawesome file"); + .expect("fontawesome filenames are unicode") + .replace(".svg", ""); + + let dirname_literal = Literal::byte_string(dirname.as_bytes()); + let filename_literal = Literal::byte_string(filename.as_bytes()); + match_arms.push(quote! { + (#dirname_literal, #filename_literal) => #data, + }); + types .entry(filename) .or_insert_with(|| (data.clone(), Vec::with_capacity(3))) @@ -60,32 +51,57 @@ fn write_fontawesome_sprite() { .push(trait_name); } } - dest_file - .write_all(b"_=>\"\"}} pub mod icons { use super::{IconStr, Regular, Brands, Solid};") - .expect("fontawesome fn write"); + + let mut types_output = vec![]; for (icon, (data, kinds)) in types { let mut type_name = "Icon".to_string(); type_name.extend(icon.split('-').map(capitalize_first_letter)); - let kinds = kinds.iter().fold(String::new(), |mut acc, k| { - let _ = writeln!(acc, "impl {k} for {type_name} {{}}"); - acc + let type_name = format_ident!("{}", type_name); + + let kind_impls: Vec<_> = kinds + .iter() + .map(|k| { + let k = format_ident!("{}", k); + quote! { + impl #k for #type_name {} + } + }) + .collect(); + + types_output.push(quote! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct #type_name; + + impl IconStr for #type_name { + fn icon_name(&self) -> &'static str { #icon } + fn icon_str(&self) -> &'static str { #data } + } + + #(#kind_impls)* }); - dest_file - .write_all( - format!( - "\n#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct {type_name}; -impl IconStr for {type_name} {{ - fn icon_name(&self) -> &'static str {{ r#\"{icon}\"# }} - fn icon_str(&self) -> &'static str {{ r#\"{data}\"# }} -}} -{kinds}" - ) - .as_bytes(), - ) - .expect("write fontawesome file types"); } - dest_file.write_all(b"}").expect("fontawesome fn write"); + let token_stream = quote! { + const fn fontawesome_svg(dir: &str, file: &str) -> &'static str { + // we are using byte literals to match because they can be evaluated in a + // `const` context, and `str` cannot. + match(dir.as_bytes(), file.as_bytes()) { + #(#match_arms)* + _=> "" + } + } + + pub mod icons { + use super::{IconStr, Regular, Brands, Solid}; + + #(#types_output)* + } + }; + + let dest_path = Path::new(&env::var("OUT_DIR").unwrap()).join("fontawesome.rs"); + + let output = prettyplease::unparse(&syn::parse2(token_stream).unwrap()); + + fs::write(&dest_path, output.as_bytes()).expect("fontawesome output write"); }