wip: restructure the entire CLI
This commit is contained in:
1 parent
6b991bf184
commit
6c351efbf0
27 files changed
+259
-232
No files matched your search
@@ -0,0 +1,12 @@
|
||||
use crate::cfg::ConfigOptsBuild;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// Build the Rust WASM app and all of its assets.
|
||||
#[derive(Clone, Debug, StructOpt)]
|
||||
#[structopt(name = "build")]
|
||||
pub struct Build {
|
||||
#[structopt(flatten)]
|
||||
pub build: ConfigOptsBuild,
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Config options for the build system.
|
||||
#[derive(Clone, Debug, Default, Deserialize, StructOpt)]
|
||||
pub struct ConfigOptsBuild {
|
||||
/// The index HTML file to drive the bundling process [default: index.html]
|
||||
#[structopt(parse(from_os_str))]
|
||||
pub target: Option<PathBuf>,
|
||||
|
||||
/// Build in release mode [default: false]
|
||||
#[structopt(long)]
|
||||
#[serde(default)]
|
||||
pub release: bool,
|
||||
|
||||
/// The output dir for all final assets [default: dist]
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
pub dist: Option<PathBuf>,
|
||||
|
||||
/// The public URL from which assets are to be served [default: /]
|
||||
#[structopt(long, parse(from_str=parse_public_url))]
|
||||
pub public_url: Option<String>,
|
||||
|
||||
/// Optional pattern for the app loader script [default: None]
|
||||
///
|
||||
/// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to
|
||||
/// properly load the application. Other sequences may be included corresponding
|
||||
/// to key/value pairs provided in `pattern_params`.
|
||||
///
|
||||
/// These values can only be provided via config file.
|
||||
#[structopt(skip)]
|
||||
#[serde(default)]
|
||||
pub pattern_script: Option<String>,
|
||||
|
||||
/// Optional pattern for the app preload element [default: None]
|
||||
///
|
||||
/// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to
|
||||
/// properly preload the application. Other sequences may be included corresponding
|
||||
/// to key/value pairs provided in `pattern_params`.
|
||||
///
|
||||
/// These values can only be provided via config file.
|
||||
#[structopt(skip)]
|
||||
#[serde(default)]
|
||||
pub pattern_preload: Option<String>,
|
||||
|
||||
#[structopt(skip)]
|
||||
#[serde(default)]
|
||||
/// Optional replacement parameters corresponding to the patterns provided in
|
||||
/// `pattern_script` and `pattern_preload`.
|
||||
///
|
||||
/// When a pattern is being replaced with its corresponding value from this map, if the value is
|
||||
/// prefixed with the symbol `@`, then the value is expected to be a file path, and the pattern
|
||||
/// will be replaced with the contents of the target file. This allows insertion of some big JSON
|
||||
/// state or even HTML files as a part of the `index.html` build.
|
||||
///
|
||||
/// Trunk will automatically insert the `base`, `wasm` and `js` key/values into this map. In order
|
||||
//// for the app to be loaded properly, the patterns `{base}`, `{wasm}` and `{js}` should be used
|
||||
/// in `pattern_script` and `pattern_preload`.
|
||||
///
|
||||
/// These values can only be provided via config file.
|
||||
pub pattern_params: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
/// Ensure the given value for `--public-url` is formatted correctly.
|
||||
pub fn parse_public_url(val: &str) -> String {
|
||||
let prefix = if !val.starts_with('/') { "/" } else { "" };
|
||||
let suffix = if !val.ends_with('/') { "/" } else { "" };
|
||||
format!("{}{}{}", prefix, val, suffix)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use crate::cfg::ConfigOptsBuild;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// Build the Rust WASM app and all of its assets.
|
||||
#[derive(Clone, Debug, StructOpt)]
|
||||
#[structopt(name = "clean")]
|
||||
pub struct Clean {}
|
||||
@@ -0,0 +1,9 @@
|
||||
use crate::cfg::ConfigOptsBuild;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// Build the Rust WASM app and all of its assets.
|
||||
#[derive(Clone, Debug, StructOpt)]
|
||||
#[structopt(name = "config")]
|
||||
pub struct Config {}
|
||||
@@ -0,0 +1,47 @@
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
pub mod build;
|
||||
pub mod cfg;
|
||||
pub mod clean;
|
||||
pub mod config;
|
||||
pub mod serve;
|
||||
pub mod translate;
|
||||
pub mod watch;
|
||||
|
||||
/// Build, bundle & ship your Rust WASM application to the web.
|
||||
#[derive(StructOpt)]
|
||||
#[structopt(name = "trunk")]
|
||||
pub struct Trunk {
|
||||
#[structopt(subcommand)]
|
||||
pub action: TrunkSubcommands,
|
||||
|
||||
/// Path to the Trunk config file [default: Trunk.toml]
|
||||
#[structopt(long, parse(from_os_str), env = "TRUNK_CONFIG")]
|
||||
pub config: Option<PathBuf>,
|
||||
|
||||
/// Enable verbose logging.
|
||||
#[structopt(short)]
|
||||
pub v: bool,
|
||||
}
|
||||
|
||||
#[derive(StructOpt)]
|
||||
pub enum TrunkSubcommands {
|
||||
/// Build the Rust WASM app and all of its assets.
|
||||
Build(build::Build),
|
||||
|
||||
/// Translate some source file into Dioxus code.
|
||||
Translate(translate::Translate),
|
||||
|
||||
/// Build, watch & serve the Rust WASM app and all of its assets.
|
||||
///
|
||||
///
|
||||
Serve(serve::Serve),
|
||||
|
||||
/// Clean output artifacts.
|
||||
Clean(clean::Clean),
|
||||
|
||||
/// Trunk config controls.
|
||||
Config(config::Config),
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use crate::{cli::DevelopOptions, config::CrateConfig, error::Result};
|
||||
use async_std::prelude::FutureExt;
|
||||
|
||||
use log::info;
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use tide::http::mime::HTML;
|
||||
use tide::http::Mime;
|
||||
|
||||
pub struct DevelopState {
|
||||
//
|
||||
reload_on_change: bool,
|
||||
}
|
||||
|
||||
pub async fn develop(options: DevelopOptions) -> Result<()> {
|
||||
//
|
||||
log::info!("Starting development server 🚀");
|
||||
let mut cfg = CrateConfig::new()?;
|
||||
cfg.with_develop_options(&options);
|
||||
|
||||
let out_dir = cfg.out_dir.clone();
|
||||
|
||||
let is_err = Arc::new(AtomicBool::new(false));
|
||||
|
||||
// Spawn the server onto a seperate task
|
||||
// This lets the task progress while we handle file updates
|
||||
let server = async_std::task::spawn(launch_server(out_dir, is_err.clone()));
|
||||
let watcher = async_std::task::spawn(watch_directory(cfg.clone(), is_err.clone()));
|
||||
|
||||
match server.race(watcher).await {
|
||||
Err(e) => log::warn!("Error running development server, {:?}", e),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn watch_directory(config: CrateConfig, is_err: ErrStatus) -> Result<()> {
|
||||
// Create a channel to receive the events.
|
||||
let (watcher_tx, watcher_rx) = async_std::channel::bounded(100);
|
||||
|
||||
// Automatically select the best implementation for your platform.
|
||||
// You can also access each implementation directly e.g. INotifyWatcher.
|
||||
let mut watcher: RecommendedWatcher = Watcher::new(move |res| {
|
||||
async_std::task::block_on(watcher_tx.send(res));
|
||||
// send an event
|
||||
let _ = async_std::task::block_on(watcher_tx.send(res));
|
||||
})
|
||||
.expect("failed to make watcher");
|
||||
|
||||
let src_dir = crate::cargo::crate_root()?;
|
||||
|
||||
// Add a path to be watched. All files and directories at that path and
|
||||
// below will be monitored for changes.
|
||||
watcher
|
||||
.watch(&src_dir.join("src"), RecursiveMode::Recursive)
|
||||
.expect("Failed to watch dir");
|
||||
|
||||
match watcher.watch(&src_dir.join("examples"), RecursiveMode::Recursive) {
|
||||
Ok(_) => {}
|
||||
Err(e) => log::warn!("Failed to watch examples dir, {:?}", e),
|
||||
}
|
||||
|
||||
'run: loop {
|
||||
match crate::builder::build(&config) {
|
||||
Ok(_) => {
|
||||
is_err.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
|
||||
}
|
||||
Err(err) => is_err.store(true, std::sync::atomic::Ordering::Relaxed),
|
||||
};
|
||||
|
||||
let mut msg = None;
|
||||
loop {
|
||||
let new_msg = watcher_rx.recv().await.unwrap().unwrap();
|
||||
if !watcher_rx.is_empty() {
|
||||
msg = Some(new_msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
info!("File updated, rebuilding app");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn launch_server(outdir: PathBuf, is_err: ErrStatus) -> Result<()> {
|
||||
let _crate_dir = crate::cargo::crate_root()?;
|
||||
let _workspace_dir = crate::cargo::workspace_root()?;
|
||||
|
||||
let mut app = tide::with_state(ServerState::new(outdir.to_owned(), is_err));
|
||||
|
||||
let file_path = format!("{}/index.html", outdir.display());
|
||||
log::info!("Serving {}", file_path);
|
||||
let p = outdir.display().to_string();
|
||||
|
||||
app.at("/")
|
||||
.get(|req: tide::Request<ServerState>| async move {
|
||||
log::info!("Connected to development server");
|
||||
let state = req.state();
|
||||
|
||||
match state.is_err.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
true => {
|
||||
//
|
||||
let mut resp =
|
||||
tide::Body::from_string(format!(include_str!("../err.html"), err = "_"));
|
||||
resp.set_mime(HTML);
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
false => {
|
||||
Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
|
||||
}
|
||||
}
|
||||
})
|
||||
.serve_dir(p)?;
|
||||
// .serve_file(file_path)
|
||||
// .unwrap();
|
||||
|
||||
let port = "8080";
|
||||
let serve_addr = format!("127.0.0.1:{}", port);
|
||||
|
||||
info!("App available at http://{}/", serve_addr);
|
||||
app.listen(serve_addr).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// https://github.com/http-rs/tide/blob/main/examples/state.rs
|
||||
/// Tide seems to prefer using state instead of injecting into the app closure
|
||||
/// The app closure needs to be static and
|
||||
#[derive(Clone)]
|
||||
struct ServerState {
|
||||
serv_path: PathBuf,
|
||||
is_err: ErrStatus,
|
||||
}
|
||||
|
||||
type ErrStatus = Arc<AtomicBool>;
|
||||
|
||||
impl ServerState {
|
||||
fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
|
||||
Self { serv_path, is_err }
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,9 @@
|
||||
use crate::cfg::ConfigOptsBuild;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// Build the Rust WASM app and all of its assets.
|
||||
#[derive(Clone, Debug, StructOpt)]
|
||||
#[structopt(name = "serve")]
|
||||
pub struct Serve {}
|
||||
@@ -0,0 +1,164 @@
|
||||
//! It's better to store all the configuration in one spot
|
||||
|
||||
use tui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
symbols,
|
||||
text::{Span, Spans},
|
||||
widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle},
|
||||
widgets::{
|
||||
Axis, BarChart, Block, BorderType, Borders, Cell, Chart, Dataset, Gauge, LineGauge, List,
|
||||
ListItem, Paragraph, Row, Sparkline, Table, Tabs, Wrap,
|
||||
},
|
||||
Frame, Terminal,
|
||||
};
|
||||
|
||||
use crate::*;
|
||||
use std::{any::Any, io::Write, path::PathBuf, process::Command};
|
||||
|
||||
pub struct Cfg {
|
||||
command: LaunchOptions,
|
||||
headless: bool,
|
||||
example: Option<String>,
|
||||
outdir: Option<String>,
|
||||
release: bool,
|
||||
hydrate: Option<String>,
|
||||
template: Option<String>,
|
||||
translate_file: Option<String>,
|
||||
crate_config: Option<CrateConfig>,
|
||||
should_quit: bool,
|
||||
}
|
||||
|
||||
pub async fn start(options: DevelopOptions) -> Result<()> {
|
||||
let mut state = Cfg {
|
||||
command: todo!(),
|
||||
headless: todo!(),
|
||||
example: todo!(),
|
||||
outdir: todo!(),
|
||||
release: todo!(),
|
||||
hydrate: todo!(),
|
||||
template: todo!(),
|
||||
translate_file: todo!(),
|
||||
crate_config: todo!(),
|
||||
should_quit: false,
|
||||
};
|
||||
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
|
||||
let backend = CrosstermBackend::new(std::io::stdout());
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
// Setup input handling
|
||||
// let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
let tick_rate = std::time::Duration::from_millis(100);
|
||||
|
||||
let mut prev_time = std::time::Instant::now();
|
||||
while !state.should_quit {
|
||||
let next_time = prev_time + tick_rate;
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
let diff = next_time - std::time::Instant::now();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct TuiStudio {
|
||||
cfg: Cfg,
|
||||
hook_idx: usize,
|
||||
hooks: Vec<Box<dyn Any>>,
|
||||
}
|
||||
impl TuiStudio {
|
||||
fn use_hook<F: 'static>(&mut self, f: impl FnOnce() -> F) -> &mut F {
|
||||
if self.hook_idx == self.hooks.len() {
|
||||
self.hooks.push(Box::new(f()));
|
||||
}
|
||||
let idx = self.hook_idx;
|
||||
self.hook_idx += 1;
|
||||
let hook = self.hooks.get_mut(idx).unwrap();
|
||||
let r = hook.downcast_mut::<F>().unwrap();
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl TuiStudio {
|
||||
fn event_handler(&self, action: crossterm::event::Event) -> anyhow::Result<()> {
|
||||
match action {
|
||||
crossterm::event::Event::Key(_) => {}
|
||||
crossterm::event::Event::Mouse(_) => {}
|
||||
crossterm::event::Event::Resize(_, _) => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key: crossterm::event::KeyEvent) {}
|
||||
|
||||
fn tick(&mut self) {}
|
||||
|
||||
fn should_quit(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn render<B: tui::backend::Backend>(&mut self, f: &mut tui::Frame<B>) {
|
||||
self.hook_idx = 0;
|
||||
|
||||
// Wrapping block for a group
|
||||
// Just draw the block and the group on the same area and build the group
|
||||
// with at least a margin of 1
|
||||
let size = f.size();
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Main block with round corners")
|
||||
.border_type(BorderType::Rounded);
|
||||
f.render_widget(block, size);
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(4)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
.split(f.size());
|
||||
|
||||
let top_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
.split(chunks[0]);
|
||||
let block = Block::default()
|
||||
.title(vec![
|
||||
Span::styled("With", Style::default().fg(Color::Yellow)),
|
||||
Span::from(" background"),
|
||||
])
|
||||
.style(Style::default().bg(Color::Green));
|
||||
f.render_widget(block, top_chunks[0]);
|
||||
|
||||
let block = Block::default().title(Span::styled(
|
||||
"Styled title",
|
||||
Style::default()
|
||||
.fg(Color::White)
|
||||
.bg(Color::Red)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
));
|
||||
f.render_widget(block, top_chunks[1]);
|
||||
|
||||
let bottom_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
.split(chunks[1]);
|
||||
let block = Block::default().title("With borders").borders(Borders::ALL);
|
||||
f.render_widget(block, bottom_chunks[0]);
|
||||
let block = Block::default()
|
||||
.title("With styled borders and doubled borders")
|
||||
.border_style(Style::default().fg(Color::Cyan))
|
||||
.borders(Borders::LEFT | Borders::RIGHT)
|
||||
.border_type(BorderType::Double);
|
||||
f.render_widget(block, bottom_chunks[1]);
|
||||
}
|
||||
}
|
||||
|
||||
impl TuiStudio {
|
||||
fn render_list<B: tui::backend::Backend>(&mut self, f: &mut tui::Frame<B>) {
|
||||
let options = [
|
||||
"Bundle", "Develop",
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1,13 @@
|
||||
use crate::cfg::ConfigOptsBuild;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
pub mod extract_svgs;
|
||||
pub mod to_component;
|
||||
pub mod translate;
|
||||
|
||||
/// Build the Rust WASM app and all of its assets.
|
||||
#[derive(Clone, Debug, StructOpt)]
|
||||
#[structopt(name = "translate")]
|
||||
pub struct Translate {}
|
||||
@@ -0,0 +1,181 @@
|
||||
//! Intelligently converts html to rsx with appropraite transformations and extractions.
|
||||
//!
|
||||
//! - [*] Creates a component
|
||||
//! - [ ] Extracts svgs
|
||||
//! - [ ] Attempts to extract lists
|
||||
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
io::Write,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use html_parser::{Dom, Element, Node};
|
||||
|
||||
pub fn convert_html_to_component(html: &str) -> Result<ComponentRenderer> {
|
||||
Ok(ComponentRenderer {
|
||||
dom: Dom::parse(html)?,
|
||||
icon_index: 0,
|
||||
})
|
||||
}
|
||||
|
||||
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, "\",")?;
|
||||
}
|
||||
write!(f, "\n")?;
|
||||
|
||||
// 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 {
|
||||
match 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 Formatter, num: u32) -> std::fmt::Result {
|
||||
for _ in 0..num {
|
||||
write!(f, " ")?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generates_svgs() {
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use html_parser::{Dom, Node};
|
||||
|
||||
pub fn translate_from_html_file(target: &str) -> Result<RsxRenderer> {
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
let mut file = File::open(target).unwrap();
|
||||
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)
|
||||
.expect("Failed to read your file.");
|
||||
translate_from_html_to_rsx(&contents, true)
|
||||
}
|
||||
|
||||
pub fn translate_from_html_to_rsx(html: &str, as_call: bool) -> Result<RsxRenderer> {
|
||||
let contents = Dom::parse(html)?;
|
||||
let renderer = RsxRenderer {
|
||||
as_call,
|
||||
dom: contents,
|
||||
};
|
||||
Ok(renderer)
|
||||
}
|
||||
|
||||
pub struct RsxRenderer {
|
||||
dom: Dom,
|
||||
as_call: bool,
|
||||
}
|
||||
|
||||
impl Display for RsxRenderer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.as_call {
|
||||
writeln!(f, r##"use dioxus::prelude::*;"##)?;
|
||||
writeln!(
|
||||
f,
|
||||
r##"
|
||||
fn component(cx: Scope) -> Element {{
|
||||
cx.render(rsx!(
|
||||
"##
|
||||
)?;
|
||||
}
|
||||
for child in &self.dom.children {
|
||||
render_child(f, child, 1)?;
|
||||
}
|
||||
if self.as_call {
|
||||
write!(
|
||||
f,
|
||||
r##"
|
||||
)
|
||||
)
|
||||
"##
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn render_child(f: &mut Formatter<'_>, child: &Node, il: u32) -> std::fmt::Result {
|
||||
write_tabs(f, il);
|
||||
match child {
|
||||
Node::Text(t) => writeln!(f, "\"{}\"", t)?,
|
||||
Node::Comment(e) => writeln!(f, "/* {} */", e)?,
|
||||
Node::Element(el) => {
|
||||
// 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, "\",")?;
|
||||
}
|
||||
write!(f, "\n")?;
|
||||
|
||||
// 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 {
|
||||
match 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)?;
|
||||
}
|
||||
|
||||
// close the tag
|
||||
write_tabs(f, il)?;
|
||||
writeln!(f, "}}")?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_tabs(f: &mut Formatter, num: u32) -> std::fmt::Result {
|
||||
for _ in 0..num {
|
||||
write!(f, " ")?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Whitespace-only changes.
Reference in new issue
Block a user