cynthia
2 years ago
commit
05821b77a4
4 changed files with 166 additions and 0 deletions
-
1.gitignore
-
7Cargo.lock
-
8Cargo.toml
-
150src/main.rs
@ -0,0 +1 @@ |
|||||
|
/target |
@ -0,0 +1,7 @@ |
|||||
|
# This file is automatically @generated by Cargo. |
||||
|
# It is not intended for manual editing. |
||||
|
version = 3 |
||||
|
|
||||
|
[[package]] |
||||
|
name = "details" |
||||
|
version = "0.1.0" |
@ -0,0 +1,8 @@ |
|||||
|
[package] |
||||
|
name = "details" |
||||
|
version = "0.1.0" |
||||
|
edition = "2021" |
||||
|
|
||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
|
||||
|
[dependencies] |
@ -0,0 +1,150 @@ |
|||||
|
#![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<String, String>,
|
||||
|
node_graph: Vec<Vec<bool>>,
|
||||
|
}
|
||||
|
|
||||
|
impl Detail {
|
||||
|
fn new(filename: &str) -> Result<Self, DetailError> {
|
||||
|
let contents = fs::read_to_string(filename).map_err(|err| DetailError::Io(err))?;
|
||||
|
|
||||
|
let mut passages = BTreeMap::new();
|
||||
|
let mut cur_passage: Option<String> = 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<String> {
|
||||
|
self.flatten(&"start")
|
||||
|
}
|
||||
|
|
||||
|
fn flatten(&self, rule: &str) -> Option<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 (display, detail_to) = piece.split_once('~').unwrap_or((piece, piece));
|
||||
|
let expand = self.flatten(&detail_to)?;
|
||||
|
output += "<details><summary>";
|
||||
|
output += display;
|
||||
|
output += "</summary>";
|
||||
|
output += &expand;
|
||||
|
output += "</details>";
|
||||
|
}
|
||||
|
}
|
||||
|
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,
|
||||
|
}
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue