#![allow(dead_code)] use std::collections::BTreeMap; use std::io::{Error as IoError, Write}; use std::{env::args, fs}; fn main() { let filename = args().nth(1).expect("no file given"); let detail = Detail::new(&filename).unwrap(); let out = filename .strip_suffix(".detail") .unwrap_or(&filename) .to_string() + ".html"; 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()) .unwrap_or_else(|_| panic!("unable to write to {}", out)); } // ~~~ data structures ~~~ struct Detail { passages: BTreeMap, node_graph: Vec>, } impl Detail { fn new(filename: &str) -> Result { let contents = fs::read_to_string(filename).map_err(|err| DetailError::Io(err))?; let mut passages = BTreeMap::new(); let mut cur_passage: Option = None; let mut cur_lines = String::new(); for line in contents.lines() { let line = line.trim(); // if line begins with ~detail if let Some(passage_name) = line.strip_prefix("~detail ") { // 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(); if !cur_lines.is_empty() { if passages.contains_key(&prev_passage) { println!("WARNING: passage name \"{}\" duplicated", prev_passage); passages.remove(&prev_passage); } else { // clear cur_lines and insert passages.insert(prev_passage, std::mem::take(&mut cur_lines)); } } } cur_passage = Some(passage_name.to_string()); } else { // case: regular line cur_lines += line; } } // add last passage if let Some(prev_passage) = cur_passage { let prev_passage = prev_passage.to_string(); if !cur_lines.is_empty() { if passages.contains_key(&prev_passage) { println!("WARNING: passage name \"{}\" duplicated", prev_passage); passages.remove(&prev_passage); } else { // clear cur_lines and insert passages.insert(prev_passage, std::mem::take(&mut cur_lines)); } } } 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 { passages, node_graph, }; detail.build_graph(); if detail.check_valid() { Ok(detail) } else { Err(DetailError::InfiniteLoop) } } fn into_html(self) -> Option { self.flatten(&"start") } fn flatten(&self, rule: &str) -> Option { 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 (display, detail_to) = piece.split_once('~').unwrap_or((piece, piece)); let expand = self.flatten(&detail_to)?; output += "
"; output += display; output += ""; output += &expand; output += "
"; } } 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; } } } } } // TODO actually write this fn check_valid(&self) -> bool { true } } #[derive(Debug)] enum DetailError { Io(IoError), InfiniteLoop, }