Merge branch 'master' of github.com:nushell/nushell

This commit is contained in:
Sam Hedin
2020-06-10 08:36:16 +02:00
43 changed files with 637 additions and 491 deletions

View File

@ -1,11 +1,9 @@
use crate::commands::{command::EvaluatedWholeStreamCommandArgs, WholeStreamCommand};
use crate::prelude::*;
use chrono::{Datelike, Local, NaiveDate};
use nu_errors::ShellError;
use nu_protocol::Dictionary;
use crate::commands::{command::EvaluatedWholeStreamCommandArgs, WholeStreamCommand};
use indexmap::IndexMap;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Signature, SyntaxShape, UntaggedValue, Value};
pub struct Cal;
@ -250,7 +248,7 @@ fn add_month_to_table(
tag: &Tag,
selected_year: i32,
current_month: u32,
_current_day_option: Option<u32>, // Can be used in the future to display current day
current_day_option: Option<u32>,
) -> Result<(), ShellError> {
let month_helper_result = MonthHelper::new(selected_year, current_month);
@ -316,14 +314,23 @@ fn add_month_to_table(
}
for day in &days_of_the_week {
let value = if (day_count <= day_limit)
&& (day_count > month_helper.day_number_month_starts_on)
{
UntaggedValue::int(day_count - month_helper.day_number_month_starts_on)
.into_value(tag)
} else {
UntaggedValue::nothing().into_value(tag)
};
let should_add_day_number_to_table =
(day_count <= day_limit) && (day_count > month_helper.day_number_month_starts_on);
let mut value = UntaggedValue::nothing().into_value(tag);
if should_add_day_number_to_table {
let day_count_with_offset = day_count - month_helper.day_number_month_starts_on;
value = UntaggedValue::int(day_count_with_offset).into_value(tag);
if let Some(current_day) = current_day_option {
if current_day == day_count_with_offset {
// TODO: Update the value here with a color when color support is added
// This colors the current day
}
}
}
indexmap.insert((*day).to_string(), value);

View File

@ -384,18 +384,16 @@ impl WholeStreamCommand for FnFilterCommand {
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let CommandArgs {
CommandArgs {
host,
ctrl_c,
shell_manager,
call_info,
mut input,
..
} = args;
}: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let host: Arc<parking_lot::Mutex<dyn Host>> = host.clone();
let registry: CommandRegistry = registry.clone();
let func = self.func;

View File

@ -37,42 +37,37 @@ impl WholeStreamCommand for EvaluateBy {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
evaluate_by(args, registry)
evaluate_by(args, registry).await
}
}
pub fn evaluate_by(
pub async fn evaluate_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let name = args.call_info.name_tag.clone();
let (EvaluateByArgs { evaluate_with }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let name = args.call_info.name_tag.clone();
let (EvaluateByArgs { evaluate_with }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
yield Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name
))
if values.is_empty() {
Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name,
))
} else {
let evaluate_with = if let Some(evaluator) = evaluate_with {
Some(evaluator.item().clone())
} else {
None
};
let evaluate_with = if let Some(evaluator) = evaluate_with {
Some(evaluator.item().clone())
} else {
None
};
match evaluate(&values[0], evaluate_with, name) {
Ok(evaluated) => yield ReturnSuccess::value(evaluated),
Err(err) => yield Err(err)
}
match evaluate(&values[0], evaluate_with, name) {
Ok(evaluated) => Ok(OutputStream::one(ReturnSuccess::value(evaluated))),
Err(err) => Err(err),
}
};
Ok(stream.to_output_stream())
}
}
#[cfg(test)]

View File

