diff --git a/src/metadata/error.rs b/src/metadata/error.rs index 14e36a65..3ef51d8a 100644 --- a/src/metadata/error.rs +++ b/src/metadata/error.rs @@ -4,7 +4,10 @@ use std::path::PathBuf; use regress::Error as RegexImplError; use saphyr::Marker; -use crate::{escape_ascii, metadata::MessageContent}; +use crate::{ + escape_ascii, + metadata::{MessageContent, PreludeDiffSpan}, +}; use super::yaml::{YamlObjectType, to_unmarked_yaml}; @@ -28,13 +31,20 @@ impl std::error::Error for MultilingualMessageContentsError {} /// Represents an error that occurred while parsing metadata. #[derive(Debug)] pub struct ParseMetadataError { - marker: saphyr::Marker, + line: usize, + column: usize, reason: MetadataParsingErrorReason, + is_within_replacement_prelude: bool, } impl ParseMetadataError { pub(super) fn new(marker: Marker, reason: MetadataParsingErrorReason) -> Self { - Self { marker, reason } + Self { + line: marker.line(), + column: marker.col(), + reason, + is_within_replacement_prelude: false, + } } pub(super) fn invalid_condition( @@ -42,10 +52,10 @@ impl ParseMetadataError { condition: String, cause: loot_condition_interpreter::Error, ) -> Self { - Self { + Self::new( marker, - reason: MetadataParsingErrorReason::InvalidCondition(Box::new((condition, cause))), - } + MetadataParsingErrorReason::InvalidCondition(Box::new((condition, cause))), + ) } pub(super) fn missing_key( @@ -53,17 +63,17 @@ impl ParseMetadataError { key: &'static str, yaml_type: YamlObjectType, ) -> Self { - Self { + Self::new( marker, - reason: MetadataParsingErrorReason::MissingKey(key, yaml_type), - } + MetadataParsingErrorReason::MissingKey(key, yaml_type), + ) } pub(super) fn duplicate_entry(marker: Marker, id: String, yaml_type: YamlObjectType) -> Self { - Self { + Self::new( marker, - reason: MetadataParsingErrorReason::DuplicateEntry(id, yaml_type), - } + MetadataParsingErrorReason::DuplicateEntry(id, yaml_type), + ) } pub(super) fn unexpected_type( @@ -71,10 +81,10 @@ impl ParseMetadataError { yaml_type: YamlObjectType, expected_type: ExpectedType, ) -> Self { - Self { + Self::new( marker, - reason: MetadataParsingErrorReason::UnexpectedType(expected_type, yaml_type), - } + MetadataParsingErrorReason::UnexpectedType(expected_type, yaml_type), + ) } pub(super) fn unexpected_value_type( @@ -83,22 +93,45 @@ impl ParseMetadataError { yaml_type: YamlObjectType, expected_type: ExpectedType, ) -> Self { - Self { + Self::new( marker, - reason: MetadataParsingErrorReason::UnexpectedValueType(key, expected_type, yaml_type), - } + MetadataParsingErrorReason::UnexpectedValueType(key, expected_type, yaml_type), + ) + } + + pub(super) fn adjust_location(&mut self, diff_span: &PreludeDiffSpan) { + let (pos, in_prelude) = adjust_location( + &Position { + line: self.line, + column: self.column, + }, + diff_span, + ); + self.line = pos.line; + self.column = pos.column; + self.is_within_replacement_prelude = in_prelude; } } impl std::fmt::Display for ParseMetadataError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "encountered a YAML parsing error at line {} column {}: {}", - self.marker.line(), - self.marker.col() + 1, - self.reason - ) + if self.is_within_replacement_prelude { + write!( + f, + "encountered a YAML parsing error at line {} column {} within the prelude: {}", + self.line, + self.column + 1, + self.reason + ) + } else { + write!( + f, + "encountered a YAML parsing error at line {} column {}: {}", + self.line, + self.column + 1, + self.reason + ) + } } } @@ -114,10 +147,46 @@ impl std::error::Error for ParseMetadataError { impl From for ParseMetadataError { fn from(value: saphyr::ScanError) -> Self { - Self { - marker: *value.marker(), - reason: MetadataParsingErrorReason::Other(Box::new(value)), - } + Self::new( + *value.marker(), + MetadataParsingErrorReason::Other(value.info().to_owned()), + ) + } +} + +struct Position { + line: usize, + column: usize, +} + +fn adjust_location(pos: &Position, diff_span: &PreludeDiffSpan) -> (Position, bool) { + if pos.line > diff_span.end_line { + ( + Position { + line: pos.line.saturating_sub_signed(diff_span.line_count_delta), + column: pos.column, + }, + false, + ) + } else if pos.line >= diff_span.start_line { + ( + Position { + // +1 because line numbers are 1-indexed. + line: pos.line - diff_span.start_line + 1, + // The prelude is indented by two spaces, need to undo the + // effect of that on the column value. + column: pos.column - 2, + }, + true, + ) + } else { + ( + Position { + line: pos.line, + column: pos.column, + }, + false, + ) } } @@ -133,7 +202,11 @@ pub(super) enum MetadataParsingErrorReason { MissingSubstitution(String), NonU32Number(i64), DuplicateEntry(String, YamlObjectType), - Other(Box), + PreludeSubstitutionOverflow { + new_prelude_count: usize, + old_prelude_count: usize, + }, + Other(String), } impl std::fmt::Display for MetadataParsingErrorReason { @@ -171,6 +244,13 @@ impl std::fmt::Display for MetadataParsingErrorReason { f, "more than one entry exists for {yaml_object_type} \"{id}\"" ), + Self::PreludeSubstitutionOverflow { + new_prelude_count, + old_prelude_count, + } => write!( + f, + "overflow when computing difference between old and new prelude line or index counts: {new_prelude_count} - {old_prelude_count}" + ), Self::Other(m) => m.fmt(f), } } @@ -321,8 +401,10 @@ impl From for MetadataDocumentParsingError { #[derive(Clone, Debug, Eq, PartialEq)] pub(super) struct YamlMergeKeyError { - start: Marker, + line: usize, + column: usize, yaml: String, + is_within_replacement_prelude: bool, } impl YamlMergeKeyError { @@ -345,21 +427,47 @@ impl YamlMergeKeyError { } YamlMergeKeyError { - start: value.span.start, + line: value.span.start.line(), + column: value.span.start.col(), yaml, + // This may not be correct, but will be fixed if not by a call to with_adjusted_start(). + is_within_replacement_prelude: false, } } + + pub(super) fn adjust_location(&mut self, diff_span: &PreludeDiffSpan) { + let (pos, in_prelude) = adjust_location( + &Position { + line: self.line, + column: self.column, + }, + diff_span, + ); + self.line = pos.line; + self.column = pos.column; + self.is_within_replacement_prelude = in_prelude; + } } impl std::fmt::Display for YamlMergeKeyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "invalid YAML merge key value at line {} column {}: {}", - self.start.line(), - self.start.col() + 1, - self.yaml - ) + if self.is_within_replacement_prelude { + write!( + f, + "invalid YAML merge key value at line {} column {} within the prelude: {}", + self.line, + self.column + 1, + self.yaml + ) + } else { + write!( + f, + "invalid YAML merge key value at line {} column {}: {}", + self.line, + self.column + 1, + self.yaml + ) + } } } diff --git a/src/metadata/metadata_document.rs b/src/metadata/metadata_document.rs index ae554454..84b2f06e 100644 --- a/src/metadata/metadata_document.rs +++ b/src/metadata/metadata_document.rs @@ -5,7 +5,10 @@ use std::{ use saphyr::{LoadableYamlNode, MarkedYaml, YamlData}; -use crate::{escape_ascii, logging}; +use crate::{ + escape_ascii, logging, + metadata::{PreludeDiffSpan, error::MetadataParsingErrorReason}, +}; use super::{ error::{ @@ -80,10 +83,32 @@ impl MetadataDocument { let prelude = std::fs::read_to_string(prelude_path) .map_err(|e| LoadMetadataError::from_io_error(masterlist_path.into(), e))?; - let masterlist = replace_prelude(masterlist, &prelude); + let masterlist = replace_prelude(masterlist, &prelude).map_err(|e| { + LoadMetadataError::new( + masterlist_path.into(), + MetadataDocumentParsingError::MetadataParsingError(e), + ) + })?; - self.load_from_str(&masterlist) - .map_err(|e| LoadMetadataError::new(masterlist_path.into(), e))?; + self.load_from_str(&masterlist.masterlist) + .map_err(|mut e| { + match &mut e { + MetadataDocumentParsingError::MetadataParsingError(err) => { + if let Some(meta) = masterlist.meta { + err.adjust_location(&meta); + } + } + MetadataDocumentParsingError::YamlMergeKeyError(err) => { + if let Some(meta) = masterlist.meta { + err.adjust_location(&meta); + } + } + _ => { + // No location to adjust. + } + } + LoadMetadataError::new(masterlist_path.into(), e) + })?; logging::trace!( "Successfully loaded metadata from file at \"{}\".", @@ -99,11 +124,13 @@ impl MetadataDocument { let doc = docs .pop() .ok_or_else(|| MetadataDocumentParsingError::NoDocuments)?; + if !docs.is_empty() { return Err(MetadataDocumentParsingError::MoreThanOneDocument( docs.len() + 1, )); } + let doc = process_merge_keys(doc)?; let YamlData::Mapping(doc) = doc.data else { @@ -339,17 +366,74 @@ impl std::default::Default for MetadataDocument { } } -fn replace_prelude(masterlist: String, prelude: &str) -> String { - if let Some((start, end)) = split_on_prelude(&masterlist) { +struct MasterlistWithReplacedPrelude { + masterlist: String, + meta: Option, +} + +fn replace_prelude( + masterlist: String, + prelude: &str, +) -> Result { + if let Some(SplitMasterlist { + before_prelude, + prelude: old_prelude, + after_prelude, + }) = split_on_prelude(&masterlist) + { let prelude = indent_prelude(prelude); - format!("{start}{prelude}{end}") + let masterlist = if after_prelude.is_empty() { + format!("{before_prelude}\n{prelude}") + } else { + format!("{before_prelude}\n{prelude}\n{after_prelude}") + }; + + // The old prelude included leading and trailing newlines but the new prelude doesn't. + let count_modifier = if after_prelude.is_empty() { 1 } else { 2 }; + let old_prelude_line_count = count_lines(old_prelude).saturating_sub(count_modifier); + + // Add one because before_prelude does not include the line break after the prelude: key. + let prelude_start_line = count_lines(before_prelude) + 1; + let new_prelude_line_count = count_lines(&prelude); + // The last line of the prelude. + let prelude_end_line = prelude_start_line + new_prelude_line_count.saturating_sub(1); + + let line_count_delta = new_prelude_line_count + .checked_signed_diff(old_prelude_line_count) + .ok_or_else(|| { + ParseMetadataError::new( + saphyr::Marker::new(before_prelude.len(), prelude_start_line, 0), + MetadataParsingErrorReason::PreludeSubstitutionOverflow { + new_prelude_count: new_prelude_line_count, + old_prelude_count: old_prelude_line_count, + }, + ) + })?; + + Ok(MasterlistWithReplacedPrelude { + masterlist, + meta: Some(PreludeDiffSpan { + start_line: prelude_start_line, + end_line: prelude_end_line, + line_count_delta, + }), + }) } else { - masterlist + Ok(MasterlistWithReplacedPrelude { + masterlist, + meta: None, + }) } } -fn split_on_prelude(masterlist: &str) -> Option<(&str, &str)> { +struct SplitMasterlist<'a> { + before_prelude: &'a str, + prelude: &'a str, + after_prelude: &'a str, +} + +fn split_on_prelude(masterlist: &str) -> Option> { let (prefix, remainder) = split_on_prelude_start(masterlist)?; let mut iter = remainder.bytes().enumerate().peekable(); @@ -362,11 +446,13 @@ fn split_on_prelude(masterlist: &str) -> Option<(&str, &str)> { && !matches!(next_byte, b' ' | b'#' | b'\n' | b'\r') { // LIMITATION: Slicing at index should never fail, but the - // compiler can't see that. A variation of str.find() that - // could take a closure that matches on substrings would - // eliminate the need for this. - if let Some(suffix) = remainder.get(index..) { - return Some((prefix, suffix)); + // compiler can't see that. + if let Some((prelude, suffix)) = remainder.split_at_checked(index + 1) { + return Some(SplitMasterlist { + before_prelude: prefix, + prelude, + after_prelude: suffix, + }); } logging::error!( @@ -376,9 +462,25 @@ fn split_on_prelude(masterlist: &str) -> Option<(&str, &str)> { } } - Some((prefix, "")) + Some(SplitMasterlist { + before_prelude: prefix, + prelude: remainder, + after_prelude: "", + }) } +fn count_lines(string: &str) -> usize { + // lines() ignores a trailing line ending. + let count = string.lines().count(); + if string.ends_with('\n') { + count + 1 + } else { + count + } +} + +/// This includes the line break following the "prelude:" YAML as part of the +/// tuple's second element. fn split_on_prelude_start(masterlist: &str) -> Option<(&str, &str)> { let prelude_on_first_line = "prelude:"; let prelude_on_new_line = "\nprelude:"; @@ -405,7 +507,7 @@ fn split_on_prelude_start(masterlist: &str) -> Option<(&str, &str)> { } fn indent_prelude(prelude: &str) -> String { - let prelude = ("\n ".to_owned() + &prelude.replace('\n', "\n ")) + let prelude = (" ".to_owned() + &prelude.replace('\n', "\n ")) .replace(" \r\n", "\r\n") .replace(" \n", "\n"); @@ -425,6 +527,8 @@ mod tests { use super::*; mod metadata_document { + use std::error::Error; + use crate::metadata::MessageType; use super::*; @@ -676,6 +780,191 @@ plugins: ); } + #[test] + fn load_with_prelude_should_not_change_yaml_parse_error_line_numbers_if_they_are_before_the_prelude() + { + let tmp_dir = tempdir().unwrap(); + + let masterlist_path = tmp_dir.path().join("masterlist.yaml"); + std::fs::write(&masterlist_path, "common:\n - *invalid\nprelude:\n - &ref\n type: say\n content: Loaded from same file\n\n - &otherRef\n type: error\n content: Error from same file\nglobals:\n - *ref\n - *otherRef").unwrap(); + + let prelude_path = tmp_dir.path().join("prelude.yaml"); + std::fs::write( + &prelude_path, + "common:\n - &ref\n type: say\n content: Loaded from prelude\n\n - &otherRef\n type: error\n content: An error message", + ) + .unwrap(); + + let mut metadata_list = MetadataDocument::default(); + let err_message = metadata_list + .load_with_prelude(&masterlist_path, &prelude_path) + .unwrap_err() + .source() + .unwrap() + .source() + .unwrap() + .to_string(); + + assert_eq!( + "encountered a YAML parsing error at line 2 column 5: while parsing node, found unknown anchor", + err_message + ); + } + + #[test] + fn load_with_prelude_should_change_yaml_parse_error_line_numbers_if_within_the_prelude() { + let tmp_dir = tempdir().unwrap(); + + let masterlist_path = tmp_dir.path().join("masterlist.yaml"); + std::fs::write(&masterlist_path, "prelude:\n - &ref\n type: say\n content: Loaded from same file\n\n - &otherRef\n type: error\n content: Error from same file\nglobals:\n - *ref\n - *otherRef").unwrap(); + + let prelude_path = tmp_dir.path().join("prelude.yaml"); + std::fs::write( + &prelude_path, + "common:\n - &ref\n type\n content: Loaded from prelude\n\n - &otherRef\n type: error\n content: An error message", + ) + .unwrap(); + + let mut metadata_list = MetadataDocument::default(); + let err_message = metadata_list + .load_with_prelude(&masterlist_path, &prelude_path) + .unwrap_err() + .source() + .unwrap() + .source() + .unwrap() + .to_string(); + + assert_eq!( + "encountered a YAML parsing error at line 4 column 12 within the prelude: mapping values are not allowed in this context", + err_message + ); + } + + #[test] + fn load_with_prelude_should_change_yaml_parse_error_line_numbers_if_they_are_after_the_prelude() + { + let tmp_dir = tempdir().unwrap(); + + let masterlist_path = tmp_dir.path().join("masterlist.yaml"); + std::fs::write(&masterlist_path, "prelude:\n - &ref\n type: say\n content: Loaded from same file\n\n - &otherRef\n type: error\n content: Error from same file\nglobals:\n - *invalid\n - *otherRef").unwrap(); + + let prelude_path = tmp_dir.path().join("prelude.yaml"); + std::fs::write( + &prelude_path, + "common:\n - &ref\n type: say\n content: Loaded from prelude\n\n - &otherRef\n type: error\n content: An error message", + ) + .unwrap(); + + let mut metadata_list = MetadataDocument::default(); + let err_message = metadata_list + .load_with_prelude(&masterlist_path, &prelude_path) + .unwrap_err() + .source() + .unwrap() + .source() + .unwrap() + .to_string(); + + assert_eq!( + "encountered a YAML parsing error at line 10 column 5: while parsing node, found unknown anchor", + err_message + ); + } + + #[test] + fn load_with_prelude_should_not_change_yaml_merge_key_error_line_numbers_if_they_are_before_the_prelude() + { + let tmp_dir = tempdir().unwrap(); + + let masterlist_path = tmp_dir.path().join("masterlist.yaml"); + std::fs::write(&masterlist_path, "common:\n - <<: 1\nprelude:\n - &ref\n type: say\n content: Loaded from same file\n\n - &otherRef\n type: error\n content: Error from same file\nglobals:\n - *ref\n - *otherRef").unwrap(); + + let prelude_path = tmp_dir.path().join("prelude.yaml"); + std::fs::write( + &prelude_path, + "common:\n - &ref\n type: say\n content: Loaded from prelude\n\n - &otherRef\n type: error\n content: An error message", + ) + .unwrap(); + + let mut metadata_list = MetadataDocument::default(); + let err_message = metadata_list + .load_with_prelude(&masterlist_path, &prelude_path) + .unwrap_err() + .source() + .unwrap() + .source() + .unwrap() + .to_string(); + + assert_eq!( + "invalid YAML merge key value at line 2 column 9: 1", + err_message + ); + } + + #[test] + fn load_with_prelude_should_change_yaml_merge_key_error_line_numbers_if_within_the_prelude() + { + let tmp_dir = tempdir().unwrap(); + + let masterlist_path = tmp_dir.path().join("masterlist.yaml"); + std::fs::write(&masterlist_path, "prelude:\n - &ref\n type: say\n content: Loaded from same file\n\n - &otherRef\n type: error\n content: Error from same file\nglobals:\n - *ref\n - *otherRef").unwrap(); + + let prelude_path = tmp_dir.path().join("prelude.yaml"); + std::fs::write( + &prelude_path, + "common:\n - &ref\n <<: type\n content: Loaded from prelude\n\n - &otherRef\n type: error\n content: An error message", + ) + .unwrap(); + + let mut metadata_list = MetadataDocument::default(); + let err_message = metadata_list + .load_with_prelude(&masterlist_path, &prelude_path) + .unwrap_err() + .source() + .unwrap() + .source() + .unwrap() + .to_string(); + + assert_eq!( + "invalid YAML merge key value at line 3 column 9 within the prelude: type", + err_message + ); + } + + #[test] + fn load_with_prelude_should_change_yaml_merge_key_error_line_numbers_if_they_are_after_the_prelude() + { + let tmp_dir = tempdir().unwrap(); + + let masterlist_path = tmp_dir.path().join("masterlist.yaml"); + std::fs::write(&masterlist_path, "prelude:\n - &ref\n type: say\n content: Loaded from same file\n\n - &otherRef\n type: error\n content: Error from same file\nglobals:\n - <<: 1\n - *otherRef").unwrap(); + + let prelude_path = tmp_dir.path().join("prelude.yaml"); + std::fs::write( + &prelude_path, + "common:\n - &ref\n type: say\n content: Loaded from prelude\n\n - &otherRef\n type: error\n content: An error message", + ) + .unwrap(); + + let mut metadata_list = MetadataDocument::default(); + let err_message = metadata_list + .load_with_prelude(&masterlist_path, &prelude_path) + .unwrap_err() + .source() + .unwrap() + .source() + .unwrap() + .to_string(); + + assert_eq!( + "invalid YAML merge key value at line 10 column 9: 1", + err_message + ); + } + #[test] fn save_should_write_the_loaded_metadata() { let tmp_dir = tempdir().unwrap(); @@ -812,9 +1101,10 @@ plugins: #[test] fn should_return_an_empty_string_if_given_empty_strings() { - let result = replace_prelude(String::new(), ""); + let result = replace_prelude(String::new(), "").unwrap(); - assert!(result.is_empty()); + assert!(result.masterlist.is_empty()); + assert!(result.meta.is_none()); } #[test] @@ -827,9 +1117,10 @@ plugins: - name: a.esp "; - let result = replace_prelude(masterlist.into(), prelude); + let result = replace_prelude(masterlist.into(), prelude).unwrap(); - assert_eq!(masterlist, result); + assert_eq!(masterlist, result.masterlist); + assert!(result.meta.is_none()); } #[test] @@ -837,9 +1128,10 @@ plugins: let prelude = "globals: [{type: note, content: A message.}]"; let masterlist = "{prelude: {}, plugins: [{name: a.esp}]}"; - let result = replace_prelude(masterlist.into(), prelude); + let result = replace_prelude(masterlist.into(), prelude).unwrap(); - assert_eq!(masterlist, result); + assert_eq!(masterlist, result.masterlist); + assert!(result.meta.is_none()); } #[test] @@ -855,7 +1147,7 @@ plugins: - name: a.esp "; - let result = replace_prelude(masterlist.into(), prelude); + let result = replace_prelude(masterlist.into(), prelude).unwrap(); let expected_result = "prelude: globals: @@ -866,7 +1158,94 @@ plugins: - name: a.esp "; - assert_eq!(expected_result, result); + assert_eq!(expected_result, result.masterlist); + assert_eq!(2, result.meta.as_ref().unwrap().start_line); + assert_eq!(5, result.meta.as_ref().unwrap().end_line); + assert_eq!(2, result.meta.as_ref().unwrap().line_count_delta); + } + + #[test] + fn should_replace_a_prelude_with_no_line_break() { + let prelude = "globals: + - type: note + content: A message. +"; + let masterlist = "prelude: + a: b +plugins: + - name: a.esp +"; + + let result = replace_prelude(masterlist.into(), prelude).unwrap(); + + let expected_result = "prelude: + globals: + - type: note + content: A message. + +plugins: + - name: a.esp +"; + + assert_eq!(expected_result, result.masterlist); + assert_eq!(2, result.meta.as_ref().unwrap().start_line); + assert_eq!(5, result.meta.as_ref().unwrap().end_line); + assert_eq!(3, result.meta.as_ref().unwrap().line_count_delta); + } + + #[test] + fn should_replace_a_prelude_with_one_that_has_no_trailing_line_break() { + let prelude = "globals: + - type: note + content: A message."; + let masterlist = "prelude: + a: b + +plugins: + - name: a.esp +"; + + let result = replace_prelude(masterlist.into(), prelude).unwrap(); + + let expected_result = "prelude: + globals: + - type: note + content: A message. +plugins: + - name: a.esp +"; + + assert_eq!(expected_result, result.masterlist); + assert_eq!(2, result.meta.as_ref().unwrap().start_line); + assert_eq!(4, result.meta.as_ref().unwrap().end_line); + assert_eq!(1, result.meta.as_ref().unwrap().line_count_delta); + } + + #[test] + fn should_replace_a_prelude_with_no_line_break_with_one_that_has_no_trailing_line_break() { + let prelude = "globals: + - type: note + content: A message."; + let masterlist = "prelude: + a: b +plugins: + - name: a.esp +"; + + let result = replace_prelude(masterlist.into(), prelude).unwrap(); + + let expected_result = "prelude: + globals: + - type: note + content: A message. +plugins: + - name: a.esp +"; + + assert_eq!(expected_result, result.masterlist); + assert_eq!(2, result.meta.as_ref().unwrap().start_line); + assert_eq!(4, result.meta.as_ref().unwrap().end_line); + assert_eq!(2, result.meta.as_ref().unwrap().line_count_delta); } #[test] @@ -882,7 +1261,7 @@ prelude: "; - let result = replace_prelude(masterlist.into(), prelude); + let result = replace_prelude(masterlist.into(), prelude).unwrap(); let expected_result = "plugins: - name: a.esp @@ -892,7 +1271,10 @@ prelude: content: A message. "; - assert_eq!(expected_result, result); + assert_eq!(expected_result, result.masterlist); + assert_eq!(4, result.meta.as_ref().unwrap().start_line); + assert_eq!(7, result.meta.as_ref().unwrap().end_line); + assert_eq!(1, result.meta.as_ref().unwrap().line_count_delta); } #[test] @@ -913,7 +1295,7 @@ plugins: - name: a.esp "; - let result = replace_prelude(masterlist.into(), prelude); + let result = replace_prelude(masterlist.into(), prelude).unwrap(); let expected_result = " common: @@ -930,7 +1312,10 @@ plugins: - name: a.esp "; - assert_eq!(expected_result, result); + assert_eq!(expected_result, result.masterlist); + assert_eq!(5, result.meta.as_ref().unwrap().start_line); + assert_eq!(11, result.meta.as_ref().unwrap().end_line); + assert_eq!(6, result.meta.as_ref().unwrap().line_count_delta); } #[test] @@ -946,7 +1331,7 @@ plugins: - name: a.esp "; - let result = replace_prelude(masterlist.into(), prelude); + let result = replace_prelude(masterlist.into(), prelude).unwrap(); let expected_result = "prelude: globals: @@ -957,7 +1342,10 @@ plugins: - name: a.esp "; - assert_eq!(expected_result, result); + assert_eq!(expected_result, result.masterlist); + assert_eq!(2, result.meta.as_ref().unwrap().start_line); + assert_eq!(5, result.meta.as_ref().unwrap().end_line); + assert_eq!(2, result.meta.as_ref().unwrap().line_count_delta); } #[test] @@ -970,7 +1358,7 @@ plugins: - name: a.esp "; - let result = replace_prelude(masterlist.into(), prelude); + let result = replace_prelude(masterlist.into(), prelude).unwrap(); let expected_result = "prelude: globals: [{type: note, content: A message.}] @@ -978,7 +1366,10 @@ plugins: - name: a.esp "; - assert_eq!(expected_result, result); + assert_eq!(expected_result, result.masterlist); + assert_eq!(2, result.meta.as_ref().unwrap().start_line); + assert_eq!(2, result.meta.as_ref().unwrap().end_line); + assert_eq!(-1, result.meta.as_ref().unwrap().line_count_delta); } #[test] @@ -996,7 +1387,7 @@ plugins: - name: a.esp "; - let result = replace_prelude(masterlist.into(), prelude); + let result = replace_prelude(masterlist.into(), prelude).unwrap(); let expected_result = "prelude: globals: @@ -1007,7 +1398,10 @@ plugins: - name: a.esp "; - assert_eq!(expected_result, result); + assert_eq!(expected_result, result.masterlist); + assert_eq!(2, result.meta.as_ref().unwrap().start_line); + assert_eq!(5, result.meta.as_ref().unwrap().end_line); + assert_eq!(0, result.meta.as_ref().unwrap().line_count_delta); } #[test] @@ -1024,7 +1418,7 @@ plugins: - name: a.esp "; - let result = replace_prelude(masterlist.into(), prelude); + let result = replace_prelude(masterlist.into(), prelude).unwrap(); let expected_result = "prelude: globals: @@ -1035,7 +1429,10 @@ plugins: - name: a.esp "; - assert_eq!(expected_result, result); + assert_eq!(expected_result, result.masterlist); + assert_eq!(2, result.meta.as_ref().unwrap().start_line); + assert_eq!(5, result.meta.as_ref().unwrap().end_line); + assert_eq!(1, result.meta.as_ref().unwrap().line_count_delta); } } } diff --git a/src/metadata/mod.rs b/src/metadata/mod.rs index 004a9c11..8d18a525 100644 --- a/src/metadata/mod.rs +++ b/src/metadata/mod.rs @@ -18,6 +18,13 @@ pub use plugin_cleaning_data::PluginCleaningData; pub use plugin_metadata::PluginMetadata; pub use tag::{Tag, TagSuggestion}; +#[derive(Debug)] +struct PreludeDiffSpan { + start_line: usize, + end_line: usize, + line_count_delta: isize, +} + #[cfg(test)] fn emit(metadata: &T) -> String { let mut emitter = yaml::YamlEmitter::new();