|
@@ -1,15 +1,14 @@
|
|
|
|
+use anyhow::Result;
|
|
use html_parser::Dom;
|
|
use html_parser::Dom;
|
|
|
|
+use html_parser::Element;
|
|
use html_parser::Node;
|
|
use html_parser::Node;
|
|
-use std::fmt::Write;
|
|
|
|
|
|
+#[allow(unused_imports)]
|
|
|
|
+use std::fmt::{Display, Formatter};
|
|
use std::io::Read;
|
|
use std::io::Read;
|
|
use std::path::PathBuf;
|
|
use std::path::PathBuf;
|
|
use std::process::exit;
|
|
use std::process::exit;
|
|
use structopt::StructOpt;
|
|
use structopt::StructOpt;
|
|
|
|
|
|
-pub mod extract_svgs;
|
|
|
|
-pub mod to_component;
|
|
|
|
-pub mod translate;
|
|
|
|
-
|
|
|
|
/// Build the Rust WASM app and all of its assets.
|
|
/// Build the Rust WASM app and all of its assets.
|
|
#[derive(Clone, Debug, StructOpt)]
|
|
#[derive(Clone, Debug, StructOpt)]
|
|
#[structopt(name = "translate")]
|
|
#[structopt(name = "translate")]
|
|
@@ -21,11 +20,7 @@ pub struct Translate {
|
|
|
|
|
|
/// Input file
|
|
/// Input file
|
|
#[structopt(short, long)]
|
|
#[structopt(short, long)]
|
|
- pub source: Option<String>,
|
|
|
|
-
|
|
|
|
- /// Input file
|
|
|
|
- #[structopt(short, long, parse(from_os_str))]
|
|
|
|
- pub input: Option<PathBuf>,
|
|
|
|
|
|
+ pub file: Option<String>,
|
|
|
|
|
|
/// Output file, stdout if not present
|
|
/// Output file, stdout if not present
|
|
#[structopt(parse(from_os_str))]
|
|
#[structopt(parse(from_os_str))]
|
|
@@ -35,23 +30,17 @@ pub struct Translate {
|
|
impl Translate {
|
|
impl Translate {
|
|
pub fn translate(self) -> anyhow::Result<()> {
|
|
pub fn translate(self) -> anyhow::Result<()> {
|
|
let Translate {
|
|
let Translate {
|
|
- component: as_component,
|
|
|
|
- input,
|
|
|
|
|
|
+ component,
|
|
output,
|
|
output,
|
|
- source,
|
|
|
|
|
|
+ file,
|
|
} = self;
|
|
} = self;
|
|
|
|
|
|
- let contents;
|
|
|
|
- let temp = input.map(|f| {
|
|
|
|
- std::fs::read_to_string(&f).unwrap_or_else(|e| {
|
|
|
|
|
|
+ let contents = match file {
|
|
|
|
+ Some(input) => std::fs::read_to_string(&input).unwrap_or_else(|e| {
|
|
log::error!("Cloud not read input file: {}.", e);
|
|
log::error!("Cloud not read input file: {}.", e);
|
|
exit(0);
|
|
exit(0);
|
|
- })
|
|
|
|
- });
|
|
|
|
- if let Some(..) = temp {
|
|
|
|
- if let Some(s) = source {
|
|
|
|
- contents = s;
|
|
|
|
- } else {
|
|
|
|
|
|
+ }),
|
|
|
|
+ None => {
|
|
if atty::is(atty::Stream::Stdin) {
|
|
if atty::is(atty::Stream::Stdin) {
|
|
log::error!("No input file, source, or stdin to translate from.");
|
|
log::error!("No input file, source, or stdin to translate from.");
|
|
exit(0);
|
|
exit(0);
|
|
@@ -60,28 +49,21 @@ impl Translate {
|
|
let mut buffer = String::new();
|
|
let mut buffer = String::new();
|
|
std::io::stdin().read_to_string(&mut buffer).unwrap();
|
|
std::io::stdin().read_to_string(&mut buffer).unwrap();
|
|
|
|
|
|
- contents = buffer.trim().to_string();
|
|
|
|
|
|
+ buffer.trim().to_string()
|
|
}
|
|
}
|
|
- } else {
|
|
|
|
- contents = temp.unwrap();
|
|
|
|
- }
|
|
|
|
|
|
+ };
|
|
|
|
|
|
- // parse the input as html and prepare the output
|
|
|
|
- let dom = Dom::parse(&contents)?;
|
|
|
|
- let mut out_buf = String::new();
|
|
|
|
-
|
|
|
|
- if as_component {
|
|
|
|
- writeln!(out_buf, "use dioxus::prelude::*;")?;
|
|
|
|
- writeln!(out_buf, "fn component(cx: Scope) -> Element {{")?;
|
|
|
|
- writeln!(out_buf, " cx.render(rsx!(")?;
|
|
|
|
- }
|
|
|
|
- for child in &dom.children {
|
|
|
|
- render_child(&mut out_buf, child, if as_component { 2 } else { 0 })?;
|
|
|
|
- }
|
|
|
|
- if as_component {
|
|
|
|
- writeln!(out_buf, " ))")?;
|
|
|
|
- writeln!(out_buf, "}}")?;
|
|
|
|
- }
|
|
|
|
|
|
+ let out_buf = match component {
|
|
|
|
+ true => format!("{}", convert_html_to_component(&contents).unwrap()),
|
|
|
|
+ false => {
|
|
|
|
+ let mut buf = String::new();
|
|
|
|
+ let dom = Dom::parse(&contents).unwrap();
|
|
|
|
+ for child in &dom.children {
|
|
|
|
+ simple_render_child(&mut buf, child, 0)?;
|
|
|
|
+ }
|
|
|
|
+ buf
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
|
|
if let Some(output) = output {
|
|
if let Some(output) = output {
|
|
std::fs::write(&output, out_buf)?;
|
|
std::fs::write(&output, out_buf)?;
|
|
@@ -93,7 +75,8 @@ impl Translate {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-fn render_child(f: &mut impl Write, child: &Node, il: u32) -> std::fmt::Result {
|
|
|
|
|
|
+/// render without handling svgs or anything
|
|
|
|
+fn simple_render_child(f: &mut impl std::fmt::Write, child: &Node, il: u32) -> std::fmt::Result {
|
|
write_tabs(f, il)?;
|
|
write_tabs(f, il)?;
|
|
match child {
|
|
match child {
|
|
Node::Text(t) => writeln!(f, "\"{}\"", t)?,
|
|
Node::Text(t) => writeln!(f, "\"{}\"", t)?,
|
|
@@ -131,9 +114,8 @@ fn render_child(f: &mut impl Write, child: &Node, il: u32) -> std::fmt::Result {
|
|
None => writeln!(f, "{}: \"\",", new_name)?,
|
|
None => writeln!(f, "{}: \"\",", new_name)?,
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
- match name.as_str() {
|
|
|
|
- "for" | "async" | "type" | "as" => write!(f, "r#")?,
|
|
|
|
- _ => {}
|
|
|
|
|
|
+ if matches!(name.as_str(), "for" | "async" | "type" | "as") {
|
|
|
|
+ write!(f, "r#")?
|
|
}
|
|
}
|
|
|
|
|
|
match value {
|
|
match value {
|
|
@@ -145,7 +127,7 @@ fn render_child(f: &mut impl Write, child: &Node, il: u32) -> std::fmt::Result {
|
|
|
|
|
|
// now the children
|
|
// now the children
|
|
for child in &el.children {
|
|
for child in &el.children {
|
|
- render_child(f, child, il + 1)?;
|
|
|
|
|
|
+ simple_render_child(f, child, il + 1)?;
|
|
}
|
|
}
|
|
|
|
|
|
// close the tag
|
|
// close the tag
|
|
@@ -156,9 +138,171 @@ fn render_child(f: &mut impl Write, child: &Node, il: u32) -> std::fmt::Result {
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
-fn write_tabs(f: &mut impl Write, num: u32) -> std::fmt::Result {
|
|
|
|
|
|
+pub fn convert_html_to_component(html: &str) -> Result<ComponentRenderer> {
|
|
|
|
+ Ok(ComponentRenderer {
|
|
|
|
+ dom: Dom::parse(html)?,
|
|
|
|
+ icon_index: 0,
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#[allow(dead_code)]
|
|
|
|
+pub struct ComponentRenderer {
|
|
|
|
+ dom: Dom,
|
|
|
|
+ icon_index: usize,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl Display for ComponentRenderer {
|
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
+ writeln!(
|
|
|
|
+ f,
|
|
|
|
+ r##"
|
|
|
|
+fn component(cx: Scope) -> Element {{
|
|
|
|
+ cx.render(rsx!("##
|
|
|
|
+ )?;
|
|
|
|
+ let mut svg_nodes = vec![];
|
|
|
|
+
|
|
|
|
+ let mut svg_idx = 0;
|
|
|
|
+ for child in &self.dom.children {
|
|
|
|
+ render_child(f, child, 2, &mut svg_nodes, true, &mut svg_idx)?;
|
|
|
|
+ }
|
|
|
|
+ write!(
|
|
|
|
+ f,
|
|
|
|
+ r##" ))
|
|
|
|
+}}"##
|
|
|
|
+ )?;
|
|
|
|
+
|
|
|
|
+ if svg_idx == 0 {
|
|
|
|
+ return Ok(());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ writeln!(f, "\n\nmod icons {{")?;
|
|
|
|
+
|
|
|
|
+ let mut id = 0;
|
|
|
|
+ while let Some(svg) = svg_nodes.pop() {
|
|
|
|
+ writeln!(
|
|
|
|
+ f,
|
|
|
|
+ r##" pub(super) fn icon_{}(cx: Scope) -> Element {{
|
|
|
|
+ cx.render(rsx!("##,
|
|
|
|
+ id
|
|
|
|
+ )?;
|
|
|
|
+ write_tabs(f, 3)?;
|
|
|
|
+
|
|
|
|
+ render_element(f, svg, 3, &mut svg_nodes, false, &mut 0)?;
|
|
|
|
+ writeln!(f, "\t\t))\n\t}}\n")?;
|
|
|
|
+ id += 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ writeln!(f, "}}")?;
|
|
|
|
+
|
|
|
|
+ Ok(())
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+fn render_child<'a>(
|
|
|
|
+ f: &mut Formatter<'_>,
|
|
|
|
+ child: &'a Node,
|
|
|
|
+ il: u32,
|
|
|
|
+ svg_buffer: &mut Vec<&'a Element>,
|
|
|
|
+ skip_svg: bool,
|
|
|
|
+ svg_idx: &mut usize,
|
|
|
|
+) -> std::fmt::Result {
|
|
|
|
+ write_tabs(f, il)?;
|
|
|
|
+ match child {
|
|
|
|
+ Node::Text(t) => writeln!(f, "\"{}\"", t)?,
|
|
|
|
+ Node::Comment(e) => writeln!(f, "/* {} */", e)?,
|
|
|
|
+ Node::Element(el) => render_element(f, el, il, svg_buffer, skip_svg, svg_idx)?,
|
|
|
|
+ };
|
|
|
|
+ Ok(())
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+fn render_element<'a>(
|
|
|
|
+ f: &mut Formatter<'_>,
|
|
|
|
+ el: &'a Element,
|
|
|
|
+ il: u32,
|
|
|
|
+ svg_buffer: &mut Vec<&'a Element>,
|
|
|
|
+ skip_svg: bool,
|
|
|
|
+ svg_idx: &mut usize,
|
|
|
|
+) -> std::fmt::Result {
|
|
|
|
+ if el.name == "svg" && skip_svg {
|
|
|
|
+ svg_buffer.push(el);
|
|
|
|
+ // todo: attach the right icon ID
|
|
|
|
+ writeln!(f, "icons::icon_{} {{}}", svg_idx)?;
|
|
|
|
+ *svg_idx += 1;
|
|
|
|
+ return Ok(());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // open the tag
|
|
|
|
+ write!(f, "{} {{ ", &el.name)?;
|
|
|
|
+
|
|
|
|
+ // todo: dioxus will eventually support classnames
|
|
|
|
+ // for now, just write them with a space between each
|
|
|
|
+ let class_iter = &mut el.classes.iter();
|
|
|
|
+ if let Some(first_class) = class_iter.next() {
|
|
|
|
+ write!(f, "class: \"{}", first_class)?;
|
|
|
|
+ for next_class in class_iter {
|
|
|
|
+ write!(f, " {}", next_class)?;
|
|
|
|
+ }
|
|
|
|
+ write!(f, "\",")?;
|
|
|
|
+ }
|
|
|
|
+ writeln!(f)?;
|
|
|
|
+
|
|
|
|
+ // write the attributes
|
|
|
|
+ if let Some(id) = &el.id {
|
|
|
|
+ write_tabs(f, il + 1)?;
|
|
|
|
+ writeln!(f, "id: \"{}\",", id)?;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (name, value) in &el.attributes {
|
|
|
|
+ write_tabs(f, il + 1)?;
|
|
|
|
+
|
|
|
|
+ use convert_case::{Case, Casing};
|
|
|
|
+ if name.chars().any(|ch| ch.is_ascii_uppercase() || ch == '-') {
|
|
|
|
+ let new_name = name.to_case(Case::Snake);
|
|
|
|
+ match value {
|
|
|
|
+ Some(val) => writeln!(f, "{}: \"{}\",", new_name, val)?,
|
|
|
|
+ None => writeln!(f, "{}: \"\",", new_name)?,
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ if matches!(name.as_str(), "for" | "async" | "type" | "as") {
|
|
|
|
+ write!(f, "r#")?
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ match value {
|
|
|
|
+ Some(val) => writeln!(f, "{}: \"{}\",", name, val)?,
|
|
|
|
+ None => writeln!(f, "{}: \"\",", name)?,
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // now the children
|
|
|
|
+ for child in &el.children {
|
|
|
|
+ render_child(f, child, il + 1, svg_buffer, skip_svg, svg_idx)?;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // close the tag
|
|
|
|
+ write_tabs(f, il)?;
|
|
|
|
+ writeln!(f, "}}")?;
|
|
|
|
+ Ok(())
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+fn write_tabs(f: &mut impl std::fmt::Write, num: u32) -> std::fmt::Result {
|
|
for _ in 0..num {
|
|
for _ in 0..num {
|
|
write!(f, " ")?
|
|
write!(f, " ")?
|
|
}
|
|
}
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+#[test]
|
|
|
|
+fn generates_svgs() {
|
|
|
|
+ use std::io::Write;
|
|
|
|
+
|
|
|
|
+ let st = include_str!("../../../tests/svg.html");
|
|
|
|
+
|
|
|
|
+ let out = format!("{:}", convert_html_to_component(st).unwrap());
|
|
|
|
+ dbg!(&out);
|
|
|
|
+
|
|
|
|
+ std::fs::File::create("svg_rsx.rs")
|
|
|
|
+ .unwrap()
|
|
|
|
+ .write_all(out.as_bytes())
|
|
|
|
+ .unwrap();
|
|
|
|
+}
|