@ -25,7 +25,7 @@ impl WholeStreamCommand for Exit {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
exit(args, registry)
exit(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@ -44,19 +44,20 @@ impl WholeStreamCommand for Exit {
}
}
pub fn exit(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
pub async fn exit(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let args = args.evaluate_once(&registry).await?;
let args = args.evaluate_once(&registry).await?;
if args.call_info.args.has("now") {
yield Ok(ReturnSuccess::Action(CommandAction::Exit));
} else {
yield Ok(ReturnSuccess::Action(CommandAction::LeaveShell));
}
let command_action = if args.call_info.args.has("now") {
CommandAction::Exit
} else {
CommandAction::LeaveShell
};
Ok(stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::action(command_action)))
}
#[cfg(test)]

View File

@ -40,7 +40,7 @@ impl WholeStreamCommand for FromEML {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
from_eml(args, registry)
from_eml(args, registry).await
}
}
@ -77,46 +77,54 @@ fn headerfieldvalue_to_value(tag: &Tag, value: &HeaderFieldValue) -> UntaggedVal
}
}
fn from_eml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn from_eml(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let registry = registry.clone();
let stream = async_stream! {
let (eml_args, mut input): (FromEMLArgs, _) = args.process(&registry).await?;
let value = input.collect_string(tag.clone()).await?;
let (eml_args, input): (FromEMLArgs, _) = args.process(&registry).await?;
let value = input.collect_string(tag.clone()).await?;
let body_preview = eml_args.preview_body.map(|b| b.item).unwrap_or(DEFAULT_BODY_PREVIEW);
let body_preview = eml_args
.preview_body
.map(|b| b.item)
.unwrap_or(DEFAULT_BODY_PREVIEW);
let eml = EmlParser::from_string(value.item)
let eml = EmlParser::from_string(value.item)
.with_body_preview(body_preview)
.parse()
.map_err(|_| ShellError::labeled_error("Could not parse .eml file", "could not parse .eml file", &tag))?;
.map_err(|_| {
ShellError::labeled_error(
"Could not parse .eml file",
"could not parse .eml file",
&tag,
)
})?;
let mut dict = TaggedDictBuilder::new(&tag);
let mut dict = TaggedDictBuilder::new(&tag);
if let Some(subj) = eml.subject {
dict.insert_untagged("Subject", UntaggedValue::string(subj));
}
if let Some(subj) = eml.subject {
dict.insert_untagged("Subject", UntaggedValue::string(subj));
}
if let Some(from) = eml.from {
dict.insert_untagged("From", headerfieldvalue_to_value(&tag, &from));
}
if let Some(from) = eml.from {
dict.insert_untagged("From", headerfieldvalue_to_value(&tag, &from));
}
if let Some(to) = eml.to {
dict.insert_untagged("To", headerfieldvalue_to_value(&tag, &to));
}
if let Some(to) = eml.to {
dict.insert_untagged("To", headerfieldvalue_to_value(&tag, &to));
}
for HeaderField{ name, value } in eml.headers.iter() {
dict.insert_untagged(name, headerfieldvalue_to_value(&tag, &value));
}
for HeaderField { name, value } in eml.headers.iter() {
dict.insert_untagged(name, headerfieldvalue_to_value(&tag, &value));
}
if let Some(body) = eml.body {
dict.insert_untagged("Body", UntaggedValue::string(body));
}
if let Some(body) = eml.body {
dict.insert_untagged("Body", UntaggedValue::string(body));
}
yield ReturnSuccess::value(dict.into_value());
};
Ok(stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
}
#[cfg(test)]

View File

@ -35,7 +35,7 @@ impl WholeStreamCommand for GroupBy {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
group_by(args, registry)
group_by(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@ -71,30 +71,27 @@ impl WholeStreamCommand for GroupBy {
}
}
pub fn group_by(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
pub async fn group_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! {
let (GroupByArgs { column_name }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
yield Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name
))
} else {
match crate::utils::data::group(column_name, &values, None, &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err),
}
let (GroupByArgs { column_name }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name,
))
} else {
match crate::utils::data::group(column_name, &values, None, &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
};
Ok(stream.to_output_stream())
}
}
pub fn group(

View File

@ -42,7 +42,7 @@ impl WholeStreamCommand for GroupByDate {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
group_by_date(args, registry)
group_by_date(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@ -58,50 +58,59 @@ enum Grouper {
ByDate(Option<String>),
}
pub fn group_by_date(
pub async fn group_by_date(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! {
let (GroupByDateArgs { column_name, format }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let (
GroupByDateArgs {
column_name,
format,
},
input,
) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
yield Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name
))
if values.is_empty() {
Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name,
))
} else {
let grouper = if let Some(Tagged { item: fmt, tag: _ }) = format {
Grouper::ByDate(Some(fmt))
} else {
Grouper::ByDate(None)
};
let grouper = if let Some(Tagged { item: fmt, tag }) = format {
Grouper::ByDate(Some(fmt))
} else {
Grouper::ByDate(None)
};
match grouper {
Grouper::ByDate(None) => {
match crate::utils::data::group(column_name, &values, Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))), &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err),
}
match grouper {
Grouper::ByDate(None) => {
match crate::utils::data::group(
column_name,
&values,
Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))),
&name,
) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
Grouper::ByDate(Some(fmt)) => {
match crate::utils::data::group(column_name, &values, Some(Box::new(move |row: &Value| {
row.format(&fmt)
})), &name) {
Ok(grouped) => yield ReturnSuccess::value(grouped),
Err(err) => yield Err(err),
}
}
Grouper::ByDate(Some(fmt)) => {
match crate::utils::data::group(
column_name,
&values,
Some(Box::new(move |row: &Value| row.format(&fmt))),
&name,
) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
}
};
Ok(stream.to_output_stream())
}
}
#[cfg(test)]

View File

@ -38,42 +38,37 @@ impl WholeStreamCommand for MapMaxBy {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
map_max_by(args, registry)
map_max_by(args, registry).await
}
}
pub fn map_max_by(
pub async fn map_max_by(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let stream = async_stream! {
let (MapMaxByArgs { column_name }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let (MapMaxByArgs { column_name }, mut input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
yield Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name
))
if values.is_empty() {
Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name,
))
} else {
let map_by_column = if let Some(column_to_map) = column_name {
Some(column_to_map.item().clone())
} else {
None
};
let map_by_column = if let Some(column_to_map) = column_name {
Some(column_to_map.item().clone())
} else {
None
};
match map_max(&values[0], map_by_column, name) {
Ok(table_maxed) => yield ReturnSuccess::value(table_maxed),
Err(err) => yield Err(err)
}
match map_max(&values[0], map_by_column, name) {
Ok(table_maxed) => Ok(OutputStream::one(ReturnSuccess::value(table_maxed))),
Err(err) => Err(err),
}
};
Ok(stream.to_output_stream())
}
}
#[cfg(test)]

View File

@ -11,6 +11,8 @@ pub struct Mkdir;
#[derive(Deserialize)]
pub struct MkdirArgs {
pub rest: Vec<Tagged<PathBuf>>,
#[serde(rename = "show-created-paths")]
pub show_created_paths: bool,
}
#[async_trait]
@ -20,7 +22,9 @@ impl WholeStreamCommand for Mkdir {
}
fn signature(&self) -> Signature {
Signature::build("mkdir").rest(SyntaxShape::Path, "the name(s) of the path(s) to create")
Signature::build("mkdir")
.rest(SyntaxShape::Path, "the name(s) of the path(s) to create")
.switch("show-created-paths", "show the path(s) created.", Some('s'))
}
fn usage(&self) -> &str {

View File

@ -42,7 +42,7 @@ impl WholeStreamCommand for Open {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
open(args, registry)
open(args, registry).await
}
fn examples(&self) -> Vec<Example> {
@ -54,39 +54,33 @@ impl WholeStreamCommand for Open {
}
}
fn open(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn open(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let cwd = PathBuf::from(args.shell_manager.path());
let full_path = cwd;
let registry = registry.clone();
let stream = async_stream! {
let (OpenArgs { path, raw }, _) = args.process(&registry).await?;
let result = fetch(&full_path, &path.item, path.tag.span).await;
let (OpenArgs { path, raw }, _) = args.process(&registry).await?;
let result = fetch(&full_path, &path.item, path.tag.span).await;
if let Err(e) = result {
yield Err(e);
return;
}
let (file_extension, contents, contents_tag) = result?;
let (file_extension, contents, contents_tag) = result?;
let file_extension = if raw.item {
None
} else {
// If the extension could not be determined via mimetype, try to use the path
// extension. Some file types do not declare their mimetypes (such as bson files).
file_extension.or(path.extension().map(|x| x.to_string_lossy().to_string()))
};
let tagged_contents = contents.into_value(&contents_tag);
if let Some(extension) = file_extension {
yield Ok(ReturnSuccess::Action(CommandAction::AutoConvert(tagged_contents, extension)))
} else {
yield ReturnSuccess::value(tagged_contents);
}
let file_extension = if raw.item {
None
} else {
// If the extension could not be determined via mimetype, try to use the path
// extension. Some file types do not declare their mimetypes (such as bson files).
file_extension.or_else(|| path.extension().map(|x| x.to_string_lossy().to_string()))
};
Ok(stream.to_output_stream())
let tagged_contents = contents.into_value(&contents_tag);
if let Some(extension) = file_extension {
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::AutoConvert(tagged_contents, extension),
)))
} else {
Ok(OutputStream::one(ReturnSuccess::value(tagged_contents)))
}
}
pub async fn fetch(

View File

@ -27,98 +27,106 @@ impl WholeStreamCommand for ToHTML {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_html(args, registry)
to_html(args, registry).await
}
}
fn to_html(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn to_html(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let args = args.evaluate_once(&registry).await?;
let name_tag = args.name_tag();
let input: Vec<Value> = args.input.collect().await;
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = "<html><body>".to_string();
let args = args.evaluate_once(&registry).await?;
let name_tag = args.name_tag();
let input: Vec<Value> = args.input.collect().await;
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = "<html><body>".to_string();
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("<table>");
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("<table>");
output_string.push_str("<tr>");
for header in &headers {
output_string.push_str("<th>");
output_string.push_str(&htmlescape::encode_minimal(&header));
output_string.push_str("</th>");
}
output_string.push_str("</tr>");
output_string.push_str("<tr>");
for header in &headers {
output_string.push_str("<th>");
output_string.push_str(&htmlescape::encode_minimal(&header));
output_string.push_str("</th>");
}
output_string.push_str("</tr>");
}
for row in input {
match row.value {
UntaggedValue::Primitive(Primitive::Binary(b)) => {
// This might be a bit much, but it's fun :)
match row.tag.anchor {
Some(AnchorLocation::Url(f)) |
Some(AnchorLocation::File(f)) => {
let extension = f.split('.').last().map(String::from);
match extension {
Some(s) if ["png", "jpg", "bmp", "gif", "tiff", "jpeg"].contains(&s.to_lowercase().as_str()) => {
output_string.push_str("<img src=\"data:image/");
output_string.push_str(&s);
output_string.push_str(";base64,");
output_string.push_str(&base64::encode(&b));
output_string.push_str("\">");
}
_ => {}
for row in input {
match row.value {
UntaggedValue::Primitive(Primitive::Binary(b)) => {
// This might be a bit much, but it's fun :)
match row.tag.anchor {
Some(AnchorLocation::Url(f)) | Some(AnchorLocation::File(f)) => {
let extension = f.split('.').last().map(String::from);
match extension {
Some(s)
if ["png", "jpg", "bmp", "gif", "tiff", "jpeg"]
.contains(&s.to_lowercase().as_str()) =>
{
output_string.push_str("<img src=\"data:image/");
output_string.push_str(&s);
output_string.push_str(";base64,");
output_string.push_str(&base64::encode(&b));
output_string.push_str("\">");
}
_ => {}
}
_ => {}
}
}
UntaggedValue::Primitive(Primitive::String(ref b)) => {
// This might be a bit much, but it's fun :)
match row.tag.anchor {
Some(AnchorLocation::Url(f)) |
Some(AnchorLocation::File(f)) => {
let extension = f.split('.').last().map(String::from);
match extension {
Some(s) if s.to_lowercase() == "svg" => {
output_string.push_str("<img src=\"data:image/svg+xml;base64,");
output_string.push_str(&base64::encode(&b.as_bytes()));
output_string.push_str("\">");
continue;
}
_ => {}
}
}
_ => {}
}
output_string.push_str(&(htmlescape::encode_minimal(&format_leaf(&row.value).plain_string(100_000)).replace("\n", "<br>")));
}
UntaggedValue::Row(row) => {
output_string.push_str("<tr>");
for header in &headers {
let data = row.get_data(header);
output_string.push_str("<td>");
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
output_string.push_str("</td>");
}
output_string.push_str("</tr>");
}
p => {
output_string.push_str(&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000)).replace("\n", "<br>")));
_ => {}
}
}
UntaggedValue::Primitive(Primitive::String(ref b)) => {
// This might be a bit much, but it's fun :)
match row.tag.anchor {
Some(AnchorLocation::Url(f)) | Some(AnchorLocation::File(f)) => {
let extension = f.split('.').last().map(String::from);
match extension {
Some(s) if s.to_lowercase() == "svg" => {
output_string.push_str("<img src=\"data:image/svg+xml;base64,");
output_string.push_str(&base64::encode(&b.as_bytes()));
output_string.push_str("\">");
continue;
}
_ => {}
}
}
_ => {}
}
output_string.push_str(
&(htmlescape::encode_minimal(&format_leaf(&row.value).plain_string(100_000))
.replace("\n", "<br>")),
);
}
UntaggedValue::Row(row) => {
output_string.push_str("<tr>");
for header in &headers {
let data = row.get_data(header);
output_string.push_str("<td>");
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
output_string.push_str("</td>");
}
output_string.push_str("</tr>");
}
p => {
output_string.push_str(
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))
.replace("\n", "<br>")),
);
}
}
}
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("</table>");
}
output_string.push_str("</body></html>");
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("</table>");
}
output_string.push_str("</body></html>");
yield ReturnSuccess::value(UntaggedValue::string(output_string).into_value(name_tag));
};
Ok(stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output_string).into_value(name_tag),
)))
}
#[cfg(test)]

