use std::collections::HashMap;
use yaml_rust2::parser::{Event, MarkedEventReceiver, Parser};
use yaml_rust2::scanner::{Marker, TScalarStyle};
pub(super) fn locate(source: &str, path: &str) -> (usize, usize) {
let mut recorder = Recorder {
lines: source.lines().collect(),
frames: Vec::new(),
positions: HashMap::new(),
};
let _ = Parser::new_from_str(source).load(&mut recorder, true);
let mut path = path;
loop {
if let Some(&position) = recorder.positions.get(path) {
return position;
}
if path.is_empty() {
return (1, 1);
}
path = &path[..path.rfind(['.', '[']).unwrap_or(0)];
}
}
fn position(mark: Marker) -> (usize, usize) {
(mark.line(), mark.col() + 1)
}
#[derive(Debug)]
struct Recorder<'source> {
lines: Vec<&'source str>,
frames: Vec<Frame>,
positions: HashMap<String, (usize, usize)>,
}
#[derive(Debug)]
enum Frame {
Mapping {
path: String,
key: Option<String>,
key_mark: Option<Marker>,
awaiting_key: bool,
},
Sequence {
path: String,
mark: Marker,
next: usize,
previous: Option<usize>,
},
}
const KEY_NODE: &str = "\0key";
impl Recorder<'_> {
fn child_path(&self) -> Option<String> {
match self.frames.last() {
None => Some(String::new()),
Some(Frame::Mapping {
awaiting_key: true, ..
}) => None,
Some(Frame::Mapping { path, key, .. }) => {
let key = key.as_deref().unwrap_or(KEY_NODE);
Some(if path.is_empty() {
key.to_owned()
} else {
format!("{path}.{key}")
})
}
Some(Frame::Sequence { path, next, .. }) => Some(format!("{path}[{next}]")),
}
}
fn implied(&self, mark: Marker) -> (usize, usize) {
match self.frames.last() {
Some(Frame::Mapping {
key_mark: Some(key),
..
}) => position(*key),
Some(Frame::Sequence {
mark: first,
previous: None,
..
}) => position(*first),
Some(Frame::Sequence {
previous: Some(previous),
..
}) => self
.lines
.iter()
.enumerate()
.skip(*previous)
.find_map(|(index, line)| {
let indent = line.len() - line.trim_start().len();
line.trim_start()
.starts_with('-')
.then_some((index + 1, indent + 1))
})
.unwrap_or_else(|| position(mark)),
_ => position(mark),
}
}
fn record(&mut self, mark: Marker, implied: bool) -> Option<String> {
let path = self.child_path();
let at = if implied && path.is_some() {
self.implied(mark)
} else {
position(mark)
};
if let Some(path) = &path {
self.positions.entry(path.clone()).or_insert(at);
}
match self.frames.last_mut() {
Some(Frame::Mapping {
path: mapping,
key_mark,
awaiting_key: true,
..
}) => {
if key_mark.is_none() {
if let Some(at) = self.positions.get_mut(mapping.as_str()) {
*at = position(mark);
}
}
*key_mark = Some(mark);
}
Some(Frame::Sequence { previous, .. }) => *previous = Some(at.0),
_ => {}
}
path
}
fn finish(&mut self, scalar: Option<String>) {
match self.frames.last_mut() {
Some(Frame::Mapping {
key, awaiting_key, ..
}) => {
if *awaiting_key {
*key = scalar;
}
*awaiting_key = !*awaiting_key;
}
Some(Frame::Sequence { next, .. }) => *next += 1,
None => {}
}
}
}
impl MarkedEventReceiver for Recorder<'_> {
fn on_event(&mut self, event: Event, mark: Marker) {
match event {
Event::Scalar(text, style, ..) => {
self.record(mark, text.is_empty() && style == TScalarStyle::Plain);
self.finish(Some(text));
}
Event::Alias(_) => {
self.record(mark, false);
self.finish(None);
}
Event::MappingStart(..) => {
let path = self
.record(mark, false)
.unwrap_or_else(|| KEY_NODE.to_owned());
self.frames.push(Frame::Mapping {
path,
key: None,
key_mark: None,
awaiting_key: true,
});
}
Event::SequenceStart(..) => {
let path = self
.record(mark, false)
.unwrap_or_else(|| KEY_NODE.to_owned());
self.frames.push(Frame::Sequence {
path,
mark,
next: 0,
previous: None,
});
}
Event::MappingEnd | Event::SequenceEnd => {
self.frames.pop();
self.finish(None);
}
_ => {}
}
}
}