diff --git a/Cargo.lock b/Cargo.lock index 0562150..9bf80e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -483,6 +483,12 @@ dependencies = [ "syn", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "either" version = "1.13.0" @@ -567,6 +573,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "getrandom" version = "0.2.15" @@ -613,6 +625,7 @@ dependencies = [ "gosub_html5", "gosub_renderer", "gosub_shared", + "mockall", "test-case", ] @@ -658,6 +671,7 @@ dependencies = [ "getrandom", "js-sys", "lazy_static", + "mockall", "rand 0.9.0-alpha.2", "thiserror", "url", @@ -990,6 +1004,32 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mockall" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c28b3fb6d753d28c20e826cd46ee611fda1cf3cde03a443a974043247c065a" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "341014e7f530314e9a1fdbc7400b244efea7122662c96bfa248c31da5bfb2020" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -1237,6 +1277,32 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "predicates" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -1731,6 +1797,12 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "test-case" version = "3.3.1" diff --git a/Cargo.toml b/Cargo.toml index 9e4873f..05da2e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ gosub_shared = { path = "./crates/gosub_shared", features = [] } gosub_html5 = { path = "./crates/gosub_html5", features = [] } gosub_css3 = { path = "./crates/gosub_css3", features = [] } gosub_renderer = { path = "./crates/gosub_renderer", features = [] } +mockall = "0.13.0" [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } diff --git a/README.md b/README.md index 5081fa8..55fc5a1 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ TODO / Test cases we need to cover: - [X] allow user to add nodes to DOM, build a document with a DocumentBuilder - [X] allow user to parse a html5 string and return (random) nodes in a DOM document from it. - [X] attach CSS stylesheets to the DOM -- [ ] let user query the DOM for nodes -- [ ] let user query the DOM for nodes with a specific CSS selector +- [X] let user query the DOM for nodes +- [-] let user query the DOM for nodes with a specific CSS selector - [X] allow user that adds an attribute to a (element) node automatically set the named_ids in the document. -- [ ] allow user to retrieve info from the node from its element data (like node_id, doc_handle) - [X] allow user to modify node data from document element (don't know it this is needed / feasable) -- [ ] add document task queue (optional for now) -- [X] Allow callers to update attributes in nodes in an efficient way \ No newline at end of file +- [X] add document task queue +- [X] Allow callers to update attributes in nodes in an efficient way +- [-] allow user to retrieve info from the node from its element data (like node_id, doc_handle) diff --git a/benches/node_update.rs b/benches/node_update.rs index cefb15b..de3bce1 100644 --- a/benches/node_update.rs +++ b/benches/node_update.rs @@ -1,14 +1,15 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use gosub_css3::MyCssSystem; +use gosub_css3::css3parser::MyCss3Parser; +use gosub_css3::stylesheet::{CssDeclaration, CssRule, CssStylesheet, CssValue}; use gosub_html5::document::builder::DocumentBuilder; use gosub_html5::document::document::MyDocument; use gosub_html5::html5parser::MyHtmlParser; -use gosub_renderer::backend::MyRenderBackend; -use gosub_renderer::layouter::MyLayouter; -use gosub_renderer::render_tree::MyRenderTree; -use gosub_renderer::tree_drawer::MyTreeDrawer; +use gosub_renderer::backend::ratatui::backend::MyRatatuiRenderBackend; +use gosub_renderer::layouter::basic_layouter::BasicLayouter; +use gosub_renderer::render_tree::render_tree::MyRenderTree; +use gosub_renderer::tree_drawer::tree_drawer::MyTreeDrawer; use gosub_shared::node_id::NodeId; -use gosub_shared::traits::css_system::HasCssSystem; +use gosub_shared::traits::css_system::{HasCssParser, HasCssSystem}; use gosub_shared::traits::document::{Document, HasDocument}; use gosub_shared::traits::html5_parser::{HasHtmlParser, HtmlParser}; use gosub_shared::traits::layouter::HasLayouter; @@ -22,7 +23,10 @@ use gosub_shared::traits::tree_drawer::HasTreeDrawer; struct MyModuleConfiguration; impl HasCssSystem for MyModuleConfiguration { - type CssSystem = MyCssSystem; + type CssStylesheet = CssStylesheet; + type CssRule = CssRule; + type CssDeclaration = CssDeclaration; + type CssValue = CssValue; } impl HasDocument for MyModuleConfiguration { @@ -35,7 +39,7 @@ impl HasHtmlParser for MyModuleConfiguration { } impl HasLayouter for MyModuleConfiguration { - type Layouter = MyLayouter; + type Layouter = BasicLayouter; } impl HasRenderTree for MyModuleConfiguration { @@ -47,9 +51,11 @@ impl HasTreeDrawer for MyModuleConfiguration { } impl HasRenderBackend for MyModuleConfiguration { - type RenderBackend = MyRenderBackend; + type RenderBackend = MyRatatuiRenderBackend; } +impl HasCssParser for MyModuleConfiguration { type CssParser = MyCss3Parser; } + impl ModuleConfiguration for MyModuleConfiguration {} fn bench_test_attach(c: &mut Criterion) { diff --git a/crates/gosub_html5/src/document.rs b/crates/gosub_html5/src/document.rs index 68049f1..7fd85e5 100644 --- a/crates/gosub_html5/src/document.rs +++ b/crates/gosub_html5/src/document.rs @@ -4,3 +4,4 @@ mod document_events; pub mod query_processor; pub mod tree_iterator; pub mod walker; +pub(crate) mod task_queue; diff --git a/crates/gosub_html5/src/document/document.rs b/crates/gosub_html5/src/document/document.rs index ce700aa..1b1d8f3 100644 --- a/crates/gosub_html5/src/document/document.rs +++ b/crates/gosub_html5/src/document/document.rs @@ -21,11 +21,6 @@ pub struct MyDocument { _marker: std::marker::PhantomData, } -// impl HasDocument for MyDocument { -// type Document = MyDocument; -// type Node = Node; -// } - impl HasCssSystem for MyDocument { type CssStylesheet = C::CssStylesheet; type CssRule = C::CssRule; @@ -134,3 +129,8 @@ impl Document for MyDocument { qp.query(query) } } + + +#[cfg(test)] +mod tests { +} \ No newline at end of file diff --git a/crates/gosub_html5/src/document/task_queue.rs b/crates/gosub_html5/src/document/task_queue.rs new file mode 100644 index 0000000..9e83ec6 --- /dev/null +++ b/crates/gosub_html5/src/document/task_queue.rs @@ -0,0 +1,202 @@ +use std::collections::HashMap; +use anyhow::Error; +use gosub_shared::document::DocumentHandle; +use gosub_shared::location::Location; +use gosub_shared::node_id::NodeId; +use gosub_shared::traits::document::{Document, HasDocument}; +use gosub_shared::traits::node::{ElementData, Node, NodeBuilder as _}; +use crate::node::builder::NodeBuilder; + + +/// Identifier for each task found in the queue. Can be recycled after a flush +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct TaskId(usize); + +/// Destination of the task. It could be a node, the root, or another task that is currently in the task queue +#[derive(Clone, Debug)] +pub enum TaskDestination { + /// Destination is an existing node + #[allow(dead_code)] + Node(NodeId, Option), + /// Destination is a task that is currently in the task queue (does not have a node-id yet) + Task(TaskId, Option), + /// Destination is the root node + DocumentRoot(Option), +} + +/// Actual task that needs to be completed on flush +#[derive(Clone, Debug)] +pub struct Task { + pub task_id: TaskId, + pub task: DocumentTask, + pub destination: TaskDestination, +} + +impl Task { + pub fn new(task_id: TaskId, task: DocumentTask, destination: TaskDestination) -> Self { + Self { + task_id, + task, + destination, + } + } +} + +#[derive(Clone, Debug)] +pub enum DocumentTask { + /// Create a new element node + CreateElement { + name: String, + namespace: String, + location: Location + }, + /// Create a new text node + CreateText { + content: String, + location: Location + }, + /// Create a new comment node + #[allow(dead_code)] + CreateComment { + content: String, + location: Location + }, + /// Insert an attribute into an element node + InsertAttribute { + key: String, + value: String, + location: Location + } +} + +pub struct DocumentTaskQueue { + /// Handle to the document + doc_handle: DocumentHandle, + /// Pending tasks that are needed to be flushed + pending_tasks: Vec, + /// Next task id + next_task_id: usize, + /// Mapping of task id to node id in case we reference a task id in a task's destination + task_nodes: HashMap, +} + +impl DocumentTaskQueue { + pub fn new(doc_handle: DocumentHandle) -> Self { + Self { + doc_handle, + pending_tasks: Vec::new(), + next_task_id: 0, + task_nodes: HashMap::new(), + } + } + + /// Returns true if there are pending tasks in the queue + #[allow(dead_code)] + pub fn has_pending_tasks(&self) -> bool { + !self.pending_tasks.is_empty() + } + + /// Add a new task to the queue on the given destination + pub fn add_task(&mut self, task: DocumentTask, destination: TaskDestination) -> TaskId { + self.next_task_id += 1; + let task_id = TaskId(self.next_task_id); + + self.pending_tasks.push(Task::new(task_id, task, destination)); + + task_id + } + + /// Executes all pending tasks into the document + pub fn flush(&mut self) -> Vec { + let mut errors = Vec::new(); + + for current_task in self.pending_tasks.clone() { + match current_task.task { + DocumentTask::CreateElement { name, namespace, location: _location } => { + let new_node = NodeBuilder::new_element_node(name.as_str(), namespace.as_str()); + match self.insert_node(new_node, current_task.destination, current_task.task_id) { + Ok(_) => {}, + Err(e) => errors.push(e.to_string()), + } + } + DocumentTask::CreateText { content, location: _location } => { + let new_node = NodeBuilder::new_text_node(content.as_str()); + match self.insert_node(new_node, current_task.destination, current_task.task_id) { + Ok(_) => {}, + Err(e) => errors.push(e.to_string()), + } + } + DocumentTask::CreateComment { content, location: _location } => { + let new_node = NodeBuilder::new_comment_node(content.as_str()); + match self.insert_node(new_node, current_task.destination, current_task.task_id) { + Ok(_) => {}, + Err(e) => errors.push(e.to_string()), + } + } + DocumentTask::InsertAttribute { key, value, location: _location } => { + // Find node_id based on destination + let node_id = match current_task.destination { + TaskDestination::Node(node_id, _) => node_id, + TaskDestination::Task(task_id, _) => { + if !self.task_nodes.contains_key(&task_id) { + errors.push(format!("Task id {:?} not found", task_id)); + continue; + } + self.task_nodes[&task_id] + }, + TaskDestination::DocumentRoot(_) => NodeId::root(), + }; + + let mut binding = self.doc_handle.get_mut(); + if let Some(mut node) = binding.detach_node(node_id) { + if let Some(element) = node.get_element_data_mut() { + element.add_attribute(&key, &value); + } else { + errors.push(format!("Node with id {} is not an element node", node_id)); + } + binding.update_node(node_id, node); + } else { + errors.push(format!("Node with id {} not found", node_id)); + } + } + }; + } + + self.pending_tasks.clear(); + self.task_nodes.clear(); + + errors + } + + /// Insert a node into the document + fn insert_node(&mut self, node: C::Node, destination: TaskDestination, task_id: TaskId) -> Result { + let mut binding = self.doc_handle.get_mut(); + + match destination { + TaskDestination::Node(parent_id, position) => { + let node_id = binding.register_node_at(node, parent_id, position); + + self.task_nodes.insert(task_id, node_id); + + Ok(node_id) + } + TaskDestination::Task(dest_task_id, position) => { + if ! self.task_nodes.contains_key(&dest_task_id) { + return Err(anyhow::anyhow!("Task id {:?} not found", dest_task_id)); + } + let parent_node_id = self.task_nodes[&dest_task_id]; + let node_id = binding.register_node_at(node, parent_node_id, position); + + self.task_nodes.insert(task_id, node_id); + + Ok(node_id) + } + TaskDestination::DocumentRoot(position) => { + let node_id = NodeId::root(); + binding.register_node_at(node, node_id, position); + self.task_nodes.insert(task_id, node_id); + Ok(node_id) + } + } + } +} \ No newline at end of file diff --git a/crates/gosub_html5/src/html5parser.rs b/crates/gosub_html5/src/html5parser.rs index 2c69ab5..cdde33b 100644 --- a/crates/gosub_html5/src/html5parser.rs +++ b/crates/gosub_html5/src/html5parser.rs @@ -1,11 +1,10 @@ -use crate::node::builder::NodeBuilder; use gosub_shared::document::DocumentHandle; -use gosub_shared::node_id::NodeId; +use gosub_shared::location::Location; use gosub_shared::traits::css_system::{CssParser, HasCssParser}; use gosub_shared::traits::document::Document; use gosub_shared::traits::document::HasDocument; use gosub_shared::traits::html5_parser::HtmlParser; -use gosub_shared::traits::node::{ElementData, Node, NodeBuilder as _}; +use crate::document::task_queue::{DocumentTask, DocumentTaskQueue, TaskDestination}; pub struct MyHtmlParser { doc_handle: DocumentHandle, @@ -36,8 +35,77 @@ impl HtmlParser for MyHtmlParser { └─ hello world! */ - let mut binding = self.doc_handle.get_mut(); + let mut task_queue = DocumentTaskQueue::new(self.doc_handle.clone()); + + let tid_1 = task_queue.add_task(DocumentTask::CreateElement { + name: "html".to_string(), + namespace: "html".to_string(), + location: Location::default(), + }, TaskDestination::DocumentRoot(None)); + + let _ = task_queue.add_task(DocumentTask::CreateElement { + name: "head".to_string(), + namespace: "html".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_1, None)); + + let tid_3 = task_queue.add_task(DocumentTask::CreateElement { + name: "body".to_string(), + namespace: "html".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_1, None)); + + let tid_41 = task_queue.add_task(DocumentTask::CreateElement { + name: "h1".to_string(), + namespace: "html".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_3, None)); + + let _ = task_queue.add_task(DocumentTask::CreateText { + content: "This is a header".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_41, None)); + + let tid_4 = task_queue.add_task(DocumentTask::CreateElement { + name: "p".to_string(), + namespace: "html".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_3, None)); + + let _ = task_queue.add_task(DocumentTask::CreateText { + content: "hello world!".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_4, None)); + + let _ = task_queue.add_task(DocumentTask::CreateText { + content: "prefix".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_4, Some(0))); + + let _ = task_queue.add_task(DocumentTask::InsertAttribute { + key: "class".to_string(), + value: "a b c".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_3, None)); + let _ = task_queue.add_task(DocumentTask::InsertAttribute { + key: "id".to_string(), + value: "myid".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_3, None)); + let _ = task_queue.add_task(DocumentTask::InsertAttribute { + key: "foo".to_string(), + value: "bar".to_string(), + location: Default::default(), + }, TaskDestination::Task(tid_3, None)); + + let errors = task_queue.flush(); + if !errors.is_empty() { + for error in errors { + println!("Parse Error: {}", error); + } + } +/* #[allow(type_alias_bounds)] type BuilderType = NodeBuilder; @@ -74,10 +142,12 @@ impl HtmlParser for MyHtmlParser { // Finally, reattach the node back into the document/arena binding.update_node(node4_id, node); } + */ // We also mimic some CSS style parsing here.. let mut parser = C::CssParser::new(); let stylesheet = parser.parse_str("h1 { color: red; }"); + let mut binding = self.doc_handle.get_mut(); binding.add_stylesheet(stylesheet); } } diff --git a/crates/gosub_shared/Cargo.toml b/crates/gosub_shared/Cargo.toml index 7f091d2..5d01669 100644 --- a/crates/gosub_shared/Cargo.toml +++ b/crates/gosub_shared/Cargo.toml @@ -18,10 +18,12 @@ encoding_rs = "0.8.34" derive_more = "0.99" + [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3.70" getrandom = { version = "0.2.15", features = ["js"] } web-sys = { version = "0.3.70", features = ["Performance", "Window"] } [dev-dependencies] -wasm-bindgen-test = "0.3.43" \ No newline at end of file +wasm-bindgen-test = "0.3.43" +mockall = "0.13.0" \ No newline at end of file diff --git a/crates/gosub_shared/src/document.rs b/crates/gosub_shared/src/document.rs index acaa491..c76a3f8 100644 --- a/crates/gosub_shared/src/document.rs +++ b/crates/gosub_shared/src/document.rs @@ -1,11 +1,23 @@ use crate::traits::document::Document; use crate::traits::document::HasDocument; use std::cell::{Ref, RefCell, RefMut}; +use std::fmt::{Debug, Formatter}; use std::rc::Rc; -#[derive(Debug)] pub struct DocumentHandle(pub Rc>); +impl Debug for DocumentHandle +where + C::Document: Debug +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("DocumentHandle") + .field(&self.0) + .finish() + } +} + + impl DocumentHandle { pub fn new(document: C::Document) -> Self { let handle = Self(Rc::new(RefCell::new(document))); @@ -39,25 +51,16 @@ impl PartialEq for DocumentHandle { #[cfg(test)] mod tests { + use crate::mock::{MockConfig, MockDocument}; use super::*; - struct MockDocument { - content: String, - } - - struct Mock; - - impl HasDocument for Mock { - type Document = MockDocument; - } - #[test] fn test_dochandle() { let mock_doc = MockDocument { content: "Hello".into(), + handle: None, }; - let doc_handle = DocumentHandle::new(mock_doc); - + let mut doc_handle: DocumentHandle = DocumentHandle::new(mock_doc); assert_eq!(doc_handle.get().content, "Hello"); doc_handle.get_mut().content = "World".into(); @@ -68,9 +71,11 @@ mod tests { assert_eq!(doc_handle, cloned_handle); let mock_doc = MockDocument { - content: "Hello".into(), + content: "World".into(), + handle: None, }; let doc2_handle = DocumentHandle::new(mock_doc); + assert_ne!(doc_handle, doc2_handle); } } diff --git a/crates/gosub_shared/src/lib.rs b/crates/gosub_shared/src/lib.rs index f69e391..af07c22 100644 --- a/crates/gosub_shared/src/lib.rs +++ b/crates/gosub_shared/src/lib.rs @@ -1,3 +1,5 @@ pub mod document; +pub mod location; pub mod node_id; pub mod traits; +mod mock; diff --git a/crates/gosub_shared/src/location.rs b/crates/gosub_shared/src/location.rs new file mode 100644 index 0000000..212accf --- /dev/null +++ b/crates/gosub_shared/src/location.rs @@ -0,0 +1,25 @@ +#[derive(Clone, Debug, PartialEq)] +pub struct Location { + line: usize, + column: usize, +} + +impl Location { + pub fn new(line: usize, column: usize) -> Self { + Self { line, column } + } + + pub fn line(&self) -> usize { + self.line + } + + pub fn column(&self) -> usize { + self.column + } +} + +impl Default for Location { + fn default() -> Self { + Self { line: 0, column: 0 } + } +} \ No newline at end of file diff --git a/crates/gosub_shared/src/mock.rs b/crates/gosub_shared/src/mock.rs new file mode 100644 index 0000000..bcd136d --- /dev/null +++ b/crates/gosub_shared/src/mock.rs @@ -0,0 +1,534 @@ +use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; +use anyhow::Error; +use crate::document::DocumentHandle; +use crate::node_id::NodeId; +use crate::traits::css_system::{CssDeclaration, CssRule, CssValue, HasCssSystem}; +use crate::traits::document::{Document, HasDocument}; +use crate::traits::document::query::{Condition, Query, SearchType}; +use crate::traits::node::{CommentData, DocTypeData, DocumentData, ElementData, Node, NodeData, TextData}; + +pub struct MockDocument { + pub(crate) content: String, + pub(crate) handle: Option>, +} + + +impl> Debug for MockDocument +where +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MockDocument") + .field("content", &self.content) + .field("handle", &self.handle.is_some()) + .finish() + } +} + +pub struct Mock; + +pub struct MockNode; + +pub struct MockNodeData; + +pub struct MockQuery; + +#[derive(PartialEq)] +pub enum MockSearchType { +} + +impl SearchType for MockSearchType { + fn uninitialized() -> Self { + todo!() + } + + fn find_first() -> Self { + todo!() + } + + fn find_all() -> Self { + todo!() + } +} + +pub enum MockCondition { +} + +impl Condition for MockCondition { + fn equals_tag(_tag_name: &str) -> Self { + todo!() + } + + fn equals_id(_id: &str) -> Self { + todo!() + } + + fn contains_class(_class: &str) -> Self { + todo!() + } + + fn contains_attribute(_attribute: &str) -> Self { + todo!() + } + + fn contains_child_tag(_child_tag: &str) -> Self { + todo!() + } + + fn has_parent_tag(_parent_tag: &str) -> Self { + todo!() + } +} + +impl Query for MockQuery { + type SearchType = MockSearchType; + type Condition = MockCondition; + + fn new(_search_type: Self::SearchType, _conditions: Vec) -> Self { + todo!() + } + + fn search_type(&self) -> Self::SearchType { + todo!() + } + + fn conditions(&self) -> Vec { + todo!() + } +} + +impl NodeData for MockNodeData { + // type Node = MockNode; + // type Element = MockElementData; + // type Text = MockTextData; + // type Comment = MockCommentData; + // type DocType = MockDocTypeData; + // type Document = MockDocumentData; +} + +impl From for MockNodeData { + fn from(_data: MockDocumentData) -> Self { + todo!() + } +} +impl From for MockNodeData { + fn from(_data: MockElementData) -> Self { + todo!() + } +} +impl From for MockNodeData { + fn from(_data: MockTextData) -> Self { + todo!() + } +} +impl From for MockNodeData { + fn from(_data: MockCommentData) -> Self { + todo!() + } +} +impl From for MockNodeData { + fn from(_data: MockDocTypeData) -> Self { + todo!() + } +} + +pub struct MockElementData; + +impl ElementData for MockElementData { + fn new(_name: &str, _namespace: &str) -> Self { + todo!() + } + + fn name(&self) -> &str { + todo!() + } + + fn namespace(&self) -> &str { + todo!() + } + + fn attributes(&self) -> &HashMap { + todo!() + } + + fn add_attribute(&mut self, _name: &str, _value: &str) { + todo!() + } + + fn remove_attribute(&mut self, _name: &str) { + todo!() + } + + fn classes(&self) -> &HashMap { + todo!() + } + + fn active_classes(&self) -> Vec { + todo!() + } + + fn add_class(&mut self, _name: &str, _active: bool) { + todo!() + } + + fn remove_class(&mut self, _name: &str) { + todo!() + } + + fn set_class_state(&mut self, _name: &str, _active: bool) { + todo!() + } +} + +pub struct MockTextData; + +impl TextData for MockTextData { + fn new(_content: &str) -> Self { + todo!() + } + + fn content(&self) -> &str { + todo!() + } +} + +pub struct MockCommentData; + +impl CommentData for MockCommentData { + fn new(_content: &str) -> Self { + todo!() + } + + fn content(&self) -> &str { + todo!() + } +} + +pub struct MockDocTypeData; + +impl DocTypeData for MockDocTypeData { + fn new(_name: &str, _public_id: &str, _system_id: &str) -> Self { + todo!() + } + + fn name(&self) -> &str { + todo!() + } + + fn public_id(&self) -> &str { + todo!() + } + + fn system_id(&self) -> &str { + todo!() + } +} + +pub struct MockDocumentData; + +impl DocumentData for MockDocumentData { + fn new() -> Self { + todo!() + } +} + +impl Node for MockNode { + type NodeData = MockNodeData; + type ElementData = MockElementData; + type TextData = MockTextData; + type CommentData = MockCommentData; + type DocTypeData = MockDocTypeData; + type DocumentData = MockDocumentData; + + fn new(_data: Self::NodeData) -> Self { + todo!() + } + + fn id(&self) -> Option { + todo!() + } + + fn is_registered(&self) -> bool { + todo!() + } + + fn register(&mut self, _node_id: NodeId) { + todo!() + } + + fn children(&self) -> &Vec { + todo!() + } + + fn is_renderable(&self) -> bool { + todo!() + } + + fn add_child_at_position(&mut self, _node_id: NodeId, _position: Option) { + todo!() + } + + fn get_element_data_mut(&mut self) -> Option<&mut Self::ElementData> { + todo!() + } + + fn get_element_data(&self) -> Option<&Self::ElementData> { + todo!() + } + + fn get_text_data(&self) -> Option<&Self::TextData> { + todo!() + } + + fn get_comment_data(&self) -> Option<&Self::CommentData> { + todo!() + } + + fn get_doctype_data(&self) -> Option<&Self::DocTypeData> { + todo!() + } + + fn get_document_data(&self) -> Option<&Self::DocumentData> { + todo!() + } +} + +pub struct MockCssStylesheet; + +pub struct MockCssRule; + +impl HasCssSystem for MockCssRule { + type CssStylesheet = MockCssStylesheet; + type CssRule = MockCssRule; + type CssDeclaration = MockCssDeclaration; + type CssValue = MockCssValue; +} + +impl Debug for MockCssRule { + fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +impl CssRule for MockCssRule { + fn new() -> Self { + todo!() + } + + fn add_selector(&mut self, _selector: &str) { + todo!() + } + + fn add_declaration(&mut self, _declaration: Self::CssDeclaration) { + todo!() + } + + fn selectors(&self) -> &Vec { + todo!() + } + + fn declarations(&self) -> &Vec { + todo!() + } +} + +pub struct MockCssDeclaration; + +impl HasCssSystem for MockCssDeclaration { + type CssStylesheet = MockCssStylesheet; + type CssRule = MockCssRule; + type CssDeclaration = MockCssDeclaration; + type CssValue = MockCssValue; +} + +impl Debug for MockCssDeclaration { + fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +impl CssDeclaration for MockCssDeclaration { + fn new(_property: &str, _value: Self::CssValue, _important: bool) -> Self { + todo!() + } + + fn name(&self) -> &str { + todo!() + } + + fn value(&self) -> MockCssValue { + todo!() + } + + fn important(&self) -> bool { + todo!() + } +} + +pub struct MockCssValue; + +impl Debug for MockCssValue { + fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +impl CssValue for MockCssValue { + fn unit(_value: f32, _unit: &str) -> Self { + todo!() + } + + fn keyword(_value: &str) -> Self { + todo!() + } + + fn colorvalue(_value: &str) -> Self { + todo!() + } + + fn list(_args: Vec) -> Self { + todo!() + } + + fn is_unit(&self) -> bool { + todo!() + } + + fn is_keyword(&self) -> bool { + todo!() + } + + fn is_color(&self) -> bool { + todo!() + } + + fn is_list(&self) -> bool { + todo!() + } +} + +impl HasCssSystem for MockCssStylesheet { + type CssStylesheet = MockCssStylesheet; + type CssRule = MockCssRule; + type CssDeclaration = MockCssDeclaration; + type CssValue = MockCssValue; +} + +impl Debug for MockCssStylesheet { + fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +impl crate::traits::css_system::CssStylesheet for MockCssStylesheet { + fn new() -> Self { + MockCssStylesheet + } + + fn add_rule(&mut self, _rule: MockCssRule) { + todo!() + } + + fn rules(&self) -> &Vec { + todo!() + } +} + +impl HasCssSystem for Mock { + type CssStylesheet = MockCssStylesheet; + type CssRule = MockCssRule; + type CssDeclaration = MockCssDeclaration; + type CssValue = MockCssValue; +} + +// impl HasDocument for Mock { +// } + +impl HasCssSystem for MockDocument { + type CssStylesheet = MockCssStylesheet; + type CssRule = MockCssRule; + type CssDeclaration = MockCssDeclaration; + type CssValue = MockCssValue; +} + + +impl Document for MockDocument { + type Node = MockNode; + type Query = MockQuery; + type Document = Self; + + fn new(_url: &str) -> Self { + todo!() + } + + fn register_node_at(&mut self, _node: Self::Node, _parent_id: NodeId, _position: Option) -> NodeId { + todo!() + } + + fn get_handle(&self) -> DocumentHandle { + self.handle.clone().unwrap() + } + + fn set_handle(&mut self, handle: DocumentHandle) { + self.handle = Some(handle); + } + + fn get_root_node(&self) -> Option<&Self::Node> { + todo!() + } + + fn get_node(&self, _id: NodeId) -> Option<&Self::Node> { + todo!() + } + + fn stylesheets(&self) -> &Vec { + todo!() + } + + fn add_stylesheet(&mut self, _stylesheet: C::CssStylesheet) { + todo!() + } + + fn detach_node(&mut self, _id: NodeId) -> Option { + todo!() + } + + fn update_node(&mut self, _id: NodeId, _node: Self::Node) { + todo!() + } + + fn get_url(&self) -> &str { + todo!() + } + + fn get_node_mut(&mut self, _id: NodeId) -> Option<&mut Self::Node> { + todo!() + } + + fn get_node_clone(&self, _id: NodeId) -> Option { + todo!() + } + + fn get_node_by_element_id(&self, _name: &str) -> Option { + todo!() + } + + fn query(&self, _query: &Self::Query) -> Result, Error> { + todo!() + } +} + + +pub struct MockConfig; + +impl HasCssSystem for MockConfig { + type CssStylesheet = MockCssStylesheet; + type CssRule = MockCssRule; + type CssDeclaration = MockCssDeclaration; + type CssValue = MockCssValue; +} + +impl HasDocument for MockConfig { + type Document = MockDocument; + type Node = MockNode; +} diff --git a/crates/gosub_shared/src/node_id.rs b/crates/gosub_shared/src/node_id.rs index f6727c1..e401679 100644 --- a/crates/gosub_shared/src/node_id.rs +++ b/crates/gosub_shared/src/node_id.rs @@ -1,8 +1,17 @@ +use std::fmt; +use std::fmt::{Display, Formatter}; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct NodeId { id: u32, } +impl Display for NodeId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "NodeId({})", self.id) + } +} + impl NodeId { pub fn new(id: u32) -> Self { Self { id } @@ -43,4 +52,10 @@ mod tests { let id3 = id2; assert_eq!(id3, id2); } + + #[test] + fn test_display() { + let id = NodeId::new(42); + assert_eq!(format!("{}", id), "NodeId(42)"); + } } diff --git a/docs/diag.md b/docs/diag.md new file mode 100644 index 0000000..60620b7 --- /dev/null +++ b/docs/diag.md @@ -0,0 +1,232 @@ +```mermaid +--- +title: Gosub Engine Traits +--- +classDiagram + HasCssSystem --> CssParser + + CssStylesheet --> HasCssSystem + CssRule --> HasCssSystem + CssDeclaration --> HasCssSystem + CssValue --> HasCssSystem + + namespace Css { + class HasCssSystem { + <> CssStylesheet + <> CssRule + <> CssDeclaration + <> CssValue + } + + class CssParser { + <> HasCssParser + +parse_str() + } + + class CssValue { + +new() CssValue + } + + class CssRule { + +new() CssRule + } + + class CssDeclaration { + +new() CssDeclaration + } + + class CssStylesheet { + +new() CssStylesheet + } + } + + HasDocument --> HtmlParser + HasDocument --> Document + + Query --> Document + Query --> QueryProcessor + + + Node --> HasDocument + Node --> Document + + NodeData --> Node + ElementData --> NodeData + TextData --> NodeData + CommentData --> NodeData + DocTypeData --> NodeData + DocumentData --> NodeData + + Node --> NodeBuilder + + Condition --> Query + SearchType --> Query + + HasCssSystem --> Document + + namespace Html5 { + + class Query { + <> Condition + <> SearchType + +new() Query + } + + class QueryProcessor { + <> HasDocument + <> Query + +new() QueryProcessor + +query() + } + + class Condition { + +contains_class() + +contains_id() + +contains_tag() + +contains_attr() + } + + class SearchType { + +uninitialized() + +find_first() + +find_all() + } + + class Node { + <> NodeData + <> ElementData + <> TextData + <> CommentData + <> DocTypeData + <> DocumentData + +new() Node + +id() + +children() + } + + class NodeData { + } + + class ElementData { + <> NodeData + +new() ElementData + +namespace() string + +name() string + +attributes() Vec + } + + class TextData { + <> NodeData + +new() TextData + +text() string + } + + class CommentData { + <> NodeData + +new() CommentData + +text() string + } + + class DocTypeData { + <> NodeData + +new() DocTypeData + +name() string + +public_id() string + +system_id() string + } + + class DocumentData { + <> NodeData + +new() DocumentData + } + + class NodeBuilder { + <>> Node + +new_element_node() Node + +new_text_node() Node + +new_comment_node() Node + +new_doctype_node() Node + +new_document_node() Node + } + + class HtmlParser { + <> HasHtmlParser + +new() HtmlParser + +parse_str() + } + + + class HasDocument { + <> Document + <> Node + } + + class Document { + <> HasDocument + <> Node + <> Query + +new() Document + +register_node_at() + +query() + } + + } + + + HasLayouter --> TreeDrawer + HasDocument --> RenderTree + HasRenderTree --> Layouter + HasTreeDrawer --> RenderBackend + + HasTreeDrawer --> TreeDrawer + HasRenderTree --> RenderTree + HasLayouter --> Layouter + HasRenderBackend --> RenderBackend + + namespace Layout { + class HasRenderTree { + <> RenderTree + } + + class HasLayouter { + <> Layouter + } + + class HasTreeDrawer { + <> HasLayouter + <> TreeDrawer + } + + class HasRenderBackend { + <> HasLayouter + } + + class RenderTree { + <> HasDocument + +from_document() RenderTree + +get_property() + +get_properties() + } + + class Layouter { + <> HasRenderTree + +from_render_tree() Layouter + +get_boxes() + } + + class TreeDrawer { + <> HasLayouter + +new() + +draw() + } + + class RenderBackend { + <> HasLayouter + +from_layouter() + +render_scene() + } + + } + + +``` \ No newline at end of file diff --git a/src/text-useragent.rs b/src/text-useragent.rs index 167b1bc..551e59a 100644 --- a/src/text-useragent.rs +++ b/src/text-useragent.rs @@ -16,10 +16,9 @@ use gosub_shared::traits::html5_parser::{HasHtmlParser, HtmlParser}; use gosub_shared::traits::layouter::{HasLayouter, Layouter}; use gosub_shared::traits::module_conf::ModuleConfiguration; use gosub_shared::traits::node::{ElementData, Node}; -use gosub_shared::traits::render_backend::{HasRenderBackend, RenderBackend}; +use gosub_shared::traits::render_backend::{HasRenderBackend}; use gosub_shared::traits::render_tree::{HasRenderTree, RenderTree}; use gosub_shared::traits::tree_drawer::HasTreeDrawer; -use std::thread::sleep; struct MyModuleConfiguration; @@ -117,18 +116,19 @@ fn main_do_things() { println!("-----------------------------------------------"); let q = Query::new(SearchType::find_all(), vec![Condition::equals_id("new-id")]); - if let Ok(entries) = handle.get().query(&q) { + let binding = handle.get(); + if let Ok(entries) = binding.query(&q) { for entry in entries { - println!("{:?}", entry); + println!("Query for new-id found: {:?}", entry); } } else { println!("Query failed"); } - println!("-----------------------------------------------"); - let mut render_backend = C::RenderBackend::from_layouter(layouter); - loop { - render_backend.render_scene(); - sleep(std::time::Duration::from_millis(1000)); - } + // println!("-----------------------------------------------"); + // let mut render_backend = C::RenderBackend::from_layouter(layouter); + // loop { + // render_backend.render_scene(); + // sleep(std::time::Duration::from_millis(1000)); + // } }