View File

@ -26,55 +26,58 @@ impl WholeStreamCommand for ToMarkdown {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_html(args, registry)
to_html(args, registry).await
}
}
fn to_html(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn to_html(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let args = args.evaluate_once(&registry).await?;
let name_tag = args.name_tag();
let input: Vec<Value> = args.input.collect().await;
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = String::new();
let args = args.evaluate_once(&registry).await?;
let name_tag = args.name_tag();
let input: Vec<Value> = args.input.collect().await;
let headers = nu_protocol::merge_descriptors(&input);
let mut output_string = String::new();
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
output_string.push_str("|");
for header in &headers {
output_string.push_str(&htmlescape::encode_minimal(&header));
output_string.push_str("|");
for header in &headers {
output_string.push_str(&htmlescape::encode_minimal(&header));
output_string.push_str("|");
}
output_string.push_str("\n|");
for _ in &headers {
output_string.push_str("-");
output_string.push_str("|");
}
output_string.push_str("\n");
}
output_string.push_str("\n|");
for _ in &headers {
output_string.push_str("-");
output_string.push_str("|");
}
output_string.push_str("\n");
}
for row in input {
match row.value {
UntaggedValue::Row(row) => {
for row in input {
match row.value {
UntaggedValue::Row(row) => {
output_string.push_str("|");
for header in &headers {
let data = row.get_data(header);
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
output_string.push_str("|");
for header in &headers {
let data = row.get_data(header);
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
output_string.push_str("|");
}
output_string.push_str("\n");
}
p => {
output_string.push_str(&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))));
output_string.push_str("\n");
}
output_string.push_str("\n");
}
p => {
output_string.push_str(
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))),
);
output_string.push_str("\n");
}
}
}
yield ReturnSuccess::value(UntaggedValue::string(output_string).into_value(name_tag));
};
Ok(stream.to_output_stream())
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output_string).into_value(name_tag),
)))
}
#[cfg(test)]

