diff --git a/README.md b/README.md index 810885d651..3831cea61e 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat | skip amount | Skip a number of rows | | first amount | Show only the first number of rows | | str (field) | Apply string function. Optional use the field of a table | +| tags | Read the tags (metadata) for values | | to-array | Collapse rows into a single list | | to-json | Convert table into .json text | | to-toml | Convert table into .toml text | diff --git a/src/cli.rs b/src/cli.rs index 5d282ddffd..e57824fb13 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -175,6 +175,7 @@ pub async fn cli() -> Result<(), Box> { command("to-toml", Box::new(to_toml::to_toml)), command("to-yaml", Box::new(to_yaml::to_yaml)), command("sort-by", Box::new(sort_by::sort_by)), + command("tags", Box::new(tags::tags)), Arc::new(Remove), Arc::new(Copycp), Arc::new(Open), @@ -496,7 +497,6 @@ fn classify_command( Ok(ClassifiedCommand::Internal(InternalCommand { command, name_span: Some(head.span().clone()), - source_map: context.source_map.clone(), args, })) } diff --git a/src/commands.rs b/src/commands.rs index 6e2d348c30..862458c0bf 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -34,6 +34,7 @@ crate mod sort_by; crate mod split_column; crate mod split_row; crate mod table; +crate mod tags; crate mod to_array; crate mod to_csv; crate mod to_json; diff --git a/src/commands/classified.rs b/src/commands/classified.rs index 8d51089335..47c7d1fc6c 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -1,5 +1,4 @@ use crate::commands::command::Sink; -use crate::context::SourceMap; use crate::parser::{registry::Args, TokenNode}; use crate::prelude::*; use bytes::{BufMut, BytesMut}; @@ -113,7 +112,6 @@ impl SinkCommand { crate struct InternalCommand { crate command: Arc, crate name_span: Option, - crate source_map: SourceMap, crate args: Args, } @@ -135,7 +133,7 @@ impl InternalCommand { let result = context.run_command( self.command, self.name_span.clone(), - self.source_map, + context.source_map.clone(), self.args, objects, )?; diff --git a/src/commands/tags.rs b/src/commands/tags.rs new file mode 100644 index 0000000000..c34e458ea9 --- /dev/null +++ b/src/commands/tags.rs @@ -0,0 +1,32 @@ +use crate::errors::ShellError; +use crate::object::{TaggedDictBuilder, Value}; +use crate::prelude::*; + +pub fn tags(args: CommandArgs) -> Result { + let source_map = args.call_info.source_map.clone(); + Ok(args + .input + .values + .map(move |v| { + let mut tags = TaggedDictBuilder::new(v.span()); + { + let span = v.span(); + let mut dict = TaggedDictBuilder::new(v.span()); + dict.insert("start", Value::int(span.start as i64)); + dict.insert("end", Value::int(span.end as i64)); + match span.source.map(|x| source_map.get(&x)).flatten() { + Some(SpanSource::File(source)) => { + dict.insert("source", Value::string(source)); + } + Some(SpanSource::Url(source)) => { + dict.insert("source", Value::string(source)); + } + _ => {} + } + tags.insert_tagged("span", dict.into_tagged_value()); + } + + tags.into_tagged_value() + }) + .to_output_stream()) +} diff --git a/src/object/meta.rs b/src/object/meta.rs new file mode 100644 index 0000000000..8fb815a67f --- /dev/null +++ b/src/object/meta.rs @@ -0,0 +1,203 @@ +use crate::Text; +use derive_new::new; +use getset::Getters; +use serde::Serialize; +use serde_derive::Deserialize; +use uuid::Uuid; + +#[derive( + new, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash, Getters, +)] +#[get = "crate"] +pub struct Tagged { + pub tag: Tag, + pub item: T, +} + +pub trait TaggedItem: Sized { + fn tagged(self, span: impl Into) -> Tagged { + Tagged::from_item(self, span.into()) + } + + // For now, this is a temporary facility. In many cases, there are other useful spans that we + // could be using, such as the original source spans of JSON or Toml files, but we don't yet + // have the infrastructure to make that work. + fn tagged_unknown(self) -> Tagged { + Tagged::from_item(self, (0, 0)) + } +} + +impl TaggedItem for T {} + +impl std::ops::Deref for Tagged { + type Target = T; + + fn deref(&self) -> &T { + &self.item + } +} + +impl Tagged { + pub fn tagged(self, span: impl Into) -> Tagged { + Tagged::from_item(self.item, span.into()) + } + + pub fn from_item(item: T, span: impl Into) -> Tagged { + Tagged { + item, + tag: Tag { span: span.into() }, + } + } + + pub fn map(self, input: impl FnOnce(T) -> U) -> Tagged { + let span = self.span(); + + let mapped = input(self.item); + Tagged::from_item(mapped, span) + } + + crate fn copy_span(&self, output: U) -> Tagged { + let span = self.span(); + + Tagged::from_item(output, span) + } + + pub fn source(&self, source: &Text) -> Text { + Text::from(self.span().slice(source)) + } + + pub fn span(&self) -> Span { + self.tag.span + } +} + +impl From<&Tagged> for Span { + fn from(input: &Tagged) -> Span { + input.span() + } +} + +impl From<&Span> for Span { + fn from(input: &Span) -> Span { + *input + } +} + +impl From> for Span { + fn from(input: nom5_locate::LocatedSpan<&str>) -> Span { + Span { + start: input.offset, + end: input.offset + input.fragment.len(), + source: None, + } + } +} + +impl From<(nom5_locate::LocatedSpan, nom5_locate::LocatedSpan)> for Span { + fn from(input: (nom5_locate::LocatedSpan, nom5_locate::LocatedSpan)) -> Span { + Span { + start: input.0.offset, + end: input.1.offset, + source: None, + } + } +} + +impl From<(usize, usize)> for Span { + fn from(input: (usize, usize)) -> Span { + Span { + start: input.0, + end: input.1, + source: None, + } + } +} + +impl From<&std::ops::Range> for Span { + fn from(input: &std::ops::Range) -> Span { + Span { + start: input.start, + end: input.end, + source: None, + } + } +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize, Hash, Getters, +)] +pub struct Tag { + pub span: Span, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize, Hash)] +pub struct Span { + crate start: usize, + crate end: usize, + pub source: Option, +} + +impl From> for Span { + fn from(input: Option) -> Span { + match input { + None => Span { + start: 0, + end: 0, + source: None, + }, + Some(span) => span, + } + } +} + +impl Span { + pub fn unknown() -> Span { + Span { + start: 0, + end: 0, + source: None, + } + } + + pub fn unknown_with_uuid(uuid: Uuid) -> Span { + Span { + start: 0, + end: 0, + source: Some(uuid), + } + } + + pub fn is_unknown(&self) -> bool { + self.start == 0 && self.end == 0 + } + + pub fn slice(&self, source: &'a str) -> &'a str { + &source[self.start..self.end] + } +} + +impl language_reporting::ReportingSpan for Span { + fn with_start(&self, start: usize) -> Self { + Span { + start, + end: self.end, + source: None, + } + } + + fn with_end(&self, end: usize) -> Self { + Span { + start: self.start, + end, + source: None, + } + } + + fn start(&self) -> usize { + self.start + } + + fn end(&self) -> usize { + self.end + } +} diff --git a/src/prelude.rs b/src/prelude.rs index d1110050db..56a655beac 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -36,7 +36,7 @@ crate use crate::cli::MaybeOwned; crate use crate::commands::command::{ Command, CommandAction, CommandArgs, ReturnSuccess, ReturnValue, Sink, SinkCommandArgs, }; -crate use crate::context::Context; +crate use crate::context::{Context, SpanSource}; crate use crate::env::host::handle_unexpected; crate use crate::env::{Environment, Host}; crate use crate::errors::ShellError;