use std::fmt;
use std::ops::Range;
use super::plan::{FormatPlan, TransportDialect};
use super::text::TmuxText;
use super::{DecoderKind, FormatDescriptor, ListProfile};
pub(super) const QUOTE_SHELL_SPECIALS: &[u8] = b"|&;<>()$`\\\"'*?[# =%";
pub(crate) const FIELD_SEPARATOR: u8 = b'=';
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
impl FormatPlan {
pub(crate) fn parse_rows(&self, stdout: &[u8]) -> Result<Vec<ParsedRow>, FormatCodecError> {
let mut cursor = 0;
let mut row = 0;
let mut parsed = Vec::new();
while cursor < stdout.len() {
parsed.push(self.parse_row(stdout, &mut cursor, row)?);
row += 1;
}
Ok(parsed)
}
fn parse_row(
&self,
stdout: &[u8],
cursor: &mut usize,
row: usize,
) -> Result<ParsedRow, FormatCodecError> {
let mut bytes = Vec::new();
let mut slots = Vec::with_capacity(self.descriptors.len());
let mut final_descriptor = self.baseline;
let mut final_field = 0;
for (field, descriptor) in self.descriptors.iter().copied().enumerate() {
final_descriptor = descriptor;
final_field = field;
slots.push(Self::parse_field(
stdout,
cursor,
row,
field,
descriptor,
self.dialect,
&mut bytes,
)?);
}
Self::consume_row_terminator(stdout, cursor, row, final_field, final_descriptor)?;
Ok(ParsedRow {
row,
bytes: bytes.into_boxed_slice(),
slots: slots.into_boxed_slice(),
})
}
fn parse_field(
stdout: &[u8],
cursor: &mut usize,
row: usize,
field: usize,
descriptor: &'static FormatDescriptor,
dialect: TransportDialect,
bytes: &mut Vec<u8>,
) -> Result<SlotMeta, FormatCodecError> {
let raw_start = *cursor;
let range_start = bytes.len();
loop {
let Some(byte) = stdout.get(*cursor).copied() else {
return Err(FormatCodecError::framing(
FormatCodecErrorKind::MissingFieldTerminator,
FormatCodecPhase::Field,
row,
field,
descriptor,
stdout.len(),
));
};
if byte == 0 {
return Err(FormatCodecError::framing(
FormatCodecErrorKind::EmbeddedNul,
FormatCodecPhase::Field,
row,
field,
descriptor,
*cursor,
));
}
if byte == b'\\' {
*cursor += 1;
Self::decode_escape(stdout, cursor, row, field, descriptor, dialect, bytes)?;
} else if byte == FIELD_SEPARATOR {
*cursor += 1;
return Ok(SlotMeta {
descriptor,
range: range_start..bytes.len(),
raw_start,
});
} else {
bytes.push(byte);
*cursor += 1;
}
}
}
fn decode_escape(
stdout: &[u8],
cursor: &mut usize,
row: usize,
field: usize,
descriptor: &'static FormatDescriptor,
dialect: TransportDialect,
bytes: &mut Vec<u8>,
) -> Result<(), FormatCodecError> {
let framing = |kind, offset| {
FormatCodecError::framing(
kind,
FormatCodecPhase::Escape,
row,
field,
descriptor,
offset,
)
};
let Some(escaped) = stdout.get(*cursor).copied() else {
return Err(framing(FormatCodecErrorKind::DanglingEscape, stdout.len()));
};
if escaped == 0 {
return Err(framing(FormatCodecErrorKind::EmbeddedNul, *cursor));
}
if QUOTE_SHELL_SPECIALS.contains(&escaped) {
bytes.push(escaped);
*cursor += 1;
return Ok(());
}
if dialect == TransportDialect::RawQ {
return Err(framing(FormatCodecErrorKind::InvalidEscape, *cursor));
}
if let Some(control) = vis_cstyle_byte(escaped) {
bytes.push(control);
*cursor += 1;
return Ok(());
}
if !matches!(escaped, b'0'..=b'3') {
return Err(framing(FormatCodecErrorKind::InvalidEscape, *cursor));
}
let Some(digits) = stdout.get(*cursor..*cursor + 3) else {
return Err(framing(FormatCodecErrorKind::DanglingEscape, stdout.len()));
};
let Some(value) = decode_octal_escape(digits) else {
return Err(framing(FormatCodecErrorKind::InvalidEscape, *cursor));
};
if value == 0 {
return Err(framing(FormatCodecErrorKind::EmbeddedNul, *cursor));
}
bytes.push(value);
*cursor += 3;
Ok(())
}
fn consume_row_terminator(
stdout: &[u8],
cursor: &mut usize,
row: usize,
field: usize,
descriptor: &'static FormatDescriptor,
) -> Result<(), FormatCodecError> {
let Some(terminator) = stdout.get(*cursor).copied() else {
return Err(FormatCodecError::framing(
FormatCodecErrorKind::MissingRowLf,
FormatCodecPhase::RowTerminator,
row,
field,
descriptor,
stdout.len(),
));
};
if terminator == 0 {
return Err(FormatCodecError::framing(
FormatCodecErrorKind::EmbeddedNul,
FormatCodecPhase::RowTerminator,
row,
field,
descriptor,
*cursor,
));
}
if terminator != b'\n' {
return Err(FormatCodecError::framing(
FormatCodecErrorKind::UnexpectedRowTerminator,
FormatCodecPhase::RowTerminator,
row,
field,
descriptor,
*cursor,
));
}
*cursor += 1;
Ok(())
}
}
const fn vis_cstyle_byte(letter: u8) -> Option<u8> {
match letter {
b'a' => Some(0x07),
b'b' => Some(0x08),
b'v' => Some(0x0b),
b'f' => Some(0x0c),
b'r' => Some(0x0d),
_ => None,
}
}
fn decode_octal_escape(digits: &[u8]) -> Option<u8> {
let mut value: u8 = 0;
for digit in digits {
let place = digit.checked_sub(b'0').filter(|place| *place < 8)?;
value = value.checked_mul(8)?.checked_add(place)?;
}
Some(value)
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum FormatCodecErrorKind {
EmptyPlan,
MissingFieldTerminator,
DanglingEscape,
InvalidEscape,
MissingRowLf,
UnexpectedRowTerminator,
EmbeddedNul,
NonAscii,
ScopeInapplicable,
DuplicateDescriptor,
RequiredFieldEmpty,
InvalidValue,
PlanRowMismatch,
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum FormatCodecPhase {
Plan,
Field,
Escape,
RowTerminator,
Decode,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct FormatCodecError {
kind: FormatCodecErrorKind,
phase: FormatCodecPhase,
row: Option<usize>,
field: Option<usize>,
field_name: Option<&'static str>,
expected: Option<DecoderKind>,
offset: Option<usize>,
profile: Option<ListProfile>,
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
impl FormatCodecError {
pub(super) const fn empty_plan() -> Self {
Self {
kind: FormatCodecErrorKind::EmptyPlan,
phase: FormatCodecPhase::Plan,
row: None,
field: None,
field_name: None,
expected: None,
offset: None,
profile: None,
}
}
pub(super) const fn plan(
kind: FormatCodecErrorKind,
descriptor: &'static FormatDescriptor,
profile: ListProfile,
) -> Self {
Self {
kind,
phase: FormatCodecPhase::Plan,
row: None,
field: None,
field_name: Some(descriptor.name),
expected: None,
offset: None,
profile: Some(profile),
}
}
const fn framing(
kind: FormatCodecErrorKind,
phase: FormatCodecPhase,
row: usize,
field: usize,
descriptor: &'static FormatDescriptor,
offset: usize,
) -> Self {
Self {
kind,
phase,
row: Some(row),
field: Some(field),
field_name: Some(descriptor.name),
expected: None,
offset: Some(offset),
profile: None,
}
}
const fn non_ascii(slot: &ParsedSlot<'_>) -> Self {
Self {
kind: FormatCodecErrorKind::NonAscii,
phase: FormatCodecPhase::Decode,
row: Some(slot.row),
field: Some(slot.field),
field_name: Some(slot.descriptor.name),
expected: Some(DecoderKind::Ascii),
offset: Some(slot.raw_start),
profile: None,
}
}
pub(crate) const fn typed(kind: FormatCodecErrorKind, slot: &ParsedSlot<'_>) -> Self {
Self {
kind,
phase: FormatCodecPhase::Decode,
row: Some(slot.row),
field: Some(slot.field),
field_name: Some(slot.descriptor.name),
expected: Some(slot.descriptor.decoder),
offset: Some(slot.raw_start),
profile: None,
}
}
pub(crate) const fn row_mismatch(
row: usize,
field: Option<usize>,
field_name: Option<&'static str>,
offset: Option<usize>,
) -> Self {
Self {
kind: FormatCodecErrorKind::PlanRowMismatch,
phase: FormatCodecPhase::Decode,
row: Some(row),
field,
field_name,
expected: None,
offset,
profile: None,
}
}
pub(crate) const fn purpose_mismatch(profile: ListProfile, row: usize) -> Self {
Self {
kind: FormatCodecErrorKind::PlanRowMismatch,
phase: FormatCodecPhase::Decode,
row: Some(row),
field: None,
field_name: None,
expected: None,
offset: None,
profile: Some(profile),
}
}
pub(crate) const fn kind(&self) -> FormatCodecErrorKind {
self.kind
}
pub(crate) const fn phase(&self) -> FormatCodecPhase {
self.phase
}
pub(crate) const fn row(&self) -> Option<usize> {
self.row
}
pub(crate) const fn field(&self) -> Option<usize> {
self.field
}
pub(crate) const fn field_name(&self) -> Option<&'static str> {
self.field_name
}
pub(crate) const fn expected(&self) -> Option<DecoderKind> {
self.expected
}
pub(crate) const fn offset(&self) -> Option<usize> {
self.offset
}
pub(crate) const fn profile(&self) -> Option<ListProfile> {
self.profile
}
}
impl fmt::Display for FormatCodecError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let kind = match self.kind {
FormatCodecErrorKind::EmptyPlan => "empty plan",
FormatCodecErrorKind::MissingFieldTerminator => "missing field terminator",
FormatCodecErrorKind::DanglingEscape => "dangling escape",
FormatCodecErrorKind::InvalidEscape => "invalid escape",
FormatCodecErrorKind::MissingRowLf => "missing row LF",
FormatCodecErrorKind::UnexpectedRowTerminator => "unexpected row terminator",
FormatCodecErrorKind::EmbeddedNul => "embedded NUL",
FormatCodecErrorKind::NonAscii => "non-ASCII field",
FormatCodecErrorKind::ScopeInapplicable => "scope inapplicable",
FormatCodecErrorKind::DuplicateDescriptor => "duplicate descriptor",
FormatCodecErrorKind::RequiredFieldEmpty => "required field empty",
FormatCodecErrorKind::InvalidValue => "invalid value",
FormatCodecErrorKind::PlanRowMismatch => "plan row mismatch",
};
let phase = match self.phase {
FormatCodecPhase::Plan => "plan",
FormatCodecPhase::Field => "field",
FormatCodecPhase::Escape => "escape",
FormatCodecPhase::RowTerminator => "row terminator",
FormatCodecPhase::Decode => "decode",
};
write!(formatter, "format codec {kind} in {phase} phase")
}
}
impl std::error::Error for FormatCodecError {}
pub(crate) struct ParsedRow {
row: usize,
bytes: Box<[u8]>,
slots: Box<[SlotMeta]>,
}
struct SlotMeta {
descriptor: &'static FormatDescriptor,
range: Range<usize>,
raw_start: usize,
}
#[derive(Clone, Copy)]
pub(crate) struct ParsedSlot<'row> {
descriptor: &'static FormatDescriptor,
bytes: &'row [u8],
row: usize,
field: usize,
raw_start: usize,
}
impl ParsedRow {
pub(crate) const fn row(&self) -> usize {
self.row
}
pub(crate) fn slot(&self, field: usize) -> Option<ParsedSlot<'_>> {
self.slots.get(field).map(|slot| ParsedSlot {
descriptor: slot.descriptor,
bytes: &self.bytes[slot.range.clone()],
row: self.row,
field,
raw_start: slot.raw_start,
})
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
pub(crate) const fn slot_count(&self) -> usize {
self.slots.len()
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
pub(crate) fn slots(&self) -> impl ExactSizeIterator<Item = ParsedSlot<'_>> + '_ {
self.slots
.iter()
.enumerate()
.map(|(field, slot)| ParsedSlot {
descriptor: slot.descriptor,
bytes: &self.bytes[slot.range.clone()],
row: self.row,
field,
raw_start: slot.raw_start,
})
}
}
impl<'row> ParsedSlot<'row> {
pub(crate) const fn descriptor(&self) -> &'static FormatDescriptor {
self.descriptor
}
pub(crate) const fn as_bytes(&self) -> &'row [u8] {
self.bytes
}
pub(crate) const fn field(&self) -> usize {
self.field
}
pub(crate) const fn raw_start(&self) -> usize {
self.raw_start
}
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
pub(crate) fn decode_ascii(slot: ParsedSlot<'_>) -> Result<&str, FormatCodecError> {
if !slot.as_bytes().is_ascii() {
return Err(FormatCodecError::non_ascii(&slot));
}
match std::str::from_utf8(slot.as_bytes()) {
Ok(text) => Ok(text),
Err(_) => Err(FormatCodecError::non_ascii(&slot)),
}
}
pub(crate) fn decode_text(slot: ParsedSlot<'_>) -> TmuxText {
TmuxText::from_bytes(slot.as_bytes())
}