Merge branch 'main' into ls_grid_output

This commit is contained in:
Darren Schroeder 2021-10-07 11:07:21 -05:00
commit 698f768a06
18 changed files with 456 additions and 8 deletions

2
Cargo.lock generated
View file

@ -837,7 +837,7 @@ dependencies = [
[[package]]
name = "reedline"
version = "0.2.0"
source = "git+https://github.com/jntrnr/reedline?branch=main#88bded3417e7f6c1242b444f403448de583357f0"
source = "git+https://github.com/nushell/reedline?branch=main#88bded3417e7f6c1242b444f403448de583357f0"
dependencies = [
"chrono",
"crossterm",

View file

@ -9,7 +9,7 @@ edition = "2018"
members = ["crates/nu-cli", "crates/nu-engine", "crates/nu-parser", "crates/nu-command", "crates/nu-protocol"]
[dependencies]
reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" }
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
crossterm = "0.21.*"
nu-cli = { path="./crates/nu-cli" }
nu-command = { path="./crates/nu-command" }

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Nushell Project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -24,6 +24,7 @@
- [x] Externals
- [x] Modules and imports
- [x] Exports
- [x] Source
- [ ] Input/output types
- [ ] Support for `$in`
- [ ] Value serialization
@ -32,7 +33,6 @@
- [ ] ctrl-c support
- [ ] operator overflow
- [ ] finish operator type-checking
- [ ] Source
- [ ] Overlays (replacement for `autoenv`)
## Maybe:

View file

@ -12,4 +12,4 @@ nu-protocol = { path = "../nu-protocol" }
miette = { version = "3.0.0", features = ["fancy"] }
thiserror = "1.0.29"
nu-ansi-term = "0.36.0"
reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" }
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }

View file

@ -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();

View file

@ -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;

View file

@ -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<Value, ShellError> {
// 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)
}
}

View file

@ -17,6 +17,7 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
working_set.add_decl(Box::new(Benchmark));
working_set.add_decl(Box::new(BuildString));
working_set.add_decl(Box::new(Cd));
working_set.add_decl(Box::new(Cp));
working_set.add_decl(Box::new(Def));
working_set.add_decl(Box::new(Do));
working_set.add_decl(Box::new(Each));
@ -50,6 +51,8 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
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");

View file

@ -0,0 +1,169 @@
use std::env::current_dir;
use std::path::PathBuf;
use nu_engine::CallExt;
use nu_path::canonicalize_with;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EvaluationContext};
use nu_protocol::{ShellError, Signature, SyntaxShape, Value};
use crate::filesystem::util::FileStructure;
pub struct Cp;
impl Command for Cp {
fn name(&self) -> &str {
"cp"
}
fn usage(&self) -> &str {
"Copy files."
}
fn signature(&self) -> Signature {
Signature::build("cp")
.required("source", SyntaxShape::GlobPattern, "the place to copy from")
.required("destination", SyntaxShape::Filepath, "the place to copy to")
.switch(
"recursive",
"copy recursively through subdirectories",
Some('r'),
)
}
fn run(
&self,
context: &EvaluationContext,
call: &Call,
_input: Value,
) -> Result<Value, ShellError> {
let source: String = call.req(context, 0)?;
let destination: String = call.req(context, 1)?;
let path: PathBuf = current_dir().unwrap();
let source = path.join(source.as_str());
let destination = path.join(destination.as_str());
let sources =
glob::glob(&source.to_string_lossy()).map_or_else(|_| Vec::new(), Iterator::collect);
if sources.is_empty() {
return Err(ShellError::FileNotFound(call.positional[0].span));
}
if sources.len() > 1 && !destination.is_dir() {
return Err(ShellError::MoveNotPossible {
source_message: "Can't move many files".to_string(),
source_span: call.positional[0].span,
destination_message: "into single file".to_string(),
destination_span: call.positional[1].span,
});
}
let any_source_is_dir = sources.iter().any(|f| matches!(f, Ok(f) if f.is_dir()));
let recursive = call.named.iter().any(|p| &p.0 == "recursive");
if any_source_is_dir && !recursive {
return Err(ShellError::MoveNotPossibleSingle(
"Directories must be copied using \"--recursive\"".to_string(),
call.positional[0].span,
));
}
for entry in sources.into_iter().flatten() {
let mut sources = FileStructure::new();
sources.walk_decorate(&entry)?;
if entry.is_file() {
let sources = sources.paths_applying_with(|(source_file, _depth_level)| {
if destination.is_dir() {
let mut dest = canonicalize_with(&destination, &path)?;
if let Some(name) = entry.file_name() {
dest.push(name);
}
Ok((source_file, dest))
} else {
Ok((source_file, destination.clone()))
}
})?;
for (src, dst) in sources {
if src.is_file() {
std::fs::copy(&src, dst).map_err(|e| {
ShellError::MoveNotPossibleSingle(
format!(
"failed to move containing file \"{}\": {}",
src.to_string_lossy(),
e
),
call.positional[0].span,
)
})?;
}
}
} else if entry.is_dir() {
let destination = if !destination.exists() {
destination.clone()
} else {
match entry.file_name() {
Some(name) => destination.join(name),
None => {
return Err(ShellError::FileNotFoundCustom(
format!("containing \"{:?}\" is not a valid path", entry),
call.positional[0].span,
))
}
}
};
std::fs::create_dir_all(&destination).map_err(|e| {
ShellError::MoveNotPossibleSingle(
format!("failed to recursively fill destination: {}", e),
call.positional[1].span,
)
})?;
let sources = sources.paths_applying_with(|(source_file, depth_level)| {
let mut dest = destination.clone();
let path = canonicalize_with(&source_file, &path)?;
let components = path
.components()
.map(|fragment| fragment.as_os_str())
.rev()
.take(1 + depth_level);
components.for_each(|fragment| dest.push(fragment));
Ok((PathBuf::from(&source_file), dest))
})?;
for (src, dst) in sources {
if src.is_dir() && !dst.exists() {
std::fs::create_dir_all(&dst).map_err(|e| {
ShellError::MoveNotPossibleSingle(
format!(
"failed to create containing directory \"{}\": {}",
dst.to_string_lossy(),
e
),
call.positional[1].span,
)
})?;
}
if src.is_file() {
std::fs::copy(&src, &dst).map_err(|e| {
ShellError::MoveNotPossibleSingle(
format!(
"failed to move containing file \"{}\": {}",
src.to_string_lossy(),
e
),
call.positional[0].span,
)
})?;
}
}
}
}
Ok(Value::Nothing { span: call.head })
}
}

View file

@ -1,7 +1,10 @@
mod cd;
mod cp;
mod ls;
mod mv;
mod util;
pub use cd::Cd;
pub use cp::Cp;
pub use ls::Ls;
pub use mv::Mv;

View file

@ -0,0 +1,81 @@
use std::path::{Path, PathBuf};
use nu_path::canonicalize_with;
use nu_protocol::ShellError;
#[derive(Default)]
pub struct FileStructure {
pub resources: Vec<Resource>,
}
#[allow(dead_code)]
impl FileStructure {
pub fn new() -> FileStructure {
FileStructure { resources: vec![] }
}
pub fn contains_more_than_one_file(&self) -> bool {
self.resources.len() > 1
}
pub fn contains_files(&self) -> bool {
!self.resources.is_empty()
}
pub fn paths_applying_with<F>(
&mut self,
to: F,
) -> Result<Vec<(PathBuf, PathBuf)>, Box<dyn std::error::Error>>
where
F: Fn((PathBuf, usize)) -> Result<(PathBuf, PathBuf), Box<dyn std::error::Error>>,
{
self.resources
.iter()
.map(|f| (PathBuf::from(&f.location), f.at))
.map(to)
.collect()
}
pub fn walk_decorate(&mut self, start_path: &Path) -> Result<(), ShellError> {
self.resources = Vec::<Resource>::new();
self.build(start_path, 0)?;
self.resources.sort();
Ok(())
}
fn build(&mut self, src: &Path, lvl: usize) -> Result<(), ShellError> {
let source = canonicalize_with(src, std::env::current_dir()?)?;
if source.is_dir() {
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
self.build(&path, lvl + 1)?;
}
self.resources.push(Resource {
location: path.to_path_buf(),
at: lvl,
});
}
} else {
self.resources.push(Resource {
location: source,
at: lvl,
});
}
Ok(())
}
}
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Resource {
pub at: usize,
pub location: PathBuf,
}
impl Resource {}

View file

@ -15,7 +15,7 @@ impl Command for Get {
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("wrap").required(
Signature::build("get").required(
"cell_path",
SyntaxShape::CellPath,
"the cell path to the data",

View file

@ -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<ParseError>) {
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),
)),
)
}

View file

@ -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])),

View file

@ -83,10 +83,18 @@ pub enum ShellError {
#[diagnostic(code(nu::shell::file_not_found), url(docsrs))]
FileNotFound(#[label("file not found")] Span),
#[error("File not found")]
#[diagnostic(code(nu::shell::file_not_found), url(docsrs))]
FileNotFoundCustom(String, #[label("{0}")] Span),
#[error("Directory not found")]
#[diagnostic(code(nu::shell::directory_not_found), url(docsrs))]
DirectoryNotFound(#[label("directory not found")] Span),
#[error("File not found")]
#[diagnostic(code(nu::shell::file_not_found), url(docsrs))]
DirectoryNotFoundCustom(String, #[label("{0}")] Span),
#[error("Move not possible")]
#[diagnostic(code(nu::shell::move_not_possible), url(docsrs))]
MoveNotPossible {
@ -97,4 +105,26 @@ pub enum ShellError {
#[label("{destination_message}")]
destination_span: Span,
},
#[error("Move not possible")]
#[diagnostic(code(nu::shell::move_not_possible_single), url(docsrs))]
MoveNotPossibleSingle(String, #[label("{0}")] Span),
}
impl From<std::io::Error> for ShellError {
fn from(input: std::io::Error) -> ShellError {
ShellError::InternalError(format!("{:?}", input))
}
}
impl std::convert::From<Box<dyn std::error::Error>> for ShellError {
fn from(input: Box<dyn std::error::Error>) -> ShellError {
ShellError::InternalError(input.to_string())
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
fn from(input: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
ShellError::InternalError(format!("{:?}", input))
}
}

View file

@ -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<Value, ShellError> {
let mut current = self;
for member in cell_path {

View file

@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
/// A Range is an iterator over integers.
use crate::{
ast::{RangeInclusion, RangeOperator},
*,