Skip to content

Commit

Permalink
Merge pull request #417 from Ogeon/codegen
Browse files Browse the repository at this point in the history
Replace the build script with a codegent crate
  • Loading branch information
Ogeon authored Aug 24, 2024
2 parents dcc802a + a423de9 commit e37e2fe
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 91 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ members = [
# Test crates
"integration_tests",
"no_std_test",
"benchmarks"
"benchmarks",

# Tool crates
"codegen"
]
resolver = "2"
17 changes: 17 additions & 0 deletions codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "codegen"
version = "0.0.0"
authors = ["Erik Hedvall <[email protected]>"]
exclude = []
description = "Code generation crate for palette."
repository = "https://github.com/Ogeon/palette"
license = "MIT OR Apache-2.0"
edition = "2018"
publish = false

[dependencies]
anyhow = "1.0.86"
proc-macro2 = "1.0.86"
quote = "1.0.37"


File renamed without changes.
72 changes: 72 additions & 0 deletions codegen/src/codegen_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::{
fs::File,
io::Write,
path::Path,
process::{Command, Output, Stdio},
};

use anyhow::{Context, Result};
use proc_macro2::TokenStream;

const HEADER_COMMENT: &str = r#"// This file is auto-generated and any manual changes to it will be overwritten.
//
// Run `cargo run -p codegen` from the project root to regenerate it.
"#;

pub struct CodegenFile {
file: File,
}

impl CodegenFile {
/// Create a new generated file.
pub fn create<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let mut file =
File::create(path).with_context(|| format!("could not open or create {path:?}"))?;

writeln!(file, "{HEADER_COMMENT}")?;

Ok(Self { file })
}

/// Formats and appends the tokens to the output file.
pub fn append(&mut self, tokens: TokenStream) -> Result<()> {
// Taken from https://github.com/Michael-F-Bryan/scad-rs/blob/4dbff0c30ce991105f1e649e678d68c2767e894b/crates/codegen/src/pretty_print.rs

let mut child = Command::new("rustfmt")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.context("unable to start `rustfmt`. Is it installed?")?;

let mut stdin = child.stdin.take().unwrap();
write!(stdin, "{tokens}")?;
stdin.flush()?;
drop(stdin);

let Output {
status,
stdout,
stderr,
} = child.wait_with_output()?;
let stdout = String::from_utf8_lossy(&stdout);
let stderr = String::from_utf8_lossy(&stderr);

if !status.success() {
eprintln!("---- Stdout ----");
eprintln!("{stdout}");
eprintln!("---- Stderr ----");
eprintln!("{stderr}");
let code = status.code();
match code {
Some(code) => anyhow::bail!("the `rustfmt` command failed with return code {code}"),
None => anyhow::bail!("the `rustfmt` command failed"),
}
}

writeln!(self.file, "{stdout}")?;

Ok(())
}
}
10 changes: 10 additions & 0 deletions codegen/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use anyhow::{Context, Result};

mod codegen_file;
mod named;

fn main() -> Result<()> {
named::generate().context("could not generate named color constants")?;

Ok(())
}
114 changes: 114 additions & 0 deletions codegen/src/named.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::{
fs::File,
io::{BufRead, BufReader},
};

use anyhow::{Context, Result};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;

use crate::codegen_file::CodegenFile;

pub fn generate() -> Result<()> {
let mut file = CodegenFile::create("palette/src/named/codegen.rs")?;

let colors = parse_colors()?;

file.append(build_colors(&colors))?;
file.append(build_from_str(&colors))?;

Ok(())
}

struct ColorEntry {
name: String,
constant: Ident,
red: u8,
green: u8,
blue: u8,
}

fn parse_colors() -> Result<Vec<ColorEntry>> {
let reader = BufReader::new(
File::open("codegen/res/svg_colors.txt").expect("could not open svg_colors.txt"),
);

// Expected format: "name\t123, 123, 123"
reader
.lines()
.map(|line| {
let line = line?;
let mut parts = line.split('\t');

let name = parts
.next()
.context("couldn't get the color name")?
.to_owned();
let mut rgb = parts
.next()
.with_context(|| format!("couldn't get RGB for {}", name))?
.split(", ");

let red: u8 = rgb
.next()
.with_context(|| format!("missing red for {name}"))?
.trim()
.parse()
.with_context(|| format!("couldn't parse red for {}", name))?;

let green: u8 = rgb
.next()
.with_context(|| format!("missing green for {name}"))?
.trim()
.parse()
.with_context(|| format!("couldn't parse green for {}", name))?;

let blue: u8 = rgb
.next()
.with_context(|| format!("missing blue for {name}"))?
.trim()
.parse()
.with_context(|| format!("couldn't parse blue for {}", name))?;

let constant = Ident::new(&name.to_ascii_uppercase(), Span::call_site());

Ok(ColorEntry {
name,
constant,
red,
green,
blue,
})
})
.collect()
}

fn build_colors(colors: &[ColorEntry]) -> TokenStream {
let constants = colors.iter().map(|ColorEntry { name, constant, red, green, blue }| {
let swatch_html = format!(
r#"<div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: {name};"></div>"#
);

quote! {
#[doc = #swatch_html]
pub const #constant: crate::rgb::Srgb<u8> = crate::rgb::Srgb::new(#red, #green, #blue);
}
});

quote! {
#(#constants)*
}
}

fn build_from_str(entries: &[ColorEntry]) -> TokenStream {
let entries = entries
.iter()
.map(|ColorEntry { name, constant, .. }| quote! {#name => #constant});

quote! {
#[cfg(feature = "named_from_str")]
pub(crate) static COLORS: ::phf::Map<&'static str, crate::rgb::Srgb<u8>> = phf::phf_map! {
#(#entries),*
};
}
}
1 change: 0 additions & 1 deletion palette/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ license = "MIT OR Apache-2.0"
edition = "2018"
resolver = "2"
categories = ["graphics", "multimedia::images", "no-std"]
build = "build/main.rs"
rust-version = "1.61.0"

[features]
Expand Down
5 changes: 0 additions & 5 deletions palette/build/main.rs

This file was deleted.

81 changes: 0 additions & 81 deletions palette/build/named.rs

This file was deleted.

14 changes: 11 additions & 3 deletions palette/src/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,21 @@
//! let from_const = Srgb::<f32>::from_format(named::OLIVE).into_linear();
#![cfg_attr(feature = "named_from_str", doc = "")]
#![cfg_attr(feature = "named_from_str", doc = "//From name string")]
#![cfg_attr(feature = "named_from_str", doc = "let olive = named::from_str(\"olive\").expect(\"unknown color\");")]
#![cfg_attr(feature = "named_from_str", doc = "let from_str = Srgb::<f32>::from_format(olive).into_linear();")]
#![cfg_attr(
feature = "named_from_str",
doc = "let olive = named::from_str(\"olive\").expect(\"unknown color\");"
)]
#![cfg_attr(
feature = "named_from_str",
doc = "let from_str = Srgb::<f32>::from_format(olive).into_linear();"
)]
#![cfg_attr(feature = "named_from_str", doc = "")]
#![cfg_attr(feature = "named_from_str", doc = "assert_eq!(from_const, from_str);")]
//! ```

include!(concat!(env!("OUT_DIR"), "/named.rs"));
pub use codegen::*;

mod codegen;

/// Get a SVG/CSS3 color by name. Can be toggled with the `"named_from_str"`
/// Cargo feature.
Expand Down
Loading

0 comments on commit e37e2fe

Please sign in to comment.