]> OzVa Git service - gn-parser/commitdiff
Framework
authorMax Value <greenwoodw50@gmail.com>
Fri, 2 May 2025 12:20:16 +0000 (13:20 +0100)
committerMax Value <greenwoodw50@gmail.com>
Fri, 2 May 2025 12:20:16 +0000 (13:20 +0100)
Added some type files
+ metadata
+ headings
+ paragraphs

Cargo.toml
schema.gn [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/main.rs
src/parser.rs [new file with mode: 0644]
src/types.rs [new file with mode: 0644]
src/types/heading.rs [new file with mode: 0644]
src/types/metadata.rs [new file with mode: 0644]
src/types/paragraph.rs [new file with mode: 0644]

index 251b14f8e648319c6a74f640f5450b2e561a35f1..91efa57cf67324db0b339bd3318e9ec0815959ac 100644 (file)
@@ -4,3 +4,4 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
+regex = "1.11.1"
diff --git a/schema.gn b/schema.gn
new file mode 100644 (file)
index 0000000..c6bb26c
--- /dev/null
+++ b/schema.gn
@@ -0,0 +1,54 @@
+# The Goodnight Markdown Specification
+
+Version 0.1.0
+
+## Features
+
+- Headings are unchanged from vanilla markdown.
+- Paragraphs are unchanged from vanilla markdown.
+- Ordered lists are unchanged from vanilla markdown.
+- URLs and email addresses are unchanged from vanilla markdown.
+- Images are unchanged from vanilla markdown.
+- Inline code with the backtick character is unchanged from vanilla markdown.
+- Escaping via the backslash character is unchanged from vanilla markdown.
+- Links are unchanged from vanilla markdown but are all reference style.
+- Block quotes are indented.
+- Code blocks are only used with 3 backticks before and after.
+- Line breaks have been removed.
+- Horizontal rules have been removed.
+- Bold styling will work only with a single asterisk character.
+- Italic styling will work only with a single underscore character.
+- Unordered lists will work only with the hyphen character.
+
+### Macros
+
+To generate the title, table of contents and bibliography, the following macros can be used:
+
+- `[TITLE]` generates the title block
+- `[TOC]` generates the table of contents
+- `[REFS]` generates the bibliography
+
+### Metadata
+
+The metadata format used is the YAML format. To generate the title, the standard LaTeX keys of "title", "author" and "date", should be used. If "style" is used, the parser will introduce the stylesheet with the relevant extension. For example, `style: stylesheet` will introduce `stylesheet.css` for html and `stylesheet.sty` for LaTeX.
+
+```
+---
+title: The Goodnight Markdown Specification
+author: William Greenwood
+---
+```
+
+### Referencing
+
+All referencing in Goodnight is APA 7th. It can be done similar to referenced links with a syntax similar to YAML metadata:
+
+```
+"Placeholder quote" [1]
+
+[1]
+title: Placeholder title
+author: Placeholder name
+```
+
+If one of the keys is placed in square brackets after the in-text reference, it will be printed. If only the reference id is used, the full in-text reference will be used. If something that is not one of the keys is placed in square brackets within the full refrence, for example a page number.
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..61aefec
--- /dev/null
@@ -0,0 +1,41 @@
+use std::fs;
+use std::error::Error;
+
+pub struct Config {
+       file_path: String
+}
+
+impl Config {
+       pub fn build(args: &[String]) -> Result<Config, &'static str> {
+               if args.len() < 2 {
+                       return Err("not enough arguments");
+               }
+
+               let file_path = args[1].clone();
+
+               Ok(Config { file_path })
+       }
+}
+
+pub fn run(config: Config) -> Result<(), Box<dyn Error>>  {
+       let contents = fs::read_to_string(config.file_path)
+       .expect("Should have been able to read the file");
+
+       let new: Vec<u8>;
+
+       for line in contents.lines() {
+               println!("{}", line);
+       }
+
+       Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+       use super::*;
+
+       #[test]
+       fn placeholder() {
+               assert!(true);
+       }
+}
index e7a11a969c037e00a796aafeff6258501ec15e9a..49767face6ad8aed1f765ded6e9f82a148c0dab6 100644 (file)
@@ -1,3 +1,22 @@
+use std::env;
+use std::process;
+use gn_parser::Config;
+
+pub mod types;
+pub mod parser;
+
 fn main() {
-    println!("Hello, world!");
+       let args: Vec<String> = env::args().collect();
+
+       let config = Config::build(&args).unwrap_or_else(|err| {
+               println!("Problem parsing arguments: {err}");
+               process::exit(1);
+       });
+
+       if let Err(e) = gn_parser::run(config) {
+               println!("Application error: {e}");
+               process::exit(1);
+       }
+
 }
