feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::borrow::Cow;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
|
||||
use crate::TomlWrite;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub trait ToTomlKey {
|
||||
fn to_toml_key(&self) -> String;
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<T> ToTomlKey for T
|
||||
where
|
||||
T: WriteTomlKey + ?Sized,
|
||||
{
|
||||
fn to_toml_key(&self) -> String {
|
||||
let mut result = String::new();
|
||||
let _ = self.write_toml_key(&mut result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WriteTomlKey {
|
||||
fn write_toml_key<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result;
|
||||
}
|
||||
|
||||
impl WriteTomlKey for str {
|
||||
fn write_toml_key<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
crate::TomlKeyBuilder::new(self)
|
||||
.as_default()
|
||||
.write_toml_key(writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl WriteTomlKey for String {
|
||||
fn write_toml_key<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
self.as_str().write_toml_key(writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl WriteTomlKey for Cow<'_, str> {
|
||||
fn write_toml_key<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
self.as_ref().write_toml_key(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: WriteTomlKey + ?Sized> WriteTomlKey for &V {
|
||||
fn write_toml_key<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
(*self).write_toml_key(writer)
|
||||
}
|
||||
}
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
//! A low-level interface for writing out TOML
|
||||
//!
|
||||
//! Considerations when serializing arbitrary data:
|
||||
//! - Verify the implementation with [`toml-test-harness`](https://docs.rs/toml-test-harness)
|
||||
//! - Be sure to group keys under a table before writing another table
|
||||
//! - Watch for extra trailing newlines and leading newlines, both when starting with top-level
|
||||
//! keys or a table
|
||||
//! - When serializing an array-of-tables, be sure to verify that all elements of the array
|
||||
//! serialize as tables
|
||||
//! - Standard tables and inline tables may need separate implementations of corner cases,
|
||||
//! requiring verifying them both
|
||||
//!
|
||||
//! When serializing Rust data structures
|
||||
//! - `Option`: Skip key-value pairs with a value of `None`, otherwise error when seeing `None`
|
||||
//! - When skipping key-value pairs, be careful that a deeply nested `None` doesn't get skipped
|
||||
//! - Scalars and arrays are unsupported as top-level data types
|
||||
//! - Tuples and tuple variants seriallize as arrays
|
||||
//! - Structs, struct variants, and maps serialize as tables
|
||||
//! - Newtype variants serialize as to the inner type
|
||||
//! - Unit variants serialize to a string
|
||||
//! - Unit and unit structs don't have a clear meaning in TOML
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use toml_write::TomlWrite as _;
|
||||
//!
|
||||
//! # fn main() -> std::fmt::Result {
|
||||
//! let mut output = String::new();
|
||||
//! output.newline()?;
|
||||
//! output.open_table_header()?;
|
||||
//! output.key("table")?;
|
||||
//! output.close_table_header()?;
|
||||
//! output.newline()?;
|
||||
//!
|
||||
//! output.key("key")?;
|
||||
//! output.space()?;
|
||||
//! output.keyval_sep()?;
|
||||
//! output.space()?;
|
||||
//! output.value("value")?;
|
||||
//! output.newline()?;
|
||||
//!
|
||||
//! assert_eq!(output, r#"
|
||||
//! [table]
|
||||
//! key = "value"
|
||||
//! "#);
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(clippy::std_instead_of_core)]
|
||||
#![warn(clippy::std_instead_of_alloc)]
|
||||
#![warn(clippy::print_stderr)]
|
||||
#![warn(clippy::print_stdout)]
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
mod key;
|
||||
mod string;
|
||||
mod value;
|
||||
mod write;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use key::ToTomlKey;
|
||||
pub use key::WriteTomlKey;
|
||||
pub use string::TomlKey;
|
||||
pub use string::TomlKeyBuilder;
|
||||
pub use string::TomlString;
|
||||
pub use string::TomlStringBuilder;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use value::ToTomlValue;
|
||||
pub use value::WriteTomlValue;
|
||||
pub use write::TomlWrite;
|
||||
|
||||
#[doc = include_str!("../README.md")]
|
||||
#[cfg(doctest)]
|
||||
pub struct ReadmeDoctests;
|
||||
Vendored
+418
@@ -0,0 +1,418 @@
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct TomlStringBuilder<'s> {
|
||||
decoded: &'s str,
|
||||
metrics: ValueMetrics,
|
||||
}
|
||||
|
||||
impl<'s> TomlStringBuilder<'s> {
|
||||
pub fn new(decoded: &'s str) -> Self {
|
||||
Self {
|
||||
decoded,
|
||||
metrics: ValueMetrics::calculate(decoded),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_default(&self) -> TomlString<'s> {
|
||||
self.as_basic_pretty()
|
||||
.or_else(|| self.as_literal())
|
||||
.or_else(|| self.as_ml_basic_pretty())
|
||||
.or_else(|| self.as_ml_literal())
|
||||
.unwrap_or_else(|| {
|
||||
if self.metrics.newline {
|
||||
self.as_ml_basic()
|
||||
} else {
|
||||
self.as_basic()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn as_literal(&self) -> Option<TomlString<'s>> {
|
||||
if self.metrics.escape_codes
|
||||
|| 0 < self.metrics.max_seq_single_quotes
|
||||
|| self.metrics.newline
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(TomlString {
|
||||
decoded: self.decoded,
|
||||
encoding: Encoding::LiteralString,
|
||||
newline: self.metrics.newline,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ml_literal(&self) -> Option<TomlString<'s>> {
|
||||
if self.metrics.escape_codes || 2 < self.metrics.max_seq_single_quotes {
|
||||
None
|
||||
} else {
|
||||
Some(TomlString {
|
||||
decoded: self.decoded,
|
||||
encoding: Encoding::MlLiteralString,
|
||||
newline: self.metrics.newline,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_basic_pretty(&self) -> Option<TomlString<'s>> {
|
||||
if self.metrics.escape_codes
|
||||
|| self.metrics.escape
|
||||
|| 0 < self.metrics.max_seq_double_quotes
|
||||
|| self.metrics.newline
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(self.as_basic())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ml_basic_pretty(&self) -> Option<TomlString<'s>> {
|
||||
if self.metrics.escape_codes
|
||||
|| self.metrics.escape
|
||||
|| 2 < self.metrics.max_seq_double_quotes
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(self.as_ml_basic())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_basic(&self) -> TomlString<'s> {
|
||||
TomlString {
|
||||
decoded: self.decoded,
|
||||
encoding: Encoding::BasicString,
|
||||
newline: self.metrics.newline,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ml_basic(&self) -> TomlString<'s> {
|
||||
TomlString {
|
||||
decoded: self.decoded,
|
||||
encoding: Encoding::MlBasicString,
|
||||
newline: self.metrics.newline,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct TomlString<'s> {
|
||||
decoded: &'s str,
|
||||
encoding: Encoding,
|
||||
newline: bool,
|
||||
}
|
||||
|
||||
impl crate::WriteTomlValue for TomlString<'_> {
|
||||
fn write_toml_value<W: crate::TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write_toml_value(self.decoded, Some(self.encoding), self.newline, writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct TomlKeyBuilder<'s> {
|
||||
decoded: &'s str,
|
||||
metrics: KeyMetrics,
|
||||
}
|
||||
|
||||
impl<'s> TomlKeyBuilder<'s> {
|
||||
pub fn new(decoded: &'s str) -> Self {
|
||||
Self {
|
||||
decoded,
|
||||
metrics: KeyMetrics::calculate(decoded),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_default(&self) -> TomlKey<'s> {
|
||||
self.as_unquoted()
|
||||
.or_else(|| self.as_basic_pretty())
|
||||
.or_else(|| self.as_literal())
|
||||
.unwrap_or_else(|| self.as_basic())
|
||||
}
|
||||
|
||||
pub fn as_unquoted(&self) -> Option<TomlKey<'s>> {
|
||||
if self.metrics.unquoted {
|
||||
Some(TomlKey {
|
||||
decoded: self.decoded,
|
||||
encoding: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_literal(&self) -> Option<TomlKey<'s>> {
|
||||
if self.metrics.escape_codes || self.metrics.single_quotes {
|
||||
None
|
||||
} else {
|
||||
Some(TomlKey {
|
||||
decoded: self.decoded,
|
||||
encoding: Some(Encoding::LiteralString),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_basic_pretty(&self) -> Option<TomlKey<'s>> {
|
||||
if self.metrics.escape_codes || self.metrics.escape || self.metrics.double_quotes {
|
||||
None
|
||||
} else {
|
||||
Some(self.as_basic())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_basic(&self) -> TomlKey<'s> {
|
||||
TomlKey {
|
||||
decoded: self.decoded,
|
||||
encoding: Some(Encoding::BasicString),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct TomlKey<'s> {
|
||||
decoded: &'s str,
|
||||
encoding: Option<Encoding>,
|
||||
}
|
||||
|
||||
impl crate::WriteTomlKey for TomlKey<'_> {
|
||||
fn write_toml_key<W: crate::TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
let newline = false;
|
||||
write_toml_value(self.decoded, self.encoding, newline, writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
#[repr(u8)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum Encoding {
|
||||
LiteralString,
|
||||
BasicString,
|
||||
MlLiteralString,
|
||||
MlBasicString,
|
||||
}
|
||||
|
||||
impl Encoding {}
|
||||
|
||||
fn write_toml_value<W: crate::TomlWrite + ?Sized>(
|
||||
decoded: &str,
|
||||
encoding: Option<Encoding>,
|
||||
newline: bool,
|
||||
writer: &mut W,
|
||||
) -> core::fmt::Result {
|
||||
let delimiter = match encoding {
|
||||
Some(Encoding::LiteralString) => "'",
|
||||
Some(Encoding::BasicString) => "\"",
|
||||
Some(Encoding::MlLiteralString) => "'''",
|
||||
Some(Encoding::MlBasicString) => "\"\"\"",
|
||||
None => "",
|
||||
};
|
||||
let escaped = match encoding {
|
||||
Some(Encoding::LiteralString) | Some(Encoding::MlLiteralString) => false,
|
||||
Some(Encoding::BasicString) | Some(Encoding::MlBasicString) => true,
|
||||
None => false,
|
||||
};
|
||||
let is_ml = match encoding {
|
||||
Some(Encoding::LiteralString) | Some(Encoding::BasicString) => false,
|
||||
Some(Encoding::MlLiteralString) | Some(Encoding::MlBasicString) => true,
|
||||
None => false,
|
||||
};
|
||||
let newline_prefix = newline && is_ml;
|
||||
|
||||
write!(writer, "{delimiter}")?;
|
||||
if newline_prefix {
|
||||
writer.newline()?;
|
||||
}
|
||||
if escaped {
|
||||
// ```bnf
|
||||
// basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii
|
||||
// wschar = %x20 ; Space
|
||||
// wschar =/ %x09 ; Horizontal tab
|
||||
// escape = %x5C ; \
|
||||
// ```
|
||||
let max_seq_double_quotes = if is_ml { 2 } else { 0 };
|
||||
let mut stream = decoded;
|
||||
while !stream.is_empty() {
|
||||
let mut unescaped_end = 0;
|
||||
let mut escaped = None;
|
||||
let mut seq_double_quotes = 0;
|
||||
for (i, b) in stream.as_bytes().iter().enumerate() {
|
||||
if *b == b'"' {
|
||||
seq_double_quotes += 1;
|
||||
if max_seq_double_quotes < seq_double_quotes {
|
||||
escaped = Some(r#"\""#);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
seq_double_quotes = 0;
|
||||
}
|
||||
|
||||
match *b {
|
||||
0x8 => {
|
||||
escaped = Some(r#"\b"#);
|
||||
break;
|
||||
}
|
||||
0x9 => {
|
||||
escaped = Some(r#"\t"#);
|
||||
break;
|
||||
}
|
||||
0xa => {
|
||||
if !is_ml {
|
||||
escaped = Some(r#"\n"#);
|
||||
break;
|
||||
}
|
||||
}
|
||||
0xc => {
|
||||
escaped = Some(r#"\f"#);
|
||||
break;
|
||||
}
|
||||
0xd => {
|
||||
escaped = Some(r#"\r"#);
|
||||
break;
|
||||
}
|
||||
0x22 => {} // double quote handled earlier
|
||||
0x5c => {
|
||||
escaped = Some(r#"\\"#);
|
||||
break;
|
||||
}
|
||||
c if c <= 0x1f || c == 0x7f => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
unescaped_end = i + 1;
|
||||
}
|
||||
let unescaped = &stream[0..unescaped_end];
|
||||
let escaped_str = escaped.unwrap_or("");
|
||||
let end = unescaped_end + if escaped.is_some() { 1 } else { 0 };
|
||||
stream = &stream[end..];
|
||||
write!(writer, "{unescaped}{escaped_str}")?;
|
||||
if escaped.is_none() && !stream.is_empty() {
|
||||
let b = stream.as_bytes().first().unwrap();
|
||||
write!(writer, "\\u{:04X}", *b as u32)?;
|
||||
stream = &stream[1..];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
write!(writer, "{decoded}")?;
|
||||
}
|
||||
write!(writer, "{delimiter}")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct ValueMetrics {
|
||||
max_seq_single_quotes: u8,
|
||||
max_seq_double_quotes: u8,
|
||||
escape_codes: bool,
|
||||
escape: bool,
|
||||
newline: bool,
|
||||
}
|
||||
|
||||
impl ValueMetrics {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
max_seq_single_quotes: 0,
|
||||
max_seq_double_quotes: 0,
|
||||
escape_codes: false,
|
||||
escape: false,
|
||||
newline: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate(s: &str) -> Self {
|
||||
let mut metrics = Self::new();
|
||||
|
||||
let mut prev_single_quotes = 0;
|
||||
let mut prev_double_quotes = 0;
|
||||
for byte in s.as_bytes() {
|
||||
if *byte == b'\'' {
|
||||
prev_single_quotes += 1;
|
||||
metrics.max_seq_single_quotes =
|
||||
metrics.max_seq_single_quotes.max(prev_single_quotes);
|
||||
} else {
|
||||
prev_single_quotes = 0;
|
||||
}
|
||||
if *byte == b'"' {
|
||||
prev_double_quotes += 1;
|
||||
metrics.max_seq_double_quotes =
|
||||
metrics.max_seq_double_quotes.max(prev_double_quotes);
|
||||
} else {
|
||||
prev_double_quotes = 0;
|
||||
}
|
||||
|
||||
// ```bnf
|
||||
// literal-char = %x09 / %x20-26 / %x28-7E / non-ascii
|
||||
//
|
||||
// basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii
|
||||
// wschar = %x20 ; Space
|
||||
// wschar =/ %x09 ; Horizontal tab
|
||||
// escape = %x5C ; \
|
||||
// ```
|
||||
match *byte {
|
||||
b'\\' => metrics.escape = true,
|
||||
// Escape codes are needed if any ascii control
|
||||
// characters are present, including \b \f \r.
|
||||
b'\t' => {} // always allowed; remaining neutral on this
|
||||
b'\n' => metrics.newline = true,
|
||||
c if c <= 0x1f || c == 0x7f => metrics.escape_codes = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
metrics
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct KeyMetrics {
|
||||
unquoted: bool,
|
||||
single_quotes: bool,
|
||||
double_quotes: bool,
|
||||
escape_codes: bool,
|
||||
escape: bool,
|
||||
}
|
||||
|
||||
impl KeyMetrics {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
unquoted: true,
|
||||
single_quotes: false,
|
||||
double_quotes: false,
|
||||
escape_codes: false,
|
||||
escape: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate(s: &str) -> Self {
|
||||
let mut metrics = Self::new();
|
||||
|
||||
metrics.unquoted = !s.is_empty();
|
||||
|
||||
for byte in s.as_bytes() {
|
||||
if !matches!(*byte, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_') {
|
||||
metrics.unquoted = false;
|
||||
}
|
||||
|
||||
// ```bnf
|
||||
// unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
|
||||
//
|
||||
// literal-char = %x09 / %x20-26 / %x28-7E / non-ascii
|
||||
//
|
||||
// basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii
|
||||
// wschar = %x20 ; Space
|
||||
// wschar =/ %x09 ; Horizontal tab
|
||||
// escape = %x5C ; \
|
||||
// ```
|
||||
match *byte {
|
||||
b'\'' => metrics.single_quotes = true,
|
||||
b'"' => metrics.double_quotes = true,
|
||||
b'\\' => metrics.escape = true,
|
||||
// Escape codes are needed if any ascii control
|
||||
// characters are present, including \b \f \r.
|
||||
b'\t' => {} // always allowed
|
||||
c if c <= 0x1f || c == 0x7f => metrics.escape_codes = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
metrics
|
||||
}
|
||||
}
|
||||
Vendored
+249
@@ -0,0 +1,249 @@
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::borrow::Cow;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::TomlWrite;
|
||||
use crate::WriteTomlKey;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub trait ToTomlValue {
|
||||
fn to_toml_value(&self) -> String;
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<T> ToTomlValue for T
|
||||
where
|
||||
T: WriteTomlValue + ?Sized,
|
||||
{
|
||||
fn to_toml_value(&self) -> String {
|
||||
let mut result = String::new();
|
||||
let _ = self.write_toml_value(&mut result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WriteTomlValue {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result;
|
||||
}
|
||||
|
||||
impl WriteTomlValue for bool {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for u8 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for i8 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for u16 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for i16 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for u32 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for i32 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for u64 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for i64 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for u128 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for i128 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for f32 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
match (self.is_sign_negative(), self.is_nan(), *self == 0.0) {
|
||||
(true, true, _) => write!(writer, "-nan"),
|
||||
(false, true, _) => write!(writer, "nan"),
|
||||
(true, false, true) => write!(writer, "-0.0"),
|
||||
(false, false, true) => write!(writer, "0.0"),
|
||||
(_, false, false) => {
|
||||
if self % 1.0 == 0.0 {
|
||||
write!(writer, "{self}.0")
|
||||
} else {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for f64 {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
match (self.is_sign_negative(), self.is_nan(), *self == 0.0) {
|
||||
(true, true, _) => write!(writer, "-nan"),
|
||||
(false, true, _) => write!(writer, "nan"),
|
||||
(true, false, true) => write!(writer, "-0.0"),
|
||||
(false, false, true) => write!(writer, "0.0"),
|
||||
(_, false, false) => {
|
||||
if self % 1.0 == 0.0 {
|
||||
write!(writer, "{self}.0")
|
||||
} else {
|
||||
write!(writer, "{self}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for char {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
let mut buf = [0; 4];
|
||||
let v = self.encode_utf8(&mut buf);
|
||||
v.write_toml_value(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTomlValue for str {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
crate::TomlStringBuilder::new(self)
|
||||
.as_default()
|
||||
.write_toml_value(writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl WriteTomlValue for String {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
self.as_str().write_toml_value(writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl WriteTomlValue for Cow<'_, str> {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
self.as_ref().write_toml_value(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: WriteTomlValue> WriteTomlValue for [V] {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
writer.open_array()?;
|
||||
let mut iter = self.iter();
|
||||
if let Some(v) = iter.next() {
|
||||
writer.value(v)?;
|
||||
}
|
||||
for v in iter {
|
||||
writer.val_sep()?;
|
||||
writer.space()?;
|
||||
writer.value(v)?;
|
||||
}
|
||||
writer.close_array()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: WriteTomlValue, const N: usize> WriteTomlValue for [V; N] {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
self.as_slice().write_toml_value(writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<V: WriteTomlValue> WriteTomlValue for Vec<V> {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
self.as_slice().write_toml_value(writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<K: WriteTomlKey, V: WriteTomlValue> WriteTomlValue for alloc::collections::BTreeMap<K, V> {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write_toml_inline_table(self.iter(), writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<K: WriteTomlKey, V: WriteTomlValue> WriteTomlValue for std::collections::HashMap<K, V> {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
write_toml_inline_table(self.iter(), writer)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_toml_inline_table<
|
||||
'i,
|
||||
I: Iterator<Item = (&'i K, &'i V)>,
|
||||
K: WriteTomlKey + 'i,
|
||||
V: WriteTomlValue + 'i,
|
||||
W: TomlWrite + ?Sized,
|
||||
>(
|
||||
mut iter: I,
|
||||
writer: &mut W,
|
||||
) -> core::fmt::Result {
|
||||
writer.open_inline_table()?;
|
||||
let mut trailing_space = false;
|
||||
if let Some((key, value)) = iter.next() {
|
||||
writer.space()?;
|
||||
writer.key(key)?;
|
||||
writer.space()?;
|
||||
writer.keyval_sep()?;
|
||||
writer.space()?;
|
||||
writer.value(value)?;
|
||||
trailing_space = true;
|
||||
}
|
||||
for (key, value) in iter {
|
||||
writer.val_sep()?;
|
||||
writer.space()?;
|
||||
writer.key(key)?;
|
||||
writer.space()?;
|
||||
writer.keyval_sep()?;
|
||||
writer.space()?;
|
||||
writer.value(value)?;
|
||||
}
|
||||
if trailing_space {
|
||||
writer.space()?;
|
||||
}
|
||||
writer.close_inline_table()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<V: WriteTomlValue + ?Sized> WriteTomlValue for &V {
|
||||
fn write_toml_value<W: TomlWrite + ?Sized>(&self, writer: &mut W) -> core::fmt::Result {
|
||||
(*self).write_toml_value(writer)
|
||||
}
|
||||
}
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
pub trait TomlWrite: core::fmt::Write {
|
||||
fn open_table_header(&mut self) -> core::fmt::Result {
|
||||
write!(self, "[")
|
||||
}
|
||||
fn close_table_header(&mut self) -> core::fmt::Result {
|
||||
write!(self, "]")
|
||||
}
|
||||
|
||||
fn open_array_of_tables_header(&mut self) -> core::fmt::Result {
|
||||
write!(self, "[[")
|
||||
}
|
||||
fn close_array_of_tables_header(&mut self) -> core::fmt::Result {
|
||||
write!(self, "]]")
|
||||
}
|
||||
|
||||
fn open_inline_table(&mut self) -> core::fmt::Result {
|
||||
write!(self, "{{")
|
||||
}
|
||||
fn close_inline_table(&mut self) -> core::fmt::Result {
|
||||
write!(self, "}}")
|
||||
}
|
||||
|
||||
fn open_array(&mut self) -> core::fmt::Result {
|
||||
write!(self, "[")
|
||||
}
|
||||
fn close_array(&mut self) -> core::fmt::Result {
|
||||
write!(self, "]")
|
||||
}
|
||||
|
||||
fn key_sep(&mut self) -> core::fmt::Result {
|
||||
write!(self, ".")
|
||||
}
|
||||
|
||||
fn keyval_sep(&mut self) -> core::fmt::Result {
|
||||
write!(self, "=")
|
||||
}
|
||||
|
||||
fn key(&mut self, value: impl crate::WriteTomlKey) -> core::fmt::Result {
|
||||
value.write_toml_key(self)
|
||||
}
|
||||
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// For floats, this preserves the sign bit for [`f32::NAN`] / [`f64::NAN`] for the sake of
|
||||
/// format-preserving editing.
|
||||
/// However, in most cases the sign bit is indeterminate and outputting signed NANs can be a
|
||||
/// cause of non-repeatable behavior.
|
||||
///
|
||||
/// For general serialization, you should discard the sign bit. For example:
|
||||
/// ```
|
||||
/// # let mut v = f64::NAN;
|
||||
/// if v.is_nan() {
|
||||
/// v = v.copysign(1.0);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// </div>
|
||||
fn value(&mut self, value: impl crate::WriteTomlValue) -> core::fmt::Result {
|
||||
value.write_toml_value(self)
|
||||
}
|
||||
|
||||
fn val_sep(&mut self) -> core::fmt::Result {
|
||||
write!(self, ",")
|
||||
}
|
||||
|
||||
fn space(&mut self) -> core::fmt::Result {
|
||||
write!(self, " ")
|
||||
}
|
||||
|
||||
fn open_comment(&mut self) -> core::fmt::Result {
|
||||
write!(self, "#")
|
||||
}
|
||||
|
||||
fn newline(&mut self) -> core::fmt::Result {
|
||||
writeln!(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> TomlWrite for W where W: core::fmt::Write {}
|
||||
Reference in new issue
Block a user