Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "github-actions"
# Workflow files stored in the
# default location of `.github/workflows`
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: ci

env:
CARGO_TERM_COLOR: always
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse

on:
merge_group:
push:
paths-ignore:
- 'README.md'
- 'LICENSE'
- '.gitignore'
branches:
- main
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.event.pull_request.number || github.sha }}-${{ inputs.additional_key }}
cancel-in-progress: true

jobs:
fmt:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- run: rustup component add rustfmt
- uses: Swatinem/rust-cache@v2

- run: cargo fmt --all -- --check

clippy:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- run: rustup component add clippy
- uses: Swatinem/rust-cache@v2

- run: cargo clippy --all

test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2

- run: cargo test

# Dummy job to have a stable name for the "all tests pass" requirement
tests-pass:
name: Tests pass
needs:
- fmt
- clippy
- test
if: always() # always run even if dependencies fail
runs-on: ubuntu-latest
steps:
# fail if ANY dependency has failed or cancelled
- if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')"
run: exit 1
- run: exit 0
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ categories = ["filesystem", "parser-implementations"]
license = "MIT"

[dependencies]
nom = "7"
winnow = { version = "0.5", features = ["simd"] }

[dev-dependencies]
tar = "0.4"
Expand Down
8 changes: 4 additions & 4 deletions examples/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tar_parser2::*;
fn main() {
let path = std::env::args_os().nth(1).unwrap();
let file = std::fs::read(path).unwrap();
let (_, entries) = parse_tar(&file).unwrap();
let entries = parse_tar(&mut &*file).unwrap();
let printer = TarPrinter::default();
printer.print(&entries);
}
Expand All @@ -28,20 +28,20 @@ impl<'a> TarPrinter<'a> {
}
TypeFlag::GnuLongName => {
debug_assert!(entry.header.size > 1);
if let Ok((_, name)) = parse_long_name(entry.contents) {
if let Ok(name) = parse_long_name(&mut &*entry.contents) {
debug_assert!(self.longname.is_none());
self.longname = Some(Cow::Borrowed(name));
}
}
TypeFlag::GnuLongLink => {
debug_assert!(entry.header.size > 1);
if let Ok((_, target)) = parse_long_name(entry.contents) {
if let Ok(target) = parse_long_name(&mut &*entry.contents) {
debug_assert!(self.longlink.is_none());
self.longlink = Some(target);
}
}
TypeFlag::Pax => {
if let Ok((_, pax)) = parse_pax(entry.contents) {
if let Ok(pax) = parse_pax(&mut &*entry.contents) {
if let Some(name) = pax.get("path") {
debug_assert!(self.longname.is_none());
self.longname = Some(Cow::Borrowed(name));
Expand Down
6 changes: 3 additions & 3 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use tar_parser2::*;

fn test_parse_tar(i: &[u8]) {
match parse_tar(i) {
Ok((_, entries)) => {
fn test_parse_tar(mut i: &[u8]) {
match parse_tar(&mut i) {
Ok(entries) => {
for e in entries.iter() {
println!("{e:?}");
}
Expand Down
102 changes: 102 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use std::{borrow::Cow, cmp, error, fmt};

use winnow::error::{AddContext, ErrMode, ErrorKind, FromExternalError, ParserError};

/// Accumulate context while backtracking errors
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
context: Vec<Cow<'static, str>>,
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
}

impl cmp::Eq for Error {}

impl cmp::PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
&& self.context == other.context
&& self.source.as_deref().map(|b| b as *const _)
== other.source.as_deref().map(|b| b as *const _)
}
}

impl Error {
pub(super) fn new(context: impl Into<Cow<'static, str>>) -> ErrMode<Self> {
ErrMode::Backtrack(Self {
kind: ErrorKind::Fail,
context: vec![context.into()],
source: None,
})
}

/// Access context from [`Parser::context`]
pub fn context(&self) -> impl Iterator<Item = &Cow<'static, str>> {
self.context.iter()
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error kind: {}", self.kind)?;

for context in &self.context {
write!(f, ", context: {context}")?;
}

if let Some(source) = self.source.as_deref() {
f.write_str(", source: ")?;
// tailcall
fmt::Display::fmt(source, f)
} else {
Ok(())
}
}
}

impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.source
.as_deref()
.map(|e| e as &(dyn error::Error + 'static))
}
}

impl<I> ParserError<I> for Error {
fn from_error_kind(_input: &I, kind: ErrorKind) -> Self {
Self {
kind,
context: Vec::new(),
source: None,
}
}

fn append(self, _input: &I, kind: ErrorKind) -> Self {
Self {
kind,
context: Vec::new(),
source: Some(Box::new(self)),
}
}
}

impl<C, I> AddContext<I, C> for Error
where
C: Into<Cow<'static, str>>,
{
fn add_context(mut self, _input: &I, ctx: C) -> Self {
self.context.push(ctx.into());
self
}
}

impl<I, E: error::Error + Send + Sync + 'static> FromExternalError<I, E> for Error {
#[inline]
fn from_external_error(_input: &I, kind: ErrorKind, e: E) -> Self {
Self {
kind,
context: Vec::new(),
source: Some(Box::new(e)),
}
}
}
Loading