From d22e812320db7600f29176db49c71a8ca4d30e8b Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Thu, 20 Aug 2020 17:58:02 -0700 Subject: [PATCH] initial prettiest release --- Cargo.toml | 1 + dom/prettiest/CHANGELOG.md | 10 +++ dom/prettiest/Cargo.toml | 28 ++++++++ dom/prettiest/src/lib.rs | 132 +++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 dom/prettiest/CHANGELOG.md create mode 100644 dom/prettiest/Cargo.toml create mode 100644 dom/prettiest/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 2c8f962eb..7bf905bd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "dom/examples/hacking", "dom/examples/ssr", "dom/examples/todo", + "dom/prettiest", "dom/raf", "dyn-cache", "illicit", diff --git a/dom/prettiest/CHANGELOG.md b/dom/prettiest/CHANGELOG.md new file mode 100644 index 000000000..38bd85fd9 --- /dev/null +++ b/dom/prettiest/CHANGELOG.md @@ -0,0 +1,10 @@ +# prettiest + +[prettiest](https://docs.rs/prettiest) provides pretty-printed `Debug` and `Display` impls +for Javascript values in the [wasm-bindgen](https://docs.rs/wasm-bindgen) crate. + + + +## [0.1.0] - 2020-08-20 + +Initial release. Only sort of works -- not recommended for use. diff --git a/dom/prettiest/Cargo.toml b/dom/prettiest/Cargo.toml new file mode 100644 index 000000000..45ed0e54a --- /dev/null +++ b/dom/prettiest/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "prettiest" +version = "0.1.0" +description = "Pretty-printer for JS values from wasm-bindgen." +categories = ["web"] +keywords = ["debug", "pretty", "javascript"] +readme = "CHANGELOG.md" + +# update here, update everywhere! +license = "MIT/Apache-2.0" +homepage = "https://moxie.rs" +repository = "https://github.com/anp/moxie.git" +authors = ["Adam Perry "] +edition = "2018" + +[dependencies] +js-sys = "0.3.25" +ordered-float = "2.0.0" +wasm-bindgen = "0.2.48" + +[dev-dependencies] +wasm-bindgen-test = "0.3" + +[dev-dependencies.web-sys] +version = "0.3.28" +features = [ + "KeyboardEvent", +] \ No newline at end of file diff --git a/dom/prettiest/src/lib.rs b/dom/prettiest/src/lib.rs new file mode 100644 index 000000000..3facb9ed7 --- /dev/null +++ b/dom/prettiest/src/lib.rs @@ -0,0 +1,132 @@ +use js_sys::{ + Array, Date, Error, Function, JsString, Map, Object, Promise, Reflect, RegExp, Set, Symbol, +}; +use ordered_float::OrderedFloat; +use std::collections::{BTreeMap, BTreeSet, HashSet}; +use wasm_bindgen::{convert::IntoWasmAbi, JsCast, JsValue}; + +// TODO impl Display +// TODO impl serialize +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] // TODO we can do Debug better! +pub enum Pretty { + Cycle, + Function, + Promise, + + Number(OrderedFloat), + Boolean(bool), + + Str(String), + Date(String), + Error(String), + Regex(String), + Symbol(String), + + Object { name: String, contents: BTreeMap }, + Array(Vec), + Map(BTreeMap), + Set(BTreeSet), +} + +impl From for Pretty +where + T: AsRef, +{ + fn from(val: T) -> Self { + let mut collector = Collector::default(); + collector.collect(val.as_ref()) + } +} + +#[derive(Default)] +struct Collector { + seen: HashSet, +} + +impl Collector { + fn have_seen(&mut self, val: &JsValue) -> bool { + let raw = val.clone().into_abi(); + let already_has = self.seen.contains(&raw); + self.seen.insert(raw); + already_has + } + + fn collect(&mut self, val: &JsValue) -> Pretty { + if self.have_seen(val) { + Pretty::Cycle + } else if let Some(a) = val.dyn_ref::() { + let mut children = vec![]; + for val in a.iter() { + children.push(self.collect(&val)); + } + + Pretty::Array(children) + } else if let Some(s) = val.dyn_ref::() { + let mut contents = BTreeSet::new(); + let entries = s.entries(); + while let Ok(next) = entries.next() { + if next.done() { + break; + } + contents.insert(self.collect(&next.value())); + } + + Pretty::Set(contents) + } else if let Some(m) = val.dyn_ref::() { + let mut contents = BTreeMap::new(); + let keys = m.keys(); + while let Ok(next) = keys.next() { + if next.done() { + break; + } + let key = next.value(); + let value = m.get(&key); + + contents.insert(self.collect(&key), self.collect(&value)); + } + + Pretty::Map(contents) + } else if let Some(d) = val.dyn_ref::() { + Pretty::Date(d.to_iso_string().as_string().unwrap()) + } else if let Some(e) = val.dyn_ref::() { + Pretty::Error(e.to_string().as_string().unwrap()) + } else if let Some(r) = val.dyn_ref::() { + Pretty::Regex(r.to_string().as_string().unwrap()) + } else if let Some(s) = val.dyn_ref::() { + Pretty::Symbol(s.to_string().as_string().unwrap()) + } else if val.dyn_ref::().is_some() { + Pretty::Function + } else if val.dyn_ref::().is_some() { + Pretty::Promise + } else if let Some(s) = val.dyn_ref::() { + Pretty::Str(s.as_string().unwrap()) + } else if let Some(n) = val.as_f64() { + Pretty::Number(OrderedFloat(n)) + } else if let Some(b) = val.as_bool() { + Pretty::Boolean(b) + } else { + let mut contents = BTreeMap::new(); + let obj = + val.dyn_ref::().expect("fallthrough condition is to an Object").clone(); + let name = obj.constructor().name().as_string().unwrap(); + let proto = obj.clone(); + + while !obj.is_falsy() { + for raw_key in Object::get_own_property_names(&proto).iter() { + let key = raw_key.as_string().expect("object keys are always strings"); + if contents.contains_key(&key) { + continue; + } + if let Ok(value) = Reflect::get(&obj, &raw_key) { + let value = self.collect(&value); + contents.insert(key, value); + } + } + // proto = Object::get_prototype_of(proto.as_ref()); + break; + } + + Pretty::Object { name, contents } + } + } +}