mirror of
https://github.com/nushell/nushell.git
synced 2025-04-10 14:08:40 +02:00
chrono version update # Description upgrade chrono to 0.4.23 # Major Changes If you're considering making any major change to nushell, before starting work on it, seek feedback from regular contributors and get approval for the idea from the core team either on [Discord](https://discordapp.com/invite/NtAbbGn) or [GitHub issue](https://github.com/nushell/nushell/issues/new/choose). Making sure we're all on board with the change saves everybody's time. Thanks! # Tests + Formatting Make sure you've done the following, if applicable: - Add tests that cover your changes (either in the command examples, the crate/tests folder, or in the /tests folder) - Try to think about corner cases and various ways how your changes could break. Cover those in the tests Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace --features=extra` to check that all tests pass # After Submitting * Help us keep the docs up to date: If your PR affects the user experience of Nushell (adding/removing a command, changing an input/output type, etc.), make sure the changes are reflected in the documentation (https://github.com/nushell/nushell.github.io) after the PR is merged. Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
264 lines
8.4 KiB
Rust
264 lines
8.4 KiB
Rust
use nu_protocol::ast::{Call, PathMember};
|
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
use nu_protocol::{
|
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
pub struct ToToml;
|
|
|
|
impl Command for ToToml {
|
|
fn name(&self) -> &str {
|
|
"to toml"
|
|
}
|
|
|
|
fn signature(&self) -> Signature {
|
|
Signature::build("to toml")
|
|
.input_output_types(vec![(Type::Any, Type::String)])
|
|
.category(Category::Formats)
|
|
}
|
|
|
|
fn usage(&self) -> &str {
|
|
"Convert table into .toml text"
|
|
}
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
vec![Example {
|
|
description: "Outputs an TOML string representing the contents of this table",
|
|
example: r#"[[foo bar]; ["1" "2"]] | to toml"#,
|
|
result: Some(Value::test_string("bar = \"2\"\nfoo = \"1\"\n")),
|
|
}]
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
engine_state: &EngineState,
|
|
_stack: &mut Stack,
|
|
call: &Call,
|
|
input: PipelineData,
|
|
) -> Result<nu_protocol::PipelineData, ShellError> {
|
|
let head = call.head;
|
|
to_toml(engine_state, input, head)
|
|
}
|
|
}
|
|
|
|
// Helper method to recursively convert nu_protocol::Value -> toml::Value
|
|
// This shouldn't be called at the top-level
|
|
fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellError> {
|
|
Ok(match &v {
|
|
Value::Bool { val, .. } => toml::Value::Boolean(*val),
|
|
Value::Int { val, .. } => toml::Value::Integer(*val),
|
|
Value::Filesize { val, .. } => toml::Value::Integer(*val),
|
|
Value::Duration { val, .. } => toml::Value::String(val.to_string()),
|
|
Value::Date { val, .. } => toml::Value::String(val.to_string()),
|
|
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
|
|
Value::Float { val, .. } => toml::Value::Float(*val),
|
|
Value::String { val, .. } => toml::Value::String(val.clone()),
|
|
Value::Record { cols, vals, .. } => {
|
|
let mut m = toml::map::Map::new();
|
|
for (k, v) in cols.iter().zip(vals.iter()) {
|
|
m.insert(k.clone(), helper(engine_state, v)?);
|
|
}
|
|
toml::Value::Table(m)
|
|
}
|
|
Value::List { vals, .. } => toml::Value::Array(toml_list(engine_state, vals)?),
|
|
Value::Block { span, .. } => {
|
|
let code = engine_state.get_span_contents(span);
|
|
let code = String::from_utf8_lossy(code).to_string();
|
|
toml::Value::String(code)
|
|
}
|
|
Value::Closure { span, .. } => {
|
|
let code = engine_state.get_span_contents(span);
|
|
let code = String::from_utf8_lossy(code).to_string();
|
|
toml::Value::String(code)
|
|
}
|
|
Value::Nothing { .. } => toml::Value::String("<Nothing>".to_string()),
|
|
Value::Error { error } => return Err(error.clone()),
|
|
Value::Binary { val, .. } => toml::Value::Array(
|
|
val.iter()
|
|
.map(|x| toml::Value::Integer(*x as i64))
|
|
.collect(),
|
|
),
|
|
Value::CellPath { val, .. } => toml::Value::Array(
|
|
val.members
|
|
.iter()
|
|
.map(|x| match &x {
|
|
PathMember::String { val, .. } => Ok(toml::Value::String(val.clone())),
|
|
PathMember::Int { val, .. } => Ok(toml::Value::Integer(*val as i64)),
|
|
})
|
|
.collect::<Result<Vec<toml::Value>, ShellError>>()?,
|
|
),
|
|
Value::CustomValue { .. } => toml::Value::String("<Custom Value>".to_string()),
|
|
})
|
|
}
|
|
|
|
fn toml_list(engine_state: &EngineState, input: &[Value]) -> Result<Vec<toml::Value>, ShellError> {
|
|
let mut out = vec![];
|
|
|
|
for value in input {
|
|
out.push(helper(engine_state, value)?);
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
fn toml_into_pipeline_data(
|
|
toml_value: &toml::Value,
|
|
value_type: Type,
|
|
span: Span,
|
|
) -> Result<PipelineData, ShellError> {
|
|
match toml::to_string(&toml_value) {
|
|
Ok(serde_toml_string) => Ok(Value::String {
|
|
val: serde_toml_string,
|
|
span,
|
|
}
|
|
.into_pipeline_data()),
|
|
_ => Ok(Value::Error {
|
|
error: ShellError::CantConvert("TOML".into(), value_type.to_string(), span, None),
|
|
}
|
|
.into_pipeline_data()),
|
|
}
|
|
}
|
|
|
|
fn value_to_toml_value(
|
|
engine_state: &EngineState,
|
|
v: &Value,
|
|
head: Span,
|
|
) -> Result<toml::Value, ShellError> {
|
|
match v {
|
|
Value::Record { .. } => helper(engine_state, v),
|
|
Value::List { ref vals, span } => match &vals[..] {
|
|
[Value::Record { .. }, _end @ ..] => helper(engine_state, v),
|
|
_ => Err(ShellError::UnsupportedInput(
|
|
"Expected a table with TOML-compatible structure from pipeline".to_string(),
|
|
*span,
|
|
)),
|
|
},
|
|
Value::String { val, span } => {
|
|
// Attempt to de-serialize the String
|
|
toml::de::from_str(val).map_err(|_| {
|
|
ShellError::UnsupportedInput(
|
|
format!("{:?} unable to de-serialize string to TOML", val),
|
|
*span,
|
|
)
|
|
})
|
|
}
|
|
_ => Err(ShellError::UnsupportedInput(
|
|
format!("{:?} is not a valid top-level TOML", v.get_type()),
|
|
v.span().unwrap_or(head),
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn to_toml(
|
|
engine_state: &EngineState,
|
|
input: PipelineData,
|
|
span: Span,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let value = input.into_value(span);
|
|
|
|
let toml_value = value_to_toml_value(engine_state, &value, span)?;
|
|
match toml_value {
|
|
toml::Value::Array(ref vec) => match vec[..] {
|
|
[toml::Value::Table(_)] => toml_into_pipeline_data(
|
|
vec.iter().next().expect("this should never trigger"),
|
|
value.get_type(),
|
|
span,
|
|
),
|
|
_ => toml_into_pipeline_data(&toml_value, value.get_type(), span),
|
|
},
|
|
_ => toml_into_pipeline_data(&toml_value, value.get_type(), span),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use nu_protocol::Spanned;
|
|
|
|
#[test]
|
|
fn test_examples() {
|
|
use crate::test_examples;
|
|
|
|
test_examples(ToToml {})
|
|
}
|
|
|
|
#[test]
|
|
fn test_value_to_toml_value() {
|
|
//
|
|
// Positive Tests
|
|
//
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut m = indexmap::IndexMap::new();
|
|
m.insert("rust".to_owned(), Value::test_string("editor"));
|
|
m.insert("is".to_owned(), Value::nothing(Span::test_data()));
|
|
m.insert(
|
|
"features".to_owned(),
|
|
Value::List {
|
|
vals: vec![Value::test_string("hello"), Value::test_string("array")],
|
|
span: Span::test_data(),
|
|
},
|
|
);
|
|
let tv = value_to_toml_value(
|
|
&engine_state,
|
|
&Value::from(Spanned {
|
|
item: m,
|
|
span: Span::test_data(),
|
|
}),
|
|
Span::test_data(),
|
|
)
|
|
.expect("Expected Ok from valid TOML dictionary");
|
|
assert_eq!(
|
|
tv.get("features"),
|
|
Some(&toml::Value::Array(vec![
|
|
toml::Value::String("hello".to_owned()),
|
|
toml::Value::String("array".to_owned())
|
|
]))
|
|
);
|
|
// TOML string
|
|
let tv = value_to_toml_value(
|
|
&engine_state,
|
|
&Value::test_string(
|
|
r#"
|
|
title = "TOML Example"
|
|
|
|
[owner]
|
|
name = "Tom Preston-Werner"
|
|
dob = 1979-05-27T07:32:00-08:00 # First class dates
|
|
|
|
[dependencies]
|
|
rustyline = "4.1.0"
|
|
sysinfo = "0.8.4"
|
|
chrono = { version = "0.4.23", features = ["serde"] }
|
|
"#,
|
|
),
|
|
Span::test_data(),
|
|
)
|
|
.expect("Expected Ok from valid TOML string");
|
|
assert_eq!(
|
|
tv.get("title").unwrap(),
|
|
&toml::Value::String("TOML Example".to_owned())
|
|
);
|
|
//
|
|
// Negative Tests
|
|
//
|
|
value_to_toml_value(
|
|
&engine_state,
|
|
&Value::test_string("not_valid"),
|
|
Span::test_data(),
|
|
)
|
|
.expect_err("Expected non-valid toml (String) to cause error!");
|
|
value_to_toml_value(
|
|
&engine_state,
|
|
&Value::List {
|
|
vals: vec![Value::test_string("1")],
|
|
span: Span::test_data(),
|
|
},
|
|
Span::test_data(),
|
|
)
|
|
.expect_err("Expected non-valid toml (Table) to cause error!");
|
|
}
|
|
}
|