+
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644 (file)
index 0000000..ec3c3ec
--- /dev/null
@@ -0,0 +1,7 @@
+use crate::types::{Renderable, paragraph, metadata};
+
+pub fn parse (string: String) -> impl Renderable {
+       paragraph::Paragraph{ text: string.to_string() }
+}
+
+
diff --git a/src/types.rs b/src/types.rs
new file mode 100644 (file)
index 0000000..c66a6ab
--- /dev/null
@@ -0,0 +1,11 @@
+pub mod paragraph;
+pub mod heading;
+pub mod metadata;
+
+pub trait Renderable {
+       fn render_latex(&self) -> String;
+       fn render_html(&self) -> String;
+       fn render_gemtext(&self) -> String;
+}
+
+
diff --git a/src/types/heading.rs b/src/types/heading.rs
new file mode 100644 (file)
index 0000000..dd184a0
--- /dev/null
@@ -0,0 +1,66 @@
+use crate::types::Renderable;
+
+// submodule for handling headings
+
+pub struct Heading {
+       pub text: String,
+       pub level: u8
+}
+
+impl Renderable for Heading {
+       fn render_latex (&self) -> String {
+               let text = self.text.to_string();
+               let section = match self.level {
+                       1 => "section",
+                       2 => "subsection",
+                       3 => "subsubsection",
+                       4 => "paragraph",
+                       _ => "subparagraph"
+               };
+               format!("\\{section}{{{text}}}")
+       }
+
+       fn render_html (&self) -> String {
+               let text = self.text.to_string();
+               let section = match self.level {
+                       1 => "h1",
+                       2 => "h2",
+                       3 => "h3",
+                       4 => "h4",
+                       5 => "h5",
+                       _ => "h6"
+               };
+               format!("<{section}>{text}</{section}>")
+       }
+
+       fn render_gemtext (&self) -> String {
+               let text = self.text.to_string();
+               let section = match self.level {
+                       1 => "#",
+                       2 => "##",
+                       _ => "###"
+               };
+               format!("{section} {text}")
+       }
+}
+
+#[cfg(test)]
+mod tests {
+       use super::*;
+
+       #[test]
+       fn test_latex_parser() {
+               let heading = Heading{ text: String::from("Goodnight"), level: 3};
+               assert_eq!(&heading.render_latex(), "\\subsubsection{Goodnight}");
+       }
+       #[test]
+       fn test_html_parser() {
+               let heading = Heading{ text: String::from("Goodnight"), level: 5};
+               assert_eq!(&heading.render_html(), "<h5>Goodnight</h5>");
+       }
+       #[test]
+       fn test_gemtext_parser() {
+               let heading = Heading{ text: String::from("Goodnight"), level: 2};
+               assert_eq!(&heading.render_gemtext(), "## Goodnight");
+       }
+}
diff --git a/src/types/metadata.rs b/src/types/metadata.rs
new file mode 100644 (file)
index 0000000..8e091eb
--- /dev/null
@@ -0,0 +1,66 @@
+use std::collections::HashMap;
+use regex::Regex;
+
+use crate::types::Renderable;
+
+pub struct Metadata {
+       pub title: String,
+       pub author: String,
+       pub date: String,
+       pub keys: HashMap<String, String>
+}
+
+impl Metadata {
+       pub fn new() -> Self {
+               Metadata {
+                       title: String::from("Title"),
+                       author: String::from("Author"),
+                       date: String::from("Today"),
+                       keys: HashMap::new()
+               }
+       }
+       pub fn from(string: String) -> (Self, String) {
+               parse_metadata(string)
+       }
+}
+
+impl Renderable for Metadata {
+       fn render_latex (&self) -> String {
+       }
+
+       fn render_html (&self) -> String {
+       }
+
+       fn render_gemtext (&self) -> String {
+       }
+}
+
+fn parse_metadata (string: String) -> (Metadata, String) {
+       let mut text = string.to_string();
+       let mut metadata = String::from("");
+
+       // there can only be one metadata block at the moment due to how the block is removed
+       // from the wider string. This is not essecial but would be nice
+       let re = Regex::new(r"---[[\w\W]&&[^-]]*").unwrap();
+       for mat in re.find_iter(&string) {
+               metadata.push_str(mat.as_str());
+               text.replace_range(mat.range(), "");
+       }
+
+       let mut data = Metadata::new();
+       for line in metadata.lines() {
+               match line.split_once(':') {
+                       Some((key,value)) => {
+                               match key {
+                                       "title" => {data.title = String::from(value)},
+                                       "author" => {data.author = String::from(value)},
+                                       "date" => {data.date = String::from(value)},
+                                       _ => {data.keys.insert(String::from(key), String::from(value));}
+                               }
+                       },
+                       None => ()
+               }
+       }
+
+       (Metadata::new(), text)
+}
diff --git a/src/types/paragraph.rs b/src/types/paragraph.rs
new file mode 100644 (file)
index 0000000..5829879
--- /dev/null
@@ -0,0 +1,70 @@
+use crate::types::Renderable;
+
+// submodule for handling paragraphs
+
+pub struct Paragraph {
+       pub text: String
+}
+
+impl Renderable for Paragraph {
+
+       fn render_latex (&self) -> String {
+               let text = parse_inline(&self.text, '*', "\\textbf{", "}");
+               let text = parse_inline(&text, '_', "\\textit{", "}");
+               parse_inline(&text, '`', "\\texttt{", "}")
+       }
+
+       fn render_html (&self) -> String {
+               let text = parse_inline(&self.text, '*', "<b>", "</b>");
+               let text = parse_inline(&text, '_', "<em>", "</em>");
+               parse_inline(&text, '`', "<code>", "</code>")
+       }
+
+       fn render_gemtext (&self) -> String {
+               let text = parse_inline(&self.text, '*', "", "");
+               let text = parse_inline(&text, '_', "", "");
+               parse_inline(&text, '`', "", "")
+       }
+}
+
+fn parse_inline (string: &str, symbol: char, front: &str, back: &str) -> String {
+       let mut text = string.to_string();
+       // makes a cycling itterable of the front and back replacement
+       // so that we always get the right one moving left to right
+       let mut front_back = [back, front].into_iter().cycle();
+       for (index, _) in string.rmatch_indices(symbol) {
+               println!("{}", index);
+               if index == 0 || !(&text[index - 1 .. index] == "\\") {
+                       text.replace_range(index .. index + 1, front_back.next().unwrap());
+               }
+       }
+       text
+}
+
+#[cfg(test)]
+mod tests {
+       use super::*;
+
+       #[test]
+       fn test_formatting_parser() {
+               let string = parse_inline("`G_ood_n*igh*t`", '*', "B", "B");
+               let string = parse_inline(&string, '_', "I", "I");
+               let string = parse_inline(&string, '`', "C", "C");
+               assert_eq!(string, "CGIoodInBighBtC");
+       }
+       #[test]
+       fn test_latex_parser() {
+               let paragraph = Paragraph{ text: String::from("*Goodnight*")};
+               assert_eq!(&paragraph.render_latex(), "\\textbf{Goodnight}");
+       }
+       #[test]
+       fn test_html_parser() {
+               let paragraph = Paragraph{ text: String::from("_Goodnight_")};
+               assert_eq!(&paragraph.render_html(), "<em>Goodnight</em>");
+       }
+       #[test]
+       fn test_gemtext_parser() {
+               let paragraph = Paragraph{ text: String::from("`Goodnight`")};
+               assert_eq!(&paragraph.render_gemtext(), "Goodnight");
+       }
+}