diff --git a/.gitignore b/.gitignore index be655f3..9c2ffa9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ parser /.vscode /*.html +/target +/target/* diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..88f2962 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "MarkdownToHtmlTranspiler" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cd62258 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "MarkdownToHtmlTranspiler" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/flake.nix b/flake.nix index 07909a0..69ff3ff 100644 --- a/flake.nix +++ b/flake.nix @@ -20,6 +20,10 @@ gcc gdb stdenv + + rustup + rustc + cargo ]; # Define the shell that will be executed. diff --git a/src/boilerplate.rs b/src/boilerplate.rs new file mode 100644 index 0000000..760eff8 --- /dev/null +++ b/src/boilerplate.rs @@ -0,0 +1,99 @@ +/// This is an old code example, moving to using enums now + +pub trait Node { + fn to_html(&self) -> String; + fn is_empty(&self) -> bool; + + fn add_child(&mut self, _: Box) { + panic!("Cannot add children to this node.") + } + + fn children(&self) -> Option<&[Box]> { + None + } +} + +pub struct DocumentNode { + pub children: Vec>, +} + +pub struct HeadingNode { + pub size: u8, + pub children: Vec>, +} + +pub struct ParagraphNode { + pub children: Vec>, +} + +impl Node for DocumentNode { + fn to_html(&self) -> String { + let inner_html = self + .children + .iter() + .map(|child| child.to_html()) + .collect::(); + format!( + "\n\n\n\n\nDocument\n\n{}\n", + inner_html + ) + } + + fn is_empty(&self) -> bool { + self.children.is_empty() + } + + fn add_child(&mut self, child: Box) { + self.children.push(child); + } + + fn children(&self) -> Option<&[Box]> { + Some(&self.children) + } +} + +impl Node for HeadingNode { + fn to_html(&self) -> String { + let inner_html = self + .children + .iter() + .map(|child| child.to_html()) + .collect::(); + format!("{}", self.size, inner_html, self.size) + } + + fn is_empty(&self) -> bool { + self.children.is_empty() + } + + fn add_child(&mut self, child: Box) { + self.children.push(child); + } + + fn children(&self) -> Option<&[Box]> { + Some(&self.children) + } +} + +impl Node for ParagraphNode { + fn to_html(&self) -> String { + let inner_html = self + .children + .iter() + .map(|child| child.to_html()) + .collect::(); + format!("

{}

", inner_html) + } + + fn is_empty(&self) -> bool { + self.children.is_empty() + } + + fn add_child(&mut self, child: Box) { + self.children.push(child); + } + + fn children(&self) -> Option<&[Box]> { + Some(&self.children) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1d14bc6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,154 @@ +pub enum Node { + // Structure Nodes + Document { children: Vec }, + Heading { level: u8, children: Vec }, + Paragraph { children: Vec }, + List { ordered: bool, children: Vec }, + ListItem { children: Vec }, + CodeBlock { children: Vec }, + BlockQuote { children: Vec }, + + // Inline Nodes + Text { content: String }, + Bold { content: String }, + Italic { content: String }, + BoldItalic { content: String }, + Code { content: String }, + Link { href: String, content: String }, +} + +impl Node { + pub fn to_html(&self) -> String { + match self { + // Structure nodes + Node::Document { children } => { + let inner = children.iter().map(|x| x.to_html()).collect::(); + format!( + "\n\n\n\n\nDocument\n\n{}\n", + inner + ) + } + Node::Heading { level, children } => { + let inner = children.iter().map(|x| x.to_html()).collect::(); + format!("{}", inner, level = level) + } + Node::Paragraph { children } => { + let inner = children.iter().map(|x| x.to_html()).collect::(); + format!("

{}

", inner) + } + Node::List { ordered, children } => { + let inner = children.iter().map(|x| x.to_html()).collect::(); + let tag = if *ordered { "ol" } else { "ul" }; + format!("<{tag}>{}", inner, tag = tag) + } + Node::ListItem { children } => { + let inner = children.iter().map(|x| x.to_html()).collect::(); + format!("
  • {}
  • ", inner) + } + Node::CodeBlock { children } => { + let inner = children.iter().map(|x| x.to_html()).collect::(); + format!("{}", inner) + } + Node::BlockQuote { children } => { + let inner = children.iter().map(|x| x.to_html()).collect::(); + format!("
    {}
    ", inner) + } + + // Inline nodes + Node::Text { content } => format!("{}", content), + Node::Bold { content } => format!("{}", content), + Node::Italic { content } => format!("{}", content), + Node::BoldItalic { content } => format!("{}", content), + Node::Code { content } => format!("{}", content), + Node::Link { href, content } => format!("{}", href, content), + } + } + + /// Determines if a node is empty. For structure nodes (those with `children`) this will be + /// true when there are no elements in the list. For inline nodes (those without `children`) + /// this will be true when the string content of all fields are blank. + pub fn is_empty(&self) -> bool { + match self { + // Structure nodes + Node::Document { children } + | Node::Heading { level: _, children } + | Node::Paragraph { children } + | Node::List { + ordered: _, + children, + } + | Node::ListItem { children } + | Node::CodeBlock { children } + | Node::BlockQuote { children } => children.is_empty(), + + // Inline nodes + Node::Text { content } + | Node::Bold { content } + | Node::Italic { content } + | Node::BoldItalic { content } + | Node::Code { content } => content.is_empty(), + + // Special rules + Node::Link { href, content } => content.is_empty() && href.is_empty(), + } + } + + /// Returns Some children if they exist, otherwise None will be returned. For nodes that do not + /// have children, None will be returned. + pub fn children(&self) -> Option<&[Node]> { + match self { + Node::Document { children } + | Node::Heading { level: _, children } + | Node::Paragraph { children } + | Node::List { + ordered: _, + children, + } + | Node::ListItem { children } + | Node::CodeBlock { children } + | Node::BlockQuote { children } => Some(&children), + + _ => None, + } + } + + /// Add a child to the back of the list of children. If the node is a type which does not allow + /// children to be added, this function will panic. + pub fn add_child(&mut self, child: Node) { + match self { + Node::Document { children } + | Node::Heading { level: _, children } + | Node::Paragraph { children } + | Node::List { + ordered: _, + children, + } + | Node::ListItem { children } + | Node::CodeBlock { children } + | Node::BlockQuote { children } => children.push(child), + + _ => panic!("Can't add child to this node type."), + }; + } +} + +pub fn main() { + let root = Node::Document { + children: vec![Node::Heading { + level: 1, + children: vec![ + Node::Text { + content: String::from("Heading level 1"), + }, + Node::Bold { + content: String::from(" this part is bold"), + }, + Node::Italic { + content: String::from(" this part is italic"), + }, + ], + }], + }; + + println!("{}", root.to_html()) +}