]> OzVa Git service - gn-parser/commitdiff
See last commit
authorMax Value <greenwoodw50@gmail.com>
Wed, 7 May 2025 17:52:13 +0000 (18:52 +0100)
committerMax Value <greenwoodw50@gmail.com>
Wed, 7 May 2025 17:52:13 +0000 (18:52 +0100)
18 files changed:
Cargo.toml
schema.gn
src/inline.rs
src/lib.rs
src/parser.rs
src/types.rs
src/types/blockquote.rs [new file with mode: 0644]
src/types/contents.rs [new file with mode: 0644]
src/types/document.rs
src/types/heading.rs
src/types/list.rs
src/types/metadata.rs
src/types/paragraph.rs
src/types/preformatted.rs [new file with mode: 0644]
src/types/reference_list.rs
src/types/reference_list/name_list.rs
src/types/reference_list/reference.rs
src/types/reference_list/work.rs

index 710695a7b4e14650c800b5dc16a90ae19db006f9..8ea5d8cccfbff42267b19d2bb4f567eca1eacedb 100644 (file)
@@ -1,6 +1,8 @@
 [package]
 name = "gn-parser"
 version = "0.1.0"
+authors = ["William Greenwood <greenwoodw50@gmail.com>"]
+description = "Goodnight Markdown parser. A markdown format for refereced works."
 edition = "2021"
 
 [dependencies]
index 6128703c7a646750965757bbab9df0fe3f89ec60..8937f378ecfaa32789346ee445e232c9a9412022 100644 (file)
--- a/schema.gn
+++ b/schema.gn
@@ -1,38 +1,71 @@
 ---
 title: The Goodnight Markdown Specification - Version 0.1.0
 author: Goodnight Publishing
+style: default
 ---
 
 [META]
 
 [TOC]
 
-[REFS]
+# Features
+
+Along with the features below, some features where removed:
 
-## Features
-
-- Headings are unchanged from vanilla markdown (6 levels).
-- Paragraphs 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.
+
+I don't belive in line breaks and horizontal rules are the responsibility of the stylesheet.
+
+## Headings
+
+Headings are prefixed with the `#` hastag character as with standard markdown. There must not be a space before the `#` character. A space may or may not be put after the `#`. Inline formatting will be properly parsed. Inline references will be properly parsed.
+
+## Links
+
+Still to be implemented
+
+## Email addresses
+
+Still to be implemented
+
+## Paragraphs
+
+`text about paragraphs`
+
+## Images
+
+Still to be implemented
+
+## Inline code
+
+Inline code blocks can be used with the `\`` backtick character.
+
+## Code blocks
+
+Code blocks can be used with 3 backtick        characters: `\`\`\``. This sequence must start on the first character on a new line.
+
+## Block quotes
+
+Block quotes are started with the tab character `\t`. They can contain any other elements.
+
+       Here is an example of a blockquote.
+
+       - Here is a list element in a blockquote
+
+       Tabbed lines with a space between them are joined into one single blockquote. If two different blockquotes are needed next to each other, a backslash at the first character of a newline can split the two up.
+
+## Inline formatting
+
 - Bold styling will work only with a single asterisk character.
 - Italic styling will work only with a single underscore character.
+
+## Lists
+
 - Unordered lists will work only with the hyphen character.
 - Ordered lists will work only with the period character.
 
-. Test
-. Of
-. Ordered
-. List
-
-### Macros
+## Macros
 
 To generate the title, table of contents and bibliography, the following macros can be used:
 
@@ -40,7 +73,7 @@ To generate the title, table of contents and bibliography, the following macros
 - `[TOC]` generates the table of contents
 - `[META]` generates the bibliography
 
-### Metadata
+## 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.
 
@@ -51,16 +84,39 @@ author: William Greenwood
 ---
 ```
 
-### Referencing
+## Referencing
 
-All referencing in Goodnight is APA 7th. It can be done similar to referenced links with a syntax similar to YAML metadata:
+All referencing in Goodnight is APA 7th [lindsey2015]. It can be done similar to referenced links with a syntax similar to YAML metadata:
 
 ```
 "Placeholder quote" [1]
 
