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

Generate attributes from config #7

Merged
merged 1 commit into from
Jul 6, 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
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