Skip to content

Commit

Permalink
Generate attributes from config
Browse files Browse the repository at this point in the history
  • Loading branch information
kmicklas committed Jul 6, 2024
1 parent 0dc4a8e commit b88e776
Show file tree
Hide file tree
Showing 7 changed files with 546 additions and 343 deletions.
2 changes: 1 addition & 1 deletion examples/todomvc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ fn item(filter: Filter, id: usize, item: &Item) -> View!(Model, '_) {
)),
)),
form((
input((class("edit"), value_(&item.text))),
input((class("edit"), value_(CloneString(&item.text)))),
on(Active(Submit), move |model: &mut Model, e| {
e.prevent_default();

Expand Down
128 changes: 122 additions & 6 deletions ravel-web/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,51 @@ use serde::Deserialize;
#[derive(Deserialize)]
struct Config {
element: std::collections::HashMap<String, Element>,
attribute: std::collections::HashMap<String, Attribute>,
}

#[derive(Deserialize)]
struct Element {
// TODO: JS element type
}

#[derive(Deserialize)]
struct Attribute {
build: Option<String>,
value_type: Option<String>,
value_trait: Option<String>,
value_wrapper: Option<String>,
}

impl Attribute {
fn build_name(&self, name: &str) -> String {
match &self.build {
Some(build) => build.clone(),
None => name.replace('-', "_"),
}
}

fn value_trait(&self) -> &str {
assert!(self.value_type.is_none());
self.value_trait.as_deref().unwrap_or("AttrValue")
}
}

fn main() {
let config = std::fs::read_to_string("generate.toml").unwrap();
let config: Config = toml::from_str(&config).unwrap();

let out_dir = std::env::var_os("OUT_DIR").unwrap();
let out_dir = std::path::PathBuf::from(out_dir);

gen_el(&config, &out_dir);
gen_el_types(&config, &out_dir);

gen_attr(&config, &out_dir);
gen_attr_types(&config, &out_dir);
}

fn gen_el_types(config: &Config, out_dir: &std::path::Path) {
let mut src = String::new();

src.push_str("#[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#\"\n");
Expand All @@ -37,16 +68,18 @@ fn main() {
src.push_str("}\n");

for name in config.element.keys() {
let t = title_case(name);
let t = type_name(name);
writeln!(&mut src, "make_el!({name}, {t}, create_{name}());").unwrap();
}

std::fs::write(out_dir.join("gen_el_types.rs"), src).unwrap();
}

fn gen_el(config: &Config, out_dir: &std::path::Path) {
let mut src = String::new();

for name in config.element.keys() {
let t = title_case(name);
let t = type_name(name);
// Ideally this would be generated by a macro, but rust-analyzer can't
// seem to handle doc attributes generated by a macro generated by a
// build script.
Expand All @@ -63,10 +96,93 @@ fn main() {
println!("cargo::rerun-if-changed=generate.toml");
}

fn title_case(s: &str) -> String {
fn gen_attr_types(config: &Config, out_dir: &std::path::Path) {
let mut src = String::new();

// TODO: Per-attr JS snippets like for elements.

for (name, attr) in &config.attribute {
let t = type_name(name);

if let Some(value_type) = &attr.value_type {
assert!(attr.value_trait.is_none());

match &attr.value_wrapper {
Some(value_wrapper) => writeln!(
&mut src,
"make_attr_value_type!(\"{name}\", {t}, {value_type}, {value_wrapper});",
),
None => writeln!(
&mut src,
"make_attr_value_type!(\"{name}\", {t}, {value_type});",
),
}
.unwrap();
} else {
let value_trait = attr.value_trait();

match &attr.value_wrapper {
Some(value_wrapper) => writeln!(
&mut src,
"make_attr_value_trait!(\"{name}\", {t}, {value_trait}, {value_wrapper});",
),
None => writeln!(
&mut src,
"make_attr_value_trait!(\"{name}\", {t}, {value_trait});",
),
}
.unwrap();
}
}

std::fs::write(out_dir.join("gen_attr_types.rs"), src).unwrap();
}

fn gen_attr(config: &Config, out_dir: &std::path::Path) {
let mut src = String::new();

for (name, attr) in &config.attribute {
let build = attr.build_name(name);
let t = type_name(name);
// Ideally this would be generated by a macro, but rust-analyzer can't
// seem to handle doc attributes generated by a macro generated by a
// build script.
writeln!(&mut src, "/// `{name}` attribute.").unwrap();
write!(&mut src, "pub fn {build}").unwrap();

if let Some(value_type) = &attr.value_type {
assert!(attr.value_trait.is_none());

write!(&mut src, "(value: {value_type}) -> types::{t}").unwrap();
} else {
let value_trait = attr.value_trait();

write!(
&mut src,
"<Value: {value_trait}>(value: Value) -> types::{t}<Value>"
)
.unwrap();
}

writeln!(&mut src, " {{ types::{t}(value) }}").unwrap();
}

std::fs::write(out_dir.join("gen_attr.rs"), src).unwrap();

println!("cargo::rerun-if-changed=generate.toml");
}

fn type_name(s: &str) -> String {
let mut cs = s.chars();
match cs.next() {
None => String::new(),
Some(c) => c.to_uppercase().collect::<String>() + cs.as_str(),
let mut s = String::with_capacity(s.len());

s.push(cs.next().unwrap().to_ascii_uppercase());
while let Some(c) = cs.next() {
s.push(match c {
'-' => cs.next().unwrap().to_ascii_uppercase(),
c => c,
});
}

s
}
15 changes: 15 additions & 0 deletions ravel-web/generate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,18 @@ summary = {}
# Web Components
slot = {}
template = {}

[attribute]
aria-hidden = {}
autofocus = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
checked = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
class = { value_trait = "ClassValue", value_wrapper = "Classes" }
for = { build = "for_" }
href = {}
id = {}
max = {}
min = {}
placeholder = {}
style = {}
type = { build = "type_" }
value = { build = "value_" } # TODO: do we really need this rename?
Loading

0 comments on commit b88e776

Please sign in to comment.