pub mod audio;
pub mod candump;
pub mod column_types;
pub mod columns;
pub mod csv_dialect;
pub mod dataflash;
pub(crate) mod dataset_files;
pub mod dbc;
pub mod delimited_spec;
pub mod elf;
pub mod excel;
pub mod fix;
pub mod fixed_records;
pub mod framed_records;
pub mod gps;
pub mod hf_splits;
pub mod indexed;
pub mod ipc_stream;
pub mod journal;
pub mod lines;
pub mod members;
pub mod midi;
pub mod model_files;
pub mod nul_tail;
pub mod numpy;
pub mod parquet_footer;
pub mod pushdown;
pub mod readers;
pub mod row_index;
pub mod schema_union;
pub mod sdf;
pub(crate) mod segments;
pub(crate) mod spec_union;
pub mod sqlite;
pub mod text_formats;
pub mod ulog;
pub mod vcd;
use crate::formats::fixed_records::{Bytes, ColumnLayout, FixedRecords, Logical, Null, Physical};
use crate::home::catalog::ColumnNote;
use globset::{Glob, GlobSet, GlobSetBuilder};
use polars::prelude::{
AnyValue, DataFrame, LazyFrame, PlSmallStr, PolarsResult, SchemaRef, TimeUnit,
};
use std::collections::BTreeMap;
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use toml::de::{DeTable, DeValue};
pub const MAX_SIZE: u64 = 64 << 20;
pub const MAX_FLATTEN: u64 = 1024;
const MAX_MATCH_READ: u64 = 64 << 10;
const DAY_NS: i64 = 86_400_000_000_000;
pub const PATH_VAR: &str = "DATUI_FORMATS_PATH";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpecError {
pub path: Option<PathBuf>,
pub line: usize,
pub column: usize,
pub message: String,
}
impl std::fmt::Display for SpecError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let at = (self.line > 0).then_some((self.line, self.column));
f.write_str(&crate::error_display::located_message(
self.path.as_deref(),
at,
&self.message,
))
}
}
impl std::error::Error for SpecError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Endian {
Little,
Big,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Layout {
Rows,
Columns,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Framing {
#[default]
Fixed,
LengthPrefixed,
Variant,
Sync,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Amount {
Given(u64),
Header {
field: String,
adjust: i64,
},
Footer {
field: String,
adjust: i64,
},
Record {
field: String,
adjust: i64,
},
Rest,
}
impl Amount {
pub fn is_fixed(&self) -> bool {
!matches!(self, Self::Record { .. } | Self::Rest)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Type {
Unsigned(u8),
Signed(u8),
Float(u8),
BFloat16,
Bool,
Str,
Strz,
Bytes,
Pad,
VarU,
VarS,
Group,
}
impl Type {
pub fn width(self) -> Option<u64> {
match self {
Self::Strz | Self::VarU | Self::VarS | Self::Group => None,
_ => self.physical().width().map(|w| w as u64),
}
}
fn is_integer(self) -> bool {
matches!(
self,
Self::Unsigned(_) | Self::Signed(_) | Self::VarU | Self::VarS
)
}
fn is_number(self) -> bool {
self.is_integer() || matches!(self, Self::Float(_) | Self::BFloat16)
}
pub fn is_text(self) -> bool {
matches!(self, Self::Str | Self::Strz)
}
fn physical(self) -> Physical {
match self {
Self::Unsigned(n) => Physical::Unsigned(n),
Self::Signed(n) => Physical::Signed(n),
Self::Float(n) => Physical::Float(n),
Self::BFloat16 => Physical::BFloat16,
Self::Bool => Physical::Bool,
Self::Str | Self::Strz => Physical::Text,
Self::VarU => Physical::Unsigned(8),
Self::VarS => Physical::Signed(8),
Self::Bytes | Self::Pad | Self::Group => Physical::Raw,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Encoding {
#[default]
Utf8,
Latin1,
Utf16Le,
Utf16Be,
}
impl Encoding {
pub fn physical(self) -> Physical {
match self {
Self::Utf8 => Physical::Text,
Self::Latin1 => Physical::Latin1,
Self::Utf16Le => Physical::Utf16 { big_endian: false },
Self::Utf16Be => Physical::Utf16 { big_endian: true },
}
}
pub fn unit(self) -> usize {
match self {
Self::Utf16Le | Self::Utf16Be => 2,
_ => 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Delta {
#[default]
None,
All,
Block,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BitField {
pub name: String,
pub bit: u32,
pub width: u32,
pub labels: Option<Arc<BTreeMap<i64, String>>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lookup {
pub file: String,
pub format: LookupFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LookupFormat {
Lines,
Nul,
Fixed(u64),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Checksum {
pub algo: ChecksumAlgo,
pub field: String,
pub from: Option<String>,
pub to: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChecksumAlgo {
Crc16Ccitt,
Crc16Xmodem,
Crc16Modbus,
Crc16Arc,
Crc32,
Crc32c,
Sum8,
Xor8,
}
impl ChecksumAlgo {
const NAMES: &[(&str, Self)] = &[
("crc16-ccitt", Self::Crc16Ccitt),
("crc16-xmodem", Self::Crc16Xmodem),
("crc16-modbus", Self::Crc16Modbus),
("crc16-arc", Self::Crc16Arc),
("crc32", Self::Crc32),
("crc32c", Self::Crc32c),
("sum8", Self::Sum8),
("xor8", Self::Xor8),
];
fn parse(name: &str) -> Option<Self> {
Self::NAMES
.iter()
.find(|(n, _)| *n == name)
.map(|(_, a)| *a)
}
pub fn compute(self, bytes: &[u8]) -> u64 {
use crc::{
CRC_16_ARC, CRC_16_IBM_3740, CRC_16_MODBUS, CRC_16_XMODEM, CRC_32_ISCSI,
CRC_32_ISO_HDLC, Crc,
};
match self {
Self::Crc16Ccitt => u64::from(Crc::<u16>::new(&CRC_16_IBM_3740).checksum(bytes)),
Self::Crc16Xmodem => u64::from(Crc::<u16>::new(&CRC_16_XMODEM).checksum(bytes)),
Self::Crc16Modbus => u64::from(Crc::<u16>::new(&CRC_16_MODBUS).checksum(bytes)),
Self::Crc16Arc => u64::from(Crc::<u16>::new(&CRC_16_ARC).checksum(bytes)),
Self::Crc32 => u64::from(Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(bytes)),
Self::Crc32c => u64::from(Crc::<u32>::new(&CRC_32_ISCSI).checksum(bytes)),
Self::Sum8 => u64::from(bytes.iter().fold(0u8, |a, b| a.wrapping_add(*b))),
Self::Xor8 => u64::from(bytes.iter().fold(0u8, |a, b| a ^ b)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeUnitSpec {
Days,
Seconds,
Millis,
Micros,
Nanos,
}
impl TimeUnitSpec {
fn nanos(self) -> i64 {
match self {
Self::Days => DAY_NS,
Self::Seconds => 1_000_000_000,
Self::Millis => 1_000_000,
Self::Micros => 1_000,
Self::Nanos => 1,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Meaning {
Plain,
Time {
unit: TimeUnitSpec,
epoch_ns: i64,
},
TimeOfDay {
unit: TimeUnitSpec,
date: Option<String>,
},
Yyyymmdd,
Scale(u32),
Linear {
factor: f64,
offset: f64,
},
Enum(Arc<BTreeMap<i64, String>>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Field {
pub name: Option<String>,
pub ty: Type,
pub size: Option<Amount>,
pub endian: Option<Endian>,
pub meaning: Meaning,
pub null: Option<Null>,
pub count: Option<Amount>,
pub flatten: bool,
pub file: Option<String>,
pub at: Option<Amount>,
pub encoding: Encoding,
pub delta: Delta,
pub bits: Vec<BitField>,
pub group: Vec<Field>,
pub string_at: Option<String>,
pub lookup: Option<Lookup>,
pub description: Option<String>,
pub unit: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Header {
pub fields: Vec<Field>,
pub size: Option<Amount>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Records {
pub framing: Framing,
pub fields: Vec<Field>,
pub size: Option<Amount>,
pub count: Option<Amount>,
pub length_suffix: bool,
pub align: u64,
pub sync: Vec<u8>,
pub type_field: Option<String>,
pub variants: Vec<Variant>,
pub checksum: Option<Checksum>,
pub ring: Option<Amount>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Variant {
pub name: String,
pub when: Vec<Expected>,
pub fields: Vec<Field>,
pub size: Option<Amount>,
pub description: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Footer {
pub fields: Vec<Field>,
pub size: Option<u64>,
pub checksum: Option<(ChecksumAlgo, String)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compression {
None,
Gzip,
Deflate,
Zlib,
Zstd,
Lz4,
Lz4Block,
Snappy,
SnappyFramed,
Brotli,
Bzip2,
Xz,
}
impl Compression {
const NAMES: &[(&str, Self)] = &[
("none", Self::None),
("gzip", Self::Gzip),
("deflate", Self::Deflate),
("zlib", Self::Zlib),
("zstd", Self::Zstd),
("lz4", Self::Lz4),
("lz4_block", Self::Lz4Block),
("snappy", Self::Snappy),
("snappy_framed", Self::SnappyFramed),
("brotli", Self::Brotli),
("bzip2", Self::Bzip2),
("xz", Self::Xz),
];
fn parse(name: &str) -> Option<Self> {
Self::NAMES
.iter()
.find(|(n, _)| *n == name)
.map(|(_, c)| *c)
}
pub fn name(self) -> &'static str {
Self::NAMES
.iter()
.find(|(_, c)| *c == self)
.map_or("none", |(n, _)| n)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Codec {
Fixed(Compression),
ByField {
field: String,
values: BTreeMap<i64, Compression>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct BlockIndex {
pub at: Amount,
pub count: Amount,
pub fields: Vec<Field>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Blocks {
pub header: Vec<Field>,
pub size: Amount,
pub codec: Codec,
pub records: Option<String>,
pub uncompressed: Option<String>,
pub index: Option<BlockIndex>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Capture {
pub header: Vec<Field>,
pub count: Option<String>,
pub time: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathPart {
pub name: String,
pub date: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Files {
pub pattern: String,
pub parts: Vec<PathPart>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Section {
pub name: String,
pub offset: Amount,
pub size: Amount,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expected {
Int(i128),
Text(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MatchChip {
pub name: String,
pub value: String,
pub kind: ChipKind,
pub offset: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChipKind {
Magic,
Hex,
Text,
Int,
Glob,
}
const CHIP_MAGIC_CHARS: usize = 16;
const CHIP_MAGIC_BYTES: usize = 8;
impl MatchChip {
pub fn value_text(&self, quote: bool) -> String {
let value = match self.kind {
ChipKind::Text if quote => format!("\"{}\"", self.value),
_ => self.value.clone(),
};
match self.offset {
Some(at) => format!("{value} @ {at}"),
None => value,
}
}
pub fn plain(&self, quote: bool) -> String {
format!("{} {}", self.name, self.value_text(quote))
}
}
pub fn chips_plain(chips: &[MatchChip]) -> String {
let sep = format!(" {} ", crate::glyphs::get().middot);
chips
.iter()
.map(|c| c.plain(true))
.collect::<Vec<_>>()
.join(&sep)
}
pub const MAX_SPEC_BYTES: u64 = 1 << 20;
pub const MAX_SPEC_SAID: &str = "1 MiB";
#[derive(Debug, Clone)]
pub struct Spec {
pub name: String,
pub description: Option<String>,
pub documentation: Option<String>,
pub notes: Vec<(String, ColumnNote)>,
pub path: Option<PathBuf>,
pub globs: Vec<String>,
glob_set: Option<GlobSet>,
pub magic: Vec<u8>,
pub magic_offset: u64,
pub expect: Vec<(String, Expected)>,
pub endian: Endian,
pub endian_auto: bool,
pub layout: Layout,
pub header: Header,
pub records: Records,
pub delimited: Option<Arc<crate::formats::delimited_spec::Delimited>>,
pub footer: Option<Footer>,
pub blocks: Option<Blocks>,
pub capture: Option<Capture>,
pub files: Option<Files>,
pub sections: Vec<Section>,
pub variant: Option<String>,
}
impl PartialEq for Spec {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.description == other.description
&& self.documentation == other.documentation
&& self.notes == other.notes
&& self.path == other.path
&& self.globs == other.globs
&& self.magic == other.magic
&& self.magic_offset == other.magic_offset
&& self.expect == other.expect
&& self.endian == other.endian
&& self.layout == other.layout
&& self.endian_auto == other.endian_auto
&& self.header == other.header
&& self.records == other.records
&& self.delimited == other.delimited
&& self.footer == other.footer
&& self.blocks == other.blocks
&& self.capture == other.capture
&& self.files == other.files
&& self.sections == other.sections
}
}
mod command;
pub mod files;
mod layout;
mod matching;
mod open;
mod parse;
mod registry;
mod types;
pub use command::*;
pub use layout::*;
pub use matching::*;
pub(crate) use parse::*;
pub use registry::*;
pub use types::*;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod docs_tests;
#[cfg(test)]
mod chip_tests;