-[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.
+
+The keys are as follows:
+
+. `author`. The authors or entities behind the work seperated by the word "and". Wrap entity names in curly brackets
+. `title`
+. `year` (optional)
+. `publisher` (optional)
+. `url` (optional)
+. `journaltitle`: Title of the book or journal the work is in (optional)
+. `issuetitle`: Issue title of the book or journal the work is in (optional, must be used with `journaltitle`)
+. `editor`: Author of the book or journal the work is in (optional, must be used with `journaltitle`)
+. `volume`: Volume of the book or journal the work is in (optional, must be used with `journaltitle`)
+
+[REFS]
+
+[lindsey2015]
+author: Marley-Vincent Lindsey
+editor: William Value
+title: The Politics of Pokémon. Socialized Gaming, Religious Themes and the Construction of Communal Narratives
+journaltitle: Heidelberg Journal of Religions on the Internet
+issuetitle: Religion in Digital Games Reloaded. Immersion into the Field
+year: 2015
+volume: 7th
index 5d2873c2949c71d9df1e49dbd5f2e440dabf04b5..d1ec55dfa295d5650561eeac15145ecd1705b0d7 100644 (file)
@@ -14,7 +14,7 @@ fn parse_inline (string: &str, symbol: char, front: &str, back: &str) -> String
 pub fn parse_inline_latex (string: &str) -> String {
        let mut text = parse_inline(string, '*', "\\textbf{", "}");
        text = parse_inline(&text, '_', "\\textit{", "}");
-       parse_inline(&text, '`', "\\texttt{", "}")
+       parse_inline(&text, '`', "\\texttt{", "}").replace("#", "\\#")
 }
 
 pub fn parse_inline_html (string: &str) -> String {
index e9cb1f0dfceec1135f507613613c8c889bbb9e3c..e1b4ed5661fef3353ee5d1f4ce14dc5c2fcc478a 100644 (file)
@@ -10,18 +10,20 @@ use crate::types::Renderable;
 
 
 pub struct Config {
-       file_path: String
+       file_path: String,
+       format: String
 }
 
 impl Config {
        pub fn build(args: &[String]) -> Result<Config, &'static str> {
-               if args.len() < 2 {
+               if args.len() < 3 {
                        return Err("not enough arguments");
                }
 
                let file_path = args[1].clone();
+               let format = args[2].clone();
 
-               Ok(Config { file_path })
+               Ok(Config { file_path, format })
        }
 }
 
@@ -30,7 +32,14 @@ pub fn run(config: Config) -> Result<(), Box<dyn Error>>  {
        .expect("Should have been able to read the file");
 
        let document = Document::from(contents);
-       println!("{}", document.render_html());
+       let format = config.format;
+       println!("{}", match format.as_str() {
+               "html" => {document.render_html()},
+               "latex" => {document.render_latex()},
+               "gemtext" => {document.render_gemtext()},
+               "metadata" => {document.render_metadata()},
+               _ => {String::from("?")}
+       });
 
        Ok(())
 }
index 2944eb5edaad9813b957189dcbbecd24c3c079fd..f253e798b7a7a8056b7656d0224365f483b8a76f 100644 (file)
@@ -5,6 +5,9 @@ use crate::types::heading::Heading;
 use crate::types::paragraph::Paragraph;
 use crate::types::metadata::Metadata;
 use crate::types::reference_list::ReferenceList;
+use crate::types::preformatted::Preformatted;
+use crate::types::blockquote::BlockQuote;
+use crate::types::contents::Contents;
 use crate::types::list::{Item, List};
 
 // this gets the full text of the file from the document class (with the reference list and metadata removed)
@@ -12,45 +15,58 @@ use crate::types::list::{Item, List};
 
 pub fn parse_body (string: String, metadata: Metadata, reference_list: Option<ReferenceList>) -> Vec<Box<dyn Renderable>> {
        let mut document: Vec<Box<dyn Renderable>> = Vec::new();
-       let mut clean_string = string.to_string();
-       let mut metadata: Option<Metadata> = Some(metadata);
-       let mut reference_list = reference_list;
+       let metadata = metadata;
+       let reference_list = reference_list;
 
-       // bellow matches and cleans up all line breaks in the middle of paragraphs
-       let re = Regex::new(r"[\w\d][ \t]*\n[\w&&\D]").unwrap();
-       for mat in re.find_iter(&string).collect::<Vec<_>>().iter().rev() {
-               clean_string.replace_range(mat.start()+1 .. mat.end()-1, "");
-       }
-
-       let mut lines = clean_string.lines().peekable();
+       let mut lines = string.lines().peekable();
        while let Some(line) = lines.next() {
                if !(line == "") {
                        let first = &(String::from(line) + "       ")[0..7].chars().collect::<Vec<_>>();
                        match (first[0], first[1], first[2], first[3], first[4], first[5]) {
+
+                               // match all code blocks
+                               ('`', '`', '`', ..) => {
+                                       let mut text = String::from(&line[3..]);
+                                       while match lines.peek() {
+                                               Some(string) => {
+                                                       if string.starts_with("```") {false}
+                                                       else {
+                                                               true
+                                                       }
+                                               },
+                                               None => false
+                                       } {
+                                               text += lines.next().unwrap();
+                                               text += "\n";
+                                       }
+                                       document.push(Box::new(Preformatted{text}));
+                                       lines.next(); // consume the final ```
+                               }
+
                                // match all heading patterns
                                ('#', '#', '#', '#', '#', '#') => {
-                                       document.push(Box::new(Heading{text: line[6..].to_string(), level: 6}))
+                                       document.push(Box::new(Heading{text: parse_reference(&reference_list, line[6..].to_string()), level: 6}))
                                },
                                ('#', '#', '#', '#', '#', ..) => {
-                                       document.push(Box::new(Heading{text: line[5..].to_string(), level: 5}))
+                                       document.push(Box::new(Heading{text: parse_reference(&reference_list, line[5..].to_string()), level: 5}))
                                },
                                ('#', '#', '#', '#', ..) => {
-                                       document.push(Box::new(Heading{text: line[4..].to_string(), level: 4}))
+                                       document.push(Box::new(Heading{text: parse_reference(&reference_list, line[4..].to_string()), level: 4}))
                                },
                                ('#', '#', '#', ..) => {
-                                       document.push(Box::new(Heading{text: line[3..].to_string(), level: 3}))
+                                       document.push(Box::new(Heading{text: parse_reference(&reference_list, line[3..].to_string()), level: 3}))
                                },
                                ('#', '#', ..) => {
-                                       document.push(Box::new(Heading{text: line[2..].to_string(), level: 2}))
+                                       document.push(Box::new(Heading{text: parse_reference(&reference_list, line[2..].to_string()), level: 2}))
                                },
                                ('#', ..) => {
-                                       document.push(Box::new(Heading{text: line[1..].to_string(), level: 1}))
+                                       document.push(Box::new(Heading{text: parse_reference(&reference_list, line[1..].to_string()), level: 1}))
                                },
 
                                // match all list patterns
                                ('-', ..) | ('.', ..) => {
                                        let matching = &line[..1];
-                                       let mut items: Vec<Item> = vec![Item{text: line[1..].to_string()}];
+                                       let mut items: Vec<Item> = vec![Item{text: parse_reference(&reference_list, line[1..].to_string())}];
                                        while match lines.peek() {
                                                Some(string) => {
                                                        if string == &"" {false}
@@ -60,36 +76,66 @@ pub fn parse_body (string: String, metadata: Metadata, reference_list: Option<Re
                                                },
                                                None => false
                                        } {
-                                               items.push(Item{text: lines.next().unwrap()[1..].to_string()});
+                                               items.push(Item{text: parse_reference(&reference_list, lines.next().unwrap()[1..].to_string())});
                                        }
                                        document.push(Box::new(List{items, ordered: matching == "."}))
                                }
 
                                // match all insert sequences like the title
                                ('[', 'R', 'E', 'F', 'S', ']') => {
-                                       match reference_list {
+                                       match reference_list.clone() {
                                                Some(bibliography) => {
                                                        document.push(Box::new(bibliography));
-                                                       reference_list = None;
                                                },
                                                None => ()
                                        }},
-                               ('[', 'M', 'E', 'T', 'A', ']') => {
-                                       match metadata {
-                                               Some(title) => {
-                                                       document.push(Box::new(title));
-                                                       metadata = None;
-                                               },
-                                               None => ()
-                                       }
-                               },
+                               ('[', 'M', 'E', 'T', 'A', ']') => {document.push(Box::new(metadata.clone()))},
                                ('[', 'T', 'O', 'C', ']', ..) => {
-                                       document.push(Box::new(Paragraph{text: String::from("This is where the table of contents will go")}))
+                                       document.push(Box::new(Contents{elements: None}))
                                },
 
+                               // match all block quotes
+                               ('\t', ..) => {
+                                       let mut text = String::from(&line[1..]);
+                                       while match lines.peek() {
+                                               Some(string) => {
+                                                       if string.is_empty() {
+                                                               text += "\n";
+                                                               text += &lines.next().unwrap();
+                                                               true
+                                                       }
+                                                       else if &string[0..1] == "\t" {
+                                                               text += "\n";
+                                                               text += &lines.next().unwrap()[1..];
+                                                               true
+                                                       }
+                                                       else {false}
+                                               },
+                                               None => false
+                                       } {}
+                                       document.push(
+                                               Box::new(BlockQuote{elements: parse_body(text, metadata.clone(), reference_list.clone())}))
+                               }
+
                                // make everything else a paragraph
                                _ => {
-                                       document.push(Box::new(Paragraph{text: line.to_string()}))
+                                       let mut text = String::from(line);
+                                       while match lines.peek() {
+                                               Some(string) => {
+                                                       // regex could use some work
+                                                       // will fail if there is a single character on the next line down
+                                                       let re = Regex::new(r"[[\w\W]&&[^-`\.#\n]][^\.]+").unwrap();
+                                                       re.is_match_at(&string, 0)
+                                               },
+                                               None => false
+                                       } {
+                                               text += " ";
+                                               text += lines.next().unwrap();
+                                       }
+                                       document.push(
+                                               Box::new(Paragraph{
+                                                       text: parse_reference(&reference_list, text.to_string())
+                                               }))
                                }
                        }
                }
@@ -97,3 +143,11 @@ pub fn parse_body (string: String, metadata: Metadata, reference_list: Option<Re
 
        document
 }
+
+fn parse_reference(reference_list: &Option<ReferenceList>, text: String) -> String {
+       match reference_list.clone() {
+               Some(bibliography) => {
+                       bibliography.parse_inline(text)},
+                       None => text
+       }
+}
index 870185390b8ac498c9525bf4b56e27958dfeac61..4c2bccaeefb8994ed2b241d6175880f1ec7d1afc 100644 (file)
@@ -4,6 +4,9 @@ pub mod metadata;
 pub mod reference_list;
 pub mod document;
 pub mod list;
+pub mod preformatted;
+pub mod blockquote;
+pub mod contents;
 
 pub trait Renderable {
        fn render_latex(&self) -> String;
@@ -11,4 +14,3 @@ pub trait Renderable {
        fn render_gemtext(&self) -> String;
 }
 
-
diff --git a/src/types/blockquote.rs b/src/types/blockquote.rs
new file mode 100644 (file)
index 0000000..1d94fdd
--- /dev/null
@@ -0,0 +1,31 @@
+use crate::types::Renderable;
+
+pub struct  BlockQuote {
+       pub elements: Vec<Box<dyn Renderable>>
+}
+
+impl Renderable for BlockQuote {
+       fn render_latex(&self) -> String {
+               let mut full_text = String::from("\\begin{quote}\n");
+               for element in &self.elements {
+                       full_text.push_str(&element.render_latex());
+               }
+               full_text + "\\end{quote}\n\n"
+       }
+
+       fn render_html(&self) -> String {
+               let mut full_text = String::from("<blockquote>");
+               for element in &self.elements {
+                       full_text.push_str(&element.render_html());
+               }
+               full_text + "</blockquote>"
+       }
+
+       fn render_gemtext(&self) -> String {
+               let mut full_text = String::new();
+               for element in &self.elements {
+                       full_text.push_str(&(String::from("\t") + &element.render_gemtext()));
+               }
+               full_text
+       }
+}
diff --git a/src/types/contents.rs b/src/types/contents.rs
new file mode 100644 (file)
index 0000000..2b3d6b1
--- /dev/null
@@ -0,0 +1,25 @@
+use crate::types::{heading::Heading, Renderable};
+
+pub struct Contents {
+       pub elements: Option<Vec<Heading>>
+}
+
+impl Renderable for Contents {
+       fn render_latex(&self) -> String {
+               String::from("\\tableofcontents\n\n")
+       }
+
+       fn render_html(&self) -> String {
+               String::from("\
+<h2>Table of Contents</h2>
+<p>This is where the table of contents will go</p>
+               ")
+       }
+
+       fn render_gemtext(&self) -> String {
+               String::from("\
+## Table of Contents
+
+This is where the Table of Contents will go.\n\n")
+       }
+}
index 90ab2cbd641e14f7d2744ac6f45a9f625aad9077..25b7e6507f4698aa147b56145d2a146d4e36d304 100644 (file)
@@ -1,32 +1,37 @@
 use crate::types::metadata::Metadata;
 use crate::types::reference_list::ReferenceList;
 use crate::types::Renderable;
-use crate::parser;
+use crate::parser::parse_body;
+
 
 pub struct Document {
        body: Vec<Box<dyn Renderable>>,
-       style: Option<String>
+       metadata: Metadata
 }
 
 impl Document {
        pub fn from(string: String) -> Self {
                let (metadata, string) = Metadata::from(string);
                let (reference_list, string) = ReferenceList::from(string);
-               Document { body: parser::parse_body(string, metadata, reference_list), style: None}
+
+               let body = parse_body(string, metadata.clone(), reference_list);
+               let document = Document { body, metadata};
+
+               document
        }
 }
 
 impl Renderable for Document {
        fn render_latex(&self) -> String {
-               let mut full_text = String::from(
-                       "\\documentclass{article}
-                       \\usepackage{hyperref}"
-               );
-               match &self.style {
-                       Some(style) => {full_text.push_str(&format!("\\usepackage{{{style}}}"));}
+               let mut full_text = String::from("\
+\\documentclass{article}
+\\usepackage{hyperref}
+               ");
+               match &self.metadata.style {
+                       Some(style) => {full_text.push_str(&format!("\\usepackage{{{style}}}\n"));}
                        None => ()
                }
-               full_text.push_str("\\begin{document}");
+               full_text.push_str("\\begin{document}\n\n");
                for element in &self.body {
                        full_text.push_str(&element.render_latex());
                }
@@ -34,16 +39,16 @@ impl Renderable for Document {
        }
 
        fn render_html(&self) -> String {
-               let mut full_text = String::from("<html><head>");
-               match &self.style {
-                       Some(style) => {full_text.push_str(&format!("<link rel='stylesheet' href='{style}.css'>"));}
+               let mut full_text = String::from("<html>\n<head>\n");
+               match &self.metadata.style {
+                       Some(style) => {full_text.push_str(&format!("<link rel='stylesheet' href='{style}.css'>\n"));}
                        None => ()
                }
-               full_text.push_str("</head><body>");
+               full_text.push_str("</head>\n<body>\n<main>\n");
                for element in &self.body {
                        full_text.push_str(&element.render_html());
                }
-               full_text + "</body></html>"
+               full_text + "</main>\n</body>\n</html>\n"
        }
 
        fn render_gemtext(&self) -> String {
@@ -54,3 +59,17 @@ impl Renderable for Document {
                full_text
        }
 }
+
+impl Document{
+       pub fn render_metadata(&self) -> String {
+               let mut data = self.metadata.keys.clone();
+
+               data.insert(String::from("title"), self.metadata.title.to_string());
+               data.insert(String::from("author"), self.metadata.author.to_string());
+               data.insert(String::from("date"), self.metadata.date.to_string());
+
+               data.insert(String::from("elements"), format!("{}", self.body.len()));
+
+               format!("{data:?}")
+       }
+}
index 91a88ac35d1dd4470be3482002663369063d885f..a98afb7967a47931d36a197017b599cc0121762a 100644 (file)
@@ -5,7 +5,7 @@ use crate::inline::{parse_inline_latex, parse_inline_html, parse_inline_gemtext}
 
 pub struct Heading {
        pub text: String,
-       pub level: u8
+       pub level: usize
 }
 
 impl Renderable for Heading {
@@ -18,17 +18,16 @@ impl Renderable for Heading {
                        4 => "paragraph",
                        _ => "subparagraph"
                };
-               format!("\\{section}{{{text}}}")
+               format!("\\{section}{{{text}}}\n\n")
        }
 
        fn render_html (&self) -> String {
                let text = parse_inline_html(self.text.trim());
                let section = match self.level {
-                       1 => "h1",
-                       2 => "h2",
-                       3 => "h3",
-                       4 => "h4",
-                       5 => "h5",
+                       1 => "h2",
+                       2 => "h3",
+                       3 => "h4",
+                       4 => "h5",
                        _ => "h6"
                };
                format!("<{section}>{text}</{section}>\n")
@@ -37,11 +36,10 @@ impl Renderable for Heading {
        fn render_gemtext (&self) -> String {
                let text = parse_inline_gemtext(self.text.trim());
                let section = match self.level {
-                       1 => "#",
-                       2 => "##",
-                       _ => "###"
+                       1 => "##",
+                       _ => "###",
                };
-               format!("{section} {text}")
+               format!("{section} {text}\n\n")
        }
 }
 
@@ -57,7 +55,7 @@ mod tests {
        #[test]
        fn test_html_parser() {
                let heading = Heading{ text: String::from("Goodnight"), level: 5};
-               assert_eq!(&heading.render_html(), "<h5>Goodnight</h5>");
+               assert_eq!(&heading.render_html(), "<h5>Goodnight</h5>\n");
        }
        #[test]
        fn test_gemtext_parser() {
index 2cc74b6d7135275bf8ded29126f873ef01dc6853..e06dbfb766afebb6ccd8cc95a85e4ce164db338c 100644 (file)
@@ -8,12 +8,12 @@ pub struct List {
 
 impl Renderable for List {
        fn render_latex(&self) -> String {
-               let element = if self.ordered {"ennumerate"} else {"itemize"};
+               let element = if self.ordered {"enumerate"} else {"itemize"};
                let mut full_text = format!("\\begin{{{element}}}\n");
                for item in &self.items {
                        full_text.push_str(&item.render_latex());
                }
-               full_text + "\\end{" + element + "}\n"
+               full_text + "\\end{" + element + "}\n\n"
        }
        fn render_html(&self) -> String {
                let element = if self.ordered {"ol"} else {"ul"};
@@ -28,7 +28,7 @@ impl Renderable for List {
                for item in &self.items {
                        full_text.push_str(&item.render_gemtext());
                }
-               full_text
+               full_text + "\n"
        }
 }
 
@@ -38,11 +38,13 @@ pub struct Item {
 
 impl Renderable for Item {
        fn render_latex(&self) -> String {
-               format!("\\item {}\n", parse_inline_latex(self.text.trim()))
+               format!("\t\\item {}\n", parse_inline_latex(self.text.trim()))
        }
+
        fn render_html(&self) -> String {
                format!("<li>{}</li>\n", parse_inline_html(self.text.trim()))
        }
+
        fn render_gemtext(&self) -> String {
                format!("* {}\n", parse_inline_gemtext(self.text.trim()))
        }
index 4a7ad48c17969c4b0cd7a6286424b1cdf6d6128b..27f29766d5abcdc8bd7944c29e3176a3ad98ea22 100644 (file)
@@ -29,14 +29,16 @@ impl Metadata {
                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
+               // there can only be one metadata block at the moment
                // regex is a little tricky, means that you cant have more than one hypen next to another at once
                // but does allow hypens in metadata
                let re = Regex::new(r"-{3}(([\w\W&&[^-]]+-?[\w\W&&[^-]]+)*)-{3}").unwrap();
-               for mat in re.find_iter(&string) {
-                       metadata.push_str(mat.as_str());
-                       text.replace_range(mat.range(), "");
+               match re.find(&string) {
+                       Some(mat) => {
+                               metadata.push_str(mat.as_str());
+                               text.replace_range(mat.range(), "");
+                       },
+                       None => ()
                }
 
                let mut data = Metadata::new();
@@ -61,12 +63,15 @@ impl Metadata {
 
 impl Renderable for Metadata {
        fn render_latex (&self) -> String {
-               format!(
-                       "\\begin{{centering}}
-                               {{\\Large{{}}{title}}}\\\\
-                               {{\\large{{}}{author}}}\\\\
-                               {{\\large{{}}{date}}}\\\\
-                       \\end{{centering}}",
+               format!("\
+\\begin{{centering}}
+       {{\\Large{{}}\\textbf{{{title}}}}}\\\\
+       \\vspace{{2mm}}
+       {{\\large{{}}{author}}}\\\\
+       \\vspace{{2mm}}
+       {{\\large{{}}{date}}}\\\\
+\\end{{centering}}\n
+                       ",
                        title = self.title.trim(),
                        author = self.author.trim(),
                        date = self.date.trim()
@@ -87,12 +92,24 @@ impl Renderable for Metadata {
        }
 
        fn render_gemtext (&self) -> String {
-               format!(
-                       "# {title}
-                       {author} - {date}",
+               format!("\
+# {title}\n
+{author} - {date}\n\n",
                  title = self.title.trim(),
                  author = self.author.trim(),
                  date = self.date.trim()
                )
        }
 }
+
+impl Clone for Metadata {
+       fn clone(&self) -> Metadata {
+               Metadata {
+                       title: self.title.clone(),
+                       author: self.author.clone(),
+                       date: self.date.clone(),
+                       style: self.style.clone(),
+                       keys: self.keys.clone()
+               }
+       }
+}
index 09ac2f108a75db0ca3b51d7ceb8f7783bbbdf239..9030448117139d8b94cce05f7458d59521fbaf0c 100644 (file)
@@ -10,7 +10,7 @@ pub struct Paragraph {
 impl Renderable for Paragraph {
 
        fn render_latex (&self) -> String {
-               parse_inline_latex(&self.text.trim())
+               parse_inline_latex(&self.text.trim()) + "\n\n"
        }
 
        fn render_html (&self) -> String {
@@ -18,7 +18,7 @@ impl Renderable for Paragraph {
        }
 
        fn render_gemtext (&self) -> String {
-               parse_inline_gemtext(&self.text.trim())
+               parse_inline_gemtext(&self.text.trim()) + "\n\n"
        }
 }
 
@@ -34,7 +34,7 @@ mod tests {
        #[test]
        fn test_html_parser() {
                let paragraph = Paragraph{ text: String::from("_Goodnight_")};
-               assert_eq!(&paragraph.render_html(), "<em>Goodnight</em>");
+               assert_eq!(&paragraph.render_html(), "<p><em>Goodnight</em></p>\n");
        }
        #[test]
        fn test_gemtext_parser() {
diff --git a/src/types/preformatted.rs b/src/types/preformatted.rs
new file mode 100644 (file)
index 0000000..036ad2f
--- /dev/null
@@ -0,0 +1,21 @@
+use crate::types::Renderable;
+
+// submodule for prerendered text and code
+
+pub struct Preformatted {
+       pub text: String
+}
+
+impl Renderable for Preformatted {
+       fn render_latex(&self) -> String {
+               String::from("\\begin{verbatim}\n") + &self.text.trim() + "\n\\end{verbatim}\n\n"
+       }
+
+       fn render_html(&self) -> String {
+               String::from("<pre>") + &self.text.trim() + "</pre>\n"
+       }
+
+       fn render_gemtext(&self) -> String {
+               String::from("```\n") + &self.text.trim() + "\n```\n\n"
+       }
+}
index 74aad5d09a7cf3be4d6e55d2796880eb4ded638b..9fa6ef192e56fd173614ee2e4a966ebc37c05c9b 100644 (file)
@@ -1,4 +1,9 @@
+use regex::{Match, Regex};
+
 use crate::types::Renderable;
+use crate::types::reference_list::reference::Reference;
+use crate::types::reference_list::name_list::NameList;
+use crate::types::reference_list::work::Work;
 
 pub mod name_list;
 pub mod reference;
@@ -10,27 +15,106 @@ pub struct ReferenceList {
 
 impl ReferenceList {
        pub fn from (string: String) -> (Option<Self>, String) {
-               (None, string)
+               let mut list: Vec<reference::Reference> = Vec::new();
+
+               // this might be too big honestly, its a pretty simple regex tho
+               let re = Regex::new(r"\[(?<id>[\d\w]+)\]\n(author:((?<author>[^\n]+\n?))|(title:(?<title>[^\n]+\n?))|(year:(?<year>[^\n]+\n?))|(publisher:(?<publisher>[^\n]+\n?))|(url:(?<url>[^\n]+\n?))|(journaltitle:(?<journaltitle>[^\n]+\n?))|(issuetitle:(?<issuetitle>[^\n]+\n?))|(editor:(?<editor>[^\n]+\n?))|(volume:(?<volume>[^\n]+\n?)))+").unwrap();
+
+               for capture in re.captures_iter(&string) {
+                       if match capture.name("id") {
+                               Some(mat) => {
+                                       if &string[mat.start()-2..mat.start()-1] == "\\" {false}
+                                       else {true}
+                               },
+                               None => false
+                       } {
+                               let new_reference = Reference {
+                                       id: capture.name("id").map_or(
+                                               String::from("none"),
+                                               |m| m.as_str().trim().to_string()
+                                       ),
+                                       names: NameList {
+                                               string: capture.name("author").map_or(
+                                                       String::from("None"),
+                                                       |m| m.as_str().trim().to_string()
+                                               )
+                                       },
+                                       title: capture.name("title").map_or(
+                                               String::from("None"),
+                                               |m| m.as_str().trim().to_string()
+                                       ),
+                                       year: match_string(capture.name("year")),
+                                       publisher: match_string(capture.name("publisher")),
+                                       url: match_string(capture.name("url")),
+                                       inside: match capture.name("journaltitle") {
+                                               Some(mat) => {
+                                                       Some(
+                                                               Work {
+                                                                       title: mat.as_str().trim().to_string(),
+                                                                       names: capture.name("editor").map_or(None, |m| Some(NameList { string: m.as_str().trim().to_string()})),
+                                                                       issue: match_string(capture.name("issuetitle")),
+                                                                       volume: match_string(capture.name("volume"))
+                                                               }
+                                                       )
+                                               }
+                                               None => None
+                                       }
+                               };
+                               list.push(new_reference);
+                       }
+               }
+
+               // order the list into alphabetical order
+               list.sort_by(|a, b| a.names.render_full().cmp(&b.names.render_full()));
+
+               // remove references from full string
+               let mut new_string = string.to_string();
+               for mat in re.find_iter(&string).collect::<Vec<_>>().iter().rev() {
+                       if &string[mat.start()-1..mat.start()] != "\\" {
+                               new_string.replace_range(mat.range(), "");
+                       }
+               }
+
+               (match list.len() {
+                       0 => None,
+                       _ => Some(ReferenceList{ list })
+               }, new_string)
+       }
+
+       pub fn parse_inline(&self, string: String) -> String {
+               let mut new_string = string.to_string();
+               let re = Regex::new(r"\[[\d\w]+\]").unwrap();
+               for mat in re.find_iter(&string).collect::<Vec<_>>().iter().rev() {
+                       if !(&string[mat.start()-1..mat.start()] == "\\") {
+                               let inline_id = &string[mat.start()+1..mat.end()-1].trim();
+                               for reference in &self.list {
+                                       if &reference.id == inline_id {
+                                               new_string.replace_range(mat.range(), &reference.render_inline());
+                                       }
+                               }
+                       }
+               }
+               new_string
        }
 }
 
 impl Renderable for ReferenceList {
        fn render_latex(&self) -> String {
-               let mut full_text = String::from("\\section{Bibliography}\n\n");
+               let mut full_text = String::from("\\section*{Bibliography}\n\n");
                for reference in &self.list {
                        full_text.push_str(&(reference.render_latex() + "\n\n"));
                }
                full_text
        }
        fn render_html(&self) -> String {
-               let mut full_text = String::from("<section><h1>Bibliography</h1>");
+               let mut full_text = String::from("<section><h2>Bibliography</h2>");
                for reference in &self.list {
                        full_text.push_str(&(reference.render_html() + "\n\n"));
                }
                full_text + "</section>"
        }
        fn render_gemtext(&self) -> String {
-               let mut full_text = String::from("# Bibliography\n\n");
+               let mut full_text = String::from("## Bibliography\n\n");
                for reference in &self.list {
                        full_text.push_str(&(reference.render_gemtext() + "\n\n"));
                }
@@ -38,4 +122,12 @@ impl Renderable for ReferenceList {
        }
 }
 
+impl Clone for ReferenceList {
+       fn clone(&self) -> Self {
+               ReferenceList { list: self.list.clone() }
+       }
+}
 
+fn match_string(option_match: Option<Match>) -> Option<String> {
+       option_match.map_or(None, |m| Some(m.as_str().trim().to_string()))
+}
index b8ac22d1cea57d424dd5ba39202811665dacd602..119c4d8e0c359b9cfe6087fdb4d26f7dcb0f1e27 100644 (file)
@@ -8,6 +8,12 @@ pub struct NameList {
 // the inline and full rendering use different ways of storing stings for some reason
 // look into this maybe
 
+impl Clone for NameList {
+       fn clone(&self) -> Self {
+               NameList { string: self.string.clone() }
+       }
+}
+
 impl NameList {
        pub fn render_full(&self) -> String {
                let mut names: Vec<String> = Vec::new();
index ea8e44e71b0ba6d69621368cffae4ef87f95a9ab..7b4e59ddd415a88bf90edd7fd2c3819623d249c4 100644 (file)
@@ -3,6 +3,7 @@ use crate::types::reference_list::work::Work;
 use crate::types::Renderable;
 
 pub struct Reference {
+       pub id: String,
        pub names: NameList,
        pub title: String,
        pub year: Option<String>,
@@ -42,7 +43,7 @@ impl Renderable for Reference {
        }
 
        fn render_html(&self) -> String {
-               let mut full_text = String::from("<cite>") + &self.names.render_full();
+               let mut full_text = String::from("<p>") + &self.names.render_full();
                match &self.year {
                        Some(year) => {full_text.push_str(&format!(" ({year})."));},
                        None => {full_text.push_str(" (n.d.).");}
@@ -67,7 +68,7 @@ impl Renderable for Reference {
                        },
                        None => ()
                };
-               full_text.push_str("</cite>");
+               full_text.push_str("</p>");
                full_text
 
        }
@@ -103,6 +104,19 @@ impl Renderable for Reference {
        }
 }
 
+impl Clone for Reference {
+       fn clone(&self) -> Self {
+               Reference {
+                       id: self.id.clone(),
+                       names: self.names.clone(),
+                       title: self.title.clone(),
+                       year: self.year.clone(),
+                       publisher: self.publisher.clone(),
+                       url: self.url.clone(),
+                       inside: self.inside.clone()}
+       }
+}
+
 impl Reference {
        pub fn render_inline(&self) -> String {
                let mut full_text = String::from("(") + &self.names.render_inline();
index cafea3f69237d846e592ba3a656da5c9c5e6e52e..fea92fa918bb6ee26c11aef8772c5e71ef1a7875 100644 (file)
@@ -77,3 +77,14 @@ impl Renderable for Work {
                full_text
        }
 }
+
+impl Clone for Work {
+       fn clone(&self) -> Self {
+               Work {
+                       names: self.names.clone(),
+                       title: self.title.clone(),
+                       issue: self.issue.clone(),
+                       volume: self.volume.clone()
+               }
+       }
+}