From: Max Value Date: Sat, 3 May 2025 19:28:07 +0000 (+0100) Subject: Added elements X-Git-Url: https://git.ozva.co.uk/?a=commitdiff_plain;h=617ad7e3f2c230e37d983a528034929c0e74d858;p=gn-parser Added elements Added elements - document - metadata - reference list - list - referencing elements Started working on parsing module --- diff --git a/Cargo.toml b/Cargo.toml index 91efa57..710695a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +chrono = "0.4.41" regex = "1.11.1" diff --git a/schema.gn b/schema.gn index c6bb26c..c91201a 100644 --- a/schema.gn +++ b/schema.gn @@ -1,10 +1,17 @@ -# The Goodnight Markdown Specification +--- +title: The Goodnight Markdown Specification - Version 0.1.0 +author: Goodnight Publishing +--- + +[META] -Version 0.1.0 +[TOC] + +[REFS] ## Features -- Headings are unchanged from vanilla markdown. +- Headings are unchanged from vanilla markdown (6 levels). - Paragraphs are unchanged from vanilla markdown. - Ordered lists are unchanged from vanilla markdown. - URLs and email addresses are unchanged from vanilla markdown. @@ -20,13 +27,19 @@ Version 0.1.0 - Italic styling will work only with a single underscore character. - Unordered lists will work only with the hyphen character. +1. Ordered +2. Lists +3. Are +4. As +5. Written + ### 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 +- `[META]` generates the bibliography ### Metadata diff --git a/src/inline.rs b/src/inline.rs new file mode 100644 index 0000000..5d2873c --- /dev/null +++ b/src/inline.rs @@ -0,0 +1,47 @@ +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) { + if index == 0 || !(&text[index - 1 .. index] == "\\") { + text.replace_range(index .. index + 1, front_back.next().unwrap()); + } + } + text +} + +pub fn parse_inline_latex (string: &str) -> String { + let mut text = parse_inline(string, '*', "\\textbf{", "}"); + text = parse_inline(&text, '_', "\\textit{", "}"); + parse_inline(&text, '`', "\\texttt{", "}") +} + +pub fn parse_inline_html (string: &str) -> String { + let mut text = parse_inline(string, '*', "", ""); + text = parse_inline(&text, '_', "", ""); + parse_inline(&text, '`', "", "") +} + +pub fn parse_inline_gemtext (string: &str) -> String { + let mut text = parse_inline(string, '*', "", ""); + text = parse_inline(&text, '_', "", ""); + parse_inline(&text, '`', "", "") +} + +pub fn parse_inline_remove (string: &str) -> String { + parse_inline_gemtext(string) +} + +#[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"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 61aefec..e9cb1f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,14 @@ use std::fs; use std::error::Error; +pub mod parser; +pub mod inline; +pub mod types; + +use crate::types::document::Document; +use crate::types::Renderable; + + pub struct Config { file_path: String } @@ -21,21 +29,8 @@ pub fn run(config: Config) -> Result<(), Box> { let contents = fs::read_to_string(config.file_path) .expect("Should have been able to read the file"); - let new: Vec; - - for line in contents.lines() { - println!("{}", line); - } + let document = Document::from(contents); + println!("{}", document.render_html()); Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn placeholder() { - assert!(true); - } -} diff --git a/src/main.rs b/src/main.rs index 49767fa..430d707 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use gn_parser::Config; pub mod types; pub mod parser; +pub mod inline; fn main() { let args: Vec = env::args().collect(); diff --git a/src/parser.rs b/src/parser.rs index ec3c3ec..349ec9c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,111 @@ -use crate::types::{Renderable, paragraph, metadata}; +use regex::Regex; -pub fn parse (string: String) -> impl Renderable { - paragraph::Paragraph{ text: string.to_string() } -} +use crate::types::Renderable; +use crate::types::heading::Heading; +use crate::types::paragraph::Paragraph; +use crate::types::metadata::Metadata; +use crate::types::reference_list::ReferenceList; +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) +// and returns the list of all the elements of the document + +pub fn parse_body (string: String, metadata: Metadata, reference_list: Option) -> Vec> { + let mut document: Vec> = Vec::new(); + let mut clean_string = string.to_string(); + let mut metadata: Option = Some(metadata); + let mut 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::>().iter().rev() { + clean_string.replace_range(mat.start()+1 .. mat.end()-1, ""); + } + + let mut lines = clean_string.lines().peekable(); + while let Some(line) = lines.next() { + if !(line == "") { + let first = &(String::from(line) + " ")[0..7].chars().collect::>(); + match (first[0], first[1], first[2], first[3], first[4], first[5]) { + // match all heading patterns + ('#', '#', '#', '#', '#', '#') => { + document.push(Box::new(Heading{text: line[6..].to_string(), level: 6})) + }, + ('#', '#', '#', '#', '#', ..) => { + document.push(Box::new(Heading{text: line[5..].to_string(), level: 5})) + }, + ('#', '#', '#', '#', ..) => { + document.push(Box::new(Heading{text: line[4..].to_string(), level: 4})) + }, + ('#', '#', '#', ..) => { + document.push(Box::new(Heading{text: line[3..].to_string(), level: 3})) + }, + ('#', '#', ..) => { + document.push(Box::new(Heading{text: line[2..].to_string(), level: 2})) + }, + ('#', ..) => { + document.push(Box::new(Heading{text: line[1..].to_string(), level: 1})) + }, + // match all unordered list patterns + ('-', ..) => { + let mut items: Vec = vec![Item{text: line[1..].to_string()}]; + while lines.peek() != None && lines.peek() != Some(&"") && &lines.peek().unwrap()[..1] == "-" { + items.push(Item{text: lines.next().unwrap()[1..].to_string()}); + } + document.push(Box::new(List{items, ordered: false})) + } + // match all ordered list patterns (matches up to 999. ...) + ('1'..='9', '.', ..) | ('1'..='9', '1'..='9', '.', ..) | ('1'..='9', '1'..='9', '1'..='9', '.', ..) => { + let mut items: Vec = vec![Item{text: line.split_once(".").unwrap().1.to_string()}]; + while match lines.peek() { + Some(string) => { + if string == &"" {false} + else { + let re = Regex::new(r"\d*.").unwrap(); + re.is_match_at(string, 0) + } + }, + None => false + } { + match lines.next().unwrap().split_once(".") { + Some((_, text)) => {items.push(Item{text: text.to_string()});}, + None => () + } + } + document.push(Box::new(List{items, ordered: true})) + } + + // match all insert sequences like the title + ('[', 'R', 'E', 'F', 'S', ']') => { + match reference_list { + 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 => () + } + }, + ('[', 'T', 'O', 'C', ']', ..) => { + document.push(Box::new(Paragraph{text: String::from("This is where the table of contents will go")})) + }, + + // make everything else a paragraph + _ => { + document.push(Box::new(Paragraph{text: line.to_string()})) + } + } + } + } + + document +} diff --git a/src/types.rs b/src/types.rs index c66a6ab..8701853 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,9 @@ pub mod paragraph; pub mod heading; pub mod metadata; +pub mod reference_list; +pub mod document; +pub mod list; pub trait Renderable { fn render_latex(&self) -> String; diff --git a/src/types/document.rs b/src/types/document.rs new file mode 100644 index 0000000..90ab2cb --- /dev/null +++ b/src/types/document.rs @@ -0,0 +1,56 @@ +use crate::types::metadata::Metadata; +use crate::types::reference_list::ReferenceList; +use crate::types::Renderable; +use crate::parser; + +pub struct Document { + body: Vec>, + style: Option +} + +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} + } +} + +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}}}"));} + None => () + } + full_text.push_str("\\begin{document}"); + for element in &self.body { + full_text.push_str(&element.render_latex()); + } + full_text + "\\end{document}" + } + + fn render_html(&self) -> String { + let mut full_text = String::from(""); + match &self.style { + Some(style) => {full_text.push_str(&format!(""));} + None => () + } + full_text.push_str(""); + for element in &self.body { + full_text.push_str(&element.render_html()); + } + full_text + "" + } + + fn render_gemtext(&self) -> String { + let mut full_text = String::new(); + for element in &self.body { + full_text.push_str(&element.render_gemtext()); + } + full_text + } +} diff --git a/src/types/heading.rs b/src/types/heading.rs index dd184a0..91a88ac 100644 --- a/src/types/heading.rs +++ b/src/types/heading.rs @@ -1,4 +1,5 @@ use crate::types::Renderable; +use crate::inline::{parse_inline_latex, parse_inline_html, parse_inline_gemtext}; // submodule for handling headings @@ -9,7 +10,7 @@ pub struct Heading { impl Renderable for Heading { fn render_latex (&self) -> String { - let text = self.text.to_string(); + let text = parse_inline_latex(self.text.trim()); let section = match self.level { 1 => "section", 2 => "subsection", @@ -21,7 +22,7 @@ impl Renderable for Heading { } fn render_html (&self) -> String { - let text = self.text.to_string(); + let text = parse_inline_html(self.text.trim()); let section = match self.level { 1 => "h1", 2 => "h2", @@ -30,11 +31,11 @@ impl Renderable for Heading { 5 => "h5", _ => "h6" }; - format!("<{section}>{text}") + format!("<{section}>{text}\n") } fn render_gemtext (&self) -> String { - let text = self.text.to_string(); + let text = parse_inline_gemtext(self.text.trim()); let section = match self.level { 1 => "#", 2 => "##", diff --git a/src/types/list.rs b/src/types/list.rs new file mode 100644 index 0000000..2cc74b6 --- /dev/null +++ b/src/types/list.rs @@ -0,0 +1,49 @@ +use crate::types::Renderable; +use crate::inline::{parse_inline_latex, parse_inline_html, parse_inline_gemtext}; + +pub struct List { + pub items: Vec, + pub ordered: bool +} + +impl Renderable for List { + fn render_latex(&self) -> String { + let element = if self.ordered {"ennumerate"} 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" + } + fn render_html(&self) -> String { + let element = if self.ordered {"ol"} else {"ul"}; + let mut full_text = format!("<{element}>\n"); + for item in &self.items { + full_text.push_str(&item.render_html()); + } + full_text + &format!("\n") + } + fn render_gemtext(&self) -> String { + let mut full_text = String::new(); + for item in &self.items { + full_text.push_str(&item.render_gemtext()); + } + full_text + } +} + +pub struct Item { + pub text: String +} + +impl Renderable for Item { + fn render_latex(&self) -> String { + format!("\\item {}\n", parse_inline_latex(self.text.trim())) + } + fn render_html(&self) -> String { + format!("
  • {}
  • \n", parse_inline_html(self.text.trim())) + } + fn render_gemtext(&self) -> String { + format!("* {}\n", parse_inline_gemtext(self.text.trim())) + } +} diff --git a/src/types/metadata.rs b/src/types/metadata.rs index 8e091eb..4a7ad48 100644 --- a/src/types/metadata.rs +++ b/src/types/metadata.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use regex::Regex; +use chrono::prelude::*; use crate::types::Renderable; @@ -7,60 +8,91 @@ pub struct Metadata { pub title: String, pub author: String, pub date: String, + pub style: Option, pub keys: HashMap } impl Metadata { pub fn new() -> Self { + let local: DateTime = Local::now(); + let date = local.format("%e %b %Y").to_string(); + Metadata { title: String::from("Title"), author: String::from("Author"), - date: String::from("Today"), + date, + style: None, keys: HashMap::new() } } pub fn from(string: String) -> (Self, String) { - parse_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 + // 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(), ""); + } + + let mut data = Metadata::new(); + for line in metadata.lines() { + match line.split_once(':') { + Some((key, value)) => { + match key.trim() { + "title" => {data.title = String::from(value.trim())}, + "author" => {data.author = String::from(value.trim())}, + "date" => {data.date = String::from(value.trim())}, + "style" => {data.style = Some(String::from(value.trim()))}, + _ => {data.keys.insert(String::from(key.trim()), String::from(value.trim()));} + } + }, + None => () + } + } + + (data, text) } } impl Renderable for Metadata { fn render_latex (&self) -> String { + format!( + "\\begin{{centering}} + {{\\Large{{}}{title}}}\\\\ + {{\\large{{}}{author}}}\\\\ + {{\\large{{}}{date}}}\\\\ + \\end{{centering}}", + title = self.title.trim(), + author = self.author.trim(), + date = self.date.trim() + ) } fn render_html (&self) -> String { + format!( +"
    +

    {title}

    +

    {author}

    +

    {date}

    +
    \n", + title = self.title.trim(), + author = self.author.trim(), + date = self.date.trim() + ) } fn render_gemtext (&self) -> String { + format!( + "# {title} + {author} - {date}", + title = self.title.trim(), + author = self.author.trim(), + date = self.date.trim() + ) } } - -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 index 5829879..09ac2f1 100644 --- a/src/types/paragraph.rs +++ b/src/types/paragraph.rs @@ -1,4 +1,5 @@ use crate::types::Renderable; +use crate::inline::{parse_inline_latex, parse_inline_html, parse_inline_gemtext}; // submodule for handling paragraphs @@ -9,49 +10,22 @@ pub struct Paragraph { 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{", "}") + parse_inline_latex(&self.text.trim()) } fn render_html (&self) -> String { - let text = parse_inline(&self.text, '*', "", ""); - let text = parse_inline(&text, '_', "", ""); - parse_inline(&text, '`', "", "") + String::from("

    ") + &parse_inline_html(&self.text.trim()) + "

    \n" } fn render_gemtext (&self) -> String { - let text = parse_inline(&self.text, '*', "", ""); - let text = parse_inline(&text, '_', "", ""); - parse_inline(&text, '`', "", "") + parse_inline_gemtext(&self.text.trim()) } } -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*")}; diff --git a/src/types/reference_list.rs b/src/types/reference_list.rs new file mode 100644 index 0000000..74aad5d --- /dev/null +++ b/src/types/reference_list.rs @@ -0,0 +1,41 @@ +use crate::types::Renderable; + +pub mod name_list; +pub mod reference; +pub mod work; + +pub struct ReferenceList { + pub list: Vec +} + +impl ReferenceList { + pub fn from (string: String) -> (Option, String) { + (None, string) + } +} + +impl Renderable for ReferenceList { + fn render_latex(&self) -> String { + 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("

    Bibliography

    "); + for reference in &self.list { + full_text.push_str(&(reference.render_html() + "\n\n")); + } + full_text + "
    " + } + fn render_gemtext(&self) -> String { + let mut full_text = String::from("# Bibliography\n\n"); + for reference in &self.list { + full_text.push_str(&(reference.render_gemtext() + "\n\n")); + } + full_text + } +} + + diff --git a/src/types/reference_list/name_list.rs b/src/types/reference_list/name_list.rs new file mode 100644 index 0000000..b8ac22d --- /dev/null +++ b/src/types/reference_list/name_list.rs @@ -0,0 +1,68 @@ +// contains a list of names that are rendereded into a valid APA 7th name list +// both for inline citations and full bibliography citations + +pub struct NameList { + pub string: String, +} + +// the inline and full rendering use different ways of storing stings for some reason +// look into this maybe + +impl NameList { + pub fn render_full(&self) -> String { + let mut names: Vec = Vec::new(); + for name in self.string.split(" and ") { + if &name[0..1] == "{" { + // the case for dealing with organisations + names.push(String::from(&name[1..name.len()-1])); + } else { + // the case for dealing with people with an indeterminate + // amount of names + let last_name = name.split_whitespace().last().unwrap(); + let mut full_name = String::from(last_name) + ", "; + name.split_whitespace() + .filter(|n| n != &last_name) + .for_each(|n| { + let initial = format!("{}. ", &n[0..1]); + full_name.push_str(&initial) + }); + names.push(full_name); + } + } + let mut all_names = String::new(); + names.sort_by(|a, b| a.cmp(b)); + + // et al. if more than 3 + if names.len() > 3 { + names = names[0..4].to_vec(); + names.push(String::from("et al.")); + } + + for name in names {all_names.push_str(&name)}; + all_names + } + + pub fn render_inline(&self) -> String { + let mut names: Vec<&str> = Vec::new(); + for name in self.string.split(" and ") { + if &name[0..1] == "{" { + // the case for dealing with organisations + names.push(&name[1..name.len()-1]); + } else { + let last_name = name.split_whitespace().last().unwrap(); + names.push(last_name); + } + } + let mut all_names = String::new(); + names.sort_by(|a, b| a.cmp(b)); + + // et al. if more than 3 + if names.len() > 3 { + names = names[0..4].to_vec(); + names.push("et al."); + } + + for name in names {all_names.push_str(&name)}; + all_names + } +} diff --git a/src/types/reference_list/reference.rs b/src/types/reference_list/reference.rs new file mode 100644 index 0000000..ea8e44e --- /dev/null +++ b/src/types/reference_list/reference.rs @@ -0,0 +1,115 @@ +use crate::types::reference_list::name_list::NameList; +use crate::types::reference_list::work::Work; +use crate::types::Renderable; + +pub struct Reference { + pub names: NameList, + pub title: String, + pub year: Option, + pub publisher: Option, + pub url: Option, + pub inside: Option +} + +impl Renderable for Reference { + fn render_latex(&self) -> String { + let mut full_text = self.names.render_full(); + match &self.year { + Some(year) => {full_text.push_str(&format!(" ({year})."));}, + None => {full_text.push_str(" (n.d.).");} + }; + match &self.inside { + Some(inside) => { + full_text.push_str(&format!(" {title}. {work}.", title = &self.title, work = inside.render_latex())); + }, + None => { + full_text.push_str(&format!(" \\textit{{{}}}.", &self.title)); + } + }; + match &self.publisher { + Some(publisher) => { + full_text.push_str(&format!(" {}.", publisher)); + }, + None => () + }; + match &self.url { + Some(url) => { + full_text.push_str(&format!(" \\url{{{}}}.", url)); + }, + None => () + }; + full_text + } + + fn render_html(&self) -> String { + let mut full_text = String::from("") + &self.names.render_full(); + match &self.year { + Some(year) => {full_text.push_str(&format!(" ({year})."));}, + None => {full_text.push_str(" (n.d.).");} + }; + match &self.inside { + Some(inside) => { + full_text.push_str(&format!(" {title}. {work}.", title = &self.title, work = inside.render_html())); + }, + None => { + full_text.push_str(&format!(" {}.", &self.title)); + } + }; + match &self.publisher { + Some(publisher) => { + full_text.push_str(&format!(" {}.", publisher)); + }, + None => () + }; + match &self.url { + Some(url) => { + full_text.push_str(&format!(" {url}.")); + }, + None => () + }; + full_text.push_str(""); + full_text + + } + + fn render_gemtext(&self) -> String { + let mut full_text = self.names.render_full(); + match &self.year { + Some(year) => {full_text.push_str(&format!(" ({year})."));}, + None => {full_text.push_str(" (n.d.).");} + }; + match &self.inside { + Some(inside) => { + full_text.push_str(&format!(" {title}. {work}.", title = &self.title, work = inside.render_gemtext())); + }, + None => { + full_text.push_str(&format!(" {}.", &self.title)); + } + }; + match &self.publisher { + Some(publisher) => { + full_text.push_str(&format!(" {}.", publisher)); + }, + None => () + }; + match &self.url { + Some(url) => { + full_text.push_str(&format!("\n=> {}.", url)); + }, + None => () + }; + full_text + + } +} + +impl Reference { + pub fn render_inline(&self) -> String { + let mut full_text = String::from("(") + &self.names.render_inline(); + match &self.year { + Some(year) => {full_text.push_str(&format!(", {year})"))}, + None => {full_text.push_str(", n.d.)")} + } + full_text + } +} diff --git a/src/types/reference_list/work.rs b/src/types/reference_list/work.rs new file mode 100644 index 0000000..cafea3f --- /dev/null +++ b/src/types/reference_list/work.rs @@ -0,0 +1,79 @@ +use crate::types::reference_list::name_list::NameList; +use crate::types::Renderable; + +// submodule for rendering a work that contains a reference such as a journal or book +// renders only part of a full reference, for example +// ... In Greenwood, W. S. (Eds.), The Goodnight Markdown Specification (2nd ed.). ... + +pub struct Work { + pub names: Option, + pub title: String, + pub issue: Option, + pub volume: Option, +} + +impl Renderable for Work { + fn render_latex(&self) -> String { + let mut full_text = String::from("In "); + match &self.names { + Some(name_list) => { + let names = name_list.render_full() + "(Eds.),"; + full_text.push_str(&names); + }, + None => () + }; + full_text.push_str(&format!(" \\textit{{{title}}}", title = self.title)); + match &self.volume { + Some(volume) => {full_text.push_str(&format!(" ({volume})"))}, + None => () + }; + match &self.issue { + Some(issue) => {full_text.push_str(&format!(" {issue}."))}, + None => {full_text.push_str(".");} + }; + + full_text + } + fn render_html(&self) -> String { + let mut full_text = String::from("In "); + match &self.names { + Some(name_list) => { + let names = name_list.render_full() + "(Eds.),"; + full_text.push_str(&names); + }, + None => () + }; + full_text.push_str(&format!(" {title}", title = self.title)); + match &self.volume { + Some(volume) => {full_text.push_str(&format!(" ({volume})"))}, + None => () + }; + match &self.issue { + Some(issue) => {full_text.push_str(&format!(" {issue}."))}, + None => {full_text.push_str(".");} + }; + + full_text + } + fn render_gemtext(&self) -> String { + let mut full_text = String::from("In "); + match &self.names { + Some(name_list) => { + let names = name_list.render_full() + "(Eds.),"; + full_text.push_str(&names); + }, + None => () + }; + full_text.push_str(&format!(" {title}", title = self.title)); + match &self.volume { + Some(volume) => {full_text.push_str(&format!(" ({volume})"))}, + None => () + }; + match &self.issue { + Some(issue) => {full_text.push_str(&format!(" {issue}."))}, + None => {full_text.push_str(".");} + }; + + full_text + } +}