Skip to content

Commit

Permalink
✨ Add Value helpers and trait impls
Browse files Browse the repository at this point in the history
  • Loading branch information
CosmicHorrorDev committed Dec 6, 2024
1 parent ba5f9c7 commit 938747d
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 309 deletions.
42 changes: 14 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,34 +85,20 @@ assert_eq!(binary.path(), Some("src/bin/my-binary.rs"));
// Using the generic raw `TOML` parsing API:
//
let manifest = parse(CARGO_TOML).unwrap();
let package = match manifest.get("package").unwrap() {
Value::Table(package) => package,
_ => panic!(),
};
assert_eq!(package.get("name").unwrap(), &Value::String("example".into()));
assert_eq!(package.get("version").unwrap(), &Value::String("0.1.0".into()));
assert_eq!(package.get("edition").unwrap(), &Value::String("2021".into()));
assert_eq!(package.get("resolver").unwrap(), &Value::String("2".into()));

let deps = match manifest.get("dependencies").unwrap() {
Value::Table(deps) => deps,
_ => panic!(),
};
let serde = match deps.get("serde").unwrap() {
Value::Table(serde) => serde,
_ => panic!(),
};
assert_eq!(serde.get("version").unwrap(), &Value::String("1.0".into()));
let serde_features = match serde.get("features").unwrap() {
Value::Array(features) => features.as_slice(),
_ => panic!(),
};
assert_eq!(serde_features, &[Value::String("std".into()), Value::String("derive".into())]);
let regex = match deps.get("regex").unwrap() {
Value::String(regex) => regex,
_ => panic!(),
};
assert_eq!(&*regex, "1.5");
let package = manifest.get("package").unwrap().as_table().unwrap();
assert_eq!(package.get("name").unwrap().as_str().unwrap(), "example");
assert_eq!(package.get("version").unwrap().as_str().unwrap(), "0.1.0");
assert_eq!(package.get("edition").unwrap().as_str().unwrap(), "2021");
assert_eq!(package.get("resolver").unwrap().as_str().unwrap(), "2");

let deps = manifest.get("dependencies").unwrap().as_table().unwrap();
let serde = deps.get("serde").unwrap().as_table().unwrap();
assert_eq!(serde.get("version").unwrap().as_str().unwrap(), "1.0");
let serde_features =
serde.get("features").unwrap().as_array().unwrap().as_slice();
assert_eq!(serde_features, &[Value::from("std"), "derive".into()]);
let regex = deps.get("regex").unwrap().as_str().unwrap();
assert_eq!(regex, "1.5");

const CARGO_TOML: &'static str = r#"
[package]
Expand Down
30 changes: 12 additions & 18 deletions src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,10 @@ use winnow::{
/// Parse a TOML document.
pub fn parse(input: &str) -> Result<Table<'_>, Error> {
let key_value = parse_key_value.map(|(keys, value)| (None, keys, value));
let table_header = parse_table_header.map(|(header, is_array)| {
(
Some((header, is_array)),
Vec::new(),
Value::Table(Table::new()),
)
});
let whitespace = multispace1.map(|_| (None, Vec::new(), Value::Table(Table::new())));
let comment_line =
parse_comment_newline.map(|_| (None, Vec::new(), Value::Table(Table::new())));
let table_header = parse_table_header
.map(|(header, is_array)| (Some((header, is_array)), Vec::new(), Table::new().into()));
let whitespace = multispace1.map(|_| (None, Vec::new(), Table::new().into()));
let comment_line = parse_comment_newline.map(|_| (None, Vec::new(), Table::new().into()));
let line_parser = alt((table_header, key_value, whitespace, comment_line));

repeat(1.., line_parser)
Expand All @@ -39,11 +33,11 @@ pub fn parse(input: &str) -> Result<Table<'_>, Error> {
let key = header.last().expect("Header should not be empty").clone();
let entry = map
.entry(key.clone())
.or_insert_with(|| Value::Array(Array::new()));
.or_insert_with(|| Array::new().into());
if let Value::Array(array) = entry {
// Append a new empty table to the array
let new_table = Table::new();
array.push(Value::Table(new_table));
array.push(new_table.into());

// Update current_table to reference the new table
current_table = Some(vec![key]);
Expand Down Expand Up @@ -139,23 +133,23 @@ fn parse_value<'i>(input: &mut &'i str) -> PResult<Value<'i>, ContextError> {

/// Parses an integer value
fn parse_integer<'i>(input: &mut &'i str) -> PResult<Value<'i>, ContextError> {
numbers::integer(input).map(Value::Integer)
numbers::integer(input).map(Into::into)
}

/// Parses a float value
fn parse_float<'i>(input: &mut &'i str) -> PResult<Value<'i>, ContextError> {
numbers::float(input).map(Value::Float)
numbers::float(input).map(Into::into)
}

/// Parses a boolean value
fn parse_boolean<'i>(input: &mut &'i str) -> PResult<Value<'i>, ContextError> {
numbers::boolean(input).map(Value::Boolean)
numbers::boolean(input).map(Into::into)
}

/// Parses an array of values
fn parse_array<'i>(input: &mut &'i str) -> PResult<Value<'i>, ContextError> {
delimited('[', cut_err(parse_multiline_array_values), cut_err(']'))
.map(Value::Array)
.map(Into::into)
.parse_next(input)
}

