瀏覽代碼

Merge pull request #686 from DioxusLabs/jk/add-translate-module

Pull translation out of CLI into its own "Rosetta" crate
Jon Kelley 2 年之前
父節點
當前提交
4d29a190d4

+ 2 - 1
.vscode/settings.json

@@ -2,5 +2,6 @@
   "editor.formatOnSave": true,
   "[toml]": {
     "editor.formatOnSave": false
-  }
+  },
+  "rust-analyzer.checkOnSave.allTargets": false,
 }

+ 1 - 0
Cargo.toml

@@ -18,6 +18,7 @@ members = [
     "packages/tui",
     "packages/native-core",
     "packages/native-core-macro",
+    "packages/rsx-rosetta",
     "docs/guide",
 ]
 

+ 26 - 2
packages/autofmt/src/lib.rs

@@ -1,3 +1,5 @@
+use dioxus_rsx::CallBody;
+
 use crate::buffer::*;
 use crate::util::*;
 
@@ -31,6 +33,11 @@ pub struct FormattedBlock {
 /// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
 ///
 /// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.
+///
+/// The point here is to provide precise modifications of a source file so an accompanying IDE tool can map these changes
+/// back to the file precisely.
+///
+/// Nested blocks of RSX will be handled automatically
 pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
     let mut formatted_blocks = Vec::new();
     let mut last_bracket_end = 0;
@@ -93,15 +100,32 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
     formatted_blocks
 }
 
+pub fn write_block_out(body: CallBody) -> Option<String> {
+    let mut buf = Buffer {
+        src: vec!["".to_string()],
+        indent: 0,
+        ..Buffer::default()
+    };
+
+    // Oneliner optimization
+    if buf.is_short_children(&body.roots).is_some() {
+        buf.write_ident(&body.roots[0]).unwrap();
+    } else {
+        buf.write_body_indented(&body.roots).unwrap();
+    }
+
+    buf.consume()
+}
+
 pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
+    let body = syn::parse_str::<dioxus_rsx::CallBody>(block).ok()?;
+
     let mut buf = Buffer {
         src: block.lines().map(|f| f.to_string()).collect(),
         indent: indent_level,
         ..Buffer::default()
     };
 
-    let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
-
     // Oneliner optimization
     if buf.is_short_children(&body.roots).is_some() {
         buf.write_ident(&body.roots[0]).unwrap();

+ 20 - 0
packages/rsx-rosetta/Cargo.toml

@@ -0,0 +1,20 @@
+[package]
+name = "rsx-rosetta"
+version = "0.0.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+dioxus-autofmt = { path = "../autofmt" }
+dioxus-rsx = { path = "../rsx" }
+html_parser = "0.6.3"
+proc-macro2 = "1.0.49"
+quote = "1.0.23"
+syn = { version = "1.0.107", features = ["full"] }
+convert_case = "0.5.0"
+
+# [features]
+# default = ["html"]
+
+# eventually more output options

+ 19 - 0
packages/rsx-rosetta/README.md

@@ -0,0 +1,19 @@
+# Rosetta for RSX
+---
+
+Dioxus sports its own templating language inspired by C#/Kotlin/RTMP, etc. It's pretty straightforward.
+
+However, it's NOT HTML. This is done since HTML is verbose and you'd need a dedicated LSP or IDE integration to get a good DX in .rs files.
+
+RSX is simple... It's similar enough to regular Rust code to trick most IDEs into automatically providing support for things like block selections, folding, highlighting, etc.
+
+To accomodate the transition from HTML to RSX, you might need to translate some existing code.
+
+This library provids a central AST that can accept a number of inputs:
+
+- HTML
+- Syn (todo)
+- Akama (todo)
+- Jinja (todo)
+
+From there, you can convert directly to a string or into some other AST.

+ 24 - 0
packages/rsx-rosetta/examples/html.rs

@@ -0,0 +1,24 @@
+use html_parser::Dom;
+
+fn main() {
+    let html = r#"
+    <div>
+        <div class="asd">hello world!</div>
+        <div id="asd">hello world!</div>
+        <div id="asd">hello world!</div>
+        <div for="asd">hello world!</div>
+        <div async="asd">hello world!</div>
+        <div LargeThing="asd">hello world!</div>
+        <ai-is-awesome>hello world!</ai-is-awesome>
+    </div>
+    "#
+    .trim();
+
+    let dom = Dom::parse(html).unwrap();
+
+    let body = rsx_rosetta::rsx_from_html(dom);
+
+    let out = dioxus_autofmt::write_block_out(body).unwrap();
+
+    println!("{}", out);
+}

+ 87 - 0
packages/rsx-rosetta/src/lib.rs

@@ -0,0 +1,87 @@
+use convert_case::{Case, Casing};
+use dioxus_rsx::{BodyNode, CallBody, Element, ElementAttr, ElementAttrNamed, IfmtInput};
+pub use html_parser::{Dom, Node};
+use proc_macro2::{Ident, Span};
+use syn::LitStr;
+
+/// Convert an HTML DOM tree into an RSX CallBody
+pub fn rsx_from_html(dom: &Dom) -> CallBody {
+    CallBody {
+        roots: dom.children.iter().filter_map(rsx_node_from_html).collect(),
+    }
+}
+
+/// Convert an HTML Node into an RSX BodyNode
+///
+/// If the node is a comment, it will be ignored since RSX doesn't support comments
+pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
+    match node {
+        Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
+        Node::Element(el) => {
+            let el_name = el.name.to_case(Case::Snake);
+            let el_name = Ident::new(el_name.as_str(), Span::call_site());
+
+            let mut attributes: Vec<_> = el
+                .attributes
+                .iter()
+                .map(|(name, value)| {
+                    let ident = if matches!(name.as_str(), "for" | "async" | "type" | "as") {
+                        Ident::new_raw(name.as_str(), Span::call_site())
+                    } else {
+                        let new_name = name.to_case(Case::Snake);
+                        Ident::new(new_name.as_str(), Span::call_site())
+                    };
+
+                    ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr::AttrText {
+                            value: ifmt_from_text(value.as_deref().unwrap_or("false")),
+                            name: ident,
+                        },
+                    }
+                })
+                .collect();
+
+            let class = el.classes.join(" ");
+            if !class.is_empty() {
+                attributes.push(ElementAttrNamed {
+                    el_name: el_name.clone(),
+                    attr: ElementAttr::AttrText {
+                        name: Ident::new("class", Span::call_site()),
+                        value: ifmt_from_text(&class),
+                    },
+                });
+            }
+
+            if let Some(id) = &el.id {
+                attributes.push(ElementAttrNamed {
+                    el_name: el_name.clone(),
+                    attr: ElementAttr::AttrText {
+                        name: Ident::new("id", Span::call_site()),
+                        value: ifmt_from_text(id),
+                    },
+                });
+            }
+
+            let children = el.children.iter().filter_map(rsx_node_from_html).collect();
+
+            Some(BodyNode::Element(Element {
+                name: el_name,
+                children,
+                attributes,
+                _is_static: false,
+                key: None,
+            }))
+        }
+
+        // We ignore comments
+        Node::Comment(_) => None,
+    }
+}
+
+fn ifmt_from_text(text: &str) -> IfmtInput {
+    IfmtInput {
+        source: Some(LitStr::new(text, Span::call_site())),
+        segments: vec![],
+    }
+}