diff --git a/Cargo.lock b/Cargo.lock index 51e1bf2..7d981c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,64 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "details" version = "0.1.0" +dependencies = [ + "markdown", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "markdown" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef3aab6a1d529b112695f72beec5ee80e729cb45af58663ec902c8fac764ecdd" +dependencies = [ + "lazy_static", + "pipeline", + "regex", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "pipeline" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15b6607fa632996eb8a17c9041cb6071cb75ac057abd45dece578723ea8c7c0" + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" diff --git a/Cargo.toml b/Cargo.toml index 4139128..0fe059c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +markdown = "0.3.0" diff --git a/src/main.rs b/src/main.rs index 3be1676..b837840 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ -#![allow(dead_code)] - +use markdown::to_html; use std::collections::BTreeMap; use std::io::{Error as IoError, Write}; use std::{env::args, fs}; @@ -16,29 +15,56 @@ fn main() { let mut file = std::fs::File::create(out.clone()).unwrap_or_else(|_| panic!("couldn't create {}", out)); - file.write_all(detail.into_html().unwrap().as_bytes()) + let header = header(); + let title = title(); + let footer = footer(); + + let content = header + &detail.title + &title + &detail.into_html().unwrap() + &footer; + + file.write_all(content.as_bytes()) .unwrap_or_else(|_| panic!("unable to write to {}", out)); } // ~~~ data structures ~~~ struct Detail { passages: BTreeMap, - node_graph: Vec>, + title: String, } impl Detail { fn new(filename: &str) -> Result { let contents = fs::read_to_string(filename).map_err(|err| DetailError::Io(err))?; + if contents.is_empty() { + return Err(DetailError::EmptyFile); + } let mut passages = BTreeMap::new(); let mut cur_passage: Option = None; let mut cur_lines = String::new(); + let mut has_title = true; + + let title = contents + .lines() + .peekable() + .peek() + .unwrap() + .strip_prefix("~title") + .unwrap_or_else(|| { + has_title = false; + "details" + }); + + let mut lines = contents.lines(); + if has_title { + lines.next(); + } - for line in contents.lines() { + for line in lines { let line = line.trim(); // if line begins with ~detail - if let Some(passage_name) = line.strip_prefix("~detail ") { + if let Some(passage_name) = line.strip_prefix("~detail") { + let passage_name = passage_name.trim(); // if this isn't the first passage, add previous passage to map if let Some(prev_passage) = cur_passage { let prev_passage = prev_passage.to_string(); @@ -57,6 +83,7 @@ impl Detail { } else { // case: regular line cur_lines += line; + cur_lines += "\n"; } } @@ -74,24 +101,15 @@ impl Detail { } } - let mut node_graph = Vec::new(); - node_graph.resize_with(passages.len(), || { - let mut row = Vec::new(); - row.resize(passages.len(), false); - row - }); - - let mut detail = Self { + let detail = Self { passages, - node_graph, + title: title.to_string(), }; - detail.build_graph(); - - if detail.check_valid() { - Ok(detail) - } else { - Err(DetailError::InfiniteLoop) + match detail.check_valid(&"start", &mut Vec::new()) { + Some(true) => Ok(detail), + Some(false) => Err(DetailError::InfiniteLoop), + None => Err(DetailError::FlattenError), } } @@ -105,7 +123,7 @@ impl Detail { for (sep, piece) in rule.split('#').enumerate() { // search keys if sep % 2 == 0 { - output += piece; + output += &to_html(&piece); } else { let (display, detail_to) = piece.split_once('~').unwrap_or((piece, piece)); let expand = self.flatten(&detail_to)?; @@ -119,27 +137,28 @@ impl Detail { Some(output) } - // don't speak to me about how nested this method is. - fn build_graph(&mut self) { - for (i, content) in self.passages.values().enumerate() { - for (sep, piece) in content.split('#').enumerate() { - // search keys - if sep % 2 == 1 { - if let Some((_, detail_to)) = piece.split_once('~') { - if let Some(j) = self.passages.keys().position(|name| name == &detail_to) { - self.node_graph[i][j] = true; - } - } else if let Some(j) = self.passages.keys().position(|name| name == &piece) { - self.node_graph[i][j] = true; - } + fn check_valid(&self, rule: &str, already_visited: &mut Vec) -> Option { + already_visited.push(rule.to_string()); + let rule = self.passages.get(rule)?; + let mut output = String::new(); + for (sep, piece) in rule.split('#').enumerate() { + // search keys + if sep % 2 == 0 { + output += piece; + } else { + let (_, detail_to) = piece.split_once('~').unwrap_or((piece, piece)); + if already_visited + .iter() + .find(|passage| *passage == detail_to) + .is_none() + { + return self.check_valid(&detail_to, already_visited); + } else { + return Some(false); } } } - } - - // TODO actually write this - fn check_valid(&self) -> bool { - true + Some(true) } } @@ -147,4 +166,36 @@ impl Detail { enum DetailError { Io(IoError), InfiniteLoop, + FlattenError, + EmptyFile, +} + +fn header() -> String { + r#" + + + + + "# + .to_string() +} + +fn title() -> String { + r#" + + + +"# + .to_string() +} + +fn footer() -> String { + r#" + +"# + .to_string() }