commit 28d7f26db6885ebf9d189c59a762990dcce87bc0 Author: niku Date: Thu May 18 09:56:53 2023 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..760724b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'roxy'", + "cargo": { + "args": [ + "build", + "--bin=roxy", + "--package=roxy" + ], + "filter": { + "name": "roxy", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'roxy'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=roxy", + "--package=roxy" + ], + "filter": { + "name": "roxy", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..581b37a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "roxy" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4b6d908 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "roxy" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/fixtures/index.html b/fixtures/index.html new file mode 100644 index 0000000..fc88ce0 --- /dev/null +++ b/fixtures/index.html @@ -0,0 +1 @@ +

Hello link there

diff --git a/fixtures/style.css b/fixtures/style.css new file mode 100644 index 0000000..7a6c701 --- /dev/null +++ b/fixtures/style.css @@ -0,0 +1 @@ +h1 { color: #00ff00; } .big {font-size: 12px; } h1.some#chain { width: auto; } diff --git a/src/dom.rs b/src/dom.rs new file mode 100644 index 0000000..233dcdf --- /dev/null +++ b/src/dom.rs @@ -0,0 +1,106 @@ +use core::fmt; +use std::collections::HashMap; + +#[derive(Debug)] +pub enum AttrValue { + Text(String), + Implicit, +} + +#[derive(Debug, Default)] +pub struct AttrMap(pub HashMap); + +impl fmt::Display for AttrMap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let i = self + .0 + .iter() + .map(|(key, value)| match &value { + AttrValue::Text(text) => format!("{}=\"{}\"", key, text), + AttrValue::Implicit => format!("{}", key), + }) + .collect::>(); + write!(f, "{}", i.join(" ")) + } +} + +#[derive(Debug)] +pub struct ElementData { + tag_name: String, + attributes: AttrMap, + child_nodes: Vec, +} + +#[derive(Debug)] +pub enum NodeType { + ElementNode(ElementData), + TextNode(String), + CommentNode(String), +} + +#[derive(Debug)] +pub struct Node { + node_type: NodeType, +} + +impl Node { + fn pretty_print(&self, f: &mut fmt::Formatter<'_>, indent: usize) { + let prepadding = " ".repeat(indent); + match &self.node_type { + NodeType::ElementNode(data) => { + write!(f, "{}<{}", prepadding, data.tag_name).unwrap(); + + if data.attributes.0.len() > 0 { + write!(f, " {}", data.attributes).unwrap(); + } + + if data.child_nodes.len() == 0 { + writeln!(f, ">", data.tag_name).unwrap(); + return; + } + + writeln!(f, ">").unwrap(); + + let _ = &data + .child_nodes + .iter() + .for_each(|node| node.pretty_print(f, indent + 1)); + + writeln!(f, "{}", prepadding, data.tag_name).unwrap(); + } + NodeType::TextNode(text) => { + writeln!(f, "{}{}", prepadding, text).unwrap(); + } + NodeType::CommentNode(text) => writeln!(f, "{}", prepadding, text).unwrap(), + } + } +} + +impl fmt::Display for Node { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.pretty_print(f, 0); + Ok(()) + } +} + +pub fn text(data: String) -> Node { + Node { + node_type: NodeType::TextNode(data), + } +} + +pub fn element(name: String, attrs: AttrMap, children: Vec) -> Node { + Node { + node_type: NodeType::ElementNode(ElementData { + tag_name: name, + attributes: attrs, + child_nodes: children, + }), + } +} + +pub fn comment(text: String) -> Node { + Node { + node_type: NodeType::CommentNode(text), + } +} diff --git a/src/html.rs b/src/html.rs new file mode 100644 index 0000000..88d7266 --- /dev/null +++ b/src/html.rs @@ -0,0 +1,174 @@ +#![allow(dead_code)] +use std::collections::HashMap; + +use crate::dom::{comment, element, text, AttrMap, AttrValue, Node}; + +struct Parser { + pos: usize, + input: String, +} + +impl Parser { + fn next_char(&self) -> char { + self.input[self.pos..].chars().next().unwrap() + } + + fn starts_with(&self, s: &str) -> bool { + self.input[self.pos..].starts_with(s) + } + + fn eof(&self) -> bool { + self.pos >= self.input.len() + } + + fn consume_char(&mut self) -> char { + let mut iter = self.input[self.pos..].char_indices(); + let (_, cur_char) = iter.next().unwrap(); + let (next_pos, _) = iter.next().unwrap_or((1, ' ')); + self.pos += next_pos; + return cur_char; + } + + fn consume_while(&mut self, test: F) -> String + where + F: Fn(char) -> bool, + { + let mut result = String::new(); + while !self.eof() && test(self.next_char()) { + result.push(self.consume_char()); + } + return result; + } + + fn consume_whitespace(&mut self) { + self.consume_while(|c| c.is_whitespace()); + } + + fn parse_tag_name(&mut self) -> String { + self.consume_while(|c| c.is_ascii_alphanumeric()) + } + + fn parse_node(&mut self) -> Node { + if self.starts_with("