mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 06:30:20 +00:00
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:
commit
4d29a190d4
7 changed files with 179 additions and 3 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -2,5 +2,6 @@
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"[toml]": {
|
"[toml]": {
|
||||||
"editor.formatOnSave": false
|
"editor.formatOnSave": false
|
||||||
}
|
},
|
||||||
|
"rust-analyzer.checkOnSave.allTargets": false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ members = [
|
||||||
"packages/tui",
|
"packages/tui",
|
||||||
"packages/native-core",
|
"packages/native-core",
|
||||||
"packages/native-core-macro",
|
"packages/native-core-macro",
|
||||||
|
"packages/rsx-rosetta",
|
||||||
"docs/guide",
|
"docs/guide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use dioxus_rsx::CallBody;
|
||||||
|
|
||||||
use crate::buffer::*;
|
use crate::buffer::*;
|
||||||
use crate::util::*;
|
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.
|
/// 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.
|
/// 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> {
|
pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
||||||
let mut formatted_blocks = Vec::new();
|
let mut formatted_blocks = Vec::new();
|
||||||
let mut last_bracket_end = 0;
|
let mut last_bracket_end = 0;
|
||||||
|
@ -93,15 +100,32 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
||||||
formatted_blocks
|
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> {
|
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 {
|
let mut buf = Buffer {
|
||||||
src: block.lines().map(|f| f.to_string()).collect(),
|
src: block.lines().map(|f| f.to_string()).collect(),
|
||||||
indent: indent_level,
|
indent: indent_level,
|
||||||
..Buffer::default()
|
..Buffer::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
|
|
||||||
|
|
||||||
// Oneliner optimization
|
// Oneliner optimization
|
||||||
if buf.is_short_children(&body.roots).is_some() {
|
if buf.is_short_children(&body.roots).is_some() {
|
||||||
buf.write_ident(&body.roots[0]).unwrap();
|
buf.write_ident(&body.roots[0]).unwrap();
|
||||||
|
|
20
packages/rsx-rosetta/Cargo.toml
Normal file
20
packages/rsx-rosetta/Cargo.toml
Normal 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
|
19
packages/rsx-rosetta/README.md
Normal file
19
packages/rsx-rosetta/README.md
Normal 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.
|
24
packages/rsx-rosetta/examples/html.rs
Normal file
24
packages/rsx-rosetta/examples/html.rs
Normal 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);
|
||||||
|
}
|
87
packages/rsx-rosetta/src/lib.rs
Normal file
87
packages/rsx-rosetta/src/lib.rs
Normal 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![],
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue