diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 10e997054d..161801e261 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -34,7 +34,6 @@ impl Highlighter for NuHighlighter { .to_string(); output.push((Style::new(), gap)); } - let next_token = line [(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)] .to_string(); diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index f58ebd5795..d840fb7d33 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -16,4 +16,4 @@ nu-table = { path = "../nu-table" } glob = "0.3.0" thiserror = "1.0.29" sysinfo = "0.20.4" -chrono = { version="0.4.19", features=["serde"] } \ No newline at end of file +chrono = { version="0.4.19", features=["serde"] } diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 3472f4d8b3..7418026a56 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -7,6 +7,7 @@ mod hide; mod if_; mod let_; mod module; +mod source; mod use_; pub use alias::Alias; @@ -18,4 +19,5 @@ pub use hide::Hide; pub use if_::If; pub use let_::Let; pub use module::Module; +pub use source::Source; pub use use_::Use; diff --git a/crates/nu-command/src/core_commands/source.rs b/crates/nu-command/src/core_commands/source.rs new file mode 100644 index 0000000000..aacd3564cd --- /dev/null +++ b/crates/nu-command/src/core_commands/source.rs @@ -0,0 +1,43 @@ +use nu_engine::{eval_block, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; + +/// Source a file for environment variables. +pub struct Source; + +impl Command for Source { + fn name(&self) -> &str { + "source" + } + + fn signature(&self) -> Signature { + Signature::build("source").required( + "filename", + SyntaxShape::Filepath, + "the filepath to the script file to source", + ) + } + + fn usage(&self) -> &str { + "Runs a script file in the current context." + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + // Note: this hidden positional is the block_id that corresponded to the 0th position + // it is put here by the parser + let block_id: i64 = call.req(context, 1)?; + + let block = context + .engine_state + .borrow() + .get_block(block_id as usize) + .clone(); + eval_block(context, &block, input) + } +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c36e7ea906..fd2ea37555 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -50,6 +50,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Git)); working_set.add_decl(Box::new(GitCheckout)); + working_set.add_decl(Box::new(Source)); + let sig = Signature::build("exit"); working_set.add_decl(sig.predeclare()); let sig = Signature::build("vars"); diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index d093cd88ce..3abad8e122 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -3,12 +3,13 @@ use nu_protocol::{ engine::StateWorkingSet, span, DeclId, Span, SyntaxShape, Type, }; +use std::path::Path; use crate::{ lex, lite_parse, parser::{ - check_name, garbage, garbage_statement, parse_block_expression, parse_import_pattern, - parse_internal_call, parse_signature, parse_string, + check_name, garbage, garbage_statement, parse, parse_block_expression, + parse_import_pattern, parse_internal_call, parse_signature, parse_string, }, ParseError, }; @@ -765,3 +766,95 @@ pub fn parse_let( )), ) } + +pub fn parse_source( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + let name = working_set.get_span_contents(spans[0]); + + if name == b"source" { + if let Some(decl_id) = working_set.find_decl(b"source") { + // Is this the right call to be using here? + // Some of the others (`parse_let`) use it, some of them (`parse_hide`) don't. + let (call, call_span, err) = + parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + + // Command and one file name + if spans.len() >= 2 { + let name_expr = working_set.get_span_contents(spans[1]); + if let Ok(filename) = String::from_utf8(name_expr.to_vec()) { + let source_file = Path::new(&filename); + + let path = source_file; + let contents = std::fs::read(path); + + if let Ok(contents) = contents { + // This will load the defs from the file into the + // working set, if it was a successful parse. + let (block, err) = parse( + working_set, + path.file_name().and_then(|x| x.to_str()), + &contents, + false, + ); + + if err.is_some() { + // Unsuccessful parse of file + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(&spans[1..]), + ty: Type::Unknown, + custom_completion: None, + }])), + // Return the file parse error + err, + ); + } else { + // Save the block into the working set + let block_id = working_set.add_block(block); + + let mut call_with_block = call; + + // Adding this expression to the positional creates a syntax highlighting error + // after writing `source example.nu` + call_with_block.positional.push(Expression { + expr: Expr::Int(block_id as i64), + span: spans[1], + ty: Type::Unknown, + custom_completion: None, + }); + + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call_with_block), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + None, + ); + } + } + } + } + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + err, + ); + } + } + ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: source statement unparseable".into(), + span(spans), + )), + ) +} diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index e1a4f62867..8cf6351c57 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1,5 +1,6 @@ use crate::{ lex, lite_parse, + parse_keywords::parse_source, type_check::{math_result_type, type_compatible}, LiteBlock, ParseError, Token, TokenContents, }; @@ -2870,6 +2871,7 @@ pub fn parse_statement( b"alias" => parse_alias(working_set, spans), b"module" => parse_module(working_set, spans), b"use" => parse_use(working_set, spans), + b"source" => parse_source(working_set, spans), b"export" => ( garbage_statement(spans), Some(ParseError::UnexpectedKeyword("export".into(), spans[0])), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 1e634964a4..afc1f90161 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -247,6 +247,7 @@ impl Value { } } + /// Follow a given column path into the value: for example accessing nth elements in a stream or list pub fn follow_cell_path(self, cell_path: &[PathMember]) -> Result { let mut current = self; for member in cell_path { diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 38fb286ac2..369ff03e87 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use std::cmp::Ordering; +/// A Range is an iterator over integers. use crate::{ ast::{RangeInclusion, RangeOperator}, *,