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