Expand Down Expand Up @@ -188,7 +182,7 @@ fn parse_inline_table<'i>(input: &mut &'i str) -> PResult<Value<'i>, ContextErro
separated(0.., separated_pair(parse_key, '=', parse_value), ','),
'}',
)
.map(|pairs: Vec<(Cow<'i, str>, Value<'i>)>| Value::Table(pairs.into_iter().collect()))
.map(|pairs: Vec<(Cow<'i, str>, Value<'i>)>| pairs.into_iter().collect())
.parse_next(input)
}

Expand All @@ -200,7 +194,7 @@ fn insert_nested_key<'a>(map: &mut Table<'a>, keys: &[Cow<'a, str>], value: Valu
} else {
let entry = map
.entry(first.clone())
.or_insert_with(|| Value::Table(Table::new()));
.or_insert_with(|| Table::new().into());

if let Value::Table(ref mut nested_map) = entry {
insert_nested_key(nested_map, rest, value);
Expand Down
8 changes: 4 additions & 4 deletions src/parse/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ pub(crate) fn parse<'i>(input: &mut &'i str) -> PResult<Value<'i>, ContextError>
/// Parses a basic string value enclosed in quotes.
pub(crate) fn parse_basic<'i>(input: &mut &'i str) -> PResult<Value<'i>, ContextError> {
delimited('"', take_until(0.., '"'), '"')
.map(|s: &str| Value::String(s.into()))
.map(Into::into)
.parse_next(input)
}

/// Parses a literal string value enclosed in single quotes.
pub(crate) fn parse_literal<'i>(input: &mut &'i str) -> PResult<Value<'i>, ContextError> {
delimited('\'', take_until(0.., '\''), '\'')
.map(|s: &str| Value::String(s.into()))
.map(Into::into)
.parse_next(input)
}

Expand All @@ -44,7 +44,7 @@ pub(crate) fn parse_multiline_basic<'i>(input: &mut &'i str) -> PResult<Value<'i
}),
"\"\"\"",
)
.map(|s| Value::String(s.into()))
.map(Into::into)
.parse_next(input)
}

Expand All @@ -55,6 +55,6 @@ pub(crate) fn parse_multiline_literal<'i>(input: &mut &'i str) -> PResult<Value<
take_until(0.., "'''").map(|s: &str| s.trim_start_matches('\n')), // Trim leading newlines
"'''",
)
.map(|s| Value::String(s.into()))
.map(Into::into)
.parse_next(input)
}
121 changes: 121 additions & 0 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,124 @@ pub enum Value<'a> {
/// A table.
Table(Table<'a>),
}

impl<'a> Value<'a> {
/// Returns the underlying `&str` if the `Value` is a string
pub fn as_str(&'a self) -> Option<&'a str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}

/// Returns the underlying `i64` if the `Value` is an integer
pub fn as_i64(&self) -> Option<i64> {
match self {
&Self::Integer(i) => Some(i),
_ => None,
}
}

/// Returns the underlying `f64` if the `Value` is a float
pub fn as_f64(&self) -> Option<f64> {
match self {
&Self::Float(f) => Some(f),
_ => None,
}
}

/// Returns the underlying `f64` if the `Value` is a float
pub fn as_bool(&self) -> Option<bool> {
match self {
&Self::Boolean(b) => Some(b),
_ => None,
}
}

/// Returns the underlying [`Array`] if the `Value` is an array
pub fn as_array(&'a self) -> Option<&'a Array<'a>> {
match self {
Self::Array(a) => Some(a),
_ => None,
}
}

/// Returns the underlying [`Table`] if the `Value` is a table
pub fn as_table(&'a self) -> Option<&'a Table<'a>> {
match self {
Self::Table(t) => Some(t),
_ => None,
}
}
}

impl From<String> for Value<'_> {
fn from(s: String) -> Self {
Cow::Owned::<str>(s).into()
}
}

impl<'a> From<&'a str> for Value<'a> {
fn from(s: &'a str) -> Self {
Cow::Borrowed(s).into()
}
}

impl<'a> From<Cow<'a, str>> for Value<'a> {
fn from(cow: Cow<'a, str>) -> Self {
Self::String(cow)
}
}

impl From<i64> for Value<'_> {
fn from(i: i64) -> Self {
Self::Integer(i)
}
}

impl From<f64> for Value<'_> {
fn from(f: f64) -> Self {
Self::Float(f)
}
}

impl From<bool> for Value<'_> {
fn from(b: bool) -> Self {
Self::Boolean(b)
}
}

impl<'a> From<Array<'a>> for Value<'a> {
fn from(a: Array<'a>) -> Self {
Self::Array(a)
}
}

impl<'a> From<Table<'a>> for Value<'a> {
fn from(t: Table<'a>) -> Self {
Self::Table(t)
}
}

impl<'a, V> FromIterator<V> for Value<'a>
where
V: Into<Value<'a>>,
{
fn from_iter<I: IntoIterator<Item = V>>(iter: I) -> Self {
Value::Array(iter.into_iter().map(|v| v.into()).collect())
}
}

impl<'a, K, V> FromIterator<(K, V)> for Value<'a>
where
K: Into<Cow<'a, str>>,
V: Into<Value<'a>>,
{
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
Value::Table(
iter.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
)
}
}
Loading

0 comments on commit 938747d

Please # to comment.