potenv 0.2.0

Rust implementation of the POSIX-compliant dotenv file format specification.
Documentation
use std::path::{Path, PathBuf};

use thiserror::Error;

use super::pos::Position;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
    Eof,
    NullCharacter,
    UnescapedSpecialCharacter(char),
    UnterminatedSingleQuotedString,
    UnterminatedDoubleQuotedString,
    UnsupportedShellParameter(String),
    UnterminatedExpansion,
    UnsupportedCommandExpansion,
    UnsupportedCommandOrArithmeticExpansion,
    InvalidCharacter(char),
}

impl std::fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Eof => write!(f, "Unexpected end of input"),
            Self::NullCharacter => write!(f, "Unexpected <NUL> character"),
            Self::UnescapedSpecialCharacter(ch) => {
                write!(f, "Unescaped special shell character '{ch}'")
            }
            Self::UnterminatedSingleQuotedString => {
                write!(f, "Unterminated single-quoted string")
            }
            Self::UnterminatedDoubleQuotedString => {
                write!(f, "Unterminated double-quoted string")
            }
            Self::UnsupportedShellParameter(p) => {
                write!(f, "Unsupported special shell parameter: {p}")
            }
            Self::UnterminatedExpansion => write!(f, "Unterminated expansion"),
            Self::UnsupportedCommandExpansion => write!(f, "Unsupported command expansion"),
            Self::UnsupportedCommandOrArithmeticExpansion => {
                write!(f, "Unsupported command or arithmetic expansion")
            }
            Self::InvalidCharacter(ch) => write!(f, "Invalid character '{ch}'"),
        }
    }
}

#[derive(Error, Debug, PartialEq, Eq)]
pub struct SyntaxError {
    kind: ErrorKind,
    position: Position,
    filename: Option<PathBuf>,
}

impl std::fmt::Display for SyntaxError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.kind().fmt(f)?;
        if let Some(file) = self.file() {
            write!(f, " in {}", file.display())?;
        }
        write!(f, " on line {}, column {}", self.line(), self.column())
    }
}

impl SyntaxError {
    pub fn new(kind: ErrorKind, position: Position, filename: Option<PathBuf>) -> Self {
        Self {
            kind,
            position,
            filename,
        }
    }

    pub fn kind(&self) -> &ErrorKind {
        &self.kind
    }

    pub fn line(&self) -> usize {
        self.position.line
    }

    pub fn column(&self) -> usize {
        self.position.column
    }

    pub fn file(&self) -> Option<&Path> {
        self.filename.as_deref()
    }
}