tiny gamemaking tool using the html details tag 💙 likely fragile, be nice to it
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
4.8 KiB

2 years ago
  1. #![allow(dead_code)]
  2. use std::collections::BTreeMap;
  3. use std::io::{Error as IoError, Write};
  4. use std::{env::args, fs};
  5. fn main() {
  6. let filename = args().nth(1).expect("no file given");
  7. let detail = Detail::new(&filename).unwrap();
  8. let out = filename
  9. .strip_suffix(".detail")
  10. .unwrap_or(&filename)
  11. .to_string()
  12. + ".html";
  13. let mut file =
  14. std::fs::File::create(out.clone()).unwrap_or_else(|_| panic!("couldn't create {}", out));
  15. file.write_all(detail.into_html().unwrap().as_bytes())
  16. .unwrap_or_else(|_| panic!("unable to write to {}", out));
  17. }
  18. // ~~~ data structures ~~~
  19. struct Detail {
  20. passages: BTreeMap<String, String>,
  21. node_graph: Vec<Vec<bool>>,
  22. }
  23. impl Detail {
  24. fn new(filename: &str) -> Result<Self, DetailError> {
  25. let contents = fs::read_to_string(filename).map_err(|err| DetailError::Io(err))?;
  26. let mut passages = BTreeMap::new();
  27. let mut cur_passage: Option<String> = None;
  28. let mut cur_lines = String::new();
  29. for line in contents.lines() {
  30. let line = line.trim();
  31. // if line begins with ~detail
  32. if let Some(passage_name) = line.strip_prefix("~detail ") {
  33. // if this isn't the first passage, add previous passage to map
  34. if let Some(prev_passage) = cur_passage {
  35. let prev_passage = prev_passage.to_string();
  36. if !cur_lines.is_empty() {
  37. if passages.contains_key(&prev_passage) {
  38. println!("WARNING: passage name \"{}\" duplicated", prev_passage);
  39. passages.remove(&prev_passage);
  40. } else {
  41. // clear cur_lines and insert
  42. passages.insert(prev_passage, std::mem::take(&mut cur_lines));
  43. }
  44. }
  45. }
  46. cur_passage = Some(passage_name.to_string());
  47. } else {
  48. // case: regular line
  49. cur_lines += line;
  50. }
  51. }
  52. // add last passage
  53. if let Some(prev_passage) = cur_passage {
  54. let prev_passage = prev_passage.to_string();
  55. if !cur_lines.is_empty() {
  56. if passages.contains_key(&prev_passage) {
  57. println!("WARNING: passage name \"{}\" duplicated", prev_passage);
  58. passages.remove(&prev_passage);
  59. } else {
  60. // clear cur_lines and insert
  61. passages.insert(prev_passage, std::mem::take(&mut cur_lines));
  62. }
  63. }
  64. }
  65. let mut node_graph = Vec::new();
  66. node_graph.resize_with(passages.len(), || {
  67. let mut row = Vec::new();
  68. row.resize(passages.len(), false);
  69. row
  70. });
  71. let mut detail = Self {
  72. passages,
  73. node_graph,
  74. };
  75. detail.build_graph();
  76. if detail.check_valid() {
  77. Ok(detail)
  78. } else {
  79. Err(DetailError::InfiniteLoop)
  80. }
  81. }
  82. fn into_html(self) -> Option<String> {
  83. self.flatten(&"start")
  84. }
  85. fn flatten(&self, rule: &str) -> Option<String> {
  86. let rule = self.passages.get(rule)?;
  87. let mut output = String::new();
  88. for (sep, piece) in rule.split('#').enumerate() {
  89. // search keys
  90. if sep % 2 == 0 {
  91. output += piece;
  92. } else {
  93. let (display, detail_to) = piece.split_once('~').unwrap_or((piece, piece));
  94. let expand = self.flatten(&detail_to)?;
  95. output += "<details><summary>";
  96. output += display;
  97. output += "</summary>";
  98. output += &expand;
  99. output += "</details>";
  100. }
  101. }
  102. Some(output)
  103. }
  104. // don't speak to me about how nested this method is.
  105. fn build_graph(&mut self) {
  106. for (i, content) in self.passages.values().enumerate() {
  107. for (sep, piece) in content.split('#').enumerate() {
  108. // search keys
  109. if sep % 2 == 1 {
  110. if let Some((_, detail_to)) = piece.split_once('~') {
  111. if let Some(j) = self.passages.keys().position(|name| name == &detail_to) {
  112. self.node_graph[i][j] = true;
  113. }
  114. } else if let Some(j) = self.passages.keys().position(|name| name == &piece) {
  115. self.node_graph[i][j] = true;
  116. }
  117. }
  118. }
  119. }
  120. }
  121. // TODO actually write this
  122. fn check_valid(&self) -> bool {
  123. true
  124. }
  125. }
  126. #[derive(Debug)]
  127. enum DetailError {
  128. Io(IoError),
  129. InfiniteLoop,
  130. }