1use crate::{
9 consts::{
10 CONST_MARKER, EXTERNAL_FUNCTION_MARKER, INCLUDE_MARKER, KNOT_MARKER, LINE_COMMENT_MARKER,
11 ROOT_KNOT_NAME, STITCH_MARKER, TAG_MARKER, TODO_COMMENT_MARKER, VARIABLE_MARKER,
12 },
13 error::{
14 parse::{
15 knot::{KnotError, KnotErrorKind, KnotNameError},
16 prelude::{PreludeError, PreludeErrorKind},
17 ParseError,
18 },
19 utils::MetaData,
20 ReadError,
21 },
22 knot::{parse_stitch_from_lines, read_knot_name, read_stitch_name, Knot, KnotSet, Stitch},
23 line::{parse_variable, Variable},
24 story::types::{VariableInfo, VariableSet},
25};
26
27use std::collections::HashMap;
28
29pub fn read_story_content_from_string(
31 content: &str,
32) -> Result<(KnotSet, VariableSet, Vec<String>), ReadError> {
33 let all_lines = content
34 .lines()
35 .zip(0..)
36 .map(|(line, line_index)| (line, MetaData { line_index }))
37 .collect::<Vec<_>>();
38
39 let mut content_lines = remove_empty_and_comment_lines(all_lines);
40
41 let (root_knot, variables, tags, prelude_errors) =
42 split_off_and_parse_prelude(&mut content_lines)?;
43
44 let (mut knots, mut knot_errors) = parse_knots_from_lines(content_lines);
45
46 match root_knot {
47 Ok(knot) => {
48 knots.insert(ROOT_KNOT_NAME.to_string(), knot);
49 }
50 Err(knot_error) => knot_errors.insert(0, knot_error),
51 }
52
53 if knot_errors.is_empty() && prelude_errors.is_empty() {
54 Ok((knots, variables, tags))
55 } else {
56 Err(ParseError {
57 knot_errors,
58 prelude_errors,
59 }
60 .into())
61 }
62}
63
64fn split_off_and_parse_prelude(
74 lines: &mut Vec<(&str, MetaData)>,
75) -> Result<
76 (
77 Result<Knot, KnotError>,
78 VariableSet,
79 Vec<String>,
80 Vec<PreludeError>,
81 ),
82 ReadError,
83> {
84 let prelude_and_root = split_off_prelude_lines(lines);
85 let (prelude_lines, root_lines) = split_prelude_into_metadata_and_text(&prelude_and_root);
86
87 let root_meta_data = root_lines
88 .first()
89 .or(lines.first())
90 .or(prelude_lines.last())
91 .map(|(_, meta_data)| meta_data.clone())
92 .ok_or(ReadError::Empty)?;
93
94 let tags = parse_global_tags(&prelude_lines);
95 let (variables, prelude_errors) = parse_global_variables(&prelude_lines);
96 let root_knot = parse_root_knot_from_lines(root_lines, root_meta_data);
97
98 Ok((root_knot, variables, tags, prelude_errors))
99}
100
101fn parse_knots_from_lines(lines: Vec<(&str, MetaData)>) -> (KnotSet, Vec<KnotError>) {
103 let knot_line_sets = divide_lines_at_marker(lines, KNOT_MARKER);
104
105 let mut knots = HashMap::new();
106 let mut knot_errors = Vec::new();
107
108 for lines in knot_line_sets.into_iter().filter(|lines| !lines.is_empty()) {
109 match get_knot_from_lines(lines) {
110 Ok((knot_name, knot_data)) => {
111 if !knots.contains_key(&knot_name) {
112 knots.insert(knot_name, knot_data);
113 } else {
114 let prev_meta_data = knots.get(&knot_name).unwrap().meta_data.clone();
115
116 knot_errors.push(KnotError {
117 knot_meta_data: knot_data.meta_data.clone(),
118 line_errors: vec![KnotErrorKind::DuplicateKnotName {
119 name: knot_name,
120 prev_meta_data,
121 }],
122 });
123 }
124 }
125 Err(error) => knot_errors.push(error),
126 }
127 }
128
129 (knots, knot_errors)
130}
131
132fn parse_root_knot_from_lines(
134 lines: Vec<(&str, MetaData)>,
135 meta_data: MetaData,
136) -> Result<Knot, KnotError> {
137 let (_, stitches, line_errors) = get_stitches_from_lines(lines, ROOT_KNOT_NAME);
138
139 if line_errors.is_empty() {
140 Ok(Knot {
141 default_stitch: ROOT_KNOT_NAME.to_string(),
142 stitches,
143 tags: Vec::new(),
144 meta_data,
145 })
146 } else {
147 Err(KnotError {
148 knot_meta_data: meta_data,
149 line_errors,
150 })
151 }
152}
153
154fn get_knot_from_lines(lines: Vec<(&str, MetaData)>) -> Result<(String, Knot), KnotError> {
160 let (head, mut tail) = lines
161 .split_first()
162 .map(|(head, tail)| (head, tail.to_vec()))
163 .unwrap();
164
165 let (head_line, knot_meta_data) = head;
166
167 let mut line_errors = Vec::new();
168
169 let knot_name = match read_knot_name(head_line) {
170 Ok(name) => name,
171 Err(kind) => {
172 let (invalid_name, error) = get_invalid_name_error(head_line, kind, &knot_meta_data);
173
174 line_errors.push(error);
175
176 invalid_name
177 }
178 };
179
180 if tail.is_empty() {
181 line_errors.push(KnotErrorKind::EmptyKnot);
182 }
183
184 let tags = get_knot_tags(&mut tail);
185
186 let (default_stitch, stitches, stitch_errors) = get_stitches_from_lines(tail, &knot_name);
187 line_errors.extend(stitch_errors);
188
189 if default_stitch.is_some() && line_errors.is_empty() {
190 Ok((
191 knot_name,
192 Knot {
193 default_stitch: default_stitch.unwrap(),
194 stitches,
195 tags,
196 meta_data: knot_meta_data.clone(),
197 },
198 ))
199 } else {
200 Err(KnotError {
201 knot_meta_data: knot_meta_data.clone(),
202 line_errors,
203 })
204 }
205}
206
207fn get_stitches_from_lines(
209 lines: Vec<(&str, MetaData)>,
210 knot_name: &str,
211) -> (Option<String>, HashMap<String, Stitch>, Vec<KnotErrorKind>) {
212 let knot_stitch_sets = divide_lines_at_marker(lines, STITCH_MARKER);
213
214 let mut default_stitch = None;
215 let mut stitches = HashMap::new();
216 let mut line_errors = Vec::new();
217
218 for (stitch_index, lines) in knot_stitch_sets
219 .into_iter()
220 .enumerate()
221 .filter(|(_, lines)| !lines.is_empty())
222 {
223 match get_stitch_from_lines(lines, stitch_index, knot_name) {
224 Ok((name, stitch)) => {
225 if default_stitch.is_none() {
226 default_stitch.replace(name.clone());
227 }
228
229 if !stitches.contains_key(&name) {
230 stitches.insert(name, stitch);
231 } else {
232 let prev_meta_data = stitches.get(&name).unwrap().meta_data.clone();
233
234 line_errors.push(KnotErrorKind::DuplicateStitchName {
235 name: name,
236 knot_name: knot_name.to_string(),
237 meta_data: stitch.meta_data.clone(),
238 prev_meta_data,
239 });
240 }
241 }
242 Err(errors) => line_errors.extend(errors),
243 }
244 }
245
246 (default_stitch, stitches, line_errors)
247}
248
249fn get_stitch_from_lines(
259 mut lines: Vec<(&str, MetaData)>,
260 stitch_index: usize,
261 knot_name: &str,
262) -> Result<(String, Stitch), Vec<KnotErrorKind>> {
263 let mut line_errors = Vec::new();
264
265 let (first_line, meta_data) = lines[0].clone();
266
267 let stitch_name = match get_stitch_name(first_line, &meta_data) {
268 Ok(name) => {
269 if name.is_some() {
270 lines.remove(0);
271 }
272
273 get_stitch_identifier(name, stitch_index)
274 }
275 Err(kind) => {
276 line_errors.push(kind);
277 "$INVALID_NAME$".to_string()
278 }
279 };
280
281 match parse_stitch_from_lines(&lines, knot_name, &stitch_name, meta_data) {
282 Ok(stitch) => {
283 if line_errors.is_empty() {
284 Ok((stitch_name, stitch))
285 } else {
286 Err(line_errors)
287 }
288 }
289 Err(errors) => {
290 line_errors.extend(errors);
291 Err(line_errors)
292 }
293 }
294}
295
296fn get_stitch_name(
301 first_line: &str,
302 meta_data: &MetaData,
303) -> Result<Option<String>, KnotErrorKind> {
304 match read_stitch_name(first_line) {
305 Ok(name) => Ok(Some(name)),
306 Err(KnotNameError::Empty) => Ok(None),
307 Err(kind) => Err(KnotErrorKind::InvalidName {
308 line: first_line.to_string(),
309 kind,
310 meta_data: meta_data.clone(),
311 }),
312 }
313}
314
315fn get_invalid_name_error(
317 line: &str,
318 kind: KnotNameError,
319 meta_data: &MetaData,
320) -> (String, KnotErrorKind) {
321 let invalid_name = "$INVALID_NAME$".to_string();
322
323 let error = KnotErrorKind::InvalidName {
324 line: line.to_string(),
325 kind,
326 meta_data: meta_data.clone(),
327 };
328
329 (invalid_name, error)
330}
331
332fn get_stitch_identifier(name: Option<String>, stitch_index: usize) -> String {
337 match (stitch_index, name) {
338 (0, None) => ROOT_KNOT_NAME.to_string(),
339 (_, Some(name)) => format!("{}", name),
340 _ => unreachable!(
341 "No stitch name was present after dividing the set of lines into groups where \
342 the first line of each group is the stitch name: this is a contradiction which \
343 should not be possible."
344 ),
345 }
346}
347
348fn get_knot_tags(lines: &mut Vec<(&str, MetaData)>) -> Vec<String> {
352 if let Some(i) = lines
353 .iter()
354 .map(|(line, _)| line.trim_start())
355 .position(|line| !(line.is_empty() || line.starts_with('#')))
356 {
357 lines
358 .drain(..i)
359 .map(|(line, _)| line.trim())
360 .filter(|line| !line.is_empty())
361 .map(|line| line.trim_start_matches("#").trim_start().to_string())
362 .collect()
363 } else {
364 Vec::new()
365 }
366}
367
368fn divide_lines_at_marker<'a>(
370 mut content: Vec<(&'a str, MetaData)>,
371 marker: &str,
372) -> Vec<Vec<(&'a str, MetaData)>> {
373 let mut buffer = Vec::new();
374
375 while let Some(i) = content
376 .iter()
377 .rposition(|(line, _)| line.trim_start().starts_with(marker))
378 {
379 buffer.push(content.split_off(i));
380 }
381
382 if !content.is_empty() {
383 buffer.push(content);
384 }
385
386 buffer.into_iter().rev().collect()
387}
388
389fn remove_empty_and_comment_lines(content: Vec<(&str, MetaData)>) -> Vec<(&str, MetaData)> {
395 content
396 .into_iter()
397 .inspect(|(line, meta_data)| {
398 if line.starts_with(TODO_COMMENT_MARKER) {
399 eprintln!("{} (line {})", &line, meta_data.line_index + 1);
400 }
401 })
402 .filter(|(line, _)| {
403 !(line.starts_with(LINE_COMMENT_MARKER) || line.starts_with(TODO_COMMENT_MARKER))
404 })
405 .filter(|(line, _)| !line.trim().is_empty())
406 .map(|(line, meta_data)| {
407 if let Some(i) = line.find("//") {
408 (line.get(..i).unwrap(), meta_data)
409 } else {
410 (line, meta_data)
411 }
412 })
413 .collect()
414}
415
416fn split_off_prelude_lines<'a>(lines: &mut Vec<(&'a str, MetaData)>) -> Vec<(&'a str, MetaData)> {
420 let i = lines
421 .iter()
422 .position(|(line, _)| line.trim_start().starts_with(KNOT_MARKER))
423 .unwrap_or(lines.len());
424
425 lines.drain(..i).collect()
426}
427
428fn split_prelude_into_metadata_and_text<'a>(
430 lines: &[(&'a str, MetaData)],
431) -> (Vec<(&'a str, MetaData)>, Vec<(&'a str, MetaData)>) {
432 let metadata_keywords = &[
434 format!("{} ", CONST_MARKER),
435 format!("{} ", EXTERNAL_FUNCTION_MARKER),
436 format!("{} ", INCLUDE_MARKER),
437 format!("{} ", VARIABLE_MARKER),
438 format!("{} ", TODO_COMMENT_MARKER),
439 format!("{}", LINE_COMMENT_MARKER),
440 ];
441
442 const METADATA_CHARS: &[char] = &[TAG_MARKER];
443
444 if let Some(i) = lines
445 .iter()
446 .map(|(line, _)| line.trim_start())
447 .position(|line| {
448 metadata_keywords.iter().all(|key| !line.starts_with(key))
449 && METADATA_CHARS.iter().all(|&c| !line.starts_with(c))
450 && !line.is_empty()
451 })
452 {
453 let (metadata, text) = lines.split_at(i);
454 (metadata.to_vec(), text.to_vec())
455 } else {
456 (lines.to_vec(), Vec::new())
457 }
458}
459
460fn parse_global_tags(lines: &[(&str, MetaData)]) -> Vec<String> {
462 lines
463 .iter()
464 .map(|(line, _)| line.trim())
465 .filter(|line| line.starts_with(TAG_MARKER))
466 .map(|line| line.get(1..).unwrap().trim().to_string())
467 .collect()
468}
469
470fn parse_global_variables(lines: &[(&str, MetaData)]) -> (VariableSet, Vec<PreludeError>) {
472 let mut variables = HashMap::new();
473 let mut errors = Vec::new();
474
475 for (line, meta_data) in lines
476 .iter()
477 .map(|(line, meta_data)| (line.trim(), meta_data))
478 .filter(|(line, _)| line.starts_with(VARIABLE_MARKER))
479 {
480 if let Err(kind) = parse_variable_with_name(line).and_then(|(name, variable)| {
481 let variable_info = VariableInfo {
482 variable,
483 meta_data: meta_data.clone(),
484 };
485
486 match variables.insert(name.clone(), variable_info) {
487 Some(_) => Err(PreludeErrorKind::DuplicateVariable { name }),
488 None => Ok(()),
489 }
490 }) {
491 errors.push(PreludeError {
492 line: line.to_string(),
493 kind,
494 meta_data: meta_data.clone(),
495 });
496 }
497 }
498
499 (variables, errors)
500}
501
502fn parse_variable_with_name(line: &str) -> Result<(String, Variable), PreludeErrorKind> {
506 line.find('=')
507 .ok_or_else(|| PreludeErrorKind::NoVariableAssignment)
508 .and_then(|i| {
509 let start = VARIABLE_MARKER.len();
510 let variable_name = line.get(start..i).unwrap().trim().to_string();
511
512 if variable_name.is_empty() {
513 Err(PreludeErrorKind::NoVariableName)
514 } else {
515 Ok((i, variable_name))
516 }
517 })
518 .and_then(|(i, variable_name)| {
519 parse_variable(line.get(i + 1..).unwrap().trim())
520 .map(|value| (variable_name, value))
521 .map_err(|err| err.into())
522 })
523}
524
525#[cfg(test)]
526pub mod tests {
527 use super::*;
528
529 use crate::knot::Address;
530
531 pub fn read_knots_from_string(content: &str) -> Result<KnotSet, Vec<KnotError>> {
532 let lines = content
533 .lines()
534 .enumerate()
535 .filter(|(_, line)| !line.trim().is_empty())
536 .map(|(i, line)| (line, MetaData::from(i)))
537 .collect();
538
539 let (knots, knot_errors) = parse_knots_from_lines(lines);
540
541 if knot_errors.is_empty() {
542 Ok(knots)
543 } else {
544 Err(knot_errors)
545 }
546 }
547
548 fn enumerate<'a>(lines: &[&'a str]) -> Vec<(&'a str, MetaData)> {
549 lines
550 .into_iter()
551 .map(|line| *line)
552 .enumerate()
553 .map(|(i, line)| (line, MetaData::from(i)))
554 .collect()
555 }
556
557 fn denumerate<'a, T>(lines: Vec<(&'a str, T)>) -> Vec<&'a str> {
558 lines.into_iter().map(|(line, _)| line).collect()
559 }
560
561 #[test]
562 fn split_lines_into_knots_and_preludes_drains_nothing_if_knots_begin_at_index_zero() {
563 let mut lines = enumerate(&["=== knot ==="]);
564
565 assert!(split_off_prelude_lines(&mut lines).is_empty());
566 assert_eq!(&denumerate(lines), &["=== knot ==="]);
567 }
568
569 #[test]
570 fn split_lines_into_knots_and_prelude_drains_all_items_if_knot_is_never_encountered() {
571 let mut lines = enumerate(&["No knot here, just prelude content"]);
572
573 split_off_prelude_lines(&mut lines);
574
575 assert!(lines.is_empty());
576 }
577
578 #[test]
579 fn split_lines_into_knots_and_prelude_drains_lines_up_until_first_knot() {
580 let mut lines = enumerate(&[
581 "Prelude content ",
582 "comes before ",
583 "the first named knot.",
584 "",
585 "=== here ===",
586 "Line one.",
587 ]);
588
589 let prelude = split_off_prelude_lines(&mut lines);
590
591 assert_eq!(
592 &denumerate(prelude),
593 &[
594 "Prelude content ",
595 "comes before ",
596 "the first named knot.",
597 ""
598 ]
599 );
600 assert_eq!(&denumerate(lines), &["=== here ===", "Line one."]);
601 }
602
603 #[test]
604 fn prelude_can_be_further_split_into_metadata_and_prelude_text() {
605 let lines = &[
606 "# All prelude content",
607 "",
608 "# comes before",
609 "The first regular string.",
610 ];
611
612 let (metadata, text) = split_prelude_into_metadata_and_text(&enumerate(lines));
613
614 assert_eq!(
615 &denumerate(metadata),
616 &["# All prelude content", "", "# comes before"]
617 );
618 assert_eq!(&denumerate(text), &["The first regular string."]);
619 }
620
621 #[test]
622 fn metadata_stops_when_it_does_not_start_with_variable_include_or_tag() {
623 let lines = &[
624 "# Tag",
625 "VAR variable",
626 "CONST constant variable",
627 "INCLUDE include",
628 "// line comment",
629 "TODO: comment",
630 "Regular line.",
631 ];
632
633 let (metadata, text) = split_prelude_into_metadata_and_text(&enumerate(lines));
634
635 assert_eq!(metadata.len(), 6);
636 assert_eq!(&denumerate(text), &["Regular line."]);
637 }
638
639 #[test]
640 fn parse_global_tags_from_metadata() {
641 let lines = &[
642 "# Tag",
643 "VAR variable",
644 "# Tag two ",
645 "// line comment",
646 "TODO: comment",
647 ];
648
649 assert_eq!(&parse_global_tags(&enumerate(lines)), &["Tag", "Tag two"]);
650 }
651
652 #[test]
653 fn parse_variables_from_metadata() {
654 let lines = &[
655 "# Tag",
656 "VAR float = 1.0",
657 "# Tag two ",
658 "VAR string = \"two words\"",
659 ];
660
661 let (variables, _) = parse_global_variables(&enumerate(lines));
662
663 assert_eq!(variables.len(), 2);
664 assert_eq!(
665 variables.get("float").unwrap().variable,
666 Variable::Float(1.0)
667 );
668 assert_eq!(
669 variables.get("string").unwrap().variable,
670 Variable::String("two words".to_string())
671 );
672 }
673
674 #[test]
675 fn two_variables_with_same_name_yields_error() {
676 let lines = &["VAR variable = 1.0", "VAR variable = \"two words\""];
677
678 let (_, errors) = parse_global_variables(&enumerate(lines));
679
680 assert_eq!(errors.len(), 1);
681 }
682
683 #[test]
684 fn global_variables_are_parsed_with_metadata() {
685 let lines = &["VAR float = 1.0", "VAR string = \"two words\""];
686
687 let (variables, _) = parse_global_variables(&enumerate(lines));
688
689 assert_eq!(variables.get("string").unwrap().meta_data, 1.into());
690 }
691
692 #[test]
693 fn parse_global_variables_returns_all_errors() {
694 let lines = &[
695 "VAR float = 1.0",
696 "VAR = 1.0", "VAR variable = ", "VAR variable 10", "VAR variable = 10chars", "VAR variable = \"two words", "VAR int = 10",
702 ];
703
704 let (variables, errors) = parse_global_variables(&enumerate(lines));
705
706 assert_eq!(variables.len(), 2);
707 assert_eq!(errors.len(), 5);
708 }
709
710 #[test]
711 fn regular_lines_can_start_with_variable_divert_or_text() {
712 let lines = &["# Tag", "Regular line."];
713
714 let (_, text) = split_prelude_into_metadata_and_text(&enumerate(lines));
715
716 assert_eq!(&denumerate(text), &["Regular line."]);
717
718 let lines_divert = &["# Tag", "-> divert"];
719
720 let (_, divert) = split_prelude_into_metadata_and_text(&enumerate(lines_divert));
721
722 assert_eq!(&denumerate(divert), &["-> divert"]);
723
724 let lines_variable = &["# Tag", "{variable}"];
725
726 let (_, variable) = split_prelude_into_metadata_and_text(&enumerate(lines_variable));
727
728 assert_eq!(&denumerate(variable), &["{variable}"]);
729 }
730
731 #[test]
732 fn read_knots_from_string_reads_several_present_knots() {
733 let content = "\
734== first ==
735First line.
736
737== second ==
738First line.
739
740== third ==
741First line.
742";
743
744 let knots = read_knots_from_string(content).unwrap();
745
746 assert_eq!(knots.len(), 3);
747
748 assert!(knots.contains_key("first"));
749 assert!(knots.contains_key("second"));
750 assert!(knots.contains_key("third"));
751 }
752
753 #[test]
754 fn read_knots_from_string_requires_named_knots() {
755 let content = "\
756First line.
757Second line.
758";
759
760 assert!(read_knots_from_string(content).is_err());
761 }
762
763 #[test]
764 fn divide_into_knots_splits_given_lines_at_knot_markers() {
765 let content = enumerate(&[
766 "== Knot one ",
767 "Line 1",
768 "Line 2",
769 "",
770 "=== Knot two ===",
771 "Line 3",
772 "",
773 ]);
774
775 let knot_lines = divide_lines_at_marker(content.clone(), KNOT_MARKER);
776
777 assert_eq!(knot_lines[0][..], content[0..4]);
778 assert_eq!(knot_lines[1][..], content[4..]);
779 }
780
781 #[test]
782 fn divide_into_knots_adds_content_from_nameless_knots_first() {
783 let content = enumerate(&["Line 1", "Line 2", "== Knot one ", "Line 3"]);
784
785 let knot_lines = divide_lines_at_marker(content.clone(), KNOT_MARKER);
786
787 assert_eq!(knot_lines[0][..], content[0..2]);
788 assert_eq!(knot_lines[1][..], content[2..]);
789 }
790
791 #[test]
792 fn divide_into_stitches_splits_lines_at_markers() {
793 let content = enumerate(&[
794 "Line 1",
795 "= Stitch one ",
796 "Line 2",
797 "Line 3",
798 "",
799 "= Stitch two",
800 "Line 4",
801 "",
802 ]);
803
804 let knot_lines = divide_lines_at_marker(content.clone(), STITCH_MARKER);
805
806 assert_eq!(knot_lines[0][..], content[0..1]);
807 assert_eq!(knot_lines[1][..], content[1..5]);
808 assert_eq!(knot_lines[2][..], content[5..]);
809 }
810
811 #[test]
812 fn empty_lines_and_comment_lines_are_removed_by_initial_processing() {
813 let content = vec![
814 "Good line",
815 "// Comment line is remove",
816 "", " ", "TODO: As is todo comments",
819 "TODO but not without a colon!",
820 ];
821
822 let lines = remove_empty_and_comment_lines(enumerate(&content));
823 assert_eq!(
824 &denumerate(lines),
825 &[content[0].clone(), content[5].clone()]
826 );
827 }
828
829 #[test]
830 fn initial_processing_splits_off_line_comments() {
831 let content = vec![
832 "Line before comment marker // Removed part",
833 "Line with no comment marker",
834 ];
835
836 let lines = remove_empty_and_comment_lines(enumerate(&content));
837 assert_eq!(lines[0].0, "Line before comment marker ");
838 assert_eq!(lines[1].0, "Line with no comment marker");
839 }
840
841 #[test]
842 fn parsing_knot_from_lines_gets_name() {
843 let content = enumerate(&["== Knot_name ==", "Line 1", "Line 2"]);
844
845 let (name, _) = get_knot_from_lines(content).unwrap();
846 assert_eq!(&name, "Knot_name");
847 }
848
849 #[test]
850 fn parsing_knot_from_lines_without_stitches_sets_content_in_default_named_stitch() {
851 let content = enumerate(&["== Knot_name ==", "Line 1", "Line 2"]);
852
853 let (_, knot) = get_knot_from_lines(content).unwrap();
854
855 assert_eq!(&knot.default_stitch, ROOT_KNOT_NAME);
856 assert_eq!(
857 knot.stitches.get(ROOT_KNOT_NAME).unwrap().root.items.len(),
858 2
859 );
860 }
861
862 #[test]
863 fn parsing_a_stitch_gets_name_if_present_else_default_root_name_if_index_is_zero() {
864 let (name, _) =
865 get_stitch_from_lines(enumerate(&["= stitch_name =", "Line 1"]), 0, "").unwrap();
866 assert_eq!(name, "stitch_name".to_string());
867
868 let (name, _) = get_stitch_from_lines(enumerate(&["Line 1"]), 0, "").unwrap();
869 assert_eq!(name, ROOT_KNOT_NAME);
870 }
871
872 #[test]
873 fn parsing_stitch_from_lines_sets_address_in_root_node() {
874 let (_, stitch) =
875 get_stitch_from_lines(enumerate(&["= cinema", "Line 1"]), 0, "tripoli").unwrap();
876
877 assert_eq!(
878 stitch.root.address,
879 Address::from_parts_unchecked("tripoli", Some("cinema"))
880 );
881
882 let (_, stitch) = get_stitch_from_lines(enumerate(&["Line 1"]), 0, "tripoli").unwrap();
883
884 assert_eq!(
885 stitch.root.address,
886 Address::from_parts_unchecked("tripoli", None)
887 );
888 }
889
890 #[test]
891 fn parsing_a_stitch_gets_all_content_regardless_of_whether_name_is_present() {
892 let (_, content) =
893 get_stitch_from_lines(enumerate(&["= stitch_name =", "Line 1"]), 0, "").unwrap();
894 assert_eq!(content.root.items.len(), 1);
895
896 let (_, content) = get_stitch_from_lines(enumerate(&["Line 1"]), 0, "").unwrap();
897 assert_eq!(content.root.items.len(), 1);
898 }
899
900 #[test]
901 fn parsing_a_knot_from_lines_sets_stitches_in_hash_map() {
902 let lines = enumerate(&[
903 "== knot_name",
904 "= stitch_one",
905 "Line one",
906 "= stitch_two",
907 "Line two",
908 ]);
909
910 let (_, knot) = get_knot_from_lines(lines).unwrap();
911
912 assert_eq!(knot.stitches.len(), 2);
913 assert!(knot.stitches.get("stitch_one").is_some());
914 assert!(knot.stitches.get("stitch_two").is_some());
915 }
916
917 #[test]
918 fn knot_with_root_content_gets_default_knot_as_first_stitch() {
919 let lines = enumerate(&[
920 "== knot_name",
921 "Line 1",
922 "= stitch_one",
923 "Line 2",
924 "= stitch_two",
925 "Line 3",
926 ]);
927
928 let (_, knot) = get_knot_from_lines(lines).unwrap();
929 assert_eq!(&knot.default_stitch, ROOT_KNOT_NAME);
930 }
931
932 #[test]
933 fn root_knot_parses_stitch_without_a_name() {
934 let lines = enumerate(&["Line 1", "Line 2"]);
935
936 let root = parse_root_knot_from_lines(lines.clone(), ().into()).unwrap();
937
938 let comparison =
939 parse_stitch_from_lines(&lines, ROOT_KNOT_NAME, ROOT_KNOT_NAME, ().into()).unwrap();
940
941 assert_eq!(
942 format!("{:?}", root.stitches.get(ROOT_KNOT_NAME).unwrap()),
943 format!("{:?}", comparison)
944 );
945 }
946
947 #[test]
948 fn root_knot_may_have_stitches() {
949 let lines = enumerate(&["Line 1", "= Stitch", "Line 2"]);
950
951 let root = parse_root_knot_from_lines(lines, ().into()).unwrap();
952
953 assert_eq!(root.stitches.len(), 2);
954 }
955
956 #[test]
957 fn knot_with_no_root_content_gets_default_knot_as_first_stitch() {
958 let lines = enumerate(&[
959 "== knot_name",
960 "= stitch_one",
961 "Line 1",
962 "= stitch_two",
963 "Line 2",
964 ]);
965
966 let (_, knot) = get_knot_from_lines(lines).unwrap();
967 assert_eq!(&knot.default_stitch, "stitch_one");
968 }
969
970 #[test]
971 fn knot_parses_tags_from_name_until_first_line_without_octothorpe() {
972 let lines = enumerate(&["== knot_name", "# Tag one", "# Tag two", "Line 1"]);
973
974 let (_, knot) = get_knot_from_lines(lines).unwrap();
975 assert_eq!(&knot.tags, &["Tag one".to_string(), "Tag two".to_string()]);
976 }
977
978 #[test]
979 fn knot_tags_ignore_empty_lines() {
980 let lines = enumerate(&["== knot_name", "", "# Tag one", "", "# Tag two", "Line 1"]);
981
982 let (_, knot) = get_knot_from_lines(lines).unwrap();
983 assert_eq!(&knot.tags, &["Tag one".to_string(), "Tag two".to_string()]);
984 }
985
986 #[test]
987 fn if_no_tags_are_set_the_tags_are_empty() {
988 let lines = enumerate(&["== knot_name", "Line 1"]);
989
990 let (_, knot) = get_knot_from_lines(lines).unwrap();
991 assert!(knot.tags.is_empty());
992 }
993
994 #[test]
995 fn tags_do_not_disturb_remaining_content() {
996 let lines_with_tags = enumerate(&["== knot_name", "# Tag one", "# Tag two", "", "Line 1"]);
997 let lines_without_tags = vec![
998 ("== knot_name", MetaData::from(0)),
999 ("Line 1", MetaData::from(4)),
1000 ];
1001
1002 let (_, knot_tags) = get_knot_from_lines(lines_with_tags).unwrap();
1003 let (_, knot_no_tags) = get_knot_from_lines(lines_without_tags).unwrap();
1004
1005 assert_eq!(
1006 format!("{:?}", knot_tags.stitches),
1007 format!("{:?}", knot_no_tags.stitches)
1008 );
1009 }
1010
1011 #[test]
1012 fn reading_story_data_gets_unordered_variables_in_prelude() {
1013 let content = "
1014# Random tag
1015VAR counter = 0
1016# Random tag
1017// Line comment
1018VAR hazardous = true
1019
1020-> introduction
1021";
1022
1023 let (_, variables, _) = read_story_content_from_string(content).unwrap();
1024
1025 assert_eq!(variables.len(), 2);
1026 assert!(variables.contains_key("counter"));
1027 assert!(variables.contains_key("hazardous"));
1028 }
1029
1030 #[test]
1031 fn variables_after_first_line_of_text_are_ignored() {
1032 let content = "
1033VAR counter = 0
1034
1035-> introduction
1036VAR hazardous = true
1037";
1038
1039 let (_, variables, _) = read_story_content_from_string(content).unwrap();
1040
1041 assert_eq!(variables.len(), 1);
1042 assert!(variables.contains_key("counter"));
1043 }
1044
1045 #[test]
1046 fn no_variables_give_empty_set() {
1047 let content = "
1048// Just a line comment!
1049-> introduction
1050";
1051
1052 let (_, variables, _) = read_story_content_from_string(content).unwrap();
1053
1054 assert_eq!(variables.len(), 0);
1055 }
1056
1057 #[test]
1058 fn reading_story_data_gets_all_global_tags_in_prelude() {
1059 let content = "
1060# title: test
1061VAR counter = 0
1062# rating: hazardous
1063// Line comment
1064VAR hazardous = true
1065
1066-> introduction
1067";
1068
1069 let (_, _, tags) = read_story_content_from_string(content).unwrap();
1070
1071 assert_eq!(
1072 &tags,
1073 &["title: test".to_string(), "rating: hazardous".to_string()]
1074 );
1075 }
1076
1077 #[test]
1078 fn reading_story_data_sets_knot_line_starting_line_indices_including_prelude_content() {
1079 let content = "\
1080# title: line_counting
1081VAR line_count = 0
1082
1083-> root
1084
1085== root
1086One line.
1087
1088== second
1089Second line.
1090";
1091
1092 let (knots, _, _) = read_story_content_from_string(content).unwrap();
1093
1094 assert_eq!(knots.get("root").unwrap().meta_data.line_index, 5);
1095 assert_eq!(knots.get("second").unwrap().meta_data.line_index, 8);
1096 }
1097
1098 #[test]
1099 fn all_prelude_and_knot_errors_are_caught_and_returned() {
1100 let content = "\
1101VAR = 0
1102
1103== knot.stitch
1104{2 +}
1105*+ Sticky or non-sticky?
1106
1107== empty_knot
1108
1109";
1110
1111 match read_story_content_from_string(content) {
1112 Err(ReadError::ParseError(error)) => {
1113 assert_eq!(error.prelude_errors.len(), 1);
1114 assert_eq!(error.knot_errors.len(), 2);
1115
1116 assert_eq!(error.knot_errors[0].line_errors.len(), 3);
1117 assert_eq!(error.knot_errors[1].line_errors.len(), 1);
1118 }
1119 other => panic!("expected `ReadError::ParseError` but got {:?}", other),
1120 }
1121 }
1122
1123 #[test]
1124 fn reading_story_content_works_if_content_only_has_text() {
1125 let content = "\
1126Line one.
1127";
1128
1129 assert!(read_story_content_from_string(content).is_ok());
1130 }
1131
1132 #[test]
1133 fn reading_story_content_works_if_content_starts_with_knot() {
1134 let content = "\
1135=== knot ===
1136Line one.
1137";
1138
1139 assert!(read_story_content_from_string(content).is_ok());
1140 }
1141
1142 #[test]
1143 fn reading_story_content_does_not_work_if_knot_has_no_content() {
1144 let content = "\
1145=== knot ===
1146";
1147
1148 assert!(read_story_content_from_string(content).is_err());
1149 }
1150
1151 #[test]
1152 fn reading_story_content_does_not_work_if_stitch_has_no_content() {
1153 let content = "\
1154=== knot ===
1155= stitch
1156";
1157
1158 assert!(read_story_content_from_string(content).is_err());
1159 }
1160
1161 #[test]
1162 fn reading_story_content_yields_error_if_duplicate_stitch_names_are_found_in_one_knot() {
1163 let content = "\
1164== knot
1165= stitch
1166Line one.
1167= stitch
1168Line two.
1169";
1170
1171 match read_story_content_from_string(content) {
1172 Err(ReadError::ParseError(err)) => match &err.knot_errors[0].line_errors[0] {
1173 KnotErrorKind::DuplicateStitchName { .. } => (),
1174 other => panic!(
1175 "expected `KnotErrorKind::DuplicateStitchName` but got {:?}",
1176 other
1177 ),
1178 },
1179 other => panic!("expected `ReadError::ParseError` but got {:?}", other),
1180 }
1181 }
1182
1183 #[test]
1184 fn reading_story_content_yields_error_if_duplicate_knot_names_are_found() {
1185 let content = "\
1186== knot
1187Line one.
1188== knot
1189Line two.
1190";
1191
1192 match read_story_content_from_string(content) {
1193 Err(ReadError::ParseError(err)) => match &err.knot_errors[0].line_errors[0] {
1194 KnotErrorKind::DuplicateKnotName { .. } => (),
1195 other => panic!(
1196 "expected `KnotErrorKind::DuplicateKnotName` but got {:?}",
1197 other
1198 ),
1199 },
1200 other => panic!("expected `ReadError::ParseError` but got {:?}", other),
1201 }
1202 }
1203}