Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace the build script with a codegent crate #417

Merged
merged 1 commit into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <hello@erikhedvall.nu>"]
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
Loading