[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]
---
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:
- `[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.
---
```
-### 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
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 {
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 })
}
}
.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(())
}
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)
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}
},
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())
+ }))
}
}
}
document
}
+
+fn parse_reference(reference_list: &Option<ReferenceList>, text: String) -> String {
+ match reference_list.clone() {
+ Some(bibliography) => {
+ bibliography.parse_inline(text)},
+ None => text
+ }
+}
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;
fn render_gemtext(&self) -> String;
}
-
--- /dev/null
+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
+ }
+}
--- /dev/null
+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")
+ }
+}
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());
}
}
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 {
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:?}")
+ }
+}
pub struct Heading {
pub text: String,
- pub level: u8
+ pub level: usize
}
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")
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")
}
}
#[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() {
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"};
for item in &self.items {
full_text.push_str(&item.render_gemtext());
}
- full_text
+ full_text + "\n"
}
}
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()))
}
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();
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()
}
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()
+ }
+ }
+}
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 {
}
fn render_gemtext (&self) -> String {
- parse_inline_gemtext(&self.text.trim())
+ parse_inline_gemtext(&self.text.trim()) + "\n\n"
}
}
#[test]
fn test_html_parser() {
let paragraph = Paragraph{ text: String::from("_Goodnight_")};
- assert_eq!(¶graph.render_html(), "<em>Goodnight</em>");
+ assert_eq!(¶graph.render_html(), "<p><em>Goodnight</em></p>\n");
}
#[test]
fn test_gemtext_parser() {
--- /dev/null
+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"
+ }
+}
+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;
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"));
}
}
}
+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()))
+}
// 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();
use crate::types::Renderable;
pub struct Reference {
+ pub id: String,
pub names: NameList,
pub title: String,
pub year: Option<String>,
}
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.).");}
},
None => ()
};
- full_text.push_str("</cite>");
+ full_text.push_str("</p>");
full_text
}
}
}
+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();
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()
+ }
+ }
+}