refactor(frontmatter): Switch to winnow

This commit is contained in:
Ed Page 2025-08-25 16:30:26 -05:00
parent a9e120f95e
commit 3184f03ef3
3 changed files with 28 additions and 19 deletions

1
Cargo.lock generated
View File

@ -399,6 +399,7 @@ dependencies = [
"url", "url",
"walkdir", "walkdir",
"windows-sys 0.60.2", "windows-sys 0.60.2",
"winnow",
] ]
[[package]] [[package]]

View File

@ -118,6 +118,7 @@ url = "2.5.4"
varisat = "0.2.2" varisat = "0.2.2"
walkdir = "2.5.0" walkdir = "2.5.0"
windows-sys = "0.60" windows-sys = "0.60"
winnow = "0.7.13"
[workspace.lints.rust] [workspace.lints.rust]
rust_2018_idioms = "warn" # TODO: could this be removed? rust_2018_idioms = "warn" # TODO: could this be removed?
@ -220,6 +221,7 @@ unicode-width.workspace = true
unicode-xid.workspace = true unicode-xid.workspace = true
url.workspace = true url.workspace = true
walkdir.workspace = true walkdir.workspace = true
winnow.workspace = true
[target.'cfg(target_has_atomic = "64")'.dependencies] [target.'cfg(target_has_atomic = "64")'.dependencies]
tracing-chrome.workspace = true tracing-chrome.workspace = true

View File

@ -10,6 +10,9 @@ pub struct ScriptSource<'s> {
impl<'s> ScriptSource<'s> { impl<'s> ScriptSource<'s> {
pub fn parse(input: &'s str) -> CargoResult<Self> { pub fn parse(input: &'s str) -> CargoResult<Self> {
use winnow::stream::FindSlice as _;
use winnow::stream::Stream as _;
let mut source = Self { let mut source = Self {
shebang: None, shebang: None,
info: None, info: None,
@ -17,25 +20,25 @@ impl<'s> ScriptSource<'s> {
content: input, content: input,
}; };
if let Some(shebang_end) = strip_shebang(source.content) { let mut input = winnow::stream::LocatingSlice::new(input);
let (shebang, content) = source.content.split_at(shebang_end);
source.shebang = Some(shebang); if let Some(shebang_end) = strip_shebang(input.as_ref()) {
source.content = content; source.shebang = Some(input.next_slice(shebang_end));
source.content = input.as_ref();
} }
let mut rest = source.content;
// Whitespace may precede a frontmatter but must end with a newline // Whitespace may precede a frontmatter but must end with a newline
if let Some(nl_end) = strip_ws_lines(rest) { if let Some(nl_end) = strip_ws_lines(input.as_ref()) {
rest = &rest[nl_end..]; let _ = input.next_slice(nl_end);
} }
// Opens with a line that starts with 3 or more `-` followed by an optional identifier // Opens with a line that starts with 3 or more `-` followed by an optional identifier
const FENCE_CHAR: char = '-'; const FENCE_CHAR: char = '-';
let fence_length = rest let fence_length = input
.as_ref()
.char_indices() .char_indices()
.find_map(|(i, c)| (c != FENCE_CHAR).then_some(i)) .find_map(|(i, c)| (c != FENCE_CHAR).then_some(i))
.unwrap_or(rest.len()); .unwrap_or_else(|| input.eof_offset());
match fence_length { match fence_length {
0 => { 0 => {
return Ok(source); return Ok(source);
@ -48,11 +51,11 @@ impl<'s> ScriptSource<'s> {
} }
_ => {} _ => {}
} }
let (fence_pattern, rest) = rest.split_at(fence_length); let fence_pattern = input.next_slice(fence_length);
let Some(info_end_index) = rest.find('\n') else { let Some(info_nl) = input.find_slice("\n") else {
anyhow::bail!("no closing `{fence_pattern}` found for frontmatter"); anyhow::bail!("no closing `{fence_pattern}` found for frontmatter");
}; };
let (info, rest) = rest.split_at(info_end_index); let info = input.next_slice(info_nl.start);
let info = info.trim_matches(is_whitespace); let info = info.trim_matches(is_whitespace);
if !info.is_empty() { if !info.is_empty() {
source.info = Some(info); source.info = Some(info);
@ -60,25 +63,28 @@ impl<'s> ScriptSource<'s> {
// Ends with a line that starts with a matching number of `-` only followed by whitespace // Ends with a line that starts with a matching number of `-` only followed by whitespace
let nl_fence_pattern = format!("\n{fence_pattern}"); let nl_fence_pattern = format!("\n{fence_pattern}");
let Some(frontmatter_nl) = rest.find(&nl_fence_pattern) else { let Some(frontmatter_nl) = input.find_slice(nl_fence_pattern.as_str()) else {
anyhow::bail!("no closing `{fence_pattern}` found for frontmatter"); anyhow::bail!("no closing `{fence_pattern}` found for frontmatter");
}; };
let frontmatter = &rest[..frontmatter_nl + 1]; let frontmatter = input.next_slice(frontmatter_nl.start + 1);
let frontmatter = frontmatter let frontmatter = frontmatter
.strip_prefix('\n') .strip_prefix('\n')
.expect("earlier `found` + `split_at` left us here"); .expect("earlier `found` + `split_at` left us here");
source.frontmatter = Some(frontmatter); source.frontmatter = Some(frontmatter);
let rest = &rest[frontmatter_nl + nl_fence_pattern.len()..]; let _ = input.next_slice(fence_length);
let (after_closing_fence, rest) = rest.split_once("\n").unwrap_or((rest, "")); let nl = input.find_slice("\n");
let after_closing_fence = input.next_slice(
nl.map(|span| span.end)
.unwrap_or_else(|| input.eof_offset()),
);
let after_closing_fence = after_closing_fence.trim_matches(is_whitespace); let after_closing_fence = after_closing_fence.trim_matches(is_whitespace);
if !after_closing_fence.is_empty() { if !after_closing_fence.is_empty() {
// extra characters beyond the original fence pattern, even if they are extra `-` // extra characters beyond the original fence pattern, even if they are extra `-`
anyhow::bail!("trailing characters found after frontmatter close"); anyhow::bail!("trailing characters found after frontmatter close");
} }
let frontmatter_len = input.len() - rest.len(); source.content = input.finish();
source.content = &input[frontmatter_len..];
let repeat = Self::parse(source.content)?; let repeat = Self::parse(source.content)?;
if repeat.frontmatter.is_some() { if repeat.frontmatter.is_some() {