451 lines
13 KiB
Rust
451 lines
13 KiB
Rust
use std::str::FromStr;
|
|
|
|
use toml_datetime::Datetime;
|
|
|
|
use crate::array_of_tables::ArrayOfTables;
|
|
use crate::table::TableLike;
|
|
use crate::{Array, InlineTable, Table, Value};
|
|
|
|
/// Type representing either a value, a table, an array of tables, or none.
|
|
#[derive(Debug, Default)]
|
|
pub enum Item {
|
|
/// Type representing none.
|
|
#[default]
|
|
None,
|
|
/// Type representing value.
|
|
Value(Value),
|
|
/// Type representing table.
|
|
Table(Table),
|
|
/// Type representing array of tables.
|
|
ArrayOfTables(ArrayOfTables),
|
|
}
|
|
|
|
impl Item {
|
|
/// Sets `self` to the given item if `self` is none and
|
|
/// returns a mutable reference to `self`.
|
|
pub fn or_insert(&mut self, item: Item) -> &mut Item {
|
|
if self.is_none() {
|
|
*self = item;
|
|
}
|
|
self
|
|
}
|
|
}
|
|
|
|
// TODO: This should be generated by macro or derive
|
|
/// Downcasting
|
|
impl Item {
|
|
/// Text description of value type
|
|
pub fn type_name(&self) -> &'static str {
|
|
match self {
|
|
Item::None => "none",
|
|
Item::Value(v) => v.type_name(),
|
|
Item::Table(..) => "table",
|
|
Item::ArrayOfTables(..) => "array of tables",
|
|
}
|
|
}
|
|
|
|
/// Index into a TOML array or map. A string index can be used to access a
|
|
/// value in a map, and a usize index can be used to access an element of an
|
|
/// array.
|
|
///
|
|
/// Returns `None` if:
|
|
/// - The type of `self` does not match the type of the
|
|
/// index, for example if the index is a string and `self` is an array or a
|
|
/// number.
|
|
/// - The given key does not exist in the map
|
|
/// or the given index is not within the bounds of the array.
|
|
pub fn get<I: crate::index::Index>(&self, index: I) -> Option<&Item> {
|
|
index.index(self)
|
|
}
|
|
|
|
/// Mutably index into a TOML array or map. A string index can be used to
|
|
/// access a value in a map, and a usize index can be used to access an
|
|
/// element of an array.
|
|
///
|
|
/// Returns `None` if:
|
|
/// - The type of `self` does not match the type of the
|
|
/// index, for example if the index is a string and `self` is an array or a
|
|
/// number.
|
|
/// - The given key does not exist in the map
|
|
/// or the given index is not within the bounds of the array.
|
|
pub fn get_mut<I: crate::index::Index>(&mut self, index: I) -> Option<&mut Item> {
|
|
index.index_mut(self)
|
|
}
|
|
|
|
/// Casts `self` to [`Value`]
|
|
pub fn as_value(&self) -> Option<&Value> {
|
|
match *self {
|
|
Item::Value(ref v) => Some(v),
|
|
_ => None,
|
|
}
|
|
}
|
|
/// Casts `self` to [`Table`]
|
|
///
|
|
/// <div class="warning">
|
|
///
|
|
/// To operate on both [`Table`]s and [`InlineTable`]s, see [`Item::as_table_like`]
|
|
///
|
|
/// </div>
|
|
pub fn as_table(&self) -> Option<&Table> {
|
|
match *self {
|
|
Item::Table(ref t) => Some(t),
|
|
_ => None,
|
|
}
|
|
}
|
|
/// Casts `self` to [`ArrayOfTables`]
|
|
pub fn as_array_of_tables(&self) -> Option<&ArrayOfTables> {
|
|
match *self {
|
|
Item::ArrayOfTables(ref a) => Some(a),
|
|
_ => None,
|
|
}
|
|
}
|
|
/// Casts `self` to mutable [`Value`].
|
|
pub fn as_value_mut(&mut self) -> Option<&mut Value> {
|
|
match *self {
|
|
Item::Value(ref mut v) => Some(v),
|
|
_ => None,
|
|
}
|
|
}
|
|
/// Casts `self` to mutable [`Table`]
|
|
///
|
|
/// <div class="warning">
|
|
///
|
|
/// To operate on both [`Table`]s and [`InlineTable`]s, see [`Item::as_table_like_mut`]
|
|
///
|
|
/// </div>
|
|
pub fn as_table_mut(&mut self) -> Option<&mut Table> {
|
|
match *self {
|
|
Item::Table(ref mut t) => Some(t),
|
|
_ => None,
|
|
}
|
|
}
|
|
/// Casts `self` to mutable [`ArrayOfTables`]
|
|
pub fn as_array_of_tables_mut(&mut self) -> Option<&mut ArrayOfTables> {
|
|
match *self {
|
|
Item::ArrayOfTables(ref mut a) => Some(a),
|
|
_ => None,
|
|
}
|
|
}
|
|
/// Casts `self` to [`Value`]
|
|
pub fn into_value(self) -> Result<Value, Self> {
|
|
match self {
|
|
Item::None => Err(self),
|
|
Item::Value(v) => Ok(v),
|
|
Item::Table(v) => {
|
|
let v = v.into_inline_table();
|
|
Ok(Value::InlineTable(v))
|
|
}
|
|
Item::ArrayOfTables(v) => {
|
|
let v = v.into_array();
|
|
Ok(Value::Array(v))
|
|
}
|
|
}
|
|
}
|
|
/// In-place convert to a value
|
|
pub fn make_value(&mut self) {
|
|
let other = std::mem::take(self);
|
|
let other = other.into_value().map(Item::Value).unwrap_or(Item::None);
|
|
*self = other;
|
|
}
|
|
/// Casts `self` to [`Table`]
|
|
///
|
|
/// <div class="warning">
|
|
///
|
|
/// This does not include [`InlineTable`]s
|
|
///
|
|
/// </div>
|
|
pub fn into_table(self) -> Result<Table, Self> {
|
|
match self {
|
|
Item::Table(t) => Ok(t),
|
|
Item::Value(Value::InlineTable(t)) => Ok(t.into_table()),
|
|
_ => Err(self),
|
|
}
|
|
}
|
|
/// Casts `self` to [`ArrayOfTables`]
|
|
pub fn into_array_of_tables(self) -> Result<ArrayOfTables, Self> {
|
|
match self {
|
|
Item::ArrayOfTables(a) => Ok(a),
|
|
Item::Value(Value::Array(a)) => {
|
|
if a.is_empty() {
|
|
Err(Item::Value(Value::Array(a)))
|
|
} else if a.iter().all(|v| v.is_inline_table()) {
|
|
let mut aot = ArrayOfTables::new();
|
|
aot.values = a.values;
|
|
for value in aot.values.iter_mut() {
|
|
value.make_item();
|
|
}
|
|
Ok(aot)
|
|
} else {
|
|
Err(Item::Value(Value::Array(a)))
|
|
}
|
|
}
|
|
_ => Err(self),
|
|
}
|
|
}
|
|
// Starting private because the name is unclear
|
|
pub(crate) fn make_item(&mut self) {
|
|
let other = std::mem::take(self);
|
|
let other = match other.into_table().map(Item::Table) {
|
|
Ok(i) => i,
|
|
Err(i) => i,
|
|
};
|
|
let other = match other.into_array_of_tables().map(Item::ArrayOfTables) {
|
|
Ok(i) => i,
|
|
Err(i) => i,
|
|
};
|
|
*self = other;
|
|
}
|
|
/// Returns true if `self` is a [`Value`]
|
|
pub fn is_value(&self) -> bool {
|
|
self.as_value().is_some()
|
|
}
|
|
/// Returns true if `self` is a [`Table`]
|
|
///
|
|
/// <div class="warning">
|
|
///
|
|
/// To operate on both [`Table`]s and [`InlineTable`]s, see [`Item::is_table_like`]
|
|
///
|
|
/// </div>
|
|
pub fn is_table(&self) -> bool {
|
|
self.as_table().is_some()
|
|
}
|
|
/// Returns true if `self` is an [`ArrayOfTables`]
|
|
pub fn is_array_of_tables(&self) -> bool {
|
|
self.as_array_of_tables().is_some()
|
|
}
|
|
/// Returns true if `self` is `None`.
|
|
pub fn is_none(&self) -> bool {
|
|
matches!(*self, Item::None)
|
|
}
|
|
|
|
// Duplicate Value downcasting API
|
|
|
|
/// Casts `self` to integer.
|
|
pub fn as_integer(&self) -> Option<i64> {
|
|
self.as_value().and_then(Value::as_integer)
|
|
}
|
|
|
|
/// Returns true if `self` is an integer.
|
|
pub fn is_integer(&self) -> bool {
|
|
self.as_integer().is_some()
|
|
}
|
|
|
|
/// Casts `self` to float.
|
|
pub fn as_float(&self) -> Option<f64> {
|
|
self.as_value().and_then(Value::as_float)
|
|
}
|
|
|
|
/// Returns true if `self` is a float.
|
|
pub fn is_float(&self) -> bool {
|
|
self.as_float().is_some()
|
|
}
|
|
|
|
/// Casts `self` to boolean.
|
|
pub fn as_bool(&self) -> Option<bool> {
|
|
self.as_value().and_then(Value::as_bool)
|
|
}
|
|
|
|
/// Returns true if `self` is a boolean.
|
|
pub fn is_bool(&self) -> bool {
|
|
self.as_bool().is_some()
|
|
}
|
|
|
|
/// Casts `self` to str.
|
|
pub fn as_str(&self) -> Option<&str> {
|
|
self.as_value().and_then(Value::as_str)
|
|
}
|
|
|
|
/// Returns true if `self` is a string.
|
|
pub fn is_str(&self) -> bool {
|
|
self.as_str().is_some()
|
|
}
|
|
|
|
/// Casts `self` to date-time.
|
|
pub fn as_datetime(&self) -> Option<&Datetime> {
|
|
self.as_value().and_then(Value::as_datetime)
|
|
}
|
|
|
|
/// Returns true if `self` is a date-time.
|
|
pub fn is_datetime(&self) -> bool {
|
|
self.as_datetime().is_some()
|
|
}
|
|
|
|
/// Casts `self` to array.
|
|
pub fn as_array(&self) -> Option<&Array> {
|
|
self.as_value().and_then(Value::as_array)
|
|
}
|
|
|
|
/// Casts `self` to mutable array.
|
|
pub fn as_array_mut(&mut self) -> Option<&mut Array> {
|
|
self.as_value_mut().and_then(Value::as_array_mut)
|
|
}
|
|
|
|
/// Returns true if `self` is an array.
|
|
pub fn is_array(&self) -> bool {
|
|
self.as_array().is_some()
|
|
}
|
|
|
|
/// Casts `self` to inline table.
|
|
pub fn as_inline_table(&self) -> Option<&InlineTable> {
|
|
self.as_value().and_then(Value::as_inline_table)
|
|
}
|
|
|
|
/// Casts `self` to mutable inline table.
|
|
pub fn as_inline_table_mut(&mut self) -> Option<&mut InlineTable> {
|
|
self.as_value_mut().and_then(Value::as_inline_table_mut)
|
|
}
|
|
|
|
/// Returns true if `self` is an inline table.
|
|
pub fn is_inline_table(&self) -> bool {
|
|
self.as_inline_table().is_some()
|
|
}
|
|
|
|
/// Casts `self` to either a table or an inline table.
|
|
pub fn as_table_like(&self) -> Option<&dyn TableLike> {
|
|
self.as_table()
|
|
.map(|t| t as &dyn TableLike)
|
|
.or_else(|| self.as_inline_table().map(|t| t as &dyn TableLike))
|
|
}
|
|
|
|
/// Casts `self` to either a table or an inline table.
|
|
pub fn as_table_like_mut(&mut self) -> Option<&mut dyn TableLike> {
|
|
match self {
|
|
Item::Table(t) => Some(t as &mut dyn TableLike),
|
|
Item::Value(Value::InlineTable(t)) => Some(t as &mut dyn TableLike),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Returns true if `self` is either a table, or an inline table.
|
|
pub fn is_table_like(&self) -> bool {
|
|
self.as_table_like().is_some()
|
|
}
|
|
|
|
/// The location within the original document
|
|
///
|
|
/// This generally requires an [`ImDocument`][crate::ImDocument].
|
|
pub fn span(&self) -> Option<std::ops::Range<usize>> {
|
|
match self {
|
|
Item::None => None,
|
|
Item::Value(v) => v.span(),
|
|
Item::Table(v) => v.span(),
|
|
Item::ArrayOfTables(v) => v.span(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn despan(&mut self, input: &str) {
|
|
match self {
|
|
Item::None => {}
|
|
Item::Value(v) => v.despan(input),
|
|
Item::Table(v) => v.despan(input),
|
|
Item::ArrayOfTables(v) => v.despan(input),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Clone for Item {
|
|
#[inline(never)]
|
|
fn clone(&self) -> Self {
|
|
match self {
|
|
Item::None => Item::None,
|
|
Item::Value(v) => Item::Value(v.clone()),
|
|
Item::Table(v) => Item::Table(v.clone()),
|
|
Item::ArrayOfTables(v) => Item::ArrayOfTables(v.clone()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
impl FromStr for Item {
|
|
type Err = crate::TomlError;
|
|
|
|
/// Parses a value from a &str
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let value = s.parse::<Value>()?;
|
|
Ok(Item::Value(value))
|
|
}
|
|
}
|
|
|
|
impl<'b> From<&'b Item> for Item {
|
|
fn from(s: &'b Item) -> Self {
|
|
s.clone()
|
|
}
|
|
}
|
|
|
|
impl From<Table> for Item {
|
|
fn from(s: Table) -> Self {
|
|
Item::Table(s)
|
|
}
|
|
}
|
|
|
|
impl From<ArrayOfTables> for Item {
|
|
fn from(s: ArrayOfTables) -> Self {
|
|
Item::ArrayOfTables(s)
|
|
}
|
|
}
|
|
|
|
impl<V: Into<Value>> From<V> for Item {
|
|
fn from(s: V) -> Self {
|
|
Item::Value(s.into())
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "display")]
|
|
impl std::fmt::Display for Item {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match &self {
|
|
Item::None => Ok(()),
|
|
Item::Value(v) => v.fmt(f),
|
|
Item::Table(v) => v.fmt(f),
|
|
Item::ArrayOfTables(v) => v.fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns a formatted value.
|
|
///
|
|
/// Since formatting is part of a `Value`, the right hand side of the
|
|
/// assignment needs to be decorated with a space before the value.
|
|
/// The `value` function does just that.
|
|
///
|
|
/// # Examples
|
|
/// ```rust
|
|
/// # #[cfg(feature = "display")] {
|
|
/// # #[cfg(feature = "parse")] {
|
|
/// # use toml_edit::*;
|
|
/// let mut table = Table::default();
|
|
/// let mut array = Array::default();
|
|
/// array.push("hello");
|
|
/// array.push("\\, world"); // \ is only allowed in a literal string
|
|
/// table["key1"] = value("value1");
|
|
/// table["key2"] = value(42);
|
|
/// table["key3"] = value(array);
|
|
/// assert_eq!(table.to_string(),
|
|
/// r#"key1 = "value1"
|
|
/// key2 = 42
|
|
/// key3 = ["hello", '\, world']
|
|
/// "#);
|
|
/// # }
|
|
/// # }
|
|
/// ```
|
|
pub fn value<V: Into<Value>>(v: V) -> Item {
|
|
Item::Value(v.into())
|
|
}
|
|
|
|
/// Returns an empty table.
|
|
pub fn table() -> Item {
|
|
Item::Table(Table::new())
|
|
}
|
|
|
|
/// Returns an empty array of tables.
|
|
pub fn array() -> Item {
|
|
Item::ArrayOfTables(ArrayOfTables::new())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "parse")]
|
|
#[cfg(feature = "display")]
|
|
fn string_roundtrip() {
|
|
value("hello").to_string().parse::<Item>().unwrap();
|
|
}
|