Skip to content

Commit 7889c7a

Browse files
committed
use quote to generate font-awesome code
1 parent 41d5e31 commit 7889c7a

File tree

3 files changed

+89
-49
lines changed

3 files changed

+89
-49
lines changed

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/font-awesome-as-a-crate/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@ license = "CC-BY-4.0 AND MIT"
66
description = "Font Awesome Free, packaged as a crate"
77
repository = "https://github.com/rust-lang/docs.rs"
88

9+
[build-dependencies]
10+
quote = "1.0.37"
11+
proc-macro2 = "1.0.86"
12+
syn = "2.0.77"
13+
prettyplease = "0.2.22"
Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
use std::{
2-
collections::HashMap,
3-
env,
4-
fmt::Write as FmtWrite,
5-
fs::{read_dir, File},
6-
io::{Read, Write},
7-
path::Path,
8-
};
1+
use proc_macro2::Literal;
2+
use quote::{format_ident, quote};
3+
use std::{collections::HashMap, env, fs, path::Path};
94

105
fn main() {
116
println!("cargo::rustc-check-cfg=cfg(font_awesome_out_dir)");
@@ -22,70 +17,94 @@ fn capitalize_first_letter(s: &str) -> String {
2217
}
2318

2419
fn write_fontawesome_sprite() {
20+
let mut match_arms = vec![];
2521
let mut types = HashMap::new();
26-
let dest_path = Path::new(&env::var("OUT_DIR").unwrap()).join("fontawesome.rs");
27-
let mut dest_file = File::create(dest_path).unwrap();
28-
dest_file
29-
.write_all(b"const fn fontawesome_svg(dir:&str,file:&str)->&'static str{match(dir.as_bytes(),file.as_bytes()){")
30-
.expect("fontawesome fn write");
22+
3123
for (dirname, trait_name) in &[
3224
("brands", "Brands"),
3325
("regular", "Regular"),
3426
("solid", "Solid"),
3527
] {
36-
let dir = read_dir(Path::new("fontawesome-free-6.2.0-desktop/svgs").join(dirname)).unwrap();
37-
let mut data = String::new();
28+
let dir =
29+
fs::read_dir(Path::new("fontawesome-free-6.2.0-desktop/svgs").join(dirname)).unwrap();
30+
3831
for file in dir {
3932
let file = file.expect("fontawesome directory access");
33+
let data = fs::read_to_string(&file.path()).expect("fontawesome file read");
34+
4035
let filename = file
4136
.file_name()
4237
.into_string()
43-
.expect("fontawesome filenames are unicode");
44-
let mut file = File::open(file.path()).expect("fontawesome file access");
45-
data.clear();
46-
file.read_to_string(&mut data)
47-
.expect("fontawesome file read");
48-
// if this assert goes off, add more hashes here and in the format! below
49-
assert!(!data.contains("###"), "file {filename} breaks raw string");
50-
let filename = filename.replace(".svg", "");
51-
dest_file
52-
.write_all(
53-
format!(r####"(b"{dirname}",b"{filename}")=>r#"{data}"#,"####).as_bytes(),
54-
)
55-
.expect("write fontawesome file");
38+
.expect("fontawesome filenames are unicode")
39+
.replace(".svg", "");
40+
41+
let dirname_literal = Literal::byte_string(dirname.as_bytes());
42+
let filename_literal = Literal::byte_string(filename.as_bytes());
43+
match_arms.push(quote! {
44+
(#dirname_literal, #filename_literal) => #data,
45+
});
46+
5647
types
5748
.entry(filename)
5849
.or_insert_with(|| (data.clone(), Vec::with_capacity(3)))
5950
.1
6051
.push(trait_name);
6152
}
6253
}
63-
dest_file
64-
.write_all(b"_=>\"\"}} pub mod icons { use super::{IconStr, Regular, Brands, Solid};")
65-
.expect("fontawesome fn write");
54+
55+
let mut types_output = vec![];
6656

6757
for (icon, (data, kinds)) in types {
6858
let mut type_name = "Icon".to_string();
6959
type_name.extend(icon.split('-').map(capitalize_first_letter));
70-
let kinds = kinds.iter().fold(String::new(), |mut acc, k| {
71-
let _ = writeln!(acc, "impl {k} for {type_name} {{}}");
72-
acc
60+
let type_name = format_ident!("{}", type_name);
61+
62+
let kind_impls: Vec<_> = kinds
63+
.iter()
64+
.map(|k| {
65+
let k = format_ident!("{}", k);
66+
quote! {
67+
impl #k for #type_name {}
68+
}
69+
})
70+
.collect();
71+
72+
types_output.push(quote! {
73+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74+
pub struct #type_name;
75+
76+
impl IconStr for #type_name {
77+
fn icon_name(&self) -> &'static str { #icon }
78+
fn icon_str(&self) -> &'static str { #data }
79+
}
80+
81+
#(#kind_impls)*
7382
});
74-
dest_file
75-
.write_all(
76-
format!(
77-
"\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78-
pub struct {type_name};
79-
impl IconStr for {type_name} {{
80-
fn icon_name(&self) -> &'static str {{ r#\"{icon}\"# }}
81-
fn icon_str(&self) -> &'static str {{ r#\"{data}\"# }}
82-
}}
83-
{kinds}"
84-
)
85-
.as_bytes(),
86-
)
87-
.expect("write fontawesome file types");
8883
}
8984

90-
dest_file.write_all(b"}").expect("fontawesome fn write");
85+
let token_stream = quote! {
86+
const fn fontawesome_svg(dir: &str, file: &str) -> &'static str {
87+
// we are using byte literals to match because they can be evaluated in a
88+
// `const` context, and `str` cannot.
89+
match(dir.as_bytes(), file.as_bytes()) {
90+
#(#match_arms)*
91+
_=> ""
92+
}
93+
}
94+
95+
pub mod icons {
96+
use super::{IconStr, Regular, Brands, Solid};
97+
98+
#(#types_output)*
99+
}
100+
};
101+
102+
let dest_path = Path::new(&env::var("OUT_DIR").unwrap()).join("fontawesome.rs");
103+
104+
let output = format!(
105+
"{}",
106+
prettyplease::unparse(&syn::parse2(token_stream).unwrap())
107+
);
108+
109+
fs::write(&dest_path, output.as_bytes()).expect("fontawesome output write");
91110
}

0 commit comments

Comments
 (0)