Skip to content
VoiceNGOPublic

About

A rust implementation of the SSV format

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

25 Commits

Folders and files

Repository files navigation

ssv — Rust SSV parser/serializer

Rust implementation of the SSV format.

Setup

[dependencies]
ssv = { path = "..." }

The crate is a workspace of two packages: ssv (the library) and ssv_derive (the proc-macros). You only depend on ssv; the derive macros are re-exported from it.

Reading SSV Files

fn from_str<T: FromRow>(src: &str)               -> Result<Tables<T>, Error>
fn from_file<T: FromRow>(path: impl AsRef<Path>) -> Result<Tables<T>, Error>

Derive FromRow on a struct that mirrors the SSV columns:

use ssv::FromRow;

#[derive(Debug, FromRow)]
struct Record {
    name:   String,
    age:    i32,
    score:  f32,
    active: bool,
    tags:   Vec<String>,
}

let tables = ssv::from_str::<Record>(src)?;
let (rows, _config) = tables.into_first();
// rows: Vec<Record>

Multiple tables

from_str always returns Tables<T>, which holds every table in the document.

// Index access
let first  = &tables[0];

// Named access (requires #! TABLE name in the source)
let colors = &tables["colors"];

// Single-table shorthand — consumes Tables
let (rows, config) = tables.into_first();

Table<T> fields:

pub struct Table<T> {
    pub name:              Option<String>,
    pub rows:              Vec<T>,
    pub config:            Config,
    pub pre_comments:      Vec<String>,
    pub inline_comments:   BTreeMap<usize, Vec<String>>,  // keyed by row index
    pub trailing_comments: Vec<String>,
}

Editing SSV Files

fn to_string<T: ToRow>(tables: &Tables<T>)                       -> Result<String, Error>
fn to_file<T: ToRow>(path: impl AsRef<Path>, tables: &Tables<T>) -> Result<(), Error>

Derive ToRow alongside FromRow, parse the file, edit rows, write it back:

#[derive(Debug, FromRow, ToRow)]
struct Record { /* ... */ }

let mut tables = ssv::from_file::<Record>("data.ssv")?;
tables[0].rows.push(Record { name: "Carol".into(), age: 28, score: 8.1, active: true, tags: vec![] });
ssv::to_file("data.ssv", &tables)?;

Reference

Derive macros

Macro Purpose
FromRow Deserialize a struct from an SSV row
ToRow Serialize a struct to an SSV row; generates the header
SsvType Use a struct as a named-tuple field in another type

Named-tuple fields with SsvType

A struct that derives SsvType can be used as a field type inside ToRow/FromRow structs. It maps to an SSV named tuple:

#[derive(SsvType)]
struct Point { x: f32, y: f32 }

#[derive(FromRow, ToRow)]
struct Path { label: String, origin: Point }

Type mapping

Rust type SSV type
String string
bool bool
i32 int
i8 / i16 / i64 / i128 int8 / int16 / int64 / int128
u32 uint
u8 / u16 / u64 / u128 uint8 / uint16 / uint64 / uint128
f32 float
f64 float64
Option<T> T?
Vec<T> T[]
(A, B) … (A, …, T) [A, B] … [A, …, T]

All element types must implement SsvType.

Errors

All public functions return Result<_, ssv::Error>. Error implements std::error::Error and Display.

Variant When
IllegalDelimiter Delimiter character is forbidden (alphanumeric, \, #, …)
IllegalPrimaryDelimiter Primary delimiter is :, ,, [, or ]
EmptyDelimiterList #! DELIMITERS with no characters
InvalidHeader Malformed header line
UnknownType Unrecognised type name in header
UnknownField Struct field not present in the parsed header
ConversionError Value cannot be parsed as its declared type
UnescapedDelimiter Primitive field contains a bare delimiter character
UnknownEscapeSequence \X where X is not a recognised escape
NotEnoughDelimiters Nesting depth exceeds the number of declared delimiters
TooManyFields Data row has more non-empty fields than the header
Io File read/write failure

Generating SSV files from scratch

It's generally simpler to just write an SSV file, import and edit its data, but you may also generate them with this crate:

Range constraints

Use #[ssv(range = "min..max")] to attach a range constraint to a numeric field in the generated header:

#[derive(ToRow, FromRow)]
struct Player {
    name:  String,
    #[ssv(range = "0..100")]
    score: u8,
    #[ssv(range = "18..")]
    age:   u8,
}
// Generated header: name:string | score:uint8(0..100) | age:uint8(18..)

Config

All parser-comment settings from the SSV spec are reflected in Config. When parsing, Config is populated from the source document and returned alongside the rows. Build one manually when generating from scratch:

pub struct Config {
    pub delimiters:                  Vec<char>,   // default: ['|', ';']
    pub disable_binary_numbers:      bool,
    pub disable_octal_numbers:       bool,
    pub disable_hex_numbers:         bool,
    pub disable_exponential_numbers: bool,
    pub disable_regex_check:         bool,
    pub decimal_separator:           char,        // default: '.'
    pub numeric_separator:           Option<char>,
    pub parenthetical_negatives:     bool,
    pub null_char:                   Option<char>,
    pub escape_char:                 char,        // default: '\\'
    pub disable_markdown_support:    bool,
    pub isolated_tables:             bool,
    pub header:                      Vec<(String, String)>,  // raw header fields
    pub type_aliases:                Vec<(String, FieldType)>,
}

Use Tables::single(rows, config) to build a Tables without parsing anything:

let mut config = Config::default();
config.delimiters = vec!['|', ';', ':'];

let rows = vec![
    Record { name: "Alice".into(), age: 30, score: 9.5, active: true, tags: vec![] },
];
let tables = Tables::single(rows, config);
let output = ssv::to_string(&tables)?;

License

This project is licensed under your choice of any of the following licenses:

Personally I suggest 0BSD as it doesn't even require keeping the license file. Do whatever you want. I don't care. If your legal department cares, the other two are available.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.

About

A rust implementation of the SSV format

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages