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

Pull translation out of CLI into its own "Rosetta" crate
This commit is contained in:
Jon Kelley 2022-12-29 01:02:50 -05:00 committed by GitHub
commit 4d29a190d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 179 additions and 3 deletions

View file

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

View file

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

View file

@ -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();

View file

@ -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

View file

@ -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.

View file

@ -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);
}

View file

@ -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![],
}
}