use crate::error::Error;
use crate::pipeline::{self, Segment};
use crate::scan::Preamble;
use crate::table::{SegTabs, Slabs};
use crate::view::EntryView;
use std::fmt;
use std::iter::FusedIterator;
pub enum Source<'a> {
Borrowed(&'a str),
Owned(String),
Mapped(crate::file::MappedText),
}
impl Source<'_> {
#[inline]
pub fn as_str(&self) -> &str {
match self {
Self::Borrowed(s) => s,
Self::Owned(s) => s,
Self::Mapped(m) => m.as_str(),
}
}
}
#[derive(Default)]
pub struct Buffers {
pub(crate) slabs: Slabs,
pub(crate) segs: Vec<Segment>,
pub(crate) text: Vec<u8>,
}
impl Buffers {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub(crate) fn clear(&mut self) {
for s in &mut self.segs {
s.tabs.clear();
}
self.text.clear();
}
}
impl fmt::Debug for Buffers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Buffers")
.field("segments", &self.segs.len())
.field(
"table_bytes",
&(self.slabs.capacity_bytes()
+ self
.segs
.iter()
.map(|s| s.tabs.spill_bytes())
.sum::<usize>()),
)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Metadata<'l> {
pub version: &'l str,
pub cache_key: &'l str,
}
pub struct Lockfile<'a> {
src: Source<'a>,
pre: Preamble,
slabs: Slabs,
segs: Vec<Segment>,
used: usize,
len: usize,
entries_end: u32,
rest_offset: u32,
}
impl<'a> Lockfile<'a> {
pub(crate) fn parse(
src: Source<'a>,
buffers: &mut Buffers,
plan: pipeline::Plan<'_>,
) -> Result<Self, Error> {
Self::parse_source(src, buffers, plan).map_err(|(e, src)| {
if let Source::Owned(s) = src {
buffers.text = s.into_bytes();
buffers.text.clear();
}
e
})
}
pub(crate) fn parse_source(
src: Source<'a>,
buffers: &mut Buffers,
plan: pipeline::Plan<'_>,
) -> Result<Self, (Error, Source<'a>)> {
match pipeline::run(src.as_str(), &mut buffers.slabs, &mut buffers.segs, plan) {
Ok(run) => Ok(Self {
src,
pre: run.pre,
slabs: std::mem::take(&mut buffers.slabs),
segs: std::mem::take(&mut buffers.segs),
used: run.used,
len: run.len,
entries_end: run.entries_end,
rest_offset: run.rest_offset,
}),
Err(e) => {
buffers.clear();
Err((e, src))
}
}
}
#[must_use]
pub fn into_buffers(self) -> Buffers {
let text = match self.src {
Source::Owned(s) => s.into_bytes(),
Source::Borrowed(_) | Source::Mapped(_) => Vec::new(),
};
let mut b = Buffers {
slabs: self.slabs,
segs: self.segs,
text,
};
b.clear();
b
}
#[inline]
#[must_use]
pub fn source(&self) -> &str {
self.src.as_str()
}
#[must_use]
pub fn metadata(&self) -> Metadata<'_> {
let src = self.source();
Metadata {
version: self.pre.version.get(src),
cache_key: self.pre.cache_key.get(src),
}
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn iter(&self) -> Entries<'_> {
self.entries()
}
#[must_use]
pub fn entries(&self) -> Entries<'_> {
Entries {
src: self.source(),
slabs: &self.slabs,
segs: self.segs[..self.used].iter(),
front: None,
back: None,
remaining: self.len,
}
}
#[must_use]
pub fn entry(&self, i: usize) -> Option<EntryView<'_>> {
if i >= self.len {
return None;
}
let segs = &self.segs[..self.used];
let s = segs
.partition_point(|s| s.base as usize <= i)
.checked_sub(1)?;
let seg = &segs[s];
let rec = self.slabs.entries(&seg.tabs).get(i - seg.base as usize)?;
Some(EntryView::new(self.source(), &self.slabs, &seg.tabs, rec))
}
#[doc(hidden)]
#[must_use]
pub fn __spills(&self) -> usize {
self.segs[..self.used].iter().map(|s| s.tabs.spills()).sum()
}
#[doc(hidden)]
#[must_use]
pub const fn __segments(&self) -> usize {
self.used
}
#[inline]
#[must_use]
pub const fn entries_end(&self) -> usize {
self.entries_end as usize
}
#[must_use]
pub fn remaining(&self) -> &str {
self.source().get(self.rest_offset as usize..).unwrap_or("")
}
}
impl fmt::Debug for Lockfile<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Lockfile")
.field("metadata", &self.metadata())
.field("entries", &self.len)
.field("segments", &self.used)
.field("entries_end", &self.entries_end)
.field("remaining_len", &self.remaining().len())
.finish_non_exhaustive()
}
}
impl<'l> IntoIterator for &'l Lockfile<'_> {
type Item = EntryView<'l>;
type IntoIter = Entries<'l>;
fn into_iter(self) -> Entries<'l> {
self.entries()
}
}
type Run<'l> = (&'l SegTabs, std::slice::Iter<'l, crate::scan::EntryRec>);
#[derive(Clone)]
pub struct Entries<'l> {
src: &'l str,
slabs: &'l Slabs,
segs: std::slice::Iter<'l, Segment>,
front: Option<Run<'l>>,
back: Option<Run<'l>>,
remaining: usize,
}
impl<'l> Iterator for Entries<'l> {
type Item = EntryView<'l>;
#[inline]
fn next(&mut self) -> Option<EntryView<'l>> {
loop {
if let Some((ix, it)) = &mut self.front {
if let Some(rec) = it.next() {
self.remaining -= 1;
return Some(EntryView::new(self.src, self.slabs, ix, rec));
}
self.front = None;
}
if let Some(s) = self.segs.next() {
self.front = Some((&s.tabs, self.slabs.entries(&s.tabs).iter()));
} else {
let (ix, it) = self.back.as_mut()?;
let rec = it.next()?;
self.remaining -= 1;
return Some(EntryView::new(self.src, self.slabs, ix, rec));
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl DoubleEndedIterator for Entries<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
loop {
if let Some((ix, it)) = &mut self.back {
if let Some(rec) = it.next_back() {
self.remaining -= 1;
return Some(EntryView::new(self.src, self.slabs, ix, rec));
}
self.back = None;
}
if let Some(s) = self.segs.next_back() {
self.back = Some((&s.tabs, self.slabs.entries(&s.tabs).iter()));
} else {
let (ix, it) = self.front.as_mut()?;
let rec = it.next_back()?;
self.remaining -= 1;
return Some(EntryView::new(self.src, self.slabs, ix, rec));
}
}
}
}
impl ExactSizeIterator for Entries<'_> {}
impl FusedIterator for Entries<'_> {}
impl fmt::Debug for Entries<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Entries")
.field("remaining", &self.remaining)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt::Write as _;
const HEAD: &str = "# This file is generated by running \"yarn install\" inside your project.\n# Manual changes might be lost - proceed with caution!\n\n__metadata:\n version: 8\n cacheKey: 10\n\n";
const fn assert_send_sync<T: Send + Sync>() {}
const _: () = {
assert_send_sync::<Lockfile<'static>>();
assert_send_sync::<EntryView<'static>>();
assert_send_sync::<Entries<'static>>();
assert_send_sync::<Buffers>();
};
#[test]
fn errors_are_compact_and_located() {
use crate::ErrorKind;
for (src, kind) in [
("", ErrorKind::InvalidHeader),
("# nope\n", ErrorKind::InvalidHeader),
(&HEAD[..HEAD.len() - 30], ErrorKind::InvalidMetadata),
] {
let e = crate::parse(src).unwrap_err();
assert_eq!(e.kind(), kind, "{src:?}");
}
let src = format!("{HEAD}\"a@npm:1\":\n version: 1\n bogus: x\n");
let e = crate::parse(&src).unwrap_err();
assert_eq!(e.kind(), ErrorKind::UnknownProperty);
assert!(src[e.offset()..].starts_with("bogus: x"));
assert_eq!(e.line_col(&src).0, 10);
let src = format!("{HEAD}\"a@npm:1\":\n version: 1\n linkType: hardlink\n");
let e = crate::parse(&src).unwrap_err();
assert_eq!(e.kind(), ErrorKind::InvalidLinkType);
assert!(src[e.offset()..].starts_with("linkType: hardlink"));
}
#[test]
fn stop_position_and_remaining() {
let src =
format!("{HEAD}\"a@npm:1\":\n version: 1\n\nnot an entry\n\"b@npm:1\":\n version: 1\n");
let lf = crate::parse(&src).unwrap();
assert_eq!(lf.len(), 1);
assert!(lf.remaining().starts_with("not an entry"));
assert!(src[lf.entries_end()..].starts_with("not an entry"));
let src = format!("{HEAD}\"a@npm:1\":\n version: 1\n\n \n");
let lf = crate::parse(&src).unwrap();
assert_eq!(lf.entries_end(), src.len() - 2);
assert_eq!(lf.remaining(), "");
}
#[test]
fn buffers_survive_errors_and_reuse() {
let mut buffers = Buffers::new();
let good = format!("{HEAD}\"a@npm:1\":\n dependencies:\n b: npm:1\n");
let lf = crate::parse_with(&good, &mut buffers).unwrap();
assert_eq!(lf.entry(0).unwrap().dependencies().len(), 1);
buffers = lf.into_buffers();
assert!(crate::parse_with("bad", &mut buffers).is_err());
let lf = crate::parse_with(&good, &mut buffers).unwrap();
assert_eq!(lf.len(), 1);
assert!(lf.entry(1).is_none());
assert!(!format!("{lf:?}").is_empty());
assert!(!format!("{buffers:?}").is_empty());
}
#[test]
fn entries_iterate_both_ways() {
let mut src = HEAD.to_owned();
for i in 0..5 {
let _ = write!(src, "\"a{i}@npm:1\":\n version: {i}\n\n");
}
let lf = crate::parse(&src).unwrap();
let fwd: Vec<_> = lf.entries().map(|e| e.version().unwrap()).collect();
assert_eq!(fwd, ["0", "1", "2", "3", "4"]);
let mut it = lf.entries();
assert_eq!(it.len(), 5);
assert_eq!(it.next_back().unwrap().version(), Some("4"));
assert_eq!(it.next().unwrap().version(), Some("0"));
assert_eq!(it.len(), 3);
let rest: Vec<_> = it.rev().map(|e| e.version().unwrap()).collect();
assert_eq!(rest, ["3", "2", "1"]);
for (i, v) in fwd.iter().enumerate() {
assert_eq!(lf.entry(i).unwrap().version(), Some(*v));
}
assert_eq!((&lf).into_iter().count(), 5);
}
}