Add nuon format for fun (#4401)

* Add nuon format for fun

* more fun

* More nuon fixes, allow comments, improve errors
This commit is contained in:
JT 2022-02-20 16:26:41 -05:00 committed by GitHub
parent 2ba12afb01
commit fd22211737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 298 additions and 7 deletions

View File

@ -76,7 +76,7 @@ itertools = "0.10.3"
plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
default = ["plugin", "inc", "example", "which"]
stable = ["default"]
extra = [ "default", "dataframe", "gstat", "zip-support", "query", ]
extra = ["default", "dataframe", "gstat", "zip-support", "query", "trash-support"]
wasi = ["inc"]
trash-support = ["nu-command/trash-support"]

View File

@ -235,6 +235,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
FromIcs,
FromIni,
FromJson,
FromNuon,
FromOds,
FromSsv,
FromToml,
@ -250,6 +251,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
ToHtml,
ToJson,
ToMd,
ToNuon,
ToToml,
ToTsv,
ToCsv,

View File

@ -142,7 +142,7 @@ impl Command for Open {
Some(converter_id) => engine_state.get_decl(converter_id).run(
engine_state,
stack,
&Call::new(call_span),
&Call::new(arg_span),
output,
),
None => Ok(output),

View File

@ -26,7 +26,7 @@ impl Command for FromJson {
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "'{ a:1 }' | from json",
example: r#"'{ "a": 1 }' | from json"#,
description: "Converts json formatted string to table",
result: Some(Value::Record {
cols: vec!["a".to_string()],
@ -38,7 +38,7 @@ impl Command for FromJson {
}),
},
Example {
example: "'{ a:1, b: [1, 2] }' | from json",
example: r#"'{ "a": 1, "b": [1, 2] }' | from json"#,
description: "Converts json formatted string to table",
result: Some(Value::Record {
cols: vec!["a".to_string(), "b".to_string()],

View File

@ -5,6 +5,7 @@ mod eml;
mod ics;
mod ini;
mod json;
mod nuon;
mod ods;
mod ssv;
mod toml;
@ -23,6 +24,7 @@ pub use eml::FromEml;
pub use ics::FromIcs;
pub use ini::FromIni;
pub use json::FromJson;
pub use nuon::FromNuon;
pub use ods::FromOds;
pub use ssv::FromSsv;
pub use tsv::FromTsv;

View File

@ -0,0 +1,187 @@
use std::collections::HashMap;
use nu_engine::{current_dir, eval_expression};
use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
CONFIG_VARIABLE_ID,
};
#[derive(Clone)]
pub struct FromNuon;
impl Command for FromNuon {
fn name(&self) -> &str {
"from nuon"
}
fn usage(&self) -> &str {
"Convert from nuon to structured data"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("from nuon").category(Category::Experimental)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "'{ a:1 }' | from nuon",
description: "Converts nuon formatted string to table",
result: Some(Value::Record {
cols: vec!["a".to_string()],
vals: vec![Value::Int {
val: 1,
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
Example {
example: "'{ a:1, b: [1, 2] }' | from nuon",
description: "Converts nuon formatted string to table",
result: Some(Value::Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![
Value::Int {
val: 1,
span: Span::test_data(),
},
Value::List {
vals: vec![
Value::Int {
val: 1,
span: Span::test_data(),
},
Value::Int {
val: 2,
span: Span::test_data(),
},
],
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> {
let head = call.head;
let config = stack.get_config().unwrap_or_default();
let string_input = input.collect_string("", &config)?;
let cwd = current_dir(engine_state, stack)?;
{
let mut engine_state = EngineState::new();
let mut working_set = StateWorkingSet::new(&engine_state);
let mut stack = stack.captures_to_stack(&HashMap::new());
let _ = working_set.add_file("nuon file".to_string(), string_input.as_bytes());
let mut error = None;
let (lexed, err) =
nu_parser::lex(string_input.as_bytes(), 0, &[b'\n', b'\r'], &[], true);
error = error.or(err);
let (lite_block, err) = nu_parser::lite_parse(&lexed);
error = error.or(err);
let (block, err) = nu_parser::parse_block(&mut working_set, &lite_block, true);
error = error.or(err);
if block.pipelines.get(1).is_some() {
return Err(ShellError::SpannedLabeledError(
"error when loading".into(),
"excess values when loading".into(),
head,
));
}
let expr = if let Some(pipeline) = block.pipelines.get(0) {
if pipeline.expressions.get(1).is_some() {
return Err(ShellError::SpannedLabeledError(
"error when loading".into(),
"detected a pipeline in nuon file".into(),
head,
));
}
if let Some(expr) = pipeline.expressions.get(0) {
expr.clone()
} else {
Expression {
expr: Expr::Nothing,
span: head,
custom_completion: None,
ty: Type::Nothing,
}
}
} else {
Expression {
expr: Expr::Nothing,
span: head,
custom_completion: None,
ty: Type::Nothing,
}
};
if let Some(err) = error {
return Err(ShellError::SpannedLabeledError(
"error when loading".into(),
err.to_string(),
head,
));
}
let delta = working_set.render();
engine_state.merge_delta(delta, Some(&mut stack), &cwd)?;
stack.add_var(
CONFIG_VARIABLE_ID,
Value::Record {
cols: vec![],
vals: vec![],
span: head,
},
);
let result = eval_expression(&engine_state, &mut stack, &expr);
match result {
Ok(result) => Ok(result.into_pipeline_data()),
Err(ShellError::ExternalNotSupported(..)) => Err(ShellError::SpannedLabeledError(
"error when loading".into(),
"running commands not supported in nuon".into(),
head,
)),
Err(err) => Err(ShellError::SpannedLabeledError(
"error when loading".into(),
err.to_string(),
head,
)),
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(FromNuon {})
}
}

View File

@ -4,6 +4,7 @@ mod delimited;
mod html;
mod json;
mod md;
mod nuon;
mod toml;
mod tsv;
mod url;
@ -17,6 +18,7 @@ pub use command::To;
pub use html::ToHtml;
pub use json::ToJson;
pub use md::ToMd;
pub use nuon::ToNuon;
pub use tsv::ToTsv;
pub use xml::ToXml;
pub use yaml::ToYaml;

View File

@ -0,0 +1,98 @@
use nu_protocol::ast::{Call, RangeInclusion};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value,
};
#[derive(Clone)]
pub struct ToNuon;
impl Command for ToNuon {
fn name(&self) -> &str {
"to nuon"
}
fn signature(&self) -> Signature {
Signature::build("to nuon").category(Category::Experimental)
}
fn usage(&self) -> &str {
"Converts table data into Nuon (Nushell Object Notation) text."
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> {
Ok(Value::String {
val: to_nuon(call, input)?,
span: call.head,
}
.into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Outputs a nuon string representing the contents of this table",
example: "[1 2 3] | to nuon",
result: Some(Value::test_string("[1, 2, 3]")),
}]
}
}
fn value_to_string(v: &Value, span: Span) -> Result<String, ShellError> {
match v {
Value::Binary { .. } => Err(ShellError::UnsupportedInput("binary".into(), span)),
Value::Block { .. } => Err(ShellError::UnsupportedInput("block".into(), span)),
Value::Bool { val, .. } => {
if *val {
Ok("$true".to_string())
} else {
Ok("$false".to_string())
}
}
Value::CellPath { .. } => Err(ShellError::UnsupportedInput("cellpath".to_string(), span)),
Value::CustomValue { .. } => Err(ShellError::UnsupportedInput("custom".to_string(), span)),
Value::Date { .. } => Err(ShellError::UnsupportedInput("date".to_string(), span)),
Value::Duration { val, .. } => Ok(format!("{}ns", *val)),
Value::Error { .. } => Err(ShellError::UnsupportedInput("error".to_string(), span)),
Value::Filesize { val, .. } => Ok(format!("{}b", *val)),
Value::Float { val, .. } => Ok(format!("{}", *val)),
Value::Int { val, .. } => Ok(format!("{}", *val)),
Value::List { vals, .. } => {
let mut collection = vec![];
for val in vals {
collection.push(value_to_string(val, span)?);
}
Ok(format!("[{}]", collection.join(", ")))
}
Value::Nothing { .. } => Ok("$nothing".to_string()),
Value::Range { val, .. } => Ok(format!(
"{}..{}{}",
value_to_string(&val.from, span)?,
if val.inclusion == RangeInclusion::RightExclusive {
"<"
} else {
""
},
value_to_string(&val.to, span)?
)),
Value::Record { cols, vals, .. } => {
let mut collection = vec![];
for (col, val) in cols.iter().zip(vals) {
collection.push(format!("\"{}\": {}", col, value_to_string(val, span)?));
}
Ok(format!("{{{}}}", collection.join(", ")))
}
Value::String { val, .. } => Ok(format!("\"{}\"", val)),
}
}
fn to_nuon(call: &Call, input: PipelineData) -> Result<String, ShellError> {
let v = input.into_value(call.head);
value_to_string(&v, call.head)
}

View File

@ -74,7 +74,7 @@ pub enum ShellError {
#[diagnostic(code(nu::shell::feature_not_enabled), url(docsrs))]
FeatureNotEnabled(#[label = "feature not enabled"] Span),
#[error("External commands not yet supported")]
#[error("Running external commands not supported")]
#[diagnostic(code(nu::shell::external_commands), url(docsrs))]
ExternalNotSupported(#[label = "external not supported"] Span),
@ -152,7 +152,7 @@ pub enum ShellError {
#[error("Unsupported input")]
#[diagnostic(code(nu::shell::unsupported_input), url(docsrs))]
UnsupportedInput(String, #[label("{0}")] Span),
UnsupportedInput(String, #[label("{0} not supported")] Span),
#[error("Network failure")]
#[diagnostic(code(nu::shell::network_failure), url(docsrs))]

View File

@ -137,7 +137,7 @@ let $config = {
animate_prompt: $false # redraw the prompt every second
float_precision: 2
use_ansi_coloring: $true
filesize_format: "b" # b, kb, kib, mb, mib, gb, gib, tb, tib, pb, pib, eb, eib, zb, zib, auto
filesize_format: "auto" # b, kb, kib, mb, mib, gb, gib, tb, tib, pb, pib, eb, eib, zb, zib, auto
edit_mode: emacs # emacs, vi
max_history_size: 10000
menu_config: {