View File

@ -27,7 +27,7 @@ impl WholeStreamCommand for ToSQLite {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_sqlite(args, registry)
to_sqlite(args, registry).await
}
fn is_binary(&self) -> bool {
@ -56,7 +56,7 @@ impl WholeStreamCommand for ToDB {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_sqlite(args, registry)
to_sqlite(args, registry).await
}
fn is_binary(&self) -> bool {
@ -203,26 +203,23 @@ fn sqlite_input_stream_to_bytes(values: Vec<Value>) -> Result<Value, std::io::Er
Ok(UntaggedValue::binary(out).into_value(tag))
}
fn to_sqlite(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn to_sqlite(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let stream = async_stream! {
let args = args.evaluate_once(&registry).await?;
let name_tag = args.name_tag();
let input: Vec<Value> = args.input.collect().await;
let args = args.evaluate_once(&registry).await?;
let name_tag = args.name_tag();
let input: Vec<Value> = args.input.collect().await;
match sqlite_input_stream_to_bytes(input) {
Ok(out) => yield ReturnSuccess::value(out),
_ => {
yield Err(ShellError::labeled_error(
"Expected a table with SQLite-compatible structure from pipeline",
"requires SQLite-compatible input",
name_tag,
))
},
}
};
Ok(stream.to_output_stream())
match sqlite_input_stream_to_bytes(input) {
Ok(out) => Ok(OutputStream::one(ReturnSuccess::value(out))),
_ => Err(ShellError::labeled_error(
"Expected a table with SQLite-compatible structure from pipeline",
"requires SQLite-compatible input",
name_tag,
)),
}
}
#[cfg(test)]

View File

@ -26,9 +26,22 @@ impl WholeStreamCommand for ToTOML {
) -> Result<OutputStream, ShellError> {
to_toml(args, registry)
}
// TODO: add an example here. What commands to run to get a Row(Dictionary)?
// fn examples(&self) -> Vec<Example> {
// vec![
// Example {
// description:
// "Outputs an TOML string representing TOML document",
// example: "echo [1 2 3] | to json",
// result: Some(vec![Value::from("[1,2,3]")]),
// },
// ]
// }
}
pub fn value_to_toml_value(v: &Value) -> Result<toml::Value, ShellError> {
// Helper method to recursively convert nu_protocol::Value -> toml::Value
// This shouldn't be called at the top-level
fn helper(v: &Value) -> Result<toml::Value, ShellError> {
Ok(match &v.value {
UntaggedValue::Primitive(Primitive::Boolean(b)) => toml::Value::Boolean(*b),
UntaggedValue::Primitive(Primitive::Bytes(b)) => toml::Value::Integer(*b as i64),
@ -66,7 +79,6 @@ pub fn value_to_toml_value(v: &Value) -> Result<toml::Value, ShellError> {
})
.collect::<Result<Vec<toml::Value>, ShellError>>()?,
),
UntaggedValue::Table(l) => toml::Value::Array(collect_values(l)?),
UntaggedValue::Error(e) => return Err(e.clone()),
UntaggedValue::Block(_) => toml::Value::String("<Block>".to_string()),
@ -77,18 +89,47 @@ pub fn value_to_toml_value(v: &Value) -> Result<toml::Value, ShellError> {
UntaggedValue::Row(o) => {
let mut m = toml::map::Map::new();
for (k, v) in o.entries.iter() {
m.insert(k.clone(), value_to_toml_value(v)?);
m.insert(k.clone(), helper(v)?);
}
toml::Value::Table(m)
}
})
}
/// Converts a nu_protocol::Value into a toml::Value
/// Will return a Shell Error, if the Nu Value is not a valid top-level TOML Value
pub fn value_to_toml_value(v: &Value) -> Result<toml::Value, ShellError> {
match &v.value {
UntaggedValue::Row(o) => {
let mut m = toml::map::Map::new();
for (k, v) in o.entries.iter() {
m.insert(k.clone(), helper(v)?);
}
Ok(toml::Value::Table(m))
}
UntaggedValue::Primitive(Primitive::String(s)) => {
// Attempt to de-serialize the String
toml::de::from_str(s).map_err(|_| {
ShellError::labeled_error(
format!("{:?} unable to de-serialize string to TOML", s),
"invalid TOML",
v.tag(),
)
})
}
_ => Err(ShellError::labeled_error(
format!("{:?} is not a valid top-level TOML", v.value),
"invalid TOML",
v.tag(),
)),
}
}
fn collect_values(input: &[Value]) -> Result<Vec<toml::Value>, ShellError> {
let mut out = vec![];
for value in input {
out.push(value_to_toml_value(value)?);
out.push(helper(value)?);
}
Ok(out)
@ -141,7 +182,8 @@ fn to_toml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
#[cfg(test)]
mod tests {
use super::ToTOML;
use super::*;
use nu_protocol::Dictionary;
#[test]
fn examples_work_as_expected() {
@ -149,4 +191,60 @@ mod tests {
test_examples(ToTOML {})
}
#[test]
fn test_value_to_toml_value() {
//
// Positive Tests
//
// Dictionary -> What we do in "crates/nu-cli/src/data/config.rs" to write the config file
let mut m = indexmap::IndexMap::new();
m.insert("rust".to_owned(), Value::from("editor"));
m.insert("is".to_owned(), Value::nothing());
m.insert(
"features".to_owned(),
UntaggedValue::Table(vec![
UntaggedValue::string("hello").into_untagged_value(),
UntaggedValue::string("array").into_untagged_value(),
])
.into_untagged_value(),
);
let tv = value_to_toml_value(&UntaggedValue::Row(Dictionary::new(m)).into_untagged_value())
.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(&Value::from(
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.6", features = ["serde"] }
"#,
))
.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(&Value::from("not_valid"))
.expect_err("Expected non-valid toml (String) to cause error!");
value_to_toml_value(&UntaggedValue::Table(vec![Value::from("1")]).into_untagged_value())
.expect_err("Expected non-valid toml (Table) to cause error!");
}
}

View File

@ -51,10 +51,10 @@ impl DirectorySpecificEnvironment {
let current_dir = std::env::current_dir()?;
let working_dir = Some(current_dir.as_path());
let keyvals_to_restore = self
self
.overwritten_env_values
.iter()
.filter_map(|(directory, keyvals)| {
.filter(|(directory, keyvals)| {
while let Some(wdir) = working_dir {
if &wdir == directory {
return false;
@ -66,6 +66,8 @@ impl DirectorySpecificEnvironment {
})
.collect();
let mut keyvals_to_restore = IndexMap::new();
Ok(keyvals_to_restore)
}

View File

@ -44,7 +44,7 @@ pub use crate::data::primitive;
pub use crate::data::value;
pub use crate::env::environment_syncer::EnvironmentSyncer;
pub use crate::env::host::BasicHost;
pub use crate::stream::OutputStream;
pub use crate::stream::{InputStream, InterruptibleStream, OutputStream};
pub use nu_value_ext::ValueExt;
pub use num_traits::cast::ToPrimitive;

View File

@ -396,11 +396,15 @@ impl Shell for FilesystemShell {
fn mkdir(
&self,
MkdirArgs { rest: directories }: MkdirArgs,
MkdirArgs {
rest: directories,
show_created_paths,
}: MkdirArgs,
name: Tag,
path: &str,
) -> Result<OutputStream, ShellError> {
let path = Path::new(path);
let mut stream = VecDeque::new();
if directories.is_empty() {
return Err(ShellError::labeled_error(
@ -413,7 +417,7 @@ impl Shell for FilesystemShell {
for dir in directories.iter() {
let create_at = path.join(&dir.item);
let dir_res = std::fs::create_dir_all(create_at);
let dir_res = std::fs::create_dir_all(&create_at);
if let Err(reason) = dir_res {
return Err(ShellError::labeled_error(
reason.to_string(),
@ -421,9 +425,13 @@ impl Shell for FilesystemShell {
dir.tag(),
));
}
if show_created_paths {
let val = format!("{:}", create_at.to_string_lossy()).into();
stream.push_back(Ok(ReturnSuccess::Value(val)));
}
}
Ok(OutputStream::empty())
Ok(stream.into())
}
fn mv(