mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 16:33:37 +01:00
Rows and values can be checked for emptiness. Allows to set a value if desired. (#1665)
This commit is contained in:
parent
a62745eefb
commit
80025ea684
@ -307,6 +307,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(Rename),
|
||||
whole_stream_command(Uniq),
|
||||
per_item_command(Each),
|
||||
per_item_command(IsEmpty),
|
||||
// Table manipulation
|
||||
whole_stream_command(Shuffle),
|
||||
whole_stream_command(Wrap),
|
||||
|
@ -54,6 +54,7 @@ pub(crate) mod help;
|
||||
pub(crate) mod histogram;
|
||||
pub(crate) mod history;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod lines;
|
||||
pub(crate) mod ls;
|
||||
@ -135,6 +136,7 @@ pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use edit::Edit;
|
||||
pub(crate) use is_empty::IsEmpty;
|
||||
pub(crate) mod kill;
|
||||
pub(crate) use kill::Kill;
|
||||
pub(crate) mod clear;
|
||||
|
203
crates/nu-cli/src/commands/is_empty.rs
Normal file
203
crates/nu-cli/src/commands/is_empty.rs
Normal file
@ -0,0 +1,203 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
CallInfo, ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
enum IsEmptyFor {
|
||||
Value,
|
||||
RowWithFieldsAndFallback(Vec<Tagged<ColumnPath>>, Value),
|
||||
RowWithField(Tagged<ColumnPath>),
|
||||
RowWithFieldAndFallback(Box<Tagged<ColumnPath>>, Value),
|
||||
}
|
||||
|
||||
pub struct IsEmpty;
|
||||
|
||||
impl PerItemCommand for IsEmpty {
|
||||
fn name(&self) -> &str {
|
||||
"empty?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("empty?").rest(
|
||||
SyntaxShape::Any,
|
||||
"the names of the columns to check emptiness followed by the replacement value.",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Checks emptiness. The last value is the replacement value for any empty column(s) given to check against the table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
value: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let value_tag = value.tag();
|
||||
|
||||
let action = if call_info.args.len() <= 2 {
|
||||
let field = call_info.args.expect_nth(0);
|
||||
let replacement_if_true = call_info.args.expect_nth(1);
|
||||
|
||||
match (field, replacement_if_true) {
|
||||
(Ok(field), Ok(replacement_if_true)) => IsEmptyFor::RowWithFieldAndFallback(
|
||||
Box::new(field.as_column_path()?),
|
||||
replacement_if_true.clone(),
|
||||
),
|
||||
(Ok(field), Err(_)) => IsEmptyFor::RowWithField(field.as_column_path()?),
|
||||
(_, _) => IsEmptyFor::Value,
|
||||
}
|
||||
} else {
|
||||
let no_args = vec![];
|
||||
let mut arguments = call_info
|
||||
.args
|
||||
.positional
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| &no_args)
|
||||
.iter()
|
||||
.rev();
|
||||
let replacement_if_true = match arguments.next() {
|
||||
Some(arg) => arg.clone(),
|
||||
None => UntaggedValue::boolean(value.is_empty()).into_value(&value_tag),
|
||||
};
|
||||
|
||||
IsEmptyFor::RowWithFieldsAndFallback(
|
||||
arguments
|
||||
.map(|a| a.as_column_path())
|
||||
.filter_map(Result::ok)
|
||||
.collect(),
|
||||
replacement_if_true,
|
||||
)
|
||||
};
|
||||
|
||||
match action {
|
||||
IsEmptyFor::Value => Ok(futures::stream::iter(vec![Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
|
||||
))])
|
||||
.to_output_stream()),
|
||||
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
|
||||
let mut out = value;
|
||||
|
||||
for field in fields.iter() {
|
||||
let val =
|
||||
out.get_data_by_column_path(&field, Box::new(move |(_, _, err)| err))?;
|
||||
|
||||
let emptiness_value = match out {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default.clone()) {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
};
|
||||
|
||||
out = emptiness_value?;
|
||||
}
|
||||
|
||||
Ok(futures::stream::iter(vec![Ok(ReturnSuccess::Value(out))]).to_output_stream())
|
||||
}
|
||||
IsEmptyFor::RowWithField(field) => {
|
||||
let val =
|
||||
value.get_data_by_column_path(&field, Box::new(move |(_, _, err)| err))?;
|
||||
|
||||
let stream = match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(
|
||||
&field,
|
||||
UntaggedValue::boolean(true).into_value(&value_tag),
|
||||
) {
|
||||
Some(v) => futures::stream::iter(vec![Ok(ReturnSuccess::Value(v))]),
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
futures::stream::iter(vec![Ok(ReturnSuccess::Value(value))])
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
|
||||
let val =
|
||||
value.get_data_by_column_path(&field, Box::new(move |(_, _, err)| err))?;
|
||||
|
||||
let stream = match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default) {
|
||||
Some(v) => futures::stream::iter(vec![Ok(ReturnSuccess::Value(v))]),
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
futures::stream::iter(vec![Ok(ReturnSuccess::Value(value))])
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
crates/nu-cli/src/env/environment_syncer.rs
vendored
77
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -58,7 +58,7 @@ impl EnvironmentSyncer {
|
||||
}
|
||||
|
||||
if let Some(variables) = environment.env() {
|
||||
for var in nu_value_ext::row_entries(&variables) {
|
||||
for var in variables.row_entries() {
|
||||
if let Ok(string) = var.1.as_string() {
|
||||
ctx.with_host(|host| {
|
||||
host.env_set(
|
||||
@ -88,7 +88,8 @@ impl EnvironmentSyncer {
|
||||
|
||||
if let Some(new_paths) = environment.path() {
|
||||
let prepared = std::env::join_paths(
|
||||
nu_value_ext::table_entries(&new_paths)
|
||||
new_paths
|
||||
.table_entries()
|
||||
.map(|p| p.as_string())
|
||||
.filter_map(Result::ok),
|
||||
);
|
||||
@ -212,16 +213,17 @@ mod tests {
|
||||
// including the newer one accounted for.
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let vars = nu_value_ext::row_entries(
|
||||
&environment.env().expect("No variables in the environment."),
|
||||
)
|
||||
.map(|(name, value)| {
|
||||
(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let vars = environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.map(|(name, value)| {
|
||||
(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vars, expected);
|
||||
});
|
||||
@ -281,16 +283,17 @@ mod tests {
|
||||
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let vars = nu_value_ext::row_entries(
|
||||
&environment.env().expect("No variables in the environment."),
|
||||
)
|
||||
.map(|(name, value)| {
|
||||
(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let vars = environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.map(|(name, value)| {
|
||||
(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vars, expected);
|
||||
});
|
||||
@ -367,14 +370,13 @@ mod tests {
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let paths = std::env::join_paths(
|
||||
&nu_value_ext::table_entries(
|
||||
&environment
|
||||
.path()
|
||||
.expect("No path variable in the environment."),
|
||||
)
|
||||
.map(|value| value.as_string().expect("Couldn't convert to string"))
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>(),
|
||||
&environment
|
||||
.path()
|
||||
.expect("No path variable in the environment.")
|
||||
.table_entries()
|
||||
.map(|value| value.as_string().expect("Couldn't convert to string"))
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.expect("Couldn't join paths.")
|
||||
.into_string()
|
||||
@ -442,14 +444,13 @@ mod tests {
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let paths = std::env::join_paths(
|
||||
&nu_value_ext::table_entries(
|
||||
&environment
|
||||
.path()
|
||||
.expect("No path variable in the environment."),
|
||||
)
|
||||
.map(|value| value.as_string().expect("Couldn't convert to string"))
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>(),
|
||||
&environment
|
||||
.path()
|
||||
.expect("No path variable in the environment.")
|
||||
.table_entries()
|
||||
.map(|value| value.as_string().expect("Couldn't convert to string"))
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.expect("Couldn't join paths.")
|
||||
.into_string()
|
||||
|
@ -69,7 +69,7 @@ impl ValueStructure {
|
||||
}
|
||||
|
||||
fn build(&mut self, src: &Value, lvl: usize) -> Result<(), ShellError> {
|
||||
for entry in nu_value_ext::row_entries(src) {
|
||||
for entry in src.row_entries() {
|
||||
let value = entry.1;
|
||||
let path = entry.0;
|
||||
|
||||
|
96
crates/nu-cli/tests/commands/is_empty.rs
Normal file
96
crates/nu-cli/tests/commands/is_empty.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn adds_value_provided_if_column_is_empty() {
|
||||
Playground::setup("is_empty_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"likes.csv",
|
||||
r#"
|
||||
first_name,last_name,rusty_at,likes
|
||||
Andrés,Robalino,10/11/2013,1
|
||||
Jonathan,Turner,10/12/2013,1
|
||||
Jason,Gedge,10/11/2013,1
|
||||
Yehuda,Katz,10/11/2013,
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open likes.csv
|
||||
| empty? likes 1
|
||||
| get likes
|
||||
| sum
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "4");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_value_provided_for_columns_that_are_empty() {
|
||||
Playground::setup("is_empty_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"checks.json",
|
||||
r#"
|
||||
[
|
||||
{"boost": 1, "check": []},
|
||||
{"boost": 1, "check": ""},
|
||||
{"boost": 1, "check": {}},
|
||||
{"boost": null, "check": ["" {} [] ""]}
|
||||
]
|
||||
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open checks.json
|
||||
| empty? boost check 1
|
||||
| get boost check
|
||||
| sum
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "8");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_emptiness_check() {
|
||||
Playground::setup("is_empty_test_3", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"checks.json",
|
||||
r#"
|
||||
{
|
||||
"are_empty": [
|
||||
{"check": []},
|
||||
{"check": ""},
|
||||
{"check": {}},
|
||||
{"check": ["" {} [] ""]}
|
||||
]
|
||||
}
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open checks.json
|
||||
| get are_empty.check
|
||||
| empty?
|
||||
| where $it
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "4");
|
||||
})
|
||||
}
|
@ -16,6 +16,7 @@ mod group_by;
|
||||
mod headers;
|
||||
mod histogram;
|
||||
mod insert;
|
||||
mod is_empty;
|
||||
mod last;
|
||||
mod lines;
|
||||
mod ls;
|
||||
|
@ -3,6 +3,7 @@ mod convert;
|
||||
mod debug;
|
||||
pub mod dict;
|
||||
pub mod evaluate;
|
||||
pub mod iter;
|
||||
pub mod primitive;
|
||||
pub mod range;
|
||||
mod serde_bigdecimal;
|
||||
@ -11,6 +12,7 @@ mod serde_bigint;
|
||||
use crate::hir;
|
||||
use crate::type_name::{ShellTypeName, SpannedTypeName};
|
||||
use crate::value::dict::Dictionary;
|
||||
use crate::value::iter::{RowValueIter, TableValueIter};
|
||||
use crate::value::primitive::Primitive;
|
||||
use crate::value::range::{Range, RangeInclusion};
|
||||
use crate::{ColumnPath, PathMember};
|
||||
@ -313,6 +315,39 @@ impl Value {
|
||||
_ => Err(ShellError::type_error("boolean", self.spanned_type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator of the values rows
|
||||
pub fn table_entries(&self) -> TableValueIter<'_> {
|
||||
crate::value::iter::table_entries(&self)
|
||||
}
|
||||
|
||||
/// Returns an iterator of the value's cells
|
||||
pub fn row_entries(&self) -> RowValueIter<'_> {
|
||||
crate::value::iter::row_entries(&self)
|
||||
}
|
||||
|
||||
/// Returns true if the value is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match &self {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(p),
|
||||
..
|
||||
} => p.is_empty(),
|
||||
t
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Table(_),
|
||||
..
|
||||
} => t.table_entries().all(|row| row.is_empty()),
|
||||
r
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => r.row_entries().all(|(_, value)| value.is_empty()),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Value> for String {
|
||||
|
50
crates/nu-protocol/src/value/iter.rs
Normal file
50
crates/nu-protocol/src/value/iter.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::value::{UntaggedValue, Value};
|
||||
|
||||
pub enum RowValueIter<'a> {
|
||||
Empty,
|
||||
Entries(indexmap::map::Iter<'a, String, Value>),
|
||||
}
|
||||
|
||||
pub enum TableValueIter<'a> {
|
||||
Empty,
|
||||
Entries(std::slice::Iter<'a, Value>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RowValueIter<'a> {
|
||||
type Item = (&'a String, &'a Value);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
RowValueIter::Empty => None,
|
||||
RowValueIter::Entries(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TableValueIter<'a> {
|
||||
type Item = &'a Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
TableValueIter::Empty => None,
|
||||
TableValueIter::Entries(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn table_entries(value: &Value) -> TableValueIter<'_> {
|
||||
match &value.value {
|
||||
UntaggedValue::Table(t) => TableValueIter::Entries(t.iter()),
|
||||
_ => TableValueIter::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_entries(value: &Value) -> RowValueIter<'_> {
|
||||
match &value.value {
|
||||
UntaggedValue::Row(o) => {
|
||||
let iter = o.entries.iter();
|
||||
RowValueIter::Entries(iter)
|
||||
}
|
||||
_ => RowValueIter::Empty,
|
||||
}
|
||||
}
|
@ -84,6 +84,15 @@ impl Primitive {
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the value is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Primitive::Nothing => true,
|
||||
Primitive::String(s) => s.is_empty(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl num_traits::Zero for Primitive {
|
||||
|
@ -8,8 +8,6 @@ use nu_source::{HasSpan, PrettyDebug, Spanned, SpannedItem, Tag, Tagged, TaggedI
|
||||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
pub trait ValueExt {
|
||||
fn row_entries(&self) -> RowValueIter<'_>;
|
||||
fn table_entries(&self) -> TableValueIter<'_>;
|
||||
fn into_parts(self) -> (UntaggedValue, Tag);
|
||||
fn get_data(&self, desc: &str) -> MaybeOwned<'_, Value>;
|
||||
fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Value>;
|
||||
@ -41,14 +39,6 @@ pub trait ValueExt {
|
||||
}
|
||||
|
||||
impl ValueExt for Value {
|
||||
fn row_entries(&self) -> RowValueIter<'_> {
|
||||
row_entries(self)
|
||||
}
|
||||
|
||||
fn table_entries(&self) -> TableValueIter<'_> {
|
||||
table_entries(self)
|
||||
}
|
||||
|
||||
fn into_parts(self) -> (UntaggedValue, Tag) {
|
||||
(self.value, self.tag)
|
||||
}
|
||||
@ -534,52 +524,3 @@ pub(crate) fn get_mut_data_by_member<'value>(
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RowValueIter<'a> {
|
||||
Empty,
|
||||
Entries(indexmap::map::Iter<'a, String, Value>),
|
||||
}
|
||||
|
||||
pub enum TableValueIter<'a> {
|
||||
Empty,
|
||||
Entries(std::slice::Iter<'a, Value>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RowValueIter<'a> {
|
||||
type Item = (&'a String, &'a Value);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
RowValueIter::Empty => None,
|
||||
RowValueIter::Entries(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TableValueIter<'a> {
|
||||
type Item = &'a Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
TableValueIter::Empty => None,
|
||||
TableValueIter::Entries(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn table_entries(value: &Value) -> TableValueIter<'_> {
|
||||
match &value.value {
|
||||
UntaggedValue::Table(t) => TableValueIter::Entries(t.iter()),
|
||||
_ => TableValueIter::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_entries(value: &Value) -> RowValueIter<'_> {
|
||||
match &value.value {
|
||||
UntaggedValue::Row(o) => {
|
||||
let iter = o.entries.iter();
|
||||
RowValueIter::Entries(iter)
|
||||
}
|
||||
_ => RowValueIter::Empty,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user