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:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1
View File
@@ -0,0 +1 @@
Tests are from https://github.com/BurntSushi/toml-test
+142
View File
@@ -0,0 +1,142 @@
use serde_json::Value;
macro_rules! bad {
($toml:expr, $msg:expr) => {
match basic_toml::from_str::<Value>($toml) {
Ok(s) => panic!("parsed to: {:#?}", s),
Err(e) => assert_eq!(e.to_string(), $msg),
}
};
}
#[test]
fn times() {
fn multi_bad(s: &str, msg: &str) {
bad!(s, msg);
bad!(&s.replace('T', " "), msg);
bad!(&s.replace('T', "t"), msg);
bad!(&s.replace('Z', "z"), msg);
}
multi_bad(
"foo = 1997-09-09T09:09:09Z",
"invalid number at line 1 column 7",
);
multi_bad(
"foo = 1997-09-09T09:09:09+09:09",
"invalid number at line 1 column 7",
);
multi_bad(
"foo = 1997-09-09T09:09:09-09:09",
"invalid number at line 1 column 7",
);
multi_bad(
"foo = 1997-09-09T09:09:09",
"invalid number at line 1 column 7",
);
multi_bad("foo = 1997-09-09", "invalid number at line 1 column 7");
bad!("foo = 1997-09-09 ", "invalid number at line 1 column 7");
bad!(
"foo = 1997-09-09 # comment",
"invalid number at line 1 column 7"
);
multi_bad("foo = 09:09:09", "invalid number at line 1 column 8");
multi_bad(
"foo = 1997-09-09T09:09:09.09Z",
"invalid number at line 1 column 7",
);
multi_bad(
"foo = 1997-09-09T09:09:09.09+09:09",
"invalid number at line 1 column 7",
);
multi_bad(
"foo = 1997-09-09T09:09:09.09-09:09",
"invalid number at line 1 column 7",
);
multi_bad(
"foo = 1997-09-09T09:09:09.09",
"invalid number at line 1 column 7",
);
multi_bad("foo = 09:09:09.09", "invalid number at line 1 column 8");
}
#[test]
fn bad_times() {
bad!("foo = 199-09-09", "invalid number at line 1 column 7");
bad!("foo = 199709-09", "invalid number at line 1 column 7");
bad!("foo = 1997-9-09", "invalid number at line 1 column 7");
bad!("foo = 1997-09-9", "invalid number at line 1 column 7");
bad!(
"foo = 1997-09-0909:09:09",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T09:09:09.",
"invalid number at line 1 column 7"
);
bad!(
"foo = T",
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
);
bad!(
"foo = T.",
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
);
bad!(
"foo = TZ",
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
);
bad!(
"foo = 1997-09-09T09:09:09.09+",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T09:09:09.09+09",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T09:09:09.09+09:9",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T09:09:09.09+0909",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T09:09:09.09-",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T09:09:09.09-09",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T09:09:09.09-09:9",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T09:09:09.09-0909",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-00-09T09:09:09.09Z",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-00T09:09:09.09Z",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T30:09:09.09Z",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T12:69:09.09Z",
"invalid number at line 1 column 7"
);
bad!(
"foo = 1997-09-09T12:09:69.09Z",
"invalid number at line 1 column 7"
);
}
+354
View File
@@ -0,0 +1,354 @@
#![allow(
clippy::elidable_lifetime_names,
clippy::needless_lifetimes,
clippy::too_many_lines
)]
use serde::{de, Deserialize};
use std::fmt;
macro_rules! bad {
($toml:expr, $ty:ty, $msg:expr) => {
match basic_toml::from_str::<$ty>($toml) {
Ok(s) => panic!("parsed to: {:#?}", s),
Err(e) => assert_eq!(e.to_string(), $msg),
}
};
}
#[derive(Debug, Deserialize, PartialEq)]
struct Parent<T> {
p_a: T,
p_b: Vec<Child<T>>,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
struct Child<T> {
c_a: T,
c_b: T,
}
#[derive(Debug, PartialEq)]
enum CasedString {
Lowercase(String),
Uppercase(String),
}
impl<'de> de::Deserialize<'de> for CasedString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct CasedStringVisitor;
impl<'de> de::Visitor<'de> for CasedStringVisitor {
type Value = CasedString;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if s.is_empty() {
Err(de::Error::invalid_length(0, &"a non-empty string"))
} else if s.chars().all(|x| x.is_ascii_lowercase()) {
Ok(CasedString::Lowercase(s.to_string()))
} else if s.chars().all(|x| x.is_ascii_uppercase()) {
Ok(CasedString::Uppercase(s.to_string()))
} else {
Err(de::Error::invalid_value(
de::Unexpected::Str(s),
&"all lowercase or all uppercase",
))
}
}
}
deserializer.deserialize_any(CasedStringVisitor)
}
}
#[test]
fn custom_errors() {
basic_toml::from_str::<Parent<CasedString>>(
"
p_a = 'a'
p_b = [{c_a = 'a', c_b = 'c'}]
",
)
.unwrap();
// Custom error at p_b value.
bad!(
"
p_a = ''
# ^
",
Parent<CasedString>,
"invalid length 0, expected a non-empty string for key `p_a` at line 2 column 19"
);
// Missing field in table.
bad!(
"
p_a = 'a'
# ^
",
Parent<CasedString>,
"missing field `p_b` at line 1 column 1"
);
// Invalid type in p_b.
bad!(
"
p_a = 'a'
p_b = 1
# ^
",
Parent<CasedString>,
"invalid type: integer `1`, expected a sequence for key `p_b` at line 3 column 19"
);
// Sub-table in Vec is missing a field.
bad!(
"
p_a = 'a'
p_b = [
{c_a = 'a'}
# ^
]
",
Parent<CasedString>,
"missing field `c_b` for key `p_b` at line 4 column 17"
);
// Sub-table in Vec has a field with a bad value.
bad!(
"
p_a = 'a'
p_b = [
{c_a = 'a', c_b = '*'}
# ^
]
",
Parent<CasedString>,
"invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b` at line 4 column 35"
);
// Sub-table in Vec is missing a field.
bad!(
"
p_a = 'a'
p_b = [
{c_a = 'a', c_b = 'b'},
{c_a = 'aa'}
# ^
]
",
Parent<CasedString>,
"missing field `c_b` for key `p_b` at line 5 column 17"
);
// Sub-table in the middle of a Vec is missing a field.
bad!(
"
p_a = 'a'
p_b = [
{c_a = 'a', c_b = 'b'},
{c_a = 'aa'},
# ^
{c_a = 'aaa', c_b = 'bbb'},
]
",
Parent<CasedString>,
"missing field `c_b` for key `p_b` at line 5 column 17"
);
// Sub-table in the middle of a Vec has a field with a bad value.
bad!(
"
p_a = 'a'
p_b = [
{c_a = 'a', c_b = 'b'},
{c_a = 'aa', c_b = 1},
# ^
{c_a = 'aaa', c_b = 'bbb'},
]
",
Parent<CasedString>,
"invalid type: integer `1`, expected a string for key `p_b` at line 5 column 36"
);
// Sub-table in the middle of a Vec has an extra field.
// FIXME: This location could be better.
bad!(
"
p_a = 'a'
p_b = [
{c_a = 'a', c_b = 'b'},
{c_a = 'aa', c_b = 'bb', c_d = 'd'},
# ^
{c_a = 'aaa', c_b = 'bbb'},
{c_a = 'aaaa', c_b = 'bbbb'},
]
",
Parent<CasedString>,
"unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 5 column 17"
);
// Sub-table in the middle of a Vec is missing a field.
// FIXME: This location is pretty off.
bad!(
"
p_a = 'a'
[[p_b]]
c_a = 'a'
c_b = 'b'
[[p_b]]
c_a = 'aa'
# c_b = 'bb' # <- missing field
[[p_b]]
c_a = 'aaa'
c_b = 'bbb'
[[p_b]]
# ^
c_a = 'aaaa'
c_b = 'bbbb'
",
Parent<CasedString>,
"missing field `c_b` for key `p_b` at line 12 column 13"
);
// Sub-table in the middle of a Vec has a field with a bad value.
bad!(
"
p_a = 'a'
[[p_b]]
c_a = 'a'
c_b = 'b'
[[p_b]]
c_a = 'aa'
c_b = '*'
# ^
[[p_b]]
c_a = 'aaa'
c_b = 'bbb'
",
Parent<CasedString>,
"invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b.c_b` at line 8 column 19"
);
// Sub-table in the middle of a Vec has an extra field.
// FIXME: This location is pretty off.
bad!(
"
p_a = 'a'
[[p_b]]
c_a = 'a'
c_b = 'b'
[[p_b]]
c_a = 'aa'
c_d = 'dd' # unknown field
[[p_b]]
c_a = 'aaa'
c_b = 'bbb'
[[p_b]]
# ^
c_a = 'aaaa'
c_b = 'bbbb'
",
Parent<CasedString>,
"unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 12 column 13"
);
}
#[test]
fn serde_derive_deserialize_errors() {
bad!(
"
p_a = ''
# ^
",
Parent<String>,
"missing field `p_b` at line 1 column 1"
);
bad!(
"
p_a = ''
p_b = [
{c_a = ''}
# ^
]
",
Parent<String>,
"missing field `c_b` for key `p_b` at line 4 column 17"
);
bad!(
"
p_a = ''
p_b = [
{c_a = '', c_b = 1}
# ^
]
",
Parent<String>,
"invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34"
);
// FIXME: This location could be better.
bad!(
"
p_a = ''
p_b = [
{c_a = '', c_b = '', c_d = ''},
# ^
]
",
Parent<String>,
"unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 4 column 17"
);
bad!(
"
p_a = 'a'
p_b = [
{c_a = '', c_b = 1, c_d = ''},
# ^
]
",
Parent<String>,
"invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34"
);
}
#[test]
fn error_handles_crlf() {
bad!(
"\r\n\
[t1]\r\n\
[t2]\r\n\
a = 1\r\n\
. = 2\r\n\
",
serde_json::Value,
"expected a table key, found a period at line 5 column 1"
);
// Should be the same as above.
bad!(
"\n\
[t1]\n\
[t2]\n\
a = 1\n\
. = 2\n\
",
serde_json::Value,
"expected a table key, found a period at line 5 column 1"
);
}
+53
View File
@@ -0,0 +1,53 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Recipe {
pub name: String,
pub description: Option<String>,
#[serde(default)]
pub modules: Vec<Modules>,
#[serde(default)]
pub packages: Vec<Packages>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Modules {
pub name: String,
pub version: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Packages {
pub name: String,
pub version: Option<String>,
}
#[test]
fn both_ends() {
let recipe_works = basic_toml::from_str::<Recipe>(
r#"
name = "testing"
description = "example"
modules = []
[[packages]]
name = "base"
"#,
)
.unwrap();
basic_toml::to_string(&recipe_works).unwrap();
let recipe_fails = basic_toml::from_str::<Recipe>(
r#"
name = "testing"
description = "example"
packages = []
[[modules]]
name = "base"
"#,
)
.unwrap();
let err = basic_toml::to_string(&recipe_fails).unwrap_err();
assert_eq!(err.to_string(), "values must be emitted before tables");
}
+30
View File
@@ -0,0 +1,30 @@
#![allow(clippy::wildcard_imports)]
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
struct Struct {
value: Enum,
}
#[derive(Debug, Deserialize, PartialEq)]
enum Enum {
Variant,
}
#[test]
fn unknown_variant() {
let error = basic_toml::from_str::<Struct>("value = \"NonExistent\"").unwrap_err();
assert_eq!(
error.to_string(),
"unknown variant `NonExistent`, expected `Variant` for key `value` at line 1 column 1"
);
}
#[test]
fn from_str() {
let s = basic_toml::from_str::<Struct>("value = \"Variant\"").unwrap();
assert_eq!(Enum::Variant, s.value);
}
+81
View File
@@ -0,0 +1,81 @@
#![allow(clippy::float_cmp)]
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[rustfmt::skip] // appears to be a bug in rustfmt to make this converge...
macro_rules! float_inf_tests {
($ty:ty) => {{
#[derive(Serialize, Deserialize)]
struct S {
sf1: $ty,
sf2: $ty,
sf3: $ty,
sf4: $ty,
sf5: $ty,
sf6: $ty,
sf7: $ty,
sf8: $ty,
}
let inf: S = basic_toml::from_str(
"
# infinity
sf1 = inf # positive infinity
sf2 = +inf # positive infinity
sf3 = -inf # negative infinity
# not a number
sf4 = nan # actual sNaN/qNaN encoding is implementation specific
sf5 = +nan # same as `nan`
sf6 = -nan # valid, actual encoding is implementation specific
# zero
sf7 = +0.0
sf8 = -0.0
",
)
.expect("Parse infinities.");
assert!(inf.sf1.is_infinite());
assert!(inf.sf1.is_sign_positive());
assert!(inf.sf2.is_infinite());
assert!(inf.sf2.is_sign_positive());
assert!(inf.sf3.is_infinite());
assert!(inf.sf3.is_sign_negative());
assert!(inf.sf4.is_nan());
assert!(inf.sf4.is_sign_positive());
assert!(inf.sf5.is_nan());
assert!(inf.sf5.is_sign_positive());
assert!(inf.sf6.is_nan());
assert!(inf.sf6.is_sign_negative()); // NOTE: but serializes to just `nan`
assert_eq!(inf.sf7, 0.0);
assert!(inf.sf7.is_sign_positive());
assert_eq!(inf.sf8, 0.0);
assert!(inf.sf8.is_sign_negative());
let s = basic_toml::to_string(&inf).unwrap();
assert_eq!(
s,
"\
sf1 = inf
sf2 = inf
sf3 = -inf
sf4 = nan
sf5 = nan
sf6 = nan
sf7 = 0.0
sf8 = -0.0
"
);
basic_toml::from_str::<Value>(&s).expect("roundtrip");
}};
}
#[test]
fn float_inf() {
float_inf_tests!(f32);
float_inf_tests!(f64);
}
+53
View File
@@ -0,0 +1,53 @@
use basic_toml::to_string;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
struct User {
pub name: String,
pub surname: String,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
struct Users {
pub user: Vec<User>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
struct TwoUsers {
pub user0: User,
pub user1: User,
}
#[test]
fn no_unnecessary_newlines_array() {
assert!(!to_string(&Users {
user: vec![
User {
name: "John".to_string(),
surname: "Doe".to_string(),
},
User {
name: "Jane".to_string(),
surname: "Dough".to_string(),
},
],
})
.unwrap()
.starts_with('\n'));
}
#[test]
fn no_unnecessary_newlines_table() {
assert!(!to_string(&TwoUsers {
user0: User {
name: "John".to_string(),
surname: "Doe".to_string(),
},
user1: User {
name: "Jane".to_string(),
surname: "Dough".to_string(),
},
})
.unwrap()
.starts_with('\n'));
}
@@ -0,0 +1,15 @@
{
"ints-and-floats": {
"type": "array",
"value": [
{
"type": "integer",
"value": "1"
},
{
"type": "float",
"value": "1.1"
}
]
}
}
+48
View File
@@ -0,0 +1,48 @@
use serde_json::Value;
macro_rules! bad {
($toml:expr, $msg:expr) => {
match basic_toml::from_str::<Value>($toml) {
Ok(s) => panic!("parsed to: {:#?}", s),
Err(e) => assert_eq!(e.to_string(), $msg),
}
};
}
#[test]
fn bad() {
bad!("a = 01", "invalid number at line 1 column 6");
bad!("a = 1__1", "invalid number at line 1 column 5");
bad!("a = 1_", "invalid number at line 1 column 5");
bad!("''", "expected an equals, found eof at line 1 column 3");
bad!("a = 9e99999", "invalid number at line 1 column 5");
bad!(
"a = \"\u{7f}\"",
"invalid character in string: `\\u{7f}` at line 1 column 6"
);
bad!(
"a = '\u{7f}'",
"invalid character in string: `\\u{7f}` at line 1 column 6"
);
bad!("a = -0x1", "invalid number at line 1 column 5");
bad!("a = 0x-1", "invalid number at line 1 column 7");
// Dotted keys.
bad!(
"a.b.c = 1
a.b = 2
",
"duplicate key: `b` for key `a` at line 2 column 12"
);
bad!(
"a = 1
a.b = 2",
"dotted key attempted to extend non-table type at line 1 column 5"
);
bad!(
"a = {k1 = 1, k1.name = \"joe\"}",
"dotted key attempted to extend non-table type at line 1 column 11"
);
}
+226
View File
@@ -0,0 +1,226 @@
use serde_json::Value;
macro_rules! bad {
($toml:expr, $msg:expr) => {
match basic_toml::from_str::<Value>($toml) {
Ok(s) => panic!("parsed to: {:#?}", s),
Err(e) => assert_eq!(e.to_string(), $msg),
}
};
}
macro_rules! test( ($name:ident, $s:expr, $msg:expr) => (
#[test]
fn $name() { bad!($s, $msg); }
) );
test!(
datetime_malformed_no_leads,
include_str!("invalid/datetime-malformed-no-leads.toml"),
"invalid number at line 1 column 12"
);
test!(
datetime_malformed_no_secs,
include_str!("invalid/datetime-malformed-no-secs.toml"),
"invalid number at line 1 column 11"
);
test!(
datetime_malformed_no_t,
include_str!("invalid/datetime-malformed-no-t.toml"),
"invalid number at line 1 column 8"
);
test!(
datetime_malformed_with_milli,
include_str!("invalid/datetime-malformed-with-milli.toml"),
"invalid number at line 1 column 14"
);
test!(
duplicate_key_table,
include_str!("invalid/duplicate-key-table.toml"),
"duplicate key: `type` for key `fruit` at line 4 column 8"
);
test!(
duplicate_keys,
include_str!("invalid/duplicate-keys.toml"),
"duplicate key: `dupe` at line 2 column 1"
);
test!(
duplicate_table,
include_str!("invalid/duplicate-table.toml"),
"redefinition of table `dependencies` for key `dependencies` at line 7 column 1"
);
test!(
duplicate_tables,
include_str!("invalid/duplicate-tables.toml"),
"redefinition of table `a` for key `a` at line 2 column 1"
);
test!(
empty_implicit_table,
include_str!("invalid/empty-implicit-table.toml"),
"expected a table key, found a period at line 1 column 10"
);
test!(
empty_table,
include_str!("invalid/empty-table.toml"),
"expected a table key, found a right bracket at line 1 column 2"
);
test!(
float_no_leading_zero,
include_str!("invalid/float-no-leading-zero.toml"),
"expected a value, found a period at line 1 column 10"
);
test!(
float_no_suffix,
include_str!("invalid/float-no-suffix.toml"),
"invalid number at line 1 column 5"
);
test!(
float_no_trailing_digits,
include_str!("invalid/float-no-trailing-digits.toml"),
"invalid number at line 1 column 12"
);
test!(
key_after_array,
include_str!("invalid/key-after-array.toml"),
"expected newline, found an identifier at line 1 column 14"
);
test!(
key_after_table,
include_str!("invalid/key-after-table.toml"),
"expected newline, found an identifier at line 1 column 11"
);
test!(
key_empty,
include_str!("invalid/key-empty.toml"),
"expected a table key, found an equals at line 1 column 2"
);
test!(
key_hash,
include_str!("invalid/key-hash.toml"),
"expected an equals, found a comment at line 1 column 2"
);
test!(
key_newline,
include_str!("invalid/key-newline.toml"),
"expected an equals, found a newline at line 1 column 2"
);
test!(
key_open_bracket,
include_str!("invalid/key-open-bracket.toml"),
"expected a right bracket, found an equals at line 1 column 6"
);
test!(
key_single_open_bracket,
include_str!("invalid/key-single-open-bracket.toml"),
"expected a table key, found eof at line 1 column 2"
);
test!(
key_space,
include_str!("invalid/key-space.toml"),
"expected an equals, found an identifier at line 1 column 3"
);
test!(
key_start_bracket,
include_str!("invalid/key-start-bracket.toml"),
"expected a right bracket, found an equals at line 2 column 6"
);
test!(
key_two_equals,
include_str!("invalid/key-two-equals.toml"),
"expected a value, found an equals at line 1 column 6"
);
test!(
string_bad_byte_escape,
include_str!("invalid/string-bad-byte-escape.toml"),
"invalid escape character in string: `x` at line 1 column 13"
);
test!(
string_bad_escape,
include_str!("invalid/string-bad-escape.toml"),
"invalid escape character in string: `a` at line 1 column 42"
);
test!(
string_bad_line_ending_escape,
include_str!("invalid/string-bad-line-ending-escape.toml"),
"invalid escape character in string: ` ` at line 2 column 79"
);
test!(
string_byte_escapes,
include_str!("invalid/string-byte-escapes.toml"),
"invalid escape character in string: `x` at line 1 column 12"
);
test!(
string_no_close,
include_str!("invalid/string-no-close.toml"),
"newline in string found at line 1 column 42"
);
test!(
table_array_implicit,
include_str!("invalid/table-array-implicit.toml"),
"table redefined as array for key `albums` at line 13 column 1"
);
test!(
table_array_malformed_bracket,
include_str!("invalid/table-array-malformed-bracket.toml"),
"expected a right bracket, found a newline at line 1 column 10"
);
test!(
table_array_malformed_empty,
include_str!("invalid/table-array-malformed-empty.toml"),
"expected a table key, found a right bracket at line 1 column 3"
);
test!(
table_empty,
include_str!("invalid/table-empty.toml"),
"expected a table key, found a right bracket at line 1 column 2"
);
test!(
table_nested_brackets_close,
include_str!("invalid/table-nested-brackets-close.toml"),
"expected newline, found an identifier at line 1 column 4"
);
test!(
table_nested_brackets_open,
include_str!("invalid/table-nested-brackets-open.toml"),
"expected a right bracket, found a left bracket at line 1 column 3"
);
test!(
table_whitespace,
include_str!("invalid/table-whitespace.toml"),
"expected a right bracket, found an identifier at line 1 column 10"
);
test!(
table_with_pound,
include_str!("invalid/table-with-pound.toml"),
"expected a right bracket, found a comment at line 1 column 5"
);
test!(
text_after_array_entries,
include_str!("invalid/text-after-array-entries.toml"),
"invalid TOML value, did you mean to use a quoted string? at line 2 column 46"
);
test!(
text_after_integer,
include_str!("invalid/text-after-integer.toml"),
"expected newline, found an identifier at line 1 column 13"
);
test!(
text_after_string,
include_str!("invalid/text-after-string.toml"),
"expected newline, found an identifier at line 1 column 41"
);
test!(
text_after_table,
include_str!("invalid/text-after-table.toml"),
"expected newline, found an identifier at line 1 column 9"
);
test!(
text_before_array_separator,
include_str!("invalid/text-before-array-separator.toml"),
"expected a right bracket, found an identifier at line 2 column 46"
);
test!(
text_in_array,
include_str!("invalid/text-in-array.toml"),
"invalid TOML value, did you mean to use a quoted string? at line 3 column 3"
);
@@ -0,0 +1 @@
no-leads = 1987-7-05T17:45:00Z
@@ -0,0 +1 @@
no-secs = 1987-07-05T17:45Z
@@ -0,0 +1 @@
no-t = 1987-07-0517:45:00Z
@@ -0,0 +1 @@
with-milli = 1987-07-5T17:45:00.12Z
@@ -0,0 +1,5 @@
[fruit]
type = "apple"
[fruit.type]
apple = "yes"
+2
View File
@@ -0,0 +1,2 @@
dupe = false
dupe = true
+8
View File
@@ -0,0 +1,8 @@
[dependencies.openssl-sys]
version = "0.5.2"
[dependencies]
libc = "0.1"
[dependencies]
bitflags = "0.1.1"
+2
View File
@@ -0,0 +1,2 @@
[a]
[a]
@@ -0,0 +1 @@
[naughty..naughty]
+1
View File
@@ -0,0 +1 @@
[]
@@ -0,0 +1,2 @@
answer = .12345
neganswer = -.12345
+1
View File
@@ -0,0 +1 @@
a = 1.2f
@@ -0,0 +1,2 @@
answer = 1.
neganswer = -1.
+1
View File
@@ -0,0 +1 @@
[[agencies]] owner = "S Cjelli"
+1
View File
@@ -0,0 +1 @@
[history] guard = "sleeping"
+1
View File
@@ -0,0 +1 @@
= 1
+1
View File
@@ -0,0 +1 @@
a# = 1
+2
View File
@@ -0,0 +1,2 @@
a
= 1
+1
View File
@@ -0,0 +1 @@
[abc = 1
@@ -0,0 +1 @@
[
+1
View File
@@ -0,0 +1 @@
a b = 1
@@ -0,0 +1,3 @@
[a]
[xyz = 5
[b]
+1
View File
@@ -0,0 +1 @@
key= = 1
@@ -0,0 +1 @@
naughty = "\xAg"
@@ -0,0 +1 @@
invalid-escape = "This string has a bad \a escape character."
@@ -0,0 +1,3 @@
invalid-escape = """\
This string has a non whitespace-character after the line ending escape. \ a
"""
@@ -0,0 +1 @@
answer = "\x33"
+1
View File
@@ -0,0 +1 @@
no-ending-quote = "One time, at band camp
@@ -0,0 +1,14 @@
# This test is a bit tricky. It should fail because the first use of
# `[[albums.songs]]` without first declaring `albums` implies that `albums`
# must be a table. The alternative would be quite weird. Namely, it wouldn't
# comply with the TOML spec: "Each double-bracketed sub-table will belong to
# the most *recently* defined table element *above* it."
#
# This is in contrast to the *valid* test, table-array-implicit where
# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared
# later. (Although, `[albums]` could be.)
[[albums.songs]]
name = "Glory Days"
[[albums]]
name = "Born in the USA"
@@ -0,0 +1,2 @@
[[albums]
name = "Born to Run"
@@ -0,0 +1,2 @@
[[]]
name = "Born to Run"
+1
View File
@@ -0,0 +1 @@
[]
@@ -0,0 +1,2 @@
[a]b]
zyx = 42
@@ -0,0 +1,2 @@
[a[b]
zyx = 42
+1
View File
@@ -0,0 +1 @@
[invalid key]
+2
View File
@@ -0,0 +1,2 @@
[key#group]
answer = 42
@@ -0,0 +1,4 @@
array = [
"Is there life after an array separator?", No
"Entry"
]
@@ -0,0 +1 @@
answer = 42 the ultimate answer?
@@ -0,0 +1 @@
string = "Is there life after strings?" No.
+1
View File
@@ -0,0 +1 @@
[error] this shouldn't be here
@@ -0,0 +1,4 @@
array = [
"Is there life before an array separator?" No,
"Entry"
]
+5
View File
@@ -0,0 +1,5 @@
array = [
"Entry 1",
I don't belong,
"Entry 2",
]
+687
View File
@@ -0,0 +1,687 @@
#![allow(clippy::let_underscore_untyped, clippy::uninlined_format_args)]
use serde_json::{json, Value};
macro_rules! bad {
($toml:expr, $msg:expr) => {
match basic_toml::from_str::<Value>($toml) {
Ok(s) => panic!("parsed to: {:#?}", s),
Err(e) => assert_eq!(e.to_string(), $msg),
}
};
}
#[test]
fn crlf() {
let toml = "\
[project]\r\n\
\r\n\
name = \"splay\"\r\n\
version = \"0.1.0\"\r\n\
authors = [\"alex@crichton.co\"]\r\n\
\r\n\
[[lib]]\r\n\
\r\n\
path = \"lib.rs\"\r\n\
name = \"splay\"\r\n\
description = \"\"\"\
A Rust implementation of a TAR file reader and writer. This library does not\r\n\
currently handle compression, but it is abstract over all I/O readers and\r\n\
writers. Additionally, great lengths are taken to ensure that the entire\r\n\
contents are never required to be entirely resident in memory all at once.\r\n\
\"\"\"\
";
basic_toml::from_str::<Value>(toml).unwrap();
}
#[test]
fn fun_with_strings() {
let toml = r#"
bar = "\U00000000"
key1 = "One\nTwo"
key2 = """One\nTwo"""
key3 = """
One
Two"""
key4 = "The quick brown fox jumps over the lazy dog."
key5 = """
The quick brown \
fox jumps over \
the lazy dog."""
key6 = """\
The quick brown \
fox jumps over \
the lazy dog.\
"""
# What you see is what you get.
winpath = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted = 'Tom "Dubs" Preston-Werner'
regex = '<\i\c*\s*>'
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
'''
"#;
let table: Value = basic_toml::from_str(toml).unwrap();
assert_eq!(table["bar"], json!("\0"));
assert_eq!(table["key1"], json!("One\nTwo"));
assert_eq!(table["key2"], json!("One\nTwo"));
assert_eq!(table["key3"], json!("One\nTwo"));
let msg = "The quick brown fox jumps over the lazy dog.";
assert_eq!(table["key4"], json!(msg));
assert_eq!(table["key5"], json!(msg));
assert_eq!(table["key6"], json!(msg));
assert_eq!(table["winpath"], json!(r"C:\Users\nodejs\templates"));
assert_eq!(table["winpath2"], json!(r"\\ServerX\admin$\system32\"));
assert_eq!(table["quoted"], json!(r#"Tom "Dubs" Preston-Werner"#));
assert_eq!(table["regex"], json!(r"<\i\c*\s*>"));
assert_eq!(table["regex2"], json!(r"I [dw]on't need \d{2} apples"));
assert_eq!(
table["lines"],
json!(
"The first newline is\n\
trimmed in raw strings.\n\
All other whitespace\n\
is preserved.\n"
)
);
}
#[test]
fn tables_in_arrays() {
let toml = "
[[foo]]
#…
[foo.bar]
#…
[[foo]] # ...
#…
[foo.bar]
#...
";
let table: Value = basic_toml::from_str(toml).unwrap();
table["foo"][0]["bar"].as_object().unwrap();
table["foo"][1]["bar"].as_object().unwrap();
}
#[test]
fn empty_table() {
let toml = "
[foo]";
let table: Value = basic_toml::from_str(toml).unwrap();
table["foo"].as_object().unwrap();
}
#[test]
fn fruit() {
let toml = r#"
[[fruit]]
name = "apple"
[fruit.physical]
color = "red"
shape = "round"
[[fruit.variety]]
name = "red delicious"
[[fruit.variety]]
name = "granny smith"
[[fruit]]
name = "banana"
[[fruit.variety]]
name = "plantain"
"#;
let table: Value = basic_toml::from_str(toml).unwrap();
assert_eq!(table["fruit"][0]["name"], json!("apple"));
assert_eq!(table["fruit"][0]["physical"]["color"], json!("red"));
assert_eq!(table["fruit"][0]["physical"]["shape"], json!("round"));
assert_eq!(
table["fruit"][0]["variety"][0]["name"],
json!("red delicious")
);
assert_eq!(
table["fruit"][0]["variety"][1]["name"],
json!("granny smith")
);
assert_eq!(table["fruit"][1]["name"], json!("banana"));
assert_eq!(table["fruit"][1]["variety"][0]["name"], json!("plantain"));
}
#[test]
fn stray_cr() {
bad!("\r", "unexpected character found: `\\r` at line 1 column 1");
bad!(
"a = [ \r ]",
"unexpected character found: `\\r` at line 1 column 7"
);
bad!(
"a = \"\"\"\r\"\"\"",
"invalid character in string: `\\r` at line 1 column 8"
);
bad!(
"a = \"\"\"\\ \r \"\"\"",
"invalid escape character in string: ` ` at line 1 column 9"
);
bad!(
"a = '''\r'''",
"invalid character in string: `\\r` at line 1 column 8"
);
bad!(
"a = '\r'",
"invalid character in string: `\\r` at line 1 column 6"
);
bad!(
"a = \"\r\"",
"invalid character in string: `\\r` at line 1 column 6"
);
}
#[test]
fn blank_literal_string() {
let table: Value = basic_toml::from_str("foo = ''").unwrap();
assert_eq!(table["foo"], json!(""));
}
#[test]
fn many_blank() {
let table: Value = basic_toml::from_str("foo = \"\"\"\n\n\n\"\"\"").unwrap();
assert_eq!(table["foo"], json!("\n\n"));
}
#[test]
fn literal_eats_crlf() {
let toml = "
foo = \"\"\"\\\r\n\"\"\"
bar = \"\"\"\\\r\n \r\n \r\n a\"\"\"
";
let table: Value = basic_toml::from_str(toml).unwrap();
assert_eq!(table["foo"], json!(""));
assert_eq!(table["bar"], json!("a"));
}
#[test]
fn string_no_newline() {
bad!("a = \"\n\"", "newline in string found at line 1 column 6");
bad!("a = '\n'", "newline in string found at line 1 column 6");
}
#[test]
fn bad_leading_zeros() {
bad!("a = 00", "invalid number at line 1 column 6");
bad!("a = -00", "invalid number at line 1 column 7");
bad!("a = +00", "invalid number at line 1 column 7");
bad!("a = 00.0", "invalid number at line 1 column 6");
bad!("a = -00.0", "invalid number at line 1 column 7");
bad!("a = +00.0", "invalid number at line 1 column 7");
bad!(
"a = 9223372036854775808",
"invalid number at line 1 column 5"
);
bad!(
"a = -9223372036854775809",
"invalid number at line 1 column 5"
);
}
#[test]
fn bad_floats() {
bad!("a = 0.", "invalid number at line 1 column 7");
bad!("a = 0.e", "invalid number at line 1 column 7");
bad!("a = 0.E", "invalid number at line 1 column 7");
bad!("a = 0.0E", "invalid number at line 1 column 5");
bad!("a = 0.0e", "invalid number at line 1 column 5");
bad!("a = 0.0e-", "invalid number at line 1 column 9");
bad!("a = 0.0e+", "invalid number at line 1 column 5");
}
#[test]
fn floats() {
macro_rules! t {
($actual:expr, $expected:expr) => {{
let f = format!("foo = {}", $actual);
println!("{}", f);
let a: Value = basic_toml::from_str(&f).unwrap();
assert_eq!(a["foo"], json!($expected));
}};
}
t!("1.0", 1.0);
t!("1.0e0", 1.0);
t!("1.0e+0", 1.0);
t!("1.0e-0", 1.0);
t!("1E-0", 1.0);
t!("1.001e-0", 1.001);
t!("2e10", 2e10);
t!("2e+10", 2e10);
t!("2e-10", 2e-10);
t!("2_0.0", 20.0);
t!("2_0.0_0e1_0", 20.0e10);
t!("2_0.1_0e1_0", 20.1e10);
}
#[test]
fn bare_key_names() {
let toml = "
foo = 3
foo_3 = 3
foo_-2--3--r23f--4-f2-4 = 3
_ = 3
- = 3
8 = 8
\"a\" = 3
\"!\" = 3
\"a^b\" = 3
\"\\\"\" = 3
\"character encoding\" = \"value\"
'ʎǝʞ' = \"value\"
";
let a: Value = basic_toml::from_str(toml).unwrap();
let _ = &a["foo"];
let _ = &a["-"];
let _ = &a["_"];
let _ = &a["8"];
let _ = &a["foo_3"];
let _ = &a["foo_-2--3--r23f--4-f2-4"];
let _ = &a["a"];
let _ = &a["!"];
let _ = &a["\""];
let _ = &a["character encoding"];
let _ = &a["ʎǝʞ"];
}
#[test]
fn bad_keys() {
bad!(
"key\n=3",
"expected an equals, found a newline at line 1 column 4"
);
bad!(
"key=\n3",
"expected a value, found a newline at line 1 column 5"
);
bad!(
"key|=3",
"unexpected character found: `|` at line 1 column 4"
);
bad!(
"=3",
"expected a table key, found an equals at line 1 column 1"
);
bad!(
"\"\"|=3",
"unexpected character found: `|` at line 1 column 3"
);
bad!("\"\n\"|=3", "newline in string found at line 1 column 2");
bad!(
"\"\r\"|=3",
"invalid character in string: `\\r` at line 1 column 2"
);
bad!(
"''''''=3",
"multiline strings are not allowed for key at line 1 column 1"
);
bad!(
"\"\"\"\"\"\"=3",
"multiline strings are not allowed for key at line 1 column 1"
);
bad!(
"'''key'''=3",
"multiline strings are not allowed for key at line 1 column 1"
);
bad!(
"\"\"\"key\"\"\"=3",
"multiline strings are not allowed for key at line 1 column 1"
);
}
#[test]
fn bad_table_names() {
bad!(
"[]",
"expected a table key, found a right bracket at line 1 column 2"
);
bad!(
"[.]",
"expected a table key, found a period at line 1 column 2"
);
bad!(
"[a.]",
"expected a table key, found a right bracket at line 1 column 4"
);
bad!("[!]", "unexpected character found: `!` at line 1 column 2");
bad!("[\"\n\"]", "newline in string found at line 1 column 3");
bad!(
"[a.b]\n[a.\"b\"]",
"redefinition of table `a.b` for key `a.b` at line 2 column 1"
);
bad!("[']", "unterminated string at line 1 column 2");
bad!("[''']", "unterminated string at line 1 column 2");
bad!(
"['''''']",
"multiline strings are not allowed for key at line 1 column 2"
);
bad!(
"['''foo''']",
"multiline strings are not allowed for key at line 1 column 2"
);
bad!(
"[\"\"\"bar\"\"\"]",
"multiline strings are not allowed for key at line 1 column 2"
);
bad!("['\n']", "newline in string found at line 1 column 3");
bad!("['\r\n']", "newline in string found at line 1 column 3");
}
#[test]
fn table_names() {
let toml = "
[a.\"b\"]
[\"f f\"]
[\"f.f\"]
[\"\\\"\"]
['a.a']
['\"\"']
";
let a: Value = basic_toml::from_str(toml).unwrap();
println!("{:?}", a);
let _ = &a["a"]["b"];
let _ = &a["f f"];
let _ = &a["f.f"];
let _ = &a["\""];
let _ = &a["\"\""];
}
#[test]
fn invalid_bare_numeral() {
bad!("4", "expected an equals, found eof at line 1 column 2");
}
#[test]
fn inline_tables() {
basic_toml::from_str::<Value>("a = {}").unwrap();
basic_toml::from_str::<Value>("a = {b=1}").unwrap();
basic_toml::from_str::<Value>("a = { b = 1 }").unwrap();
basic_toml::from_str::<Value>("a = {a=1,b=2}").unwrap();
basic_toml::from_str::<Value>("a = {a=1,b=2,c={}}").unwrap();
bad!(
"a = {a=1,}",
"expected a table key, found a right brace at line 1 column 10"
);
bad!(
"a = {,}",
"expected a table key, found a comma at line 1 column 6"
);
bad!(
"a = {a=1,a=1}",
"duplicate key: `a` for key `a` at line 1 column 10"
);
bad!(
"a = {\n}",
"expected a table key, found a newline at line 1 column 6"
);
bad!(
"a = {",
"expected a table key, found eof at line 1 column 6"
);
basic_toml::from_str::<Value>("a = {a=[\n]}").unwrap();
basic_toml::from_str::<Value>("a = {\"a\"=[\n]}").unwrap();
basic_toml::from_str::<Value>("a = [\n{},\n{},\n]").unwrap();
}
#[test]
fn number_underscores() {
macro_rules! t {
($actual:expr, $expected:expr) => {{
let f = format!("foo = {}", $actual);
let table: Value = basic_toml::from_str(&f).unwrap();
assert_eq!(table["foo"], json!($expected));
}};
}
t!("1_0", 10);
t!("1_0_0", 100);
t!("1_000", 1000);
t!("+1_000", 1000);
t!("-1_000", -1000);
}
#[test]
fn bad_underscores() {
bad!("foo = 0_", "invalid number at line 1 column 7");
bad!("foo = 0__0", "invalid number at line 1 column 7");
bad!(
"foo = __0",
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
);
bad!("foo = 1_0_", "invalid number at line 1 column 7");
}
#[test]
fn bad_unicode_codepoint() {
bad!(
"foo = \"\\uD800\"",
"invalid escape value: `55296` at line 1 column 9"
);
}
#[test]
fn bad_strings() {
bad!(
"foo = \"\\uxx\"",
"invalid hex escape character in string: `x` at line 1 column 10"
);
bad!(
"foo = \"\\u\"",
"invalid hex escape character in string: `\\\"` at line 1 column 10"
);
bad!("foo = \"\\", "unterminated string at line 1 column 7");
bad!("foo = '", "unterminated string at line 1 column 7");
}
#[test]
fn empty_string() {
let table: Value = basic_toml::from_str::<Value>("foo = \"\"").unwrap();
assert_eq!(table["foo"], json!(""));
}
#[test]
fn booleans() {
let table: Value = basic_toml::from_str("foo = true").unwrap();
assert_eq!(table["foo"], json!(true));
let table: Value = basic_toml::from_str("foo = false").unwrap();
assert_eq!(table["foo"], json!(false));
bad!(
"foo = true2",
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
);
bad!(
"foo = false2",
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
);
bad!(
"foo = t1",
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
);
bad!(
"foo = f2",
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
);
}
#[test]
fn bad_nesting() {
bad!(
"
a = [2]
[[a]]
b = 5
",
"duplicate key: `a` at line 3 column 11"
);
bad!(
"
a = 1
[a.b]
",
"duplicate key: `a` at line 3 column 10"
);
bad!(
"
a = []
[a.b]
",
"duplicate key: `a` at line 3 column 10"
);
bad!(
"
a = []
[[a.b]]
",
"duplicate key: `a` at line 3 column 11"
);
bad!(
"
[a]
b = { c = 2, d = {} }
[a.b]
c = 2
",
"duplicate key: `b` for key `a` at line 4 column 12"
);
}
#[test]
fn bad_table_redefine() {
bad!(
"
[a]
foo=\"bar\"
[a.b]
foo=\"bar\"
[a]
",
"redefinition of table `a` for key `a` at line 6 column 9"
);
bad!(
"
[a]
foo=\"bar\"
b = { foo = \"bar\" }
[a]
",
"redefinition of table `a` for key `a` at line 5 column 9"
);
bad!(
"
[a]
b = {}
[a.b]
",
"duplicate key: `b` for key `a` at line 4 column 12"
);
bad!(
"
[a]
b = {}
[a]
",
"redefinition of table `a` for key `a` at line 4 column 9"
);
}
#[test]
fn datetimes() {
bad!(
"foo = 2016-09-09T09:09:09Z",
"invalid number at line 1 column 7"
);
bad!(
"foo = 2016-09-09T09:09:09.1Z",
"invalid number at line 1 column 7"
);
bad!(
"foo = 2016-09-09T09:09:09.2+10:00",
"invalid number at line 1 column 7"
);
bad!(
"foo = 2016-09-09T09:09:09.123456789-02:00",
"invalid number at line 1 column 7"
);
bad!(
"foo = 2016-09-09T09:09:09.Z",
"invalid number at line 1 column 7"
);
bad!(
"foo = 2016-9-09T09:09:09Z",
"invalid number at line 1 column 7"
);
bad!(
"foo = 2016-09-09T09:09:09+2:00",
"invalid number at line 1 column 7"
);
bad!(
"foo = 2016-09-09T09:09:09-2:00",
"invalid number at line 1 column 7"
);
bad!(
"foo = 2016-09-09T09:09:09Z-2:00",
"invalid number at line 1 column 7"
);
}
#[test]
fn require_newline_after_value() {
bad!("0=0r=false", "invalid number at line 1 column 3");
bad!(
r#"
0=""o=""m=""r=""00="0"q="""0"""e="""0"""
"#,
"expected newline, found an identifier at line 2 column 5"
);
bad!(
r#"
[[0000l0]]
0="0"[[0000l0]]
0="0"[[0000l0]]
0="0"l="0"
"#,
"expected newline, found a left bracket at line 3 column 6"
);
bad!(
r#"
0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
"#,
"expected newline, found an identifier at line 2 column 6"
);
bad!(
"
0=0r0=0r=false
",
"invalid number at line 2 column 3"
);
bad!(
"
0=0r0=0r=falsefal=false
",
"invalid number at line 2 column 3"
);
}
+190
View File
@@ -0,0 +1,190 @@
#![allow(
clippy::elidable_lifetime_names,
clippy::let_underscore_untyped,
clippy::manual_range_contains,
clippy::needless_lifetimes,
clippy::needless_pass_by_value,
clippy::type_complexity
)]
#[path = "../src/tokens.rs"]
#[allow(dead_code)]
mod tokens;
use crate::tokens::{Error, Token, Tokenizer};
use std::borrow::Cow;
fn err(input: &str, err: Error) {
let mut t = Tokenizer::new(input);
let token = t.next().unwrap_err();
assert_eq!(token, err);
assert!(t.next().unwrap().is_none());
}
#[test]
fn literal_strings() {
fn t(input: &str, val: &str, multiline: bool) {
let mut t = Tokenizer::new(input);
let (_, token) = t.next().unwrap().unwrap();
assert_eq!(
token,
Token::String {
src: input,
val: Cow::Borrowed(val),
multiline,
}
);
assert!(t.next().unwrap().is_none());
}
t("''", "", false);
t("''''''", "", true);
t("'''\n'''", "", true);
t("'a'", "a", false);
t("'\"a'", "\"a", false);
t("''''a'''", "'a", true);
t("'''\n'a\n'''", "'a\n", true);
t("'''a\n'a\r\n'''", "a\n'a\n", true);
}
#[test]
fn basic_strings() {
fn t(input: &str, val: &str, multiline: bool) {
let mut t = Tokenizer::new(input);
let (_, token) = t.next().unwrap().unwrap();
assert_eq!(
token,
Token::String {
src: input,
val: Cow::Borrowed(val),
multiline,
}
);
assert!(t.next().unwrap().is_none());
}
t(r#""""#, "", false);
t(r#""""""""#, "", true);
t(r#""a""#, "a", false);
t(r#""""a""""#, "a", true);
t(r#""\t""#, "\t", false);
t(r#""\u0000""#, "\0", false);
t(r#""\U00000000""#, "\0", false);
t(r#""\U000A0000""#, "\u{A0000}", false);
t(r#""\\t""#, "\\t", false);
t("\"\t\"", "\t", false);
t("\"\"\"\n\t\"\"\"", "\t", true);
t("\"\"\"\\\n\"\"\"", "", true);
t(
"\"\"\"\\\n \t \t \\\r\n \t \n \t \r\n\"\"\"",
"",
true,
);
t(r#""\r""#, "\r", false);
t(r#""\n""#, "\n", false);
t(r#""\b""#, "\u{8}", false);
t(r#""a\fa""#, "a\u{c}a", false);
t(r#""\"a""#, "\"a", false);
t("\"\"\"\na\"\"\"", "a", true);
t("\"\"\"\n\"\"\"", "", true);
t(r#""""a\"""b""""#, "a\"\"\"b", true);
err(r#""\a"#, Error::InvalidEscape(2, 'a'));
err("\"\\\n", Error::InvalidEscape(2, '\n'));
err("\"\\\r\n", Error::InvalidEscape(2, '\n'));
err("\"\\", Error::UnterminatedString(0));
err("\"\u{0}", Error::InvalidCharInString(1, '\u{0}'));
err(r#""\U00""#, Error::InvalidHexEscape(5, '"'));
err(r#""\U00"#, Error::UnterminatedString(0));
err(r#""\uD800"#, Error::InvalidEscapeValue(2, 0xd800));
err(r#""\UFFFFFFFF"#, Error::InvalidEscapeValue(2, 0xffff_ffff));
}
#[test]
fn keylike() {
fn t(input: &str) {
let mut t = Tokenizer::new(input);
let (_, token) = t.next().unwrap().unwrap();
assert_eq!(token, Token::Keylike(input));
assert!(t.next().unwrap().is_none());
}
t("foo");
t("0bar");
t("bar0");
t("1234");
t("a-b");
t("a_B");
t("-_-");
t("___");
}
#[test]
fn all() {
fn t(input: &str, expected: &[((usize, usize), Token, &str)]) {
let mut tokens = Tokenizer::new(input);
let mut actual: Vec<((usize, usize), Token, &str)> = Vec::new();
while let Some((span, token)) = tokens.next().unwrap() {
actual.push((span.into(), token, &input[span.start..span.end]));
}
for (a, b) in actual.iter().zip(expected) {
assert_eq!(a, b);
}
assert_eq!(actual.len(), expected.len());
}
t(
" a ",
&[
((0, 1), Token::Whitespace(" "), " "),
((1, 2), Token::Keylike("a"), "a"),
((2, 3), Token::Whitespace(" "), " "),
],
);
t(
" a\t [[]] \t [] {} , . =\n# foo \r\n#foo \n ",
&[
((0, 1), Token::Whitespace(" "), " "),
((1, 2), Token::Keylike("a"), "a"),
((2, 4), Token::Whitespace("\t "), "\t "),
((4, 5), Token::LeftBracket, "["),
((5, 6), Token::LeftBracket, "["),
((6, 7), Token::RightBracket, "]"),
((7, 8), Token::RightBracket, "]"),
((8, 11), Token::Whitespace(" \t "), " \t "),
((11, 12), Token::LeftBracket, "["),
((12, 13), Token::RightBracket, "]"),
((13, 14), Token::Whitespace(" "), " "),
((14, 15), Token::LeftBrace, "{"),
((15, 16), Token::RightBrace, "}"),
((16, 17), Token::Whitespace(" "), " "),
((17, 18), Token::Comma, ","),
((18, 19), Token::Whitespace(" "), " "),
((19, 20), Token::Period, "."),
((20, 21), Token::Whitespace(" "), " "),
((21, 22), Token::Equals, "="),
((22, 23), Token::Newline, "\n"),
((23, 29), Token::Comment("# foo "), "# foo "),
((29, 31), Token::Newline, "\r\n"),
((31, 36), Token::Comment("#foo "), "#foo "),
((36, 37), Token::Newline, "\n"),
((37, 38), Token::Whitespace(" "), " "),
],
);
}
#[test]
fn bare_cr_bad() {
err("\r", Error::Unexpected(0, '\r'));
err("'\n", Error::NewlineInString(1));
err("'\u{0}", Error::InvalidCharInString(1, '\u{0}'));
err("'", Error::UnterminatedString(0));
err("\u{0}", Error::Unexpected(0, '\u{0}'));
}
#[test]
fn bad_comment() {
let mut t = Tokenizer::new("#\u{0}");
t.next().unwrap().unwrap();
assert_eq!(t.next(), Err(Error::Unexpected(1, '\u{0}')));
assert!(t.next().unwrap().is_none());
}
+368
View File
@@ -0,0 +1,368 @@
#![allow(
clippy::match_like_matches_macro,
clippy::needless_pass_by_value,
clippy::uninlined_format_args
)]
use serde_json::{json, Value};
fn to_json(toml: Value) -> Value {
fn doit(s: &str, json: Value) -> Value {
json!({ "type": s, "value": json })
}
match toml {
Value::Null => unreachable!(),
Value::String(s) => doit("string", Value::String(s)),
Value::Number(n) => {
let repr = n.to_string();
if repr.contains('.') {
let float: f64 = repr.parse().unwrap();
let mut repr = format!("{:.15}", float);
repr.truncate(repr.trim_end_matches('0').len());
if repr.ends_with('.') {
repr.push('0');
}
doit("float", Value::String(repr))
} else {
doit("integer", Value::String(repr))
}
}
Value::Bool(b) => doit("bool", Value::String(format!("{}", b))),
Value::Array(arr) => {
let is_table = match arr.first() {
Some(&Value::Object(_)) => true,
_ => false,
};
let json = Value::Array(arr.into_iter().map(to_json).collect());
if is_table {
json
} else {
doit("array", json)
}
}
Value::Object(table) => {
let mut map = serde_json::Map::new();
for (k, v) in table {
map.insert(k, to_json(v));
}
Value::Object(map)
}
}
}
fn run(toml_raw: &str, json_raw: &str) {
println!("parsing:\n{}", toml_raw);
let toml: Value = basic_toml::from_str(toml_raw).unwrap();
let json: Value = serde_json::from_str(json_raw).unwrap();
// Assert toml == json
let toml_json = to_json(toml.clone());
assert!(
json == toml_json,
"expected\n{}\ngot\n{}\n",
serde_json::to_string_pretty(&json).unwrap(),
serde_json::to_string_pretty(&toml_json).unwrap()
);
// Assert round trip
println!("round trip parse: {}", toml);
let toml2: Value = basic_toml::from_str(&basic_toml::to_string(&toml).unwrap()).unwrap();
assert_eq!(toml, toml2);
}
macro_rules! test( ($name:ident, $toml:expr, $json:expr) => (
#[test]
fn $name() { run($toml, $json); }
) );
test!(
array_empty,
include_str!("valid/array-empty.toml"),
include_str!("valid/array-empty.json")
);
test!(
array_nospaces,
include_str!("valid/array-nospaces.toml"),
include_str!("valid/array-nospaces.json")
);
test!(
arrays_hetergeneous,
include_str!("valid/arrays-hetergeneous.toml"),
include_str!("valid/arrays-hetergeneous.json")
);
#[cfg(any())]
test!(
arrays,
include_str!("valid/arrays.toml"),
include_str!("valid/arrays.json")
);
test!(
arrays_nested,
include_str!("valid/arrays-nested.toml"),
include_str!("valid/arrays-nested.json")
);
test!(
array_mixed_types_ints_and_floats,
include_str!("valid/array-mixed-types-ints-and-floats.toml"),
include_str!("valid/array-mixed-types-ints-and-floats.json")
);
test!(
array_mixed_types_arrays_and_ints,
include_str!("valid/array-mixed-types-arrays-and-ints.toml"),
include_str!("valid/array-mixed-types-arrays-and-ints.json")
);
test!(
array_mixed_types_strings_and_ints,
include_str!("valid/array-mixed-types-strings-and-ints.toml"),
include_str!("valid/array-mixed-types-strings-and-ints.json")
);
test!(
empty,
include_str!("valid/empty.toml"),
include_str!("valid/empty.json")
);
test!(
bool,
include_str!("valid/bool.toml"),
include_str!("valid/bool.json")
);
test!(
comments_everywhere,
include_str!("valid/comments-everywhere.toml"),
include_str!("valid/comments-everywhere.json")
);
#[cfg(any())]
test!(
datetime,
include_str!("valid/datetime.toml"),
include_str!("valid/datetime.json")
);
#[cfg(any())]
test!(
example,
include_str!("valid/example.toml"),
include_str!("valid/example.json")
);
test!(
float,
include_str!("valid/float.toml"),
include_str!("valid/float.json")
);
#[cfg(any())]
test!(
implicit_and_explicit_after,
include_str!("valid/implicit-and-explicit-after.toml"),
include_str!("valid/implicit-and-explicit-after.json")
);
#[cfg(any())]
test!(
implicit_and_explicit_before,
include_str!("valid/implicit-and-explicit-before.toml"),
include_str!("valid/implicit-and-explicit-before.json")
);
test!(
implicit_groups,
include_str!("valid/implicit-groups.toml"),
include_str!("valid/implicit-groups.json")
);
test!(
integer,
include_str!("valid/integer.toml"),
include_str!("valid/integer.json")
);
test!(
key_equals_nospace,
include_str!("valid/key-equals-nospace.toml"),
include_str!("valid/key-equals-nospace.json")
);
test!(
key_space,
include_str!("valid/key-space.toml"),
include_str!("valid/key-space.json")
);
test!(
key_special_chars,
include_str!("valid/key-special-chars.toml"),
include_str!("valid/key-special-chars.json")
);
test!(
key_with_pound,
include_str!("valid/key-with-pound.toml"),
include_str!("valid/key-with-pound.json")
);
test!(
key_empty,
include_str!("valid/key-empty.toml"),
include_str!("valid/key-empty.json")
);
test!(
long_float,
include_str!("valid/long-float.toml"),
include_str!("valid/long-float.json")
);
test!(
long_integer,
include_str!("valid/long-integer.toml"),
include_str!("valid/long-integer.json")
);
test!(
multiline_string,
include_str!("valid/multiline-string.toml"),
include_str!("valid/multiline-string.json")
);
test!(
raw_multiline_string,
include_str!("valid/raw-multiline-string.toml"),
include_str!("valid/raw-multiline-string.json")
);
test!(
raw_string,
include_str!("valid/raw-string.toml"),
include_str!("valid/raw-string.json")
);
test!(
string_empty,
include_str!("valid/string-empty.toml"),
include_str!("valid/string-empty.json")
);
test!(
string_escapes,
include_str!("valid/string-escapes.toml"),
include_str!("valid/string-escapes.json")
);
test!(
string_simple,
include_str!("valid/string-simple.toml"),
include_str!("valid/string-simple.json")
);
test!(
string_with_pound,
include_str!("valid/string-with-pound.toml"),
include_str!("valid/string-with-pound.json")
);
test!(
table_array_implicit,
include_str!("valid/table-array-implicit.toml"),
include_str!("valid/table-array-implicit.json")
);
test!(
table_array_many,
include_str!("valid/table-array-many.toml"),
include_str!("valid/table-array-many.json")
);
test!(
table_array_nest,
include_str!("valid/table-array-nest.toml"),
include_str!("valid/table-array-nest.json")
);
test!(
table_array_one,
include_str!("valid/table-array-one.toml"),
include_str!("valid/table-array-one.json")
);
test!(
table_empty,
include_str!("valid/table-empty.toml"),
include_str!("valid/table-empty.json")
);
test!(
table_sub_empty,
include_str!("valid/table-sub-empty.toml"),
include_str!("valid/table-sub-empty.json")
);
test!(
table_multi_empty,
include_str!("valid/table-multi-empty.toml"),
include_str!("valid/table-multi-empty.json")
);
test!(
table_whitespace,
include_str!("valid/table-whitespace.toml"),
include_str!("valid/table-whitespace.json")
);
test!(
table_with_pound,
include_str!("valid/table-with-pound.toml"),
include_str!("valid/table-with-pound.json")
);
test!(
unicode_escape,
include_str!("valid/unicode-escape.toml"),
include_str!("valid/unicode-escape.json")
);
test!(
unicode_literal,
include_str!("valid/unicode-literal.toml"),
include_str!("valid/unicode-literal.json")
);
#[cfg(any())]
test!(
hard_example,
include_str!("valid/hard_example.toml"),
include_str!("valid/hard_example.json")
);
#[cfg(any())]
test!(
example2,
include_str!("valid/example2.toml"),
include_str!("valid/example2.json")
);
#[cfg(any())]
test!(
example3,
include_str!("valid/example-v0.3.0.toml"),
include_str!("valid/example-v0.3.0.json")
);
#[cfg(any())]
test!(
example4,
include_str!("valid/example-v0.4.0.toml"),
include_str!("valid/example-v0.4.0.json")
);
#[cfg(any())]
test!(
example_bom,
include_str!("valid/example-bom.toml"),
include_str!("valid/example.json")
);
#[cfg(any())]
test!(
datetime_truncate,
include_str!("valid/datetime-truncate.toml"),
include_str!("valid/datetime-truncate.json")
);
test!(
key_quote_newline,
include_str!("valid/key-quote-newline.toml"),
include_str!("valid/key-quote-newline.json")
);
test!(
table_array_nest_no_keys,
include_str!("valid/table-array-nest-no-keys.toml"),
include_str!("valid/table-array-nest-no-keys.json")
);
test!(
dotted_keys,
include_str!("valid/dotted-keys.toml"),
include_str!("valid/dotted-keys.json")
);
test!(
quote_surrounded_value,
include_str!("valid/quote-surrounded-value.toml"),
include_str!("valid/quote-surrounded-value.json")
);
test!(
float_exponent,
include_str!("valid/float-exponent.toml"),
include_str!("valid/float-exponent.json")
);
test!(
string_delim_end,
include_str!("valid/string-delim-end.toml"),
include_str!("valid/string-delim-end.json")
);
+11
View File
@@ -0,0 +1,11 @@
{
"thevoid": { "type": "array", "value": [
{"type": "array", "value": [
{"type": "array", "value": [
{"type": "array", "value": [
{"type": "array", "value": []}
]}
]}
]}
]}
}
+1
View File
@@ -0,0 +1 @@
thevoid = [[[[[]]]]]
@@ -0,0 +1,11 @@
{
"arrays-and-ints": {
"type": "array",
"value": [
{"type": "integer", "value": "1"},
{"type": "array", "value": [
{ "type": "string", "value":"Arrays are not integers."}
]}
]
}
}
@@ -0,0 +1 @@
arrays-and-ints = [1, ["Arrays are not integers."]]
@@ -0,0 +1,9 @@
{
"ints-and-floats": {
"type": "array",
"value": [
{"type": "integer", "value": "1"},
{"type": "float", "value": "1.1"}
]
}
}
@@ -0,0 +1 @@
ints-and-floats = [1, 1.1]
@@ -0,0 +1,9 @@
{
"strings-and-ints": {
"type": "array",
"value": [
{"type": "string", "value": "hi"},
{"type": "integer", "value": "42"}
]
}
}
@@ -0,0 +1 @@
strings-and-ints = ["hi", 42]
+10
View File
@@ -0,0 +1,10 @@
{
"ints": {
"type": "array",
"value": [
{"type": "integer", "value": "1"},
{"type": "integer", "value": "2"},
{"type": "integer", "value": "3"}
]
}
}
+1
View File
@@ -0,0 +1 @@
ints = [1,2,3]
+19
View File
@@ -0,0 +1,19 @@
{
"mixed": {
"type": "array",
"value": [
{"type": "array", "value": [
{"type": "integer", "value": "1"},
{"type": "integer", "value": "2"}
]},
{"type": "array", "value": [
{"type": "string", "value": "a"},
{"type": "string", "value": "b"}
]},
{"type": "array", "value": [
{"type": "float", "value": "1.1"},
{"type": "float", "value": "2.1"}
]}
]
}
}
@@ -0,0 +1 @@
mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]
+13
View File
@@ -0,0 +1,13 @@
{
"nest": {
"type": "array",
"value": [
{"type": "array", "value": [
{"type": "string", "value": "a"}
]},
{"type": "array", "value": [
{"type": "string", "value": "b"}
]}
]
}
}
+1
View File
@@ -0,0 +1 @@
nest = [["a"], ["b"]]
+34
View File
@@ -0,0 +1,34 @@
{
"ints": {
"type": "array",
"value": [
{"type": "integer", "value": "1"},
{"type": "integer", "value": "2"},
{"type": "integer", "value": "3"}
]
},
"floats": {
"type": "array",
"value": [
{"type": "float", "value": "1.1"},
{"type": "float", "value": "2.1"},
{"type": "float", "value": "3.1"}
]
},
"strings": {
"type": "array",
"value": [
{"type": "string", "value": "a"},
{"type": "string", "value": "b"},
{"type": "string", "value": "c"}
]
},
"dates": {
"type": "array",
"value": [
{"type": "datetime", "value": "1987-07-05T17:45:00Z"},
{"type": "datetime", "value": "1979-05-27T07:32:00Z"},
{"type": "datetime", "value": "2006-06-01T11:00:00Z"}
]
}
}
+8
View File
@@ -0,0 +1,8 @@
ints = [1, 2, 3]
floats = [1.1, 2.1, 3.1]
strings = ["a", "b", "c"]
dates = [
1987-07-05T17:45:00Z,
1979-05-27T07:32:00Z,
2006-06-01T11:00:00Z,
]
+4
View File
@@ -0,0 +1,4 @@
{
"f": {"type": "bool", "value": "false"},
"t": {"type": "bool", "value": "true"}
}
+2
View File
@@ -0,0 +1,2 @@
t = true
f = false
+12
View File
@@ -0,0 +1,12 @@
{
"group": {
"answer": {"type": "integer", "value": "42"},
"more": {
"type": "array",
"value": [
{"type": "integer", "value": "42"},
{"type": "integer", "value": "42"}
]
}
}
}
+24
View File
@@ -0,0 +1,24 @@
# Top comment.
# Top comment.
# Top comment.
# [no-extraneous-groups-please]
[group] # Comment
answer = 42 # Comment
# no-extraneous-keys-please = 999
# In between comment.
more = [ # Comment
# What about multiple # comments?
# Can you handle it?
#
# Evil.
# Evil.
42, 42, # Comments within arrays are fun.
# What about multiple # comments?
# Can you handle it?
#
# Evil.
# Evil.
# ] Did I fool you?
] # Hopefully not.
+6
View File
@@ -0,0 +1,6 @@
{
"bestdayever": {
"type": "datetime",
"value": "1987-07-05T17:45:00.123456789Z"
}
}
+1
View File
@@ -0,0 +1 @@
bestdayever = 1987-07-05T17:45:00.123456789012345Z
+3
View File
@@ -0,0 +1,3 @@
{
"bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}
}
+1
View File
@@ -0,0 +1 @@
bestdayever = 1987-07-05T17:45:00Z
+34
View File
@@ -0,0 +1,34 @@
{
"a": {
"b": {
"type": "integer",
"value": "123"
}
},
"table": {
"a": {
"b": {
"c": {
"type": "integer",
"value": "1"
},
"d": {
"type": "integer",
"value": "2"
}
}
},
"in": {
"type": {
"color": {
"type": "string",
"value": "blue"
},
"name": {
"type": "string",
"value": "cat"
}
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
a.b = 123
[table]
a.b.c = 1
a . b . d = 2
in = { type.name = "cat", type.color = "blue" }
+1
View File
@@ -0,0 +1 @@
{}
View File
Whitespace-only changes.
+5
View File
@@ -0,0 +1,5 @@
best-day-ever = 1987-07-05T17:45:00Z
[numtheory]
boring = false
perfection = [6, 28, 496]
+1
View File
@@ -0,0 +1 @@
{"Array":{"key1":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key2":{"type":"array","value":[{"type":"string","value":"red"},{"type":"string","value":"yellow"},{"type":"string","value":"green"}]},"key3":{"type":"array","value":[{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]},{"type":"array","value":[{"type":"integer","value":"3"},{"type":"integer","value":"4"},{"type":"integer","value":"5"}]}]},"key4":{"type":"array","value":[{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]},{"type":"array","value":[{"type":"string","value":"a"},{"type":"string","value":"b"},{"type":"string","value":"c"}]}]},"key5":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key6":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]}},"Booleans":{"False":{"type":"bool","value":"false"},"True":{"type":"bool","value":"true"}},"Datetime":{"key1":{"type":"datetime","value":"1979-05-27T07:32:00Z"}},"Float":{"both":{},"exponent":{},"fractional":{"key1":{"type":"float","value":"1.0"},"key2":{"type":"float","value":"3.1415"},"key3":{"type":"float","value":"-0.01"}}},"Integer":{"key1":{"type":"integer","value":"99"},"key2":{"type":"integer","value":"42"},"key3":{"type":"integer","value":"0"},"key4":{"type":"integer","value":"-17"}},"String":{"Literal":{"Multiline":{"lines":{"type":"string","value":"The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n"},"regex2":{"type":"string","value":"I [dw]on't need \\d{2} apples"}},"quoted":{"type":"string","value":"Tom \"Dubs\" Preston-Werner"},"regex":{"type":"string","value":"\u003c\\i\\c*\\s*\u003e"},"winpath":{"type":"string","value":"C:\\Users\\nodejs\\templates"},"winpath2":{"type":"string","value":"\\\\ServerX\\admin$\\system32\\"}},"Multiline":{"key1":{"type":"string","value":"One\nTwo"},"key2":{"type":"string","value":"One\nTwo"},"key3":{"type":"string","value":"One\nTwo"}},"Multilined":{"Singleline":{"key1":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key2":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key3":{"type":"string","value":"The quick brown fox jumps over the lazy dog."}}},"basic":{"type":"string","value":"I'm a string. \"You can quote me\". Name\u0009José\nLocation\u0009SF."}},"Table":{"key":{"type":"string","value":"value"}},"dog":{"tater":{"type":{"type":"string","value":"pug"}}},"fruit":[{"name":{"type":"string","value":"apple"},"physical":{"color":{"type":"string","value":"red"},"shape":{"type":"string","value":"round"}},"variety":[{"name":{"type":"string","value":"red delicious"}},{"name":{"type":"string","value":"granny smith"}}]},{"name":{"type":"string","value":"banana"},"variety":[{"name":{"type":"string","value":"plantain"}}]}],"products":[{"name":{"type":"string","value":"Hammer"},"sku":{"type":"integer","value":"738594937"}},{},{"color":{"type":"string","value":"gray"},"name":{"type":"string","value":"Nail"},"sku":{"type":"integer","value":"284758393"}}],"x":{"y":{"z":{"w":{}}}}}
+182
View File
@@ -0,0 +1,182 @@
# Comment
# I am a comment. Hear me roar. Roar.
# Table
# Tables (also known as hash tables or dictionaries) are collections of key/value pairs.
# They appear in square brackets on a line by themselves.
[Table]
key = "value" # Yeah, you can do this.
# Nested tables are denoted by table names with dots in them. Name your tables whatever crap you please, just don't use #, ., [ or ].
[dog.tater]
type = "pug"
# You don't need to specify all the super-tables if you don't want to. TOML knows how to do it for you.
# [x] you
# [x.y] don't
# [x.y.z] need these
[x.y.z.w] # for this to work
# String
# There are four ways to express strings: basic, multi-line basic, literal, and multi-line literal.
# All strings must contain only valid UTF-8 characters.
[String]
basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
[String.Multiline]
# The following strings are byte-for-byte equivalent:
key1 = "One\nTwo"
key2 = """One\nTwo"""
key3 = """
One
Two"""
[String.Multilined.Singleline]
# The following strings are byte-for-byte equivalent:
key1 = "The quick brown fox jumps over the lazy dog."
key2 = """
The quick brown \
fox jumps over \
the lazy dog."""
key3 = """\
The quick brown \
fox jumps over \
the lazy dog.\
"""
[String.Literal]
# What you see is what you get.
winpath = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted = 'Tom "Dubs" Preston-Werner'
regex = '<\i\c*\s*>'
[String.Literal.Multiline]
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
'''
# Integer
# Integers are whole numbers. Positive numbers may be prefixed with a plus sign.
# Negative numbers are prefixed with a minus sign.
[Integer]
key1 = +99
key2 = 42
key3 = 0
key4 = -17
# Float
# A float consists of an integer part (which may be prefixed with a plus or minus sign)
# followed by a fractional part and/or an exponent part.
[Float.fractional]
# fractional
key1 = +1.0
key2 = 3.1415
key3 = -0.01
[Float.exponent]
# exponent
#key1 = 5e+22
#key2 = 1e6
#key3 = -2E-2
[Float.both]
# both
#key = 6.626e-34
# Boolean
# Booleans are just the tokens you're used to. Always lowercase.
[Booleans]
True = true
False = false
# Datetime
# Datetimes are RFC 3339 dates.
[Datetime]
key1 = 1979-05-27T07:32:00Z
#key2 = 1979-05-27T00:32:00-07:00
#key3 = 1979-05-27T00:32:00.999999-07:00
# Array
# Arrays are square brackets with other primitives inside. Whitespace is ignored. Elements are separated by commas. Data types may not be mixed.
[Array]
key1 = [ 1, 2, 3 ]
key2 = [ "red", "yellow", "green" ]
key3 = [ [ 1, 2 ], [3, 4, 5] ]
key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok
#Arrays can also be multiline. So in addition to ignoring whitespace, arrays also ignore newlines between the brackets.
# Terminating commas are ok before the closing bracket.
key5 = [
1, 2, 3
]
key6 = [
1,
2, # this is ok
]
# Array of Tables
# These can be expressed by using a table name in double brackets.
# Each table with the same double bracketed name will be an element in the array.
# The tables are inserted in the order encountered.
[[products]]
name = "Hammer"
sku = 738594937
[[products]]
[[products]]
name = "Nail"
sku = 284758393
color = "gray"
# You can create nested arrays of tables as well.
[[fruit]]
name = "apple"
[fruit.physical]
color = "red"
shape = "round"
[[fruit.variety]]
name = "red delicious"
[[fruit.variety]]
name = "granny smith"
[[fruit]]
name = "banana"
[[fruit.variety]]
name = "plantain"
+1
View File
@@ -0,0 +1 @@
{"array":{"key1":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key2":{"type":"array","value":[{"type":"string","value":"red"},{"type":"string","value":"yellow"},{"type":"string","value":"green"}]},"key3":{"type":"array","value":[{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]},{"type":"array","value":[{"type":"integer","value":"3"},{"type":"integer","value":"4"},{"type":"integer","value":"5"}]}]},"key4":{"type":"array","value":[{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]},{"type":"array","value":[{"type":"string","value":"a"},{"type":"string","value":"b"},{"type":"string","value":"c"}]}]},"key5":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key6":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]}},"boolean":{"False":{"type":"bool","value":"false"},"True":{"type":"bool","value":"true"}},"datetime":{},"float":{"both":{},"exponent":{},"fractional":{"key1":{"type":"float","value":"1.0"},"key2":{"type":"float","value":"3.1415"},"key3":{"type":"float","value":"-0.01"}},"underscores":{}},"fruit":[{"name":{"type":"string","value":"apple"},"physical":{"color":{"type":"string","value":"red"},"shape":{"type":"string","value":"round"}},"variety":[{"name":{"type":"string","value":"red delicious"}},{"name":{"type":"string","value":"granny smith"}}]},{"name":{"type":"string","value":"banana"},"variety":[{"name":{"type":"string","value":"plantain"}}]}],"integer":{"key1":{"type":"integer","value":"99"},"key2":{"type":"integer","value":"42"},"key3":{"type":"integer","value":"0"},"key4":{"type":"integer","value":"-17"},"underscores":{"key1":{"type":"integer","value":"1000"},"key2":{"type":"integer","value":"5349221"},"key3":{"type":"integer","value":"12345"}}},"products":[{"name":{"type":"string","value":"Hammer"},"sku":{"type":"integer","value":"738594937"}},{},{"color":{"type":"string","value":"gray"},"name":{"type":"string","value":"Nail"},"sku":{"type":"integer","value":"284758393"}}],"string":{"basic":{"basic":{"type":"string","value":"I'm a string. \"You can quote me\". Name\u0009José\nLocation\u0009SF."}},"literal":{"multiline":{"lines":{"type":"string","value":"The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n"},"regex2":{"type":"string","value":"I [dw]on't need \\d{2} apples"}},"quoted":{"type":"string","value":"Tom \"Dubs\" Preston-Werner"},"regex":{"type":"string","value":"\u003c\\i\\c*\\s*\u003e"},"winpath":{"type":"string","value":"C:\\Users\\nodejs\\templates"},"winpath2":{"type":"string","value":"\\\\ServerX\\admin$\\system32\\"}},"multiline":{"continued":{"key1":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key2":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key3":{"type":"string","value":"The quick brown fox jumps over the lazy dog."}},"key1":{"type":"string","value":"One\nTwo"},"key2":{"type":"string","value":"One\nTwo"},"key3":{"type":"string","value":"One\nTwo"}}},"table":{"inline":{"name":{"first":{"type":"string","value":"Tom"},"last":{"type":"string","value":"Preston-Werner"}},"point":{"x":{"type":"integer","value":"1"},"y":{"type":"integer","value":"2"}}},"key":{"type":"string","value":"value"},"subtable":{"key":{"type":"string","value":"another value"}}},"x":{"y":{"z":{"w":{}}}}}
+236
View File
@@ -0,0 +1,236 @@
################################################################################
## Comment
# Speak your mind with the hash symbol. They go from the symbol to the end of
# the line.
################################################################################
## Table
# Tables (also known as hash tables or dictionaries) are collections of
# key/value pairs. They appear in square brackets on a line by themselves.
[table]
key = "value" # Yeah, you can do this.
# Nested tables are denoted by table names with dots in them. Name your tables
# whatever crap you please, just don't use #, ., [ or ].
[table.subtable]
key = "another value"
# You don't need to specify all the super-tables if you don't want to. TOML
# knows how to do it for you.
# [x] you
# [x.y] don't
# [x.y.z] need these
[x.y.z.w] # for this to work
################################################################################
## Inline Table
# Inline tables provide a more compact syntax for expressing tables. They are
# especially useful for grouped data that can otherwise quickly become verbose.
# Inline tables are enclosed in curly braces `{` and `}`. No newlines are
# allowed between the curly braces unless they are valid within a value.
[table.inline]
name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
################################################################################
## String
# There are four ways to express strings: basic, multi-line basic, literal, and
# multi-line literal. All strings must contain only valid UTF-8 characters.
[string.basic]
basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
[string.multiline]
# The following strings are byte-for-byte equivalent:
key1 = "One\nTwo"
key2 = """One\nTwo"""
key3 = """
One
Two"""
[string.multiline.continued]
# The following strings are byte-for-byte equivalent:
key1 = "The quick brown fox jumps over the lazy dog."
key2 = """
The quick brown \
fox jumps over \
the lazy dog."""
key3 = """\
The quick brown \
fox jumps over \
the lazy dog.\
"""
[string.literal]
# What you see is what you get.
winpath = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted = 'Tom "Dubs" Preston-Werner'
regex = '<\i\c*\s*>'
[string.literal.multiline]
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
'''
################################################################################
## Integer
# Integers are whole numbers. Positive numbers may be prefixed with a plus sign.
# Negative numbers are prefixed with a minus sign.
[integer]
key1 = +99
key2 = 42
key3 = 0
key4 = -17
[integer.underscores]
# For large numbers, you may use underscores to enhance readability. Each
# underscore must be surrounded by at least one digit.
key1 = 1_000
key2 = 5_349_221
key3 = 1_2_3_4_5 # valid but inadvisable
################################################################################
## Float
# A float consists of an integer part (which may be prefixed with a plus or
# minus sign) followed by a fractional part and/or an exponent part.
[float.fractional]
key1 = +1.0
key2 = 3.1415
key3 = -0.01
[float.exponent]
[float.both]
[float.underscores]
################################################################################
## Boolean
# Booleans are just the tokens you're used to. Always lowercase.
[boolean]
True = true
False = false
################################################################################
## Datetime
# Datetimes are RFC 3339 dates.
[datetime]
#key1 = 1979-05-27T07:32:00Z
#key2 = 1979-05-27T00:32:00-07:00
#key3 = 1979-05-27T00:32:00.999999-07:00
################################################################################
## Array
# Arrays are square brackets with other primitives inside. Whitespace is
# ignored. Elements are separated by commas. Since 2019-11-06 data types can be
# mixed.
[array]
key1 = [ 1, 2, 3 ]
key2 = [ "red", "yellow", "green" ]
key3 = [ [ 1, 2 ], [3, 4, 5] ]
key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok
# Arrays can also be multiline. So in addition to ignoring whitespace, arrays
# also ignore newlines between the brackets. Terminating commas are ok before
# the closing bracket.
key5 = [
1, 2, 3
]
key6 = [
1,
2, # this is ok
]
################################################################################
## Array of Tables
# These can be expressed by using a table name in double brackets. Each table
# with the same double bracketed name will be an element in the array. The
# tables are inserted in the order encountered.
[[products]]
name = "Hammer"
sku = 738594937
[[products]]
[[products]]
name = "Nail"
sku = 284758393
color = "gray"
# You can create nested arrays of tables as well.
[[fruit]]
name = "apple"
[fruit.physical]
color = "red"
shape = "round"
[[fruit.variety]]
name = "red delicious"
[[fruit.variety]]
name = "granny smith"
[[fruit]]
name = "banana"
[[fruit.variety]]
name = "plantain"
+14
View File
@@ -0,0 +1,14 @@
{
"best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"},
"numtheory": {
"boring": {"type": "bool", "value": "false"},
"perfection": {
"type": "array",
"value": [
{"type": "integer", "value": "6"},
{"type": "integer", "value": "28"},
{"type": "integer", "value": "496"}
]
}
}
}
+5
View File
@@ -0,0 +1,5 @@
best-day-ever = 1987-07-05T17:45:00Z
[numtheory]
boring = false
perfection = [6, 28, 496]
+1
View File
@@ -0,0 +1 @@
{"clients":{"data":{"type":"array","value":[{"type":"array","value":[{"type":"string","value":"gamma"},{"type":"string","value":"delta"}]},{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]}]},"hosts":{"type":"array","value":[{"type":"string","value":"alpha"},{"type":"string","value":"omega"}]}},"database":{"connection_max":{"type":"integer","value":"5000"},"enabled":{"type":"bool","value":"true"},"ports":{"type":"array","value":[{"type":"integer","value":"8001"},{"type":"integer","value":"8001"},{"type":"integer","value":"8002"}]},"server":{"type":"string","value":"192.168.1.1"}},"owner":{"bio":{"type":"string","value":"GitHub Cofounder \u0026 CEO\nLikes tater tots and beer."},"dob":{"type":"datetime","value":"1979-05-27T07:32:00Z"},"name":{"type":"string","value":"Tom Preston-Werner"},"organization":{"type":"string","value":"GitHub"}},"products":[{"name":{"type":"string","value":"Hammer"},"sku":{"type":"integer","value":"738594937"}},{"color":{"type":"string","value":"gray"},"name":{"type":"string","value":"Nail"},"sku":{"type":"integer","value":"284758393"}}],"servers":{"alpha":{"dc":{"type":"string","value":"eqdc10"},"ip":{"type":"string","value":"10.0.0.1"}},"beta":{"country":{"type":"string","value":"中国"},"dc":{"type":"string","value":"eqdc10"},"ip":{"type":"string","value":"10.0.0.2"}}},"title":{"type":"string","value":"TOML Example"}}
+47
View File
@@ -0,0 +1,47 @@
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
country = "中国" # This should be parsed as UTF-8
[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
]
# Products
[[products]]
name = "Hammer"
sku = 738594937
[[products]]
name = "Nail"
sku = 284758393
color = "gray"
+11
View File
@@ -0,0 +1,11 @@
{
"lower": {"type": "float", "value": "300.0"},
"upper": {"type": "float", "value": "300.0"},
"neg": {"type": "float", "value": "0.03"},
"pos": {"type": "float", "value": "300.0"},
"zero": {"type": "float", "value": "3.0"},
"pointlower": {"type": "float", "value": "310.0"},
"pointupper": {"type": "float", "value": "310.0"},
"prefix-zero-exp": {"type": "float", "value": "1000000.0"},
"prefix-zero-exp-plus": {"type": "float", "value": "1000000.0"}
}
+9
View File
@@ -0,0 +1,9 @@
lower = 3e2
upper = 3E2
neg = 3e-2
pos = 3E+2
zero = 3e0
pointlower = 3.1e2
pointupper = 3.1E2
prefix-zero-exp = 1e06
prefix-zero-exp-plus = 1e+06
+4
View File
@@ -0,0 +1,4 @@
{
"pi": {"type": "float", "value": "3.14"},
"negpi": {"type": "float", "value": "-3.14"}
}
+2
View File
@@ -0,0 +1,2 @@
pi = 3.14
negpi = -3.14
+1
View File
@@ -0,0 +1 @@
{"the":{"hard":{"another_test_string":{"type":"string","value":" Same thing, but with a string #"},"bit#":{"multi_line_array":{"type":"array","value":[{"type":"string","value":"]"}]},"what?":{"type":"string","value":"You don't think some user won't do that?"}},"harder_test_string":{"type":"string","value":" And when \"'s are in the string, along with # \""},"test_array":{"type":"array","value":[{"type":"string","value":"] "},{"type":"string","value":" # "}]},"test_array2":{"type":"array","value":[{"type":"string","value":"Test #11 ]proved that"},{"type":"string","value":"Experiment #9 was a success"}]}},"test_string":{"type":"string","value":"You'll hate me after this - #"}}}
+33
View File
@@ -0,0 +1,33 @@
# Test file for TOML
# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
# This part you'll really hate
[the]
test_string = "You'll hate me after this - #" # " Annoying, isn't it?
[the.hard]
test_array = [ "] ", " # "] # ] There you go, parse this!
test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
# You didn't think it'd as easy as chucking out the last #, did you?
another_test_string = " Same thing, but with a string #"
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
# Things will get harder
[the.hard."bit#"]
"what?" = "You don't think some user won't do that?"
multi_line_array = [
"]",
# ] Oh yes I did
]
# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test
#[error] if you didn't catch this, your parser is broken
#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this
#array = [
# "This might most likely happen in multiline arrays",
# Like here,
# "or here,
# and here"
# ] End of array comment, forgot the #
#number = 3.14 pi <--again forgot the #
@@ -0,0 +1,10 @@
{
"a": {
"better": {"type": "integer", "value": "43"},
"b": {
"c": {
"answer": {"type": "integer", "value": "42"}
}
}
}
}
@@ -0,0 +1,5 @@
[a.b.c]
answer = 42
[a]
better = 43
Loaded 100 of 17624 files, more files were not shown because too many files have changed in this diff. Show more