nushell/crates/nu-command/src/formats/to/text.rs
Jack Wright 0d060aeae8
Use pipeline data for http post|put|patch|delete commands. (#13254)
# Description
Provides the ability to use http commands as part of a pipeline.
Additionally, this pull requests extends the pipeline metadata to add a
content_type field. The content_type metadata field allows commands such
as `to json` to set the metadata in the pipeline allowing the http
commands to use it when making requests.

This pull request also introduces the ability to directly stream http
requests from streaming pipelines.

One other small change is that Content-Type will always be set if it is
passed in to the http commands, either indirectly or throw the content
type flag. Previously it was not preserved with requests that were not
of type json or form data.

# User-Facing Changes
* `http post`, `http put`, `http patch`, `http delete` can be used as
part of a pipeline
* `to text`, `to json`, `from json` all set the content_type metadata
field and the http commands will utilize them when making requests.
2024-07-01 12:34:19 -07:00

154 lines
5.0 KiB
Rust

use chrono_humanize::HumanTime;
use nu_engine::command_prelude::*;
use nu_protocol::{
format_duration, format_filesize_from_conf, ByteStream, Config, PipelineMetadata,
};
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
"\r\n"
} else {
"\n"
};
#[derive(Clone)]
pub struct ToText;
impl Command for ToText {
fn name(&self) -> &str {
"to text"
}
fn signature(&self) -> Signature {
Signature::build("to text")
.input_output_types(vec![(Type::Any, Type::String)])
.category(Category::Formats)
}
fn usage(&self) -> &str {
"Converts data into simple text."
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let input = input.try_expand_range()?;
match input {
PipelineData::Empty => Ok(Value::string(String::new(), span)
.into_pipeline_data_with_metadata(update_metadata(None))),
PipelineData::Value(value, ..) => {
let str = local_into_string(value, LINE_ENDING, engine_state.get_config());
Ok(
Value::string(str, span)
.into_pipeline_data_with_metadata(update_metadata(None)),
)
}
PipelineData::ListStream(stream, meta) => {
let span = stream.span();
let config = engine_state.get_config().clone();
let iter = stream.into_inner().map(move |value| {
let mut str = local_into_string(value, LINE_ENDING, &config);
str.push_str(LINE_ENDING);
str
});
Ok(PipelineData::ByteStream(
ByteStream::from_iter(
iter,
span,
engine_state.ctrlc.clone(),
ByteStreamType::String,
),
update_metadata(meta),
))
}
PipelineData::ByteStream(stream, meta) => {
Ok(PipelineData::ByteStream(stream, update_metadata(meta)))
}
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Outputs data as simple text",
example: "1 | to text",
result: Some(Value::test_string("1")),
},
Example {
description: "Outputs external data as simple text",
example: "git help -a | lines | find -r '^ ' | to text",
result: None,
},
Example {
description: "Outputs records as simple text",
example: "ls | to text",
result: None,
},
]
}
}
fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
let span = value.span();
match value {
Value::Bool { val, .. } => val.to_string(),
Value::Int { val, .. } => val.to_string(),
Value::Float { val, .. } => val.to_string(),
Value::Filesize { val, .. } => format_filesize_from_conf(val, config),
Value::Duration { val, .. } => format_duration(val),
Value::Date { val, .. } => {
format!("{} ({})", val.to_rfc2822(), HumanTime::from(val))
}
Value::Range { val, .. } => val.to_string(),
Value::String { val, .. } => val,
Value::Glob { val, .. } => val,
Value::List { vals: val, .. } => val
.into_iter()
.map(|x| local_into_string(x, ", ", config))
.collect::<Vec<_>>()
.join(separator),
Value::Record { val, .. } => val
.into_owned()
.into_iter()
.map(|(x, y)| format!("{}: {}", x, local_into_string(y, ", ", config)))
.collect::<Vec<_>>()
.join(separator),
Value::Closure { val, .. } => format!("<Closure {}>", val.block_id),
Value::Nothing { .. } => String::new(),
Value::Error { error, .. } => format!("{error:?}"),
Value::Binary { val, .. } => format!("{val:?}"),
Value::CellPath { val, .. } => val.to_string(),
// If we fail to collapse the custom value, just print <{type_name}> - failure is not
// that critical here
Value::Custom { val, .. } => val
.to_base_value(span)
.map(|val| local_into_string(val, separator, config))
.unwrap_or_else(|_| format!("<{}>", val.type_name())),
}
}
fn update_metadata(metadata: Option<PipelineMetadata>) -> Option<PipelineMetadata> {
metadata
.map(|md| md.with_content_type(Some("text/plain".to_string())))
.or_else(|| {
Some(PipelineMetadata::default().with_content_type(Some("text/plain".to_string())))
})
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(ToText {})
}
}