diff --git a/Cargo.lock b/Cargo.lock index c7b31d9..4908c1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi-style" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f4e7e5cab3f073c5b74e264cd4059249411a0f767af2c3047f26c9271f6bc1" + [[package]] name = "anstyle" version = "1.0.8" @@ -155,6 +161,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "pretty-format" +version = "0.0.2" +dependencies = [ + "ansi-style", + "thiserror", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "proc-macro2" version = "1.0.86" diff --git a/packages/dom/src/config.rs b/packages/dom/src/config.rs index 3fd98bf..977f5d5 100644 --- a/packages/dom/src/config.rs +++ b/packages/dom/src/config.rs @@ -15,7 +15,7 @@ static CONFIG: LazyLock>> = LazyLock::new(|| { show_original_stack_trace: false, throw_suggestions: false, get_element_error: Arc::new(|message, container| { - let prettified_dom = pretty_dom(Some(container)); + let prettified_dom = pretty_dom(Some(container.into()), None); let default_ignore = { let config = CONFIG.lock().expect("Config mutex should be acquired."); diff --git a/packages/dom/src/helpers.rs b/packages/dom/src/helpers.rs new file mode 100644 index 0000000..0a66520 --- /dev/null +++ b/packages/dom/src/helpers.rs @@ -0,0 +1,7 @@ +use web_sys::{window, Document}; + +pub fn get_document() -> Document { + window() + .and_then(|window| window.document()) + .expect("Could not find default container") +} diff --git a/packages/dom/src/lib.rs b/packages/dom/src/lib.rs index e5d877b..9cafe92 100644 --- a/packages/dom/src/lib.rs +++ b/packages/dom/src/lib.rs @@ -2,6 +2,7 @@ mod config; mod error; mod get_node_text; mod get_queries_for_element; +mod helpers; mod matches; mod pretty_dom; pub mod queries; diff --git a/packages/dom/src/pretty_dom.rs b/packages/dom/src/pretty_dom.rs index ec15f36..96b722d 100644 --- a/packages/dom/src/pretty_dom.rs +++ b/packages/dom/src/pretty_dom.rs @@ -1,6 +1,46 @@ -use web_sys::Element; +use web_sys::{Document, Element}; + +use crate::helpers::get_document; + +pub enum DocumentOrElement { + Document(Document), + Element(Element), +} + +impl From for DocumentOrElement { + fn from(value: Document) -> Self { + Self::Document(value) + } +} + +impl From for DocumentOrElement { + fn from(value: Element) -> Self { + Self::Element(value) + } +} + +pub fn pretty_dom(dom: Option, max_length: Option) -> String { + let dom = dom.unwrap_or_else(|| get_document().into()); + let max_length = max_length.unwrap_or(7000); + + if max_length == 0 { + return "".into(); + } + + let _dom = match dom { + DocumentOrElement::Document(document) => match document.document_element() { + Some(element) => DocumentOrElement::Element(element), + None => DocumentOrElement::Document(document), + }, + dom => dom, + }; -pub fn pretty_dom(_dom: Option) -> String { // TODO - "".into() + let debug_content = "".to_string(); + + if debug_content.len() > max_length { + format!("{}...", &debug_content[0..max_length]) + } else { + debug_content + } } diff --git a/packages/pretty-format/Cargo.toml b/packages/pretty-format/Cargo.toml new file mode 100644 index 0000000..67a7a17 --- /dev/null +++ b/packages/pretty-format/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "pretty-format" +description = "Rust port of pretty-format." + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +ansi-style = "1.2.1" +thiserror.workspace = true +wasm-bindgen.workspace = true +web-sys = { workspace = true, features = [ + "Document", + "Element", + "HtmlElement", + "HtmlInputElement", + "NodeList", + "Window", +] } diff --git a/packages/pretty-format/README.md b/packages/pretty-format/README.md new file mode 100644 index 0000000..ca324d8 --- /dev/null +++ b/packages/pretty-format/README.md @@ -0,0 +1,3 @@ +# Rust Pretty Format + +Rust port of [pretty-format](https://github.com/jestjs/jest/tree/main/packages/pretty-format). diff --git a/packages/pretty-format/src/error.rs b/packages/pretty-format/src/error.rs new file mode 100644 index 0000000..3cf7539 --- /dev/null +++ b/packages/pretty-format/src/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum PrettyFormatError { + #[error("{0}")] + Configuration(String), +} diff --git a/packages/pretty-format/src/lib.rs b/packages/pretty-format/src/lib.rs new file mode 100644 index 0000000..6a89b61 --- /dev/null +++ b/packages/pretty-format/src/lib.rs @@ -0,0 +1,174 @@ +// TODO: remove +#![allow(dead_code, unused)] + +mod error; +mod types; + +use std::rc::Rc; + +pub use error::PrettyFormatError; +pub use types::{Config, PrettyFormatOptions, PrettyFormatValue}; + +use types::{Colors, Plugin, Plugins, Refs}; + +pub fn print_basic_value( + val: &PrettyFormatValue, + print_function_name: bool, + escape_regex: bool, + escape_string: bool, +) -> Option { + match val { + PrettyFormatValue::Bool(val) => Some(format!("{val}")), + // _ => None, + } +} + +pub fn print_complex_value( + val: &PrettyFormatValue, + config: Config, + indentation: String, + depth: usize, + refs: Refs, + has_called_to_json: Option, +) -> String { + "".into() +} + +fn print_plugin( + plugin: Rc, + val: PrettyFormatValue, + config: Config, + indentation: String, + depth: usize, + refs: Refs, +) -> String { + plugin.serialize(val, config, indentation, depth, refs, &printer) +} + +fn find_plugin(plugins: &Plugins, val: &PrettyFormatValue) -> Option> { + plugins.iter().find(|plugin| plugin.test(val)).cloned() +} + +fn printer( + val: PrettyFormatValue, + config: Config, + indentation: String, + depth: usize, + refs: Refs, + has_called_to_json: Option, +) -> String { + "".into() +} + +fn validate_options(options: &PrettyFormatOptions) -> Result<(), PrettyFormatError> { + if options.min.is_some() && options.indent.is_some_and(|indent| indent != 0) { + Err(PrettyFormatError::Configuration( + "Options `min` and `indent` cannot be used togther.".into(), + )) + } else { + Ok(()) + } +} + +fn get_colors_highlight(options: &PrettyFormatOptions) -> Colors { + let theme = options.theme.clone().unwrap_or_default(); + + Colors { + comment: theme.comment, + content: theme.content, + prop: theme.prop, + tag: theme.tag, + value: theme.value, + } +} + +fn get_colors_empty() -> Colors { + Colors::default() +} + +fn get_print_function_name(options: &PrettyFormatOptions) -> bool { + options.print_function_name.unwrap_or(true) +} + +fn get_escape_regex(options: &PrettyFormatOptions) -> bool { + options.escape_regex.unwrap_or(false) +} + +fn get_escape_string(options: &PrettyFormatOptions) -> bool { + options.escape_string.unwrap_or(true) +} + +fn get_config(options: PrettyFormatOptions) -> Config { + Config { + call_to_json: options.call_to_json.unwrap_or(true), + compare_keys: options.compare_keys.clone(), + colors: match options.highlight { + Some(true) => get_colors_highlight(&options), + _ => get_colors_empty(), + }, + escape_regex: options.escape_regex.unwrap_or(false), + escape_string: options.escape_string.unwrap_or(true), + indent: match options.min { + Some(true) => "".into(), + _ => create_indent(options.indent.unwrap_or(2)), + }, + max_depth: options.max_depth.unwrap_or(usize::MAX), + max_width: options.max_width.unwrap_or(usize::MAX), + min: options.min.unwrap_or(false), + plugins: options.plugins.unwrap_or_default(), + print_function_name: options.print_function_name.unwrap_or(true), + spacing_inner: match options.min { + Some(true) => " ", + _ => "\n", + } + .into(), + spacing_outer: match options.min { + Some(true) => "", + _ => "\n", + } + .into(), + } +} + +fn create_indent(indent: usize) -> String { + " ".repeat(indent + 1) +} + +pub fn format( + val: PrettyFormatValue, + options: PrettyFormatOptions, +) -> Result { + validate_options(&options)?; + + if let Some(plugins) = &options.plugins { + if let Some(plugin) = find_plugin(plugins, &val) { + return Ok(print_plugin( + plugin, + val, + get_config(options), + "".into(), + 0, + vec![], + )); + } + } + + let basic_result = print_basic_value( + &val, + get_print_function_name(&options), + get_escape_regex(&options), + get_escape_string(&options), + ); + if let Some(basic_result) = basic_result { + Ok(basic_result) + } else { + Ok(print_complex_value( + &val, + get_config(options), + "".into(), + 0, + vec![], + None, + )) + } +} diff --git a/packages/pretty-format/src/types.rs b/packages/pretty-format/src/types.rs new file mode 100644 index 0000000..b473bf3 --- /dev/null +++ b/packages/pretty-format/src/types.rs @@ -0,0 +1,101 @@ +use std::rc::Rc; + +use ansi_style::{Style, StyleBuilder}; + +// TODO: consider replacing this with JsValue, so code can match the JS implementation +#[derive(Debug)] +pub enum PrettyFormatValue { + Bool(bool), +} + +impl From for PrettyFormatValue { + fn from(value: bool) -> Self { + Self::Bool(value) + } +} + +#[derive(Debug, Default)] +pub struct Colors { + pub comment: Style, + pub content: Style, + pub prop: Style, + pub tag: Style, + pub value: Style, +} + +#[derive(Clone, Debug)] +pub struct Theme { + pub comment: Style, + pub content: Style, + pub prop: Style, + pub tag: Style, + pub value: Style, +} + +impl Default for Theme { + fn default() -> Self { + Self { + comment: StyleBuilder::new().black_bright().build(), + content: StyleBuilder::new().build(), + prop: StyleBuilder::new().yellow().build(), + tag: StyleBuilder::new().cyan().build(), + value: StyleBuilder::new().green().build(), + } + } +} + +pub type Refs = Vec; + +pub type CompareKeys = Rc usize>; + +#[derive(Default)] +pub struct PrettyFormatOptions { + pub call_to_json: Option, + pub escape_regex: Option, + pub escape_string: Option, + pub highlight: Option, + pub indent: Option, + pub max_depth: Option, + pub max_width: Option, + pub min: Option, + // pub print_basic_prototype: Option, + pub print_function_name: Option, + pub theme: Option, + pub compare_keys: Option, + pub plugins: Option, +} + +pub struct Config { + pub call_to_json: bool, + pub compare_keys: Option, + pub colors: Colors, + pub escape_regex: bool, + pub escape_string: bool, + pub indent: String, + pub max_depth: usize, + pub max_width: usize, + pub min: bool, + pub plugins: Plugins, + // pub print_basic_prototype: bool, + pub print_function_name: bool, + pub spacing_inner: String, + pub spacing_outer: String, +} + +pub type Printer = dyn Fn(PrettyFormatValue, Config, String, usize, Refs, Option) -> String; + +pub trait Plugin { + fn serialize( + &self, + val: PrettyFormatValue, + config: Config, + indentation: String, + depth: usize, + refs: Refs, + printer: &Printer, + ) -> String; + + fn test(&self, val: &PrettyFormatValue) -> bool; +} + +pub type Plugins = Vec>;