mirror of
https://github.com/nushell/nushell.git
synced 2025-06-21 02:18:44 +02:00
MVP
This commit is contained in:
parent
e1ffaf2548
commit
254cb3177a
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -2846,6 +2846,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
@ -3593,6 +3602,7 @@ dependencies = [
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"uucore",
|
||||
"v_htmlescape",
|
||||
]
|
||||
|
||||
@ -7787,6 +7797,7 @@ dependencies = [
|
||||
"dunce",
|
||||
"glob",
|
||||
"iana-time-zone",
|
||||
"itertools 0.14.0",
|
||||
"libc",
|
||||
"nix 0.29.0",
|
||||
"number_prefix",
|
||||
|
@ -35,6 +35,7 @@ serde_urlencoded = { workspace = true }
|
||||
v_htmlescape = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
uucore = {workspace = true, features = ["format"]}
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.1" }
|
||||
|
@ -1,5 +1,6 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ast::PathMember, engine::StateWorkingSet, Config, ListStream};
|
||||
use uucore::format::{parse_spec_and_escape, FormatArgument, FormatError, FormatItem};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FormatPattern;
|
||||
@ -12,20 +13,26 @@ impl Command for FormatPattern {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("format pattern")
|
||||
.input_output_types(vec![
|
||||
(Type::list(Type::Any), Type::String),
|
||||
(Type::table(), Type::List(Box::new(Type::String))),
|
||||
(Type::record(), Type::Any),
|
||||
(Type::Any, Type::String),
|
||||
])
|
||||
.required(
|
||||
"pattern",
|
||||
SyntaxShape::String,
|
||||
"The pattern to output. e.g.) \"{foo}: {bar}\".",
|
||||
)
|
||||
.switch("printf", "Use printf style pattern.", None)
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Format columns into a string using a simple pattern."
|
||||
r"Format values into a string using either a simple pattern or `printf`-compatible pattern.
|
||||
Simple pattern supports input of type list<any>, table, and record;
|
||||
`printf` pattern supports a limited set of primitive types as input, namely string, int and float, and composite types such as list, table
|
||||
"
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -37,6 +44,7 @@ impl Command for FormatPattern {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let use_printf = call.has_flag(engine_state, stack, "printf")?;
|
||||
let specified_pattern: Result<Value, ShellError> = call.req(engine_state, stack, 0);
|
||||
let input_val = input.into_value(call.head)?;
|
||||
// add '$it' variable to support format like this: $it.column1.column2.
|
||||
@ -45,9 +53,9 @@ impl Command for FormatPattern {
|
||||
|
||||
let config = stack.get_config(engine_state);
|
||||
|
||||
match specified_pattern {
|
||||
Err(e) => Err(e),
|
||||
Ok(pattern) => {
|
||||
match (specified_pattern, use_printf) {
|
||||
(Err(e), _) => Err(e),
|
||||
(Ok(pattern), false) => {
|
||||
let string_span = pattern.span();
|
||||
let string_pattern = pattern.coerce_into_string()?;
|
||||
// the string span is start as `"`, we don't need the character
|
||||
@ -60,6 +68,7 @@ impl Command for FormatPattern {
|
||||
|
||||
format(input_val, &ops, engine_state, &config, call.head)
|
||||
}
|
||||
(Ok(pattern), true) => format_printf(input_val, pattern, call.head),
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,6 +87,16 @@ impl Command for FormatPattern {
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Unescape a fully quoted json using printf",
|
||||
example: r#""\{\\\"foo\\\": \\\"bar\\\"\}" | format pattern --printf "%b""#,
|
||||
result: Some(Value::test_string(r#"{"foo": "bar"}"#)),
|
||||
},
|
||||
Example {
|
||||
description: "Using printf to substitute multiple args",
|
||||
example: r#"["a", 1] | format pattern --printf "first = %s, second = %d""#,
|
||||
result: Some(Value::test_string("first = a, second = 1")),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -265,6 +284,86 @@ fn format_record(
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn assert_specifier_count_eq_arg_count(
|
||||
spec_count: usize,
|
||||
arg_count: usize,
|
||||
span: Span,
|
||||
) -> Result<(), ShellError> {
|
||||
if spec_count != arg_count {
|
||||
Err(ShellError::IncompatibleParametersSingle {
|
||||
msg: format!(
|
||||
"Number of arguments ({}) provided does not match the number of specifiers ({}) within the pattern.",
|
||||
arg_count, spec_count,
|
||||
)
|
||||
.into(),
|
||||
span: span,
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn format_printf(
|
||||
input_data: Value,
|
||||
pattern: Value,
|
||||
head_span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern_str = pattern.coerce_into_string()?;
|
||||
let spec_count = parse_spec_and_escape(pattern_str.as_ref())
|
||||
.filter_map(|item| match item {
|
||||
Ok(FormatItem::Spec(_)) => Some(()),
|
||||
_ => None,
|
||||
})
|
||||
.count();
|
||||
let args: Vec<String> = match input_data {
|
||||
v @ Value::List { .. } => {
|
||||
let span = v.span();
|
||||
let vals = v.into_list()?;
|
||||
let arg_count = Vec::len(&vals);
|
||||
assert_specifier_count_eq_arg_count(spec_count, arg_count, span)?;
|
||||
vals.into_iter()
|
||||
.map(Value::coerce_into_string)
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
}
|
||||
v @ Value::Nothing {..} => {
|
||||
assert_specifier_count_eq_arg_count(spec_count, 0, v.span())?;
|
||||
vec![]
|
||||
}
|
||||
v => {
|
||||
assert_specifier_count_eq_arg_count(spec_count, 1, v.span())?;
|
||||
vec![v.coerce_into_string()?]
|
||||
}
|
||||
};
|
||||
|
||||
match printf_spec_escape(pattern_str, args) {
|
||||
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
|
||||
Err(err) => Err(ShellError::GenericError {
|
||||
error: err.to_string(),
|
||||
msg: err.to_string(),
|
||||
span: Some(head_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn printf_spec_escape(pattern: String, args: Vec<String>) -> Result<String, FormatError> {
|
||||
let mut writer: Vec<_> = Vec::new();
|
||||
let args: Vec<FormatArgument> = args.into_iter().map(FormatArgument::Unparsed).collect();
|
||||
let mut args = args.iter().peekable();
|
||||
|
||||
for item in parse_spec_and_escape(pattern.as_ref()) {
|
||||
match item {
|
||||
Ok(item) => {
|
||||
item.write(&mut writer, &mut args)?;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&writer).to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
|
Loading…
x
Reference in New Issue
Block a user