diff --git a/Cargo.lock b/Cargo.lock index 3215ea8508..5ba1ed0793 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1696,6 +1696,7 @@ dependencies = [ "toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "toml-query 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3181,6 +3182,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 21d32ad171..f0789c3375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ crossterm = "0.9.6" tempfile = "3.1.0" image = "0.21.2" semver = "0.9.0" +uuid = {version = "0.7.4", features = [ "v4", "serde" ]} [dev-dependencies] pretty_assertions = "0.6.1" diff --git a/src/cli.rs b/src/cli.rs index a36e28e075..741f6e5739 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -494,6 +494,7 @@ fn classify_command( Ok(ClassifiedCommand::Internal(InternalCommand { command, name_span: Some(head.span().clone()), + span_sources: context.span_sources.clone(), args, })) } diff --git a/src/commands/classified.rs b/src/commands/classified.rs index 7d3fd288f0..b6f2fe9757 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -1,13 +1,16 @@ use crate::commands::command::Sink; +use crate::context::SpanSource; use crate::parser::{registry::Args, Span, Spanned, TokenNode}; use crate::prelude::*; use bytes::{BufMut, BytesMut}; use futures::stream::StreamExt; use futures_codec::{Decoder, Encoder, Framed}; use log::{log_enabled, trace}; +use std::collections::HashMap; use std::io::{Error, ErrorKind}; use std::sync::Arc; use subprocess::Exec; +use uuid::Uuid; /// A simple `Codec` implementation that splits up data into lines. pub struct LinesCodec {} @@ -116,6 +119,7 @@ impl SinkCommand { crate struct InternalCommand { crate command: Arc, crate name_span: Option, + crate span_sources: HashMap, crate args: Args, } @@ -134,8 +138,13 @@ impl InternalCommand { let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input.objects); - let result = - context.run_command(self.command, self.name_span.clone(), self.args, objects)?; + let result = context.run_command( + self.command, + self.name_span.clone(), + self.span_sources, + self.args, + objects, + )?; let mut result = result.values; @@ -146,6 +155,9 @@ impl InternalCommand { CommandAction::ChangePath(path) => { context.env.lock().unwrap().path = path; } + CommandAction::AddSpanSource(uuid, span_source) => { + context.add_span_source(uuid, span_source); + } CommandAction::Exit => std::process::exit(0), }, diff --git a/src/commands/command.rs b/src/commands/command.rs index 6dd31e216c..cdd0b1a5c4 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -1,3 +1,4 @@ +use crate::context::SpanSource; use crate::errors::ShellError; use crate::object::Value; use crate::parser::{ @@ -7,7 +8,9 @@ use crate::parser::{ use crate::prelude::*; use getset::Getters; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::path::PathBuf; +use uuid::Uuid; #[derive(Getters)] #[get = "crate"] @@ -15,6 +18,7 @@ pub struct CommandArgs { pub host: Arc>, pub env: Arc>, pub name_span: Option, + pub span_sources: HashMap, pub args: Args, pub input: InputStream, } @@ -49,6 +53,7 @@ impl CommandArgs { pub struct SinkCommandArgs { pub ctx: Context, pub name_span: Option, + pub span_sources: HashMap, pub args: Args, pub input: Vec>, } @@ -56,6 +61,7 @@ pub struct SinkCommandArgs { #[derive(Debug, Serialize, Deserialize)] pub enum CommandAction { ChangePath(PathBuf), + AddSpanSource(Uuid, SpanSource), Exit, } @@ -82,6 +88,10 @@ impl ReturnSuccess { Ok(ReturnSuccess::Value(input.into())) } + pub fn action(input: CommandAction) -> ReturnValue { + Ok(ReturnSuccess::Action(input)) + } + pub fn spanned_value(input: Value, span: Span) -> ReturnValue { Ok(ReturnSuccess::Value(Spanned::from_item(input, span))) } diff --git a/src/commands/open.rs b/src/commands/open.rs index fda4fa3350..5ca0731510 100644 --- a/src/commands/open.rs +++ b/src/commands/open.rs @@ -1,3 +1,4 @@ +use crate::context::SpanSource; use crate::errors::ShellError; use crate::object::{Primitive, Switch, Value}; use crate::parser::parse::span::Span; @@ -5,6 +6,7 @@ use crate::prelude::*; use mime::Mime; use std::path::{Path, PathBuf}; use std::str::FromStr; +use uuid::Uuid; command! { Open as open(args, path: Spanned, --raw: Switch,) { @@ -21,19 +23,7 @@ command! { let path_str = path.to_str().ok_or(ShellError::type_error("Path", "invalid path".spanned(path.span)))?; - let (file_extension, contents, contents_span) = fetch(&full_path, path_str, path.span)?; - // let (file_extension, contents, contents_span) = match &args.expect_nth(0)?.item { - // Value::Primitive(Primitive::String(s)) => fetch(&full_path, s, args.expect_nth(0)?.span)?, - // _ => { - // return Err(ShellError::labeled_error( - // "Expected string value for filename", - // "expected filename", - // args.expect_nth(0)?.span, - // )); - // } - // }; - - let mut stream = VecDeque::new(); + let (file_extension, contents, contents_span, span_source) = fetch(&full_path, path_str, path.span)?; let file_extension = if raw.is_present() { None @@ -41,6 +31,13 @@ command! { file_extension }; + let mut stream = VecDeque::new(); + + if let Some(uuid) = contents_span.source { + // If we have loaded something, track its source + stream.push_back(ReturnSuccess::action(CommandAction::AddSpanSource(uuid, span_source))) + } + match contents { Value::Primitive(Primitive::String(string)) => stream.push_back(ReturnSuccess::value(parse_as_value( @@ -62,7 +59,7 @@ pub fn fetch( cwd: &PathBuf, location: &str, span: Span, -) -> Result<(Option, Value, Span), ShellError> { +) -> Result<(Option, Value, Span, SpanSource), ShellError> { let mut cwd = cwd.clone(); if location.starts_with("http:") || location.starts_with("https:") { let response = reqwest::get(location); @@ -74,12 +71,14 @@ pub fn fetch( (mime::APPLICATION, mime::XML) => Ok(( Some("xml".to_string()), Value::string(r.text().unwrap()), - span, + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::Url(r.url().to_string()), )), (mime::APPLICATION, mime::JSON) => Ok(( Some("json".to_string()), Value::string(r.text().unwrap()), - span, + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::Url(r.url().to_string()), )), (mime::APPLICATION, mime::OCTET_STREAM) => { let mut buf: Vec = vec![]; @@ -90,7 +89,12 @@ pub fn fetch( span, ) })?; - Ok((None, Value::Binary(buf), span)) + Ok(( + None, + Value::Binary(buf), + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::Url(r.url().to_string()), + )) } (mime::IMAGE, image_ty) => { let mut buf: Vec = vec![]; @@ -101,12 +105,18 @@ pub fn fetch( span, ) })?; - Ok((Some(image_ty.to_string()), Value::Binary(buf), span)) + Ok(( + Some(image_ty.to_string()), + Value::Binary(buf), + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::Url(r.url().to_string()), + )) } (mime::TEXT, mime::HTML) => Ok(( Some("html".to_string()), Value::string(r.text().unwrap()), - span, + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::Url(r.url().to_string()), )), (mime::TEXT, mime::PLAIN) => { let path_extension = r @@ -120,16 +130,27 @@ pub fn fetch( .map(|name| name.to_string_lossy().to_string()) }); - Ok((path_extension, Value::string(r.text().unwrap()), span)) + Ok(( + path_extension, + Value::string(r.text().unwrap()), + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::Url(r.url().to_string()), + )) } (ty, sub_ty) => Ok(( None, Value::string(format!("Not yet support MIME type: {} {}", ty, sub_ty)), - span, + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::Url(r.url().to_string()), )), } } - None => Ok((None, Value::string(format!("No content type found")), span)), + None => Ok(( + None, + Value::string(format!("No content type found")), + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::Url(r.url().to_string()), + )), }, Err(_) => { return Err(ShellError::labeled_error( @@ -147,9 +168,15 @@ pub fn fetch( cwd.extension() .map(|name| name.to_string_lossy().to_string()), Value::string(s), - span, + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::File(cwd.to_string_lossy().to_string()), + )), + Err(_) => Ok(( + None, + Value::Binary(bytes), + Span::unknown_with_uuid(Uuid::new_v4()), + SpanSource::File(cwd.to_string_lossy().to_string()), )), - Err(_) => Ok((None, Value::Binary(bytes), span)), }, Err(_) => { return Err(ShellError::labeled_error( diff --git a/src/commands/trim.rs b/src/commands/trim.rs index e0f2d42b0c..838875faa0 100644 --- a/src/commands/trim.rs +++ b/src/commands/trim.rs @@ -2,8 +2,6 @@ use crate::errors::ShellError; use crate::object::Value; use crate::prelude::*; -// TODO: "Amount remaining" wrapper - pub fn trim(args: CommandArgs) -> Result { let input = args.input; diff --git a/src/context.rs b/src/context.rs index 05be6ae0f3..654e8a3a7a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -4,15 +4,24 @@ use crate::parser::{ Span, }; use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; use indexmap::IndexMap; +use std::collections::HashMap; use std::error::Error; use std::sync::Arc; +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum SpanSource { + Url(String), + File(String), +} #[derive(Clone)] pub struct Context { commands: IndexMap>, sinks: IndexMap>, + crate span_sources: HashMap, crate host: Arc>, crate env: Arc>, } @@ -22,6 +31,7 @@ impl Context { Ok(Context { commands: indexmap::IndexMap::new(), sinks: indexmap::IndexMap::new(), + span_sources: HashMap::new(), host: Arc::new(Mutex::new(crate::env::host::BasicHost)), env: Arc::new(Mutex::new(Environment::basic()?)), }) @@ -39,6 +49,10 @@ impl Context { } } + pub fn add_span_source(&mut self, uuid: Uuid, span_source: SpanSource) { + self.span_sources.insert(uuid, span_source); + } + crate fn has_sink(&self, name: &str) -> bool { self.sinks.contains_key(name) } @@ -57,6 +71,7 @@ impl Context { let command_args = SinkCommandArgs { ctx: self.clone(), name_span, + span_sources: self.span_sources.clone(), args, input, }; @@ -80,6 +95,7 @@ impl Context { &mut self, command: Arc, name_span: Option, + span_sources: HashMap, args: Args, input: InputStream, ) -> Result { @@ -87,6 +103,7 @@ impl Context { host: self.host.clone(), env: self.env.clone(), name_span, + span_sources, args, input, }; diff --git a/src/errors.rs b/src/errors.rs index 3b84eba147..8d9f44fd97 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,7 +17,12 @@ impl Description { pub fn from(item: Spanned>) -> Description { match item { Spanned { - span: Span { start: 0, end: 0 }, + span: + Span { + start: 0, + end: 0, + source: None, + }, item, } => Description::Synthetic(item.into()), Spanned { span, item } => Description::Source(Spanned::from_item(item.into(), span)), diff --git a/src/parser/parse/span.rs b/src/parser/parse/span.rs index d82f43468f..7d6aa7334d 100644 --- a/src/parser/parse/span.rs +++ b/src/parser/parse/span.rs @@ -3,6 +3,7 @@ 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, @@ -75,13 +76,17 @@ impl Spanned { pub struct Span { crate start: usize, crate end: usize, - // source: &'source str, + pub source: Option, } impl From> for Span { fn from(input: Option) -> Span { match input { - None => Span { start: 0, end: 0 }, + None => Span { + start: 0, + end: 0, + source: None, + }, Some(span) => span, } } @@ -104,6 +109,7 @@ impl From> for Span { Span { start: input.offset, end: input.offset + input.fragment.len(), + source: None, } } } @@ -113,6 +119,7 @@ impl From<(nom5_locate::LocatedSpan, nom5_locate::LocatedSpan)> for Spa Span { start: input.0.offset, end: input.1.offset, + source: None, } } } @@ -122,6 +129,7 @@ impl From<(usize, usize)> for Span { Span { start: input.0, end: input.1, + source: None, } } } @@ -131,13 +139,26 @@ impl From<&std::ops::Range> for Span { Span { start: input.start, end: input.end, + source: None, } } } impl Span { pub fn unknown() -> Span { - Span { start: 0, end: 0 } + 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 { @@ -154,6 +175,7 @@ impl language_reporting::ReportingSpan for Span { Span { start, end: self.end, + source: None, } } @@ -161,6 +183,7 @@ impl language_reporting::ReportingSpan for Span { Span { start: self.start, end, + source: None, } }