1pub mod audio;
10pub mod candump;
11pub mod column_types;
12pub mod columns;
13pub mod csv_dialect;
14pub mod dataflash;
15pub(crate) mod dataset_files;
16pub mod dbc;
17pub mod delimited_spec;
18pub mod elf;
19pub mod excel;
20pub mod fix;
21pub mod fixed_records;
22pub mod framed_records;
23pub mod gps;
24pub mod hf_splits;
25pub mod indexed;
26pub mod ipc_stream;
27pub mod journal;
28pub mod lines;
29pub mod members;
30pub mod midi;
31pub mod model_files;
32pub mod nul_tail;
33pub mod numpy;
34pub mod parquet_footer;
35pub mod pushdown;
36pub mod readers;
37pub mod row_index;
38pub mod schema_union;
39pub mod sdf;
40pub(crate) mod segments;
41pub(crate) mod spec_union;
42pub mod sqlite;
43pub mod text_formats;
44pub mod ulog;
45pub mod vcd;
46
47use crate::formats::fixed_records::{Bytes, ColumnLayout, FixedRecords, Logical, Null, Physical};
48use crate::home::catalog::ColumnNote;
49use globset::{Glob, GlobSet, GlobSetBuilder};
50use polars::prelude::{
51 AnyValue, DataFrame, LazyFrame, PlSmallStr, PolarsResult, SchemaRef, TimeUnit,
52};
53use std::collections::BTreeMap;
54use std::ops::Range;
55use std::path::{Path, PathBuf};
56use std::sync::Arc;
57use toml::de::{DeTable, DeValue};
58
59pub const MAX_SIZE: u64 = 64 << 20;
63
64pub const MAX_FLATTEN: u64 = 1024;
66
67const MAX_MATCH_READ: u64 = 64 << 10;
70
71const DAY_NS: i64 = 86_400_000_000_000;
73
74pub const PATH_VAR: &str = "DATUI_FORMATS_PATH";
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct SpecError {
80 pub path: Option<PathBuf>,
81 pub line: usize,
83 pub column: usize,
84 pub message: String,
85}
86
87impl std::fmt::Display for SpecError {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 let at = (self.line > 0).then_some((self.line, self.column));
92 f.write_str(&crate::error_display::located_message(
93 self.path.as_deref(),
94 at,
95 &self.message,
96 ))
97 }
98}
99
100impl std::error::Error for SpecError {}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum Endian {
105 Little,
106 Big,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum Layout {
112 Rows,
114 Columns,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
122pub enum Framing {
123 #[default]
125 Fixed,
126 LengthPrefixed,
128 Variant,
130 Sync,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
136pub enum Amount {
137 Given(u64),
138 Header {
140 field: String,
141 adjust: i64,
142 },
143 Footer {
145 field: String,
146 adjust: i64,
147 },
148 Record {
151 field: String,
152 adjust: i64,
153 },
154 Rest,
156}
157
158impl Amount {
159 pub fn is_fixed(&self) -> bool {
161 !matches!(self, Self::Record { .. } | Self::Rest)
162 }
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum Type {
168 Unsigned(u8),
170 Signed(u8),
171 Float(u8),
173 BFloat16,
175 Bool,
176 Str,
177 Strz,
179 Bytes,
180 Pad,
181 VarU,
183 VarS,
184 Group,
186}
187
188impl Type {
189 pub fn width(self) -> Option<u64> {
191 match self {
192 Self::Strz | Self::VarU | Self::VarS | Self::Group => None,
193 _ => self.physical().width().map(|w| w as u64),
194 }
195 }
196
197 fn is_integer(self) -> bool {
198 matches!(
199 self,
200 Self::Unsigned(_) | Self::Signed(_) | Self::VarU | Self::VarS
201 )
202 }
203
204 fn is_number(self) -> bool {
205 self.is_integer() || matches!(self, Self::Float(_) | Self::BFloat16)
206 }
207
208 pub fn is_text(self) -> bool {
209 matches!(self, Self::Str | Self::Strz)
210 }
211
212 fn physical(self) -> Physical {
213 match self {
214 Self::Unsigned(n) => Physical::Unsigned(n),
215 Self::Signed(n) => Physical::Signed(n),
216 Self::Float(n) => Physical::Float(n),
217 Self::BFloat16 => Physical::BFloat16,
218 Self::Bool => Physical::Bool,
219 Self::Str | Self::Strz => Physical::Text,
220 Self::VarU => Physical::Unsigned(8),
222 Self::VarS => Physical::Signed(8),
223 Self::Bytes | Self::Pad | Self::Group => Physical::Raw,
224 }
225 }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
230pub enum Encoding {
231 #[default]
232 Utf8,
233 Latin1,
234 Utf16Le,
235 Utf16Be,
236}
237
238impl Encoding {
239 pub fn physical(self) -> Physical {
240 match self {
241 Self::Utf8 => Physical::Text,
242 Self::Latin1 => Physical::Latin1,
243 Self::Utf16Le => Physical::Utf16 { big_endian: false },
244 Self::Utf16Be => Physical::Utf16 { big_endian: true },
245 }
246 }
247
248 pub fn unit(self) -> usize {
250 match self {
251 Self::Utf16Le | Self::Utf16Be => 2,
252 _ => 1,
253 }
254 }
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
259pub enum Delta {
260 #[default]
261 None,
262 All,
264 Block,
266}
267
268#[derive(Debug, Clone, PartialEq)]
270pub struct BitField {
271 pub name: String,
272 pub bit: u32,
274 pub width: u32,
275 pub labels: Option<Arc<BTreeMap<i64, String>>>,
276}
277
278#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct Lookup {
281 pub file: String,
283 pub format: LookupFormat,
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum LookupFormat {
289 Lines,
291 Nul,
293 Fixed(u64),
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
299pub struct Checksum {
300 pub algo: ChecksumAlgo,
301 pub field: String,
303 pub from: Option<String>,
305 pub to: Option<String>,
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub enum ChecksumAlgo {
312 Crc16Ccitt,
313 Crc16Xmodem,
314 Crc16Modbus,
315 Crc16Arc,
316 Crc32,
317 Crc32c,
318 Sum8,
319 Xor8,
320}
321
322impl ChecksumAlgo {
323 const NAMES: &[(&str, Self)] = &[
324 ("crc16-ccitt", Self::Crc16Ccitt),
325 ("crc16-xmodem", Self::Crc16Xmodem),
326 ("crc16-modbus", Self::Crc16Modbus),
327 ("crc16-arc", Self::Crc16Arc),
328 ("crc32", Self::Crc32),
329 ("crc32c", Self::Crc32c),
330 ("sum8", Self::Sum8),
331 ("xor8", Self::Xor8),
332 ];
333
334 fn parse(name: &str) -> Option<Self> {
335 Self::NAMES
336 .iter()
337 .find(|(n, _)| *n == name)
338 .map(|(_, a)| *a)
339 }
340
341 pub fn compute(self, bytes: &[u8]) -> u64 {
343 use crc::{
344 CRC_16_ARC, CRC_16_IBM_3740, CRC_16_MODBUS, CRC_16_XMODEM, CRC_32_ISCSI,
345 CRC_32_ISO_HDLC, Crc,
346 };
347 match self {
348 Self::Crc16Ccitt => u64::from(Crc::<u16>::new(&CRC_16_IBM_3740).checksum(bytes)),
349 Self::Crc16Xmodem => u64::from(Crc::<u16>::new(&CRC_16_XMODEM).checksum(bytes)),
350 Self::Crc16Modbus => u64::from(Crc::<u16>::new(&CRC_16_MODBUS).checksum(bytes)),
351 Self::Crc16Arc => u64::from(Crc::<u16>::new(&CRC_16_ARC).checksum(bytes)),
352 Self::Crc32 => u64::from(Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(bytes)),
353 Self::Crc32c => u64::from(Crc::<u32>::new(&CRC_32_ISCSI).checksum(bytes)),
354 Self::Sum8 => u64::from(bytes.iter().fold(0u8, |a, b| a.wrapping_add(*b))),
355 Self::Xor8 => u64::from(bytes.iter().fold(0u8, |a, b| a ^ b)),
356 }
357 }
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362pub enum TimeUnitSpec {
363 Days,
364 Seconds,
365 Millis,
366 Micros,
367 Nanos,
368}
369
370impl TimeUnitSpec {
371 fn nanos(self) -> i64 {
372 match self {
373 Self::Days => DAY_NS,
374 Self::Seconds => 1_000_000_000,
375 Self::Millis => 1_000_000,
376 Self::Micros => 1_000,
377 Self::Nanos => 1,
378 }
379 }
380}
381
382#[derive(Debug, Clone, PartialEq)]
384pub enum Meaning {
385 Plain,
386 Time {
389 unit: TimeUnitSpec,
390 epoch_ns: i64,
391 },
392 TimeOfDay {
395 unit: TimeUnitSpec,
396 date: Option<String>,
397 },
398 Yyyymmdd,
400 Scale(u32),
402 Linear {
404 factor: f64,
405 offset: f64,
406 },
407 Enum(Arc<BTreeMap<i64, String>>),
409}
410
411#[derive(Debug, Clone, PartialEq)]
413pub struct Field {
414 pub name: Option<String>,
416 pub ty: Type,
417 pub size: Option<Amount>,
419 pub endian: Option<Endian>,
421 pub meaning: Meaning,
422 pub null: Option<Null>,
424 pub count: Option<Amount>,
426 pub flatten: bool,
427 pub file: Option<String>,
430 pub at: Option<Amount>,
432 pub encoding: Encoding,
434 pub delta: Delta,
435 pub bits: Vec<BitField>,
437 pub group: Vec<Field>,
439 pub string_at: Option<String>,
441 pub lookup: Option<Lookup>,
442 pub description: Option<String>,
444 pub unit: Option<String>,
446}
447
448#[derive(Debug, Clone, Default, PartialEq)]
450pub struct Header {
451 pub fields: Vec<Field>,
452 pub size: Option<Amount>,
453}
454
455#[derive(Debug, Clone, Default, PartialEq)]
457pub struct Records {
458 pub framing: Framing,
459 pub fields: Vec<Field>,
461 pub size: Option<Amount>,
464 pub count: Option<Amount>,
466 pub length_suffix: bool,
468 pub align: u64,
470 pub sync: Vec<u8>,
472 pub type_field: Option<String>,
474 pub variants: Vec<Variant>,
475 pub checksum: Option<Checksum>,
476 pub ring: Option<Amount>,
478}
479
480#[derive(Debug, Clone, PartialEq)]
482pub struct Variant {
483 pub name: String,
484 pub when: Vec<Expected>,
486 pub fields: Vec<Field>,
488 pub size: Option<Amount>,
490 pub description: Option<String>,
492}
493
494#[derive(Debug, Clone, Default, PartialEq)]
496pub struct Footer {
497 pub fields: Vec<Field>,
498 pub size: Option<u64>,
499 pub checksum: Option<(ChecksumAlgo, String)>,
501}
502
503#[derive(Debug, Clone, Copy, PartialEq, Eq)]
505pub enum Compression {
506 None,
507 Gzip,
508 Deflate,
509 Zlib,
510 Zstd,
511 Lz4,
513 Lz4Block,
515 Snappy,
517 SnappyFramed,
519 Brotli,
520 Bzip2,
521 Xz,
522}
523
524impl Compression {
525 const NAMES: &[(&str, Self)] = &[
526 ("none", Self::None),
527 ("gzip", Self::Gzip),
528 ("deflate", Self::Deflate),
529 ("zlib", Self::Zlib),
530 ("zstd", Self::Zstd),
531 ("lz4", Self::Lz4),
532 ("lz4_block", Self::Lz4Block),
533 ("snappy", Self::Snappy),
534 ("snappy_framed", Self::SnappyFramed),
535 ("brotli", Self::Brotli),
536 ("bzip2", Self::Bzip2),
537 ("xz", Self::Xz),
538 ];
539
540 fn parse(name: &str) -> Option<Self> {
541 Self::NAMES
542 .iter()
543 .find(|(n, _)| *n == name)
544 .map(|(_, c)| *c)
545 }
546
547 pub fn name(self) -> &'static str {
548 Self::NAMES
549 .iter()
550 .find(|(_, c)| *c == self)
551 .map_or("none", |(n, _)| n)
552 }
553}
554
555#[derive(Debug, Clone, PartialEq, Eq)]
557pub enum Codec {
558 Fixed(Compression),
559 ByField {
560 field: String,
561 values: BTreeMap<i64, Compression>,
562 },
563}
564
565#[derive(Debug, Clone, PartialEq)]
567pub struct BlockIndex {
568 pub at: Amount,
570 pub count: Amount,
572 pub fields: Vec<Field>,
574}
575
576#[derive(Debug, Clone, PartialEq)]
578pub struct Blocks {
579 pub header: Vec<Field>,
580 pub size: Amount,
582 pub codec: Codec,
583 pub records: Option<String>,
585 pub uncompressed: Option<String>,
587 pub index: Option<BlockIndex>,
588}
589
590#[derive(Debug, Clone, PartialEq)]
592pub struct Capture {
593 pub header: Vec<Field>,
595 pub count: Option<String>,
597 pub time: Option<String>,
599}
600
601#[derive(Debug, Clone, PartialEq, Eq)]
603pub struct PathPart {
604 pub name: String,
605 pub date: Option<String>,
607}
608
609#[derive(Debug, Clone, PartialEq, Eq)]
611pub struct Files {
612 pub pattern: String,
613 pub parts: Vec<PathPart>,
614}
615
616#[derive(Debug, Clone, PartialEq, Eq)]
618pub struct Section {
619 pub name: String,
620 pub offset: Amount,
621 pub size: Amount,
622}
623
624#[derive(Debug, Clone, PartialEq, Eq)]
626pub enum Expected {
627 Int(i128),
628 Text(String),
629}
630
631#[derive(Debug, Clone, PartialEq, Eq)]
635pub struct MatchChip {
636 pub name: String,
637 pub value: String,
638 pub kind: ChipKind,
639 pub offset: Option<u64>,
641}
642
643#[derive(Debug, Clone, Copy, PartialEq, Eq)]
645pub enum ChipKind {
646 Magic,
648 Hex,
650 Text,
652 Int,
654 Glob,
656}
657
658const CHIP_MAGIC_CHARS: usize = 16;
660const CHIP_MAGIC_BYTES: usize = 8;
661
662impl MatchChip {
663 pub fn value_text(&self, quote: bool) -> String {
665 let value = match self.kind {
666 ChipKind::Text if quote => format!("\"{}\"", self.value),
667 _ => self.value.clone(),
668 };
669 match self.offset {
670 Some(at) => format!("{value} @ {at}"),
671 None => value,
672 }
673 }
674
675 pub fn plain(&self, quote: bool) -> String {
677 format!("{} {}", self.name, self.value_text(quote))
678 }
679}
680
681pub fn chips_plain(chips: &[MatchChip]) -> String {
684 let sep = format!(" {} ", crate::glyphs::get().middot);
685 chips
686 .iter()
687 .map(|c| c.plain(true))
688 .collect::<Vec<_>>()
689 .join(&sep)
690}
691
692pub const MAX_SPEC_BYTES: u64 = 1 << 20;
695pub const MAX_SPEC_SAID: &str = "1 MiB";
697
698#[derive(Debug, Clone)]
700pub struct Spec {
701 pub name: String,
702 pub description: Option<String>,
703 pub documentation: Option<String>,
705 pub notes: Vec<(String, ColumnNote)>,
707 pub path: Option<PathBuf>,
709 pub globs: Vec<String>,
710 glob_set: Option<GlobSet>,
711 pub magic: Vec<u8>,
712 pub magic_offset: u64,
713 pub expect: Vec<(String, Expected)>,
715 pub endian: Endian,
716 pub endian_auto: bool,
719 pub layout: Layout,
720 pub header: Header,
721 pub records: Records,
722 pub delimited: Option<Arc<crate::formats::delimited_spec::Delimited>>,
725 pub footer: Option<Footer>,
726 pub blocks: Option<Blocks>,
727 pub capture: Option<Capture>,
728 pub files: Option<Files>,
729 pub sections: Vec<Section>,
730 pub variant: Option<String>,
732}
733
734impl PartialEq for Spec {
735 fn eq(&self, other: &Self) -> bool {
736 self.name == other.name
737 && self.description == other.description
738 && self.documentation == other.documentation
739 && self.notes == other.notes
740 && self.path == other.path
741 && self.globs == other.globs
742 && self.magic == other.magic
743 && self.magic_offset == other.magic_offset
744 && self.expect == other.expect
745 && self.endian == other.endian
746 && self.layout == other.layout
747 && self.endian_auto == other.endian_auto
748 && self.header == other.header
749 && self.records == other.records
750 && self.delimited == other.delimited
751 && self.footer == other.footer
752 && self.blocks == other.blocks
753 && self.capture == other.capture
754 && self.files == other.files
755 && self.sections == other.sections
756 }
757}
758
759mod command;
760pub mod files;
761mod layout;
762mod matching;
763mod open;
764mod parse;
765mod registry;
766mod types;
767
768pub use command::*;
769pub use layout::*;
770pub use matching::*;
771pub(crate) use parse::*;
772pub use registry::*;
773pub use types::*;
774
775#[cfg(test)]
776mod tests;
777
778#[cfg(test)]
779mod docs_tests;
780
781#[cfg(test)]
782mod chip_tests;