engine-q merge

This commit is contained in:
Fernando Herrera
2022-02-07 19:11:34 +00:00
1965 changed files with 119062 additions and 20 deletions

View File

@ -0,0 +1,100 @@
use nu_protocol::{
ast::Call,
engine::{EngineState, Stack},
FromValue, ShellError,
};
use crate::eval_expression;
pub trait CallExt {
fn get_flag<T: FromValue>(
&self,
engine_state: &EngineState,
stack: &mut Stack,
name: &str,
) -> Result<Option<T>, ShellError>;
fn rest<T: FromValue>(
&self,
engine_state: &EngineState,
stack: &mut Stack,
starting_pos: usize,
) -> Result<Vec<T>, ShellError>;
fn opt<T: FromValue>(
&self,
engine_state: &EngineState,
stack: &mut Stack,
pos: usize,
) -> Result<Option<T>, ShellError>;
fn req<T: FromValue>(
&self,
engine_state: &EngineState,
stack: &mut Stack,
pos: usize,
) -> Result<T, ShellError>;
}
impl CallExt for Call {
fn get_flag<T: FromValue>(
&self,
engine_state: &EngineState,
stack: &mut Stack,
name: &str,
) -> Result<Option<T>, ShellError> {
if let Some(expr) = self.get_flag_expr(name) {
let result = eval_expression(engine_state, stack, &expr)?;
FromValue::from_value(&result).map(Some)
} else {
Ok(None)
}
}
fn rest<T: FromValue>(
&self,
engine_state: &EngineState,
stack: &mut Stack,
starting_pos: usize,
) -> Result<Vec<T>, ShellError> {
let mut output = vec![];
for expr in self.positional.iter().skip(starting_pos) {
let result = eval_expression(engine_state, stack, expr)?;
output.push(FromValue::from_value(&result)?);
}
Ok(output)
}
fn opt<T: FromValue>(
&self,
engine_state: &EngineState,
stack: &mut Stack,
pos: usize,
) -> Result<Option<T>, ShellError> {
if let Some(expr) = self.nth(pos) {
let result = eval_expression(engine_state, stack, &expr)?;
FromValue::from_value(&result).map(Some)
} else {
Ok(None)
}
}
fn req<T: FromValue>(
&self,
engine_state: &EngineState,
stack: &mut Stack,
pos: usize,
) -> Result<T, ShellError> {
if let Some(expr) = self.nth(pos) {
let result = eval_expression(engine_state, stack, &expr)?;
FromValue::from_value(&result)
} else {
Err(ShellError::AccessBeyondEnd(
self.positional.len(),
self.head,
))
}
}
}

View File

@ -1,27 +0,0 @@
use crate::evaluate::evaluate_args::evaluate_args;
use crate::evaluation_context::EvaluationContext;
use nu_errors::ShellError;
use nu_protocol::hir;
use nu_protocol::CallInfo;
use nu_source::Tag;
#[derive(Debug, Clone)]
pub struct UnevaluatedCallInfo {
pub args: hir::Call,
pub name_tag: Tag,
}
impl UnevaluatedCallInfo {
pub fn evaluate(self, ctx: &EvaluationContext) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, ctx)?;
Ok(CallInfo {
args,
name_tag: self.name_tag,
})
}
pub fn switch_present(&self, switch: &str) -> bool {
self.args.switch_preset(switch)
}
}

View File

@ -0,0 +1,38 @@
use nu_protocol::Value;
use std::collections::HashSet;
pub fn get_columns(input: &[Value]) -> Vec<String> {
let mut columns = vec![];
for item in input {
if let Value::Record { cols, vals: _, .. } = item {
for col in cols {
if !columns.contains(col) {
columns.push(col.to_string());
}
}
}
}
columns
}
/*
* Check to see if any of the columns inside the input
* does not exist in a vec of columns
*/
pub fn column_does_not_exist(inputs: Vec<String>, columns: Vec<String>) -> bool {
let mut set = HashSet::new();
for column in columns {
set.insert(column);
}
for input in &inputs {
if set.contains(input) {
continue;
}
return true;
}
false
}

View File

@ -1,141 +0,0 @@
use crate::evaluate::scope::Scope;
use crate::evaluation_context::EvaluationContext;
use crate::shell::shell_manager::ShellManager;
use crate::FromValue;
use crate::{call_info::UnevaluatedCallInfo, config_holder::ConfigHolder};
use crate::{env::host::Host, evaluate_baseline_expr};
use getset::Getters;
use nu_errors::ShellError;
use nu_protocol::hir::SpannedExpression;
use nu_source::Tag;
use nu_stream::InputStream;
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
#[derive(Getters)]
#[get = "pub"]
pub struct CommandArgs {
pub context: EvaluationContext,
pub call_info: UnevaluatedCallInfo,
pub input: InputStream,
}
impl CommandArgs {
pub fn scope(&self) -> &Scope {
&self.context.scope
}
pub fn host(&self) -> Arc<parking_lot::Mutex<Box<dyn Host>>> {
self.context.host().clone()
}
pub fn current_errors(&self) -> Arc<Mutex<Vec<ShellError>>> {
self.context.current_errors().clone()
}
pub fn ctrl_c(&self) -> Arc<AtomicBool> {
self.context.ctrl_c().clone()
}
pub fn configs(&self) -> Arc<Mutex<ConfigHolder>> {
self.context.configs().clone()
}
pub fn shell_manager(&self) -> ShellManager {
self.context.shell_manager().clone()
}
pub fn nth(&self, pos: usize) -> Option<&SpannedExpression> {
if let Some(positional) = &self.call_info.args.positional {
positional.get(pos)
} else {
None
}
}
pub fn req<T: FromValue>(&self, pos: usize) -> Result<T, ShellError> {
if let Some(expr) = self.nth(pos) {
let result = evaluate_baseline_expr(expr, &self.context)?;
FromValue::from_value(&result)
} else {
Err(ShellError::labeled_error(
"Position beyond end of command arguments",
"can't access beyond end of command arguments",
self.call_info.name_tag.span,
))
}
}
pub fn req_named<T: FromValue>(&self, name: &str) -> Result<T, ShellError> {
match self.get_flag(name)? {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"Missing flag",
format!("expected {} flag", name),
&self.call_info.name_tag,
)),
}
}
pub fn has_flag(&self, name: &str) -> bool {
self.call_info.args.switch_preset(name)
}
pub fn get_flag<T: FromValue>(&self, name: &str) -> Result<Option<T>, ShellError> {
if let Some(expr) = self.call_info.args.get_flag(name) {
let result = evaluate_baseline_expr(expr, &self.context)?;
FromValue::from_value(&result).map(Some)
} else {
Ok(None)
}
}
pub fn opt<T: FromValue>(&self, pos: usize) -> Result<Option<T>, ShellError> {
if let Some(expr) = self.nth(pos) {
let result = evaluate_baseline_expr(expr, &self.context)?;
FromValue::from_value(&result).map(Some)
} else {
Ok(None)
}
}
pub fn rest<T: FromValue>(&self, starting_pos: usize) -> Result<Vec<T>, ShellError> {
let mut output = vec![];
if let Some(positional) = &self.call_info.args.positional {
for expr in positional.iter().skip(starting_pos) {
let result = evaluate_baseline_expr(expr, &self.context)?;
output.push(FromValue::from_value(&result)?);
}
}
Ok(output)
}
pub fn rest_with_minimum<T: FromValue>(
&self,
pos: usize,
count: usize,
) -> Result<Vec<T>, ShellError> {
let mut output = vec![];
for i in pos..pos + count {
output.push(self.req(i)?);
}
output.extend(self.rest(pos + count)?);
Ok(output)
}
pub fn name_tag(&self) -> Tag {
self.call_info.name_tag.clone()
}
}
pub type RunnableContext = CommandArgs;
impl std::fmt::Debug for CommandArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.call_info.fmt(f)
}
}

View File

@ -1,73 +0,0 @@
use crate::shell::palette::ThemedPalette;
use nu_data::config::NuConfig;
use nu_protocol::ConfigPath;
use std::path::Path;
/// ConfigHolder holds information which configs have been loaded.
#[derive(Clone)]
pub struct ConfigHolder {
pub global_config: Option<NuConfig>,
pub local_configs: Vec<NuConfig>,
pub syntax_config: Option<ThemedPalette>,
}
impl Default for ConfigHolder {
fn default() -> Self {
Self::new()
}
}
impl ConfigHolder {
pub fn new() -> ConfigHolder {
ConfigHolder {
global_config: None,
local_configs: vec![],
syntax_config: None,
}
}
pub fn global_config(&self) -> NuConfig {
match &self.global_config {
Some(config) => config.clone(),
None => NuConfig::default(),
}
}
pub fn syntax_colors(&self) -> ThemedPalette {
match &self.syntax_config {
Some(cfg) => cfg.clone(),
None => ThemedPalette::default(),
}
}
pub fn add_local_cfg(&mut self, cfg: NuConfig) {
self.local_configs.push(cfg);
}
pub fn set_global_cfg(&mut self, cfg: NuConfig) {
self.global_config = Some(cfg);
}
pub fn set_syntax_colors(&mut self, cfg: ThemedPalette) {
self.syntax_config = Some(cfg);
}
pub fn remove_cfg(&mut self, cfg_path: &ConfigPath) {
match cfg_path {
ConfigPath::Global(_) => self.global_config = None,
ConfigPath::Local(p) => self.remove_local_cfg(p),
}
}
fn remove_local_cfg<P: AsRef<Path>>(&mut self, cfg_path: P) {
// Remove the first loaded local config with specified cfg_path
if let Some(index) = self
.local_configs
.iter()
.rev()
.position(|cfg| cfg.file_path == cfg_path.as_ref())
{
self.local_configs.remove(index);
}
}
}

View File

@ -1,9 +1,18 @@
<<<<<<< HEAD
use crate::evaluate::scope::Scope;
use crate::whole_stream_command::WholeStreamCommand;
use indexmap::IndexMap;
use itertools::Itertools;
use nu_protocol::{NamedType, PositionalType, Signature, UntaggedValue, Value};
use nu_source::PrettyDebug;
=======
use itertools::Itertools;
use nu_protocol::{
ast::Call,
engine::{EngineState, Stack},
Example, IntoPipelineData, Signature, Span, Value,
};
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
use std::collections::HashMap;
const COMMANDS_DOCS_DIR: &str = "docs/commands";
@ -11,10 +20,16 @@ const COMMANDS_DOCS_DIR: &str = "docs/commands";
#[derive(Default)]
pub struct DocumentationConfig {
no_subcommands: bool,
<<<<<<< HEAD
=======
//FIXME: add back in color support
#[allow(dead_code)]
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
no_color: bool,
brief: bool,
}
<<<<<<< HEAD
fn generate_doc(name: &str, scope: &Scope) -> IndexMap<String, Value> {
let mut row_entries = IndexMap::new();
let command = scope
@ -39,11 +54,55 @@ fn generate_doc(name: &str, scope: &Scope) -> IndexMap<String, Value> {
UntaggedValue::string(get_documentation(
command.stream_command(),
scope,
=======
fn generate_doc(
name: &str,
engine_state: &EngineState,
stack: &mut Stack,
head: Span,
) -> (Vec<String>, Vec<Value>) {
let mut cols = vec![];
let mut vals = vec![];
let command = engine_state
.find_decl(name.as_bytes())
.map(|decl_id| engine_state.get_decl(decl_id))
.unwrap_or_else(|| panic!("Expected command '{}' from names to be in registry", name));
cols.push("name".to_string());
vals.push(Value::String {
val: name.into(),
span: head,
});
cols.push("usage".to_string());
vals.push(Value::String {
val: command.usage().to_owned(),
span: head,
});
if let Some(link) = retrieve_doc_link(name) {
cols.push("doc_link".into());
vals.push(Value::String {
val: link,
span: head,
});
}
cols.push("documentation".to_owned());
vals.push(Value::String {
val: get_documentation(
&command.signature(),
&command.examples(),
engine_state,
stack,
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
&DocumentationConfig {
no_subcommands: true,
no_color: true,
brief: false,
},
<<<<<<< HEAD
))
.into_untagged_value(),
);
@ -69,11 +128,39 @@ pub fn generate_docs(scope: &Scope) -> Value {
}
} else {
cmap.insert(name.to_owned(), Vec::new());
=======
),
span: head,
});
(cols, vals)
}
// generate_docs gets the documentation from each command and returns a Table as output
pub fn generate_docs(engine_state: &EngineState, stack: &mut Stack, head: Span) -> Value {
let signatures = engine_state.get_signatures(true);
// cmap will map parent commands to it's subcommands e.g. to -> [to csv, to yaml, to bson]
let mut cmap: HashMap<String, Vec<String>> = HashMap::new();
for sig in &signatures {
if sig.name.contains(' ') {
let mut split_name = sig.name.split_whitespace();
let parent_name = split_name.next().expect("Expected a parent command name");
if cmap.contains_key(parent_name) {
let sub_names = cmap
.get_mut(parent_name)
.expect("Expected an entry for parent");
sub_names.push(sig.name.to_owned());
}
} else {
cmap.insert(sig.name.to_owned(), Vec::new());
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
};
}
// Return documentation for each command
// Sub-commands are nested under their respective parent commands
let mut table = Vec::new();
<<<<<<< HEAD
for name in &sorted_names {
// Must be a sub-command, skip since it's being handled underneath when we hit the parent command
if !cmap.contains_key(name) {
@ -96,6 +183,42 @@ pub fn generate_docs(scope: &Scope) -> Value {
table.push(UntaggedValue::row(row_entries).into_untagged_value());
}
UntaggedValue::table(&table).into_untagged_value()
=======
for sig in &signatures {
// Must be a sub-command, skip since it's being handled underneath when we hit the parent command
if !cmap.contains_key(&sig.name) {
continue;
}
let mut row_entries = generate_doc(&sig.name, engine_state, stack, head);
// Iterate over all the subcommands of the parent command
let mut sub_table = Vec::new();
for sub_name in cmap.get(&sig.name).unwrap_or(&Vec::new()) {
let (cols, vals) = generate_doc(sub_name, engine_state, stack, head);
sub_table.push(Value::Record {
cols,
vals,
span: head,
});
}
if !sub_table.is_empty() {
row_entries.0.push("subcommands".into());
row_entries.1.push(Value::List {
vals: sub_table,
span: head,
});
}
table.push(Value::Record {
cols: row_entries.0,
vals: row_entries.1,
span: head,
});
}
Value::List {
vals: table,
span: head,
}
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}
fn retrieve_doc_link(name: &str) -> Option<String> {
@ -115,6 +238,7 @@ fn retrieve_doc_link(name: &str) -> Option<String> {
#[allow(clippy::cognitive_complexity)]
pub fn get_documentation(
<<<<<<< HEAD
cmd: &dyn WholeStreamCommand,
scope: &Scope,
config: &DocumentationConfig,
@ -124,12 +248,28 @@ pub fn get_documentation(
let mut long_desc = String::new();
let usage = &cmd.usage();
=======
sig: &Signature,
examples: &[Example],
engine_state: &EngineState,
stack: &mut Stack,
config: &DocumentationConfig,
) -> String {
let cmd_name = &sig.name;
let mut long_desc = String::new();
let usage = &sig.usage;
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
if !usage.is_empty() {
long_desc.push_str(usage);
long_desc.push_str("\n\n");
}
<<<<<<< HEAD
let extra_usage = if config.brief { "" } else { &cmd.extra_usage() };
=======
let extra_usage = if config.brief { "" } else { &sig.extra_usage };
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
if !extra_usage.is_empty() {
long_desc.push_str(extra_usage);
long_desc.push_str("\n\n");
@ -137,15 +277,23 @@ pub fn get_documentation(
let mut subcommands = vec![];
if !config.no_subcommands {
<<<<<<< HEAD
for name in scope.get_command_names() {
if name.starts_with(&format!("{} ", cmd_name)) {
let subcommand = scope.get_command(&name).expect("This shouldn't happen");
subcommands.push(format!(" {} - {}", name, subcommand.usage()));
=======
let signatures = engine_state.get_signatures(true);
for sig in signatures {
if sig.name.starts_with(&format!("{} ", cmd_name)) {
subcommands.push(format!(" {} - {}", sig.name, sig.usage));
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}
}
}
<<<<<<< HEAD
let mut one_liner = String::new();
one_liner.push_str(&signature.name);
one_liner.push(' ');
@ -174,6 +322,9 @@ pub fn get_documentation(
}
long_desc.push_str(&format!("Usage:\n > {}\n", one_liner));
=======
long_desc.push_str(&format!("Usage:\n > {}\n", sig.call_signature()));
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
if !subcommands.is_empty() {
long_desc.push_str("\nSubcommands:\n");
@ -182,6 +333,7 @@ pub fn get_documentation(
long_desc.push('\n');
}
<<<<<<< HEAD
if !signature.positional.is_empty() || signature.rest_positional.is_some() {
long_desc.push_str("\nParameters:\n");
for positional in &signature.positional {
@ -208,6 +360,39 @@ pub fn get_documentation(
if !examples.is_empty() {
long_desc.push_str("\nExamples:");
}
=======
if !sig.named.is_empty() {
long_desc.push_str(&get_flags_section(sig))
}
if !sig.required_positional.is_empty()
|| !sig.optional_positional.is_empty()
|| sig.rest_positional.is_some()
{
long_desc.push_str("\nParameters:\n");
for positional in &sig.required_positional {
long_desc.push_str(&format!(" {}: {}\n", positional.name, positional.desc));
}
for positional in &sig.optional_positional {
long_desc.push_str(&format!(
" (optional) {}: {}\n",
positional.name, positional.desc
));
}
if let Some(rest_positional) = &sig.rest_positional {
long_desc.push_str(&format!(
" ...{}: {}\n",
rest_positional.name, rest_positional.desc
));
}
}
if !examples.is_empty() {
long_desc.push_str("\nExamples:");
}
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
for example in examples {
long_desc.push('\n');
long_desc.push_str(" ");
@ -215,10 +400,43 @@ pub fn get_documentation(
if config.no_color {
long_desc.push_str(&format!("\n > {}\n", example.example));
<<<<<<< HEAD
} else {
let colored_example =
crate::shell::painter::Painter::paint_string(example.example, scope, &palette);
long_desc.push_str(&format!("\n > {}\n", colored_example));
=======
} else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") {
let decl = engine_state.get_decl(highlighter);
match decl.run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
Value::String {
val: example.example.to_string(),
span: Span { start: 0, end: 0 },
}
.into_pipeline_data(),
) {
Ok(output) => {
let result = output.into_value(Span { start: 0, end: 0 });
match result.as_string() {
Ok(s) => {
long_desc.push_str(&format!("\n > {}\n", s));
}
_ => {
long_desc.push_str(&format!("\n > {}\n", example.example));
}
}
}
Err(_) => {
long_desc.push_str(&format!("\n > {}\n", example.example));
}
}
} else {
long_desc.push_str(&format!("\n > {}\n", example.example));
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}
}
@ -227,6 +445,7 @@ pub fn get_documentation(
long_desc
}
<<<<<<< HEAD
fn get_flags_section(signature: &Signature) -> String {
let mut long_desc = String::new();
long_desc.push_str("\nFlags:\n");
@ -290,16 +509,102 @@ fn get_flags_section(signature: &Signature) -> String {
)
}
}
=======
pub fn get_flags_section(signature: &Signature) -> String {
let mut long_desc = String::new();
long_desc.push_str("\nFlags:\n");
for flag in &signature.named {
let msg = if let Some(arg) = &flag.arg {
if let Some(short) = flag.short {
if flag.required {
format!(
" -{}{} (required parameter) {:?}\n {}\n",
short,
if !flag.long.is_empty() {
format!(", --{}", flag.long)
} else {
"".into()
},
arg,
flag.desc
)
} else {
format!(
" -{}{} <{:?}>\n {}\n",
short,
if !flag.long.is_empty() {
format!(", --{}", flag.long)
} else {
"".into()
},
arg,
flag.desc
)
}
} else if flag.required {
format!(
" --{} (required parameter) <{:?}>\n {}\n",
flag.long, arg, flag.desc
)
} else {
format!(" --{} {:?}\n {}\n", flag.long, arg, flag.desc)
}
} else if let Some(short) = flag.short {
if flag.required {
format!(
" -{}{} (required parameter)\n {}\n",
short,
if !flag.long.is_empty() {
format!(", --{}", flag.long)
} else {
"".into()
},
flag.desc
)
} else {
format!(
" -{}{}\n {}\n",
short,
if !flag.long.is_empty() {
format!(", --{}", flag.long)
} else {
"".into()
},
flag.desc
)
}
} else if flag.required {
format!(
" --{} (required parameter)\n {}\n",
flag.long, flag.desc
)
} else {
format!(" --{}\n {}\n", flag.long, flag.desc)
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
};
long_desc.push_str(&msg);
}
long_desc
}
<<<<<<< HEAD
pub fn get_brief_help(cmd: &dyn WholeStreamCommand, scope: &Scope) -> String {
get_documentation(
cmd,
scope,
=======
pub fn get_brief_help(
sig: &Signature,
examples: &[Example],
engine_state: &EngineState,
stack: &mut Stack,
) -> String {
get_documentation(
sig,
examples,
engine_state,
stack,
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
&DocumentationConfig {
no_subcommands: false,
no_color: false,
@ -308,6 +613,22 @@ pub fn get_brief_help(cmd: &dyn WholeStreamCommand, scope: &Scope) -> String {
)
}
<<<<<<< HEAD
pub fn get_full_help(cmd: &dyn WholeStreamCommand, scope: &Scope) -> String {
get_documentation(cmd, scope, &DocumentationConfig::default())
=======
pub fn get_full_help(
sig: &Signature,
examples: &[Example],
engine_state: &EngineState,
stack: &mut Stack,
) -> String {
get_documentation(
sig,
examples,
engine_state,
stack,
&DocumentationConfig::default(),
)
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}

167
crates/nu-engine/src/env.rs Normal file
View File

@ -0,0 +1,167 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{Config, PipelineData, ShellError, Value};
use crate::eval_block;
#[cfg(windows)]
const ENV_SEP: &str = ";";
#[cfg(not(windows))]
const ENV_SEP: &str = ":";
/// Translate environment variables from Strings to Values. Requires config to be already set up in
/// case the user defined custom env conversions in config.nu.
///
/// It returns Option instead of Result since we do want to translate all the values we can and
/// skip errors. This function is called in the main() so we want to keep running, we cannot just
/// exit.
pub fn convert_env_values(
engine_state: &mut EngineState,
stack: &Stack,
config: &Config,
) -> Option<ShellError> {
let mut error = None;
let mut new_scope = HashMap::new();
for (name, val) in &engine_state.env_vars {
if let Some(env_conv) = config.env_conversions.get(name) {
if let Some((block_id, from_span)) = env_conv.from_string {
let val_span = match val.span() {
Ok(sp) => sp,
Err(e) => {
error = error.or(Some(e));
continue;
}
};
let block = engine_state.get_block(block_id);
if let Some(var) = block.signature.get_positional(0) {
let mut stack = stack.gather_captures(&block.captures);
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, val.clone());
}
let result =
eval_block(engine_state, &mut stack, block, PipelineData::new(val_span));
match result {
Ok(data) => {
let val = data.into_value(val_span);
new_scope.insert(name.to_string(), val);
}
Err(e) => error = error.or(Some(e)),
}
} else {
error = error.or_else(|| {
Some(ShellError::MissingParameter(
"block input".into(),
from_span,
))
});
}
} else {
new_scope.insert(name.to_string(), val.clone());
}
} else {
new_scope.insert(name.to_string(), val.clone());
}
}
for (k, v) in new_scope {
engine_state.env_vars.insert(k, v);
}
error
}
/// Translate one environment variable from Value to String
pub fn env_to_string(
env_name: &str,
value: Value,
engine_state: &EngineState,
stack: &Stack,
config: &Config,
) -> Result<String, ShellError> {
if let Some(env_conv) = config.env_conversions.get(env_name) {
if let Some((block_id, to_span)) = env_conv.to_string {
let block = engine_state.get_block(block_id);
if let Some(var) = block.signature.get_positional(0) {
let val_span = value.span()?;
let mut stack = stack.gather_captures(&block.captures);
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, value);
}
Ok(
// This one is OK to fail: We want to know if custom conversion is working
eval_block(engine_state, &mut stack, block, PipelineData::new(val_span))?
.into_value(val_span)
.as_string()?,
)
} else {
Err(ShellError::MissingParameter("block input".into(), to_span))
}
} else {
// Do not fail here. Must succeed, otherwise setting a non-string env var would constantly
// throw errors when running externals etc.
Ok(value.into_string(ENV_SEP, config))
}
} else {
// Do not fail here. Must succeed, otherwise setting a non-string env var would constantly
// throw errors when running externals etc.
Ok(value.into_string(ENV_SEP, config))
}
}
/// Translate all environment variables from Values to Strings
pub fn env_to_strings(
engine_state: &EngineState,
stack: &Stack,
config: &Config,
) -> Result<HashMap<String, String>, ShellError> {
let env_vars = stack.get_env_vars(engine_state);
let mut env_vars_str = HashMap::new();
for (env_name, val) in env_vars {
let val_str = env_to_string(&env_name, val, engine_state, stack, config)?;
env_vars_str.insert(env_name, val_str);
}
Ok(env_vars_str)
}
/// Shorthand for env_to_string() for PWD with custom error
pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
let config = stack.get_config()?;
if let Some(pwd) = stack.get_env_var(engine_state, "PWD") {
match env_to_string("PWD", pwd, engine_state, stack, &config) {
Ok(cwd) => {
if Path::new(&cwd).is_absolute() {
Ok(cwd)
} else {
println!("cwd is: {}", cwd);
Err(ShellError::LabeledError(
"Invalid current directory".to_string(),
format!("The 'PWD' environment variable must be set to an absolute path. Found: '{}'", cwd)
))
}
}
Err(e) => Err(e),
}
} else {
Err(ShellError::LabeledError(
"Current directory not found".to_string(),
"The environment variable 'PWD' was not found. It is required to define the current directory.".to_string(),
))
}
}
/// Calls current_dir_str() and returns the current directory as a PathBuf
pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf, ShellError> {
current_dir_str(engine_state, stack).map(PathBuf::from)
}

View File

@ -1,119 +0,0 @@
use crate::Host;
use nu_errors::ShellError;
use nu_protocol::{errln, outln};
use nu_source::Text;
use std::ffi::OsString;
#[derive(Debug)]
pub struct BasicHost;
impl Host for BasicHost {
fn stdout(&mut self, out: &str) {
match out {
"\n" => outln!(""),
other => outln!("{}", other),
}
}
fn stderr(&mut self, out: &str) {
match out {
"\n" => errln!(""),
other => errln!("{}", other),
}
}
fn print_err(&mut self, err: ShellError, source: &Text) {
let diag = err.into_diagnostic();
let source = source.to_string();
let mut files = codespan_reporting::files::SimpleFiles::new();
files.add("shell", source);
let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto);
let config = codespan_reporting::term::Config::default();
let _ = std::panic::catch_unwind(move || {
let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
});
}
#[allow(unused_variables)]
fn vars(&self) -> Vec<(String, String)> {
#[cfg(not(target_arch = "wasm32"))]
{
std::env::vars().collect::<Vec<_>>()
}
#[cfg(target_arch = "wasm32")]
{
vec![]
}
}
#[allow(unused_variables)]
fn env_get(&mut self, key: OsString) -> Option<OsString> {
#[cfg(not(target_arch = "wasm32"))]
{
std::env::var_os(key)
}
#[cfg(target_arch = "wasm32")]
{
None
}
}
#[allow(unused_variables)]
fn env_set(&mut self, key: OsString, value: OsString) {
#[cfg(not(target_arch = "wasm32"))]
{
std::env::set_var(key, value);
}
}
#[allow(unused_variables)]
fn env_rm(&mut self, key: OsString) {
#[cfg(not(target_arch = "wasm32"))]
{
std::env::remove_var(key);
}
}
fn width(&self) -> usize {
let (mut term_width, _) = term_size::dimensions().unwrap_or((80, 20));
term_width -= 1;
term_width
}
fn height(&self) -> usize {
let (_, term_height) = term_size::dimensions().unwrap_or((80, 20));
term_height
}
fn is_external_cmd(&self, #[allow(unused)] cmd_name: &str) -> bool {
#[cfg(any(target_arch = "wasm32", not(feature = "which")))]
{
true
}
#[cfg(all(unix, feature = "which"))]
{
which::which(cmd_name).is_ok()
}
#[cfg(all(windows, feature = "which"))]
{
if which::which(cmd_name).is_ok() {
true
} else {
// Reference: https://ss64.com/nt/syntax-internal.html
let cmd_builtins = [
"assoc", "break", "color", "copy", "date", "del", "dir", "dpath", "echo",
"erase", "for", "ftype", "md", "mkdir", "mklink", "move", "path", "ren",
"rename", "rd", "rmdir", "start", "time", "title", "type", "ver", "verify",
"vol",
];
cmd_builtins.contains(&cmd_name)
}
}
}
}

View File

@ -1,143 +0,0 @@
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_source::Text;
use std::ffi::OsString;
use std::fmt::Debug;
use super::basic_host::BasicHost;
pub trait Host: Debug + Send {
fn stdout(&mut self, out: &str);
fn stderr(&mut self, out: &str);
fn print_err(&mut self, err: ShellError, source: &Text);
fn vars(&self) -> Vec<(String, String)>;
fn env_get(&mut self, key: OsString) -> Option<OsString>;
fn env_set(&mut self, k: OsString, v: OsString);
fn env_rm(&mut self, k: OsString);
fn width(&self) -> usize;
fn height(&self) -> usize;
fn is_external_cmd(&self, cmd_name: &str) -> bool;
}
impl Default for Box<dyn Host> {
fn default() -> Self {
Box::new(BasicHost)
}
}
impl Host for Box<dyn Host> {
fn stdout(&mut self, out: &str) {
(**self).stdout(out)
}
fn stderr(&mut self, out: &str) {
(**self).stderr(out)
}
fn print_err(&mut self, err: ShellError, source: &Text) {
(**self).print_err(err, source)
}
fn vars(&self) -> Vec<(String, String)> {
(**self).vars()
}
fn env_get(&mut self, key: OsString) -> Option<OsString> {
(**self).env_get(key)
}
fn env_set(&mut self, key: OsString, value: OsString) {
(**self).env_set(key, value);
}
fn env_rm(&mut self, key: OsString) {
(**self).env_rm(key)
}
fn width(&self) -> usize {
(**self).width()
}
fn height(&self) -> usize {
(**self).height()
}
fn is_external_cmd(&self, name: &str) -> bool {
(**self).is_external_cmd(name)
}
}
#[derive(Debug)]
pub struct FakeHost {
line_written: String,
env_vars: IndexMap<String, String>,
}
impl FakeHost {
pub fn new() -> FakeHost {
FakeHost {
line_written: String::from(""),
env_vars: IndexMap::default(),
}
}
}
impl Default for FakeHost {
fn default() -> Self {
FakeHost::new()
}
}
impl Host for FakeHost {
fn stdout(&mut self, out: &str) {
self.line_written = out.to_string();
}
fn stderr(&mut self, out: &str) {
self.line_written = out.to_string();
}
fn print_err(&mut self, err: ShellError, source: &Text) {
BasicHost {}.print_err(err, source);
}
fn vars(&self) -> Vec<(String, String)> {
self.env_vars
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Vec<_>>()
}
fn env_get(&mut self, key: OsString) -> Option<OsString> {
let key = key.into_string().expect("Couldn't convert to string.");
self.env_vars.get(&key).map(OsString::from)
}
fn env_set(&mut self, key: OsString, value: OsString) {
self.env_vars.insert(
key.into_string().expect("Couldn't convert to string."),
value.into_string().expect("Couldn't convert to string."),
);
}
fn env_rm(&mut self, key: OsString) {
self.env_vars
.shift_remove(&key.into_string().expect("Couldn't convert to string."));
}
fn width(&self) -> usize {
1
}
fn height(&self) -> usize {
1
}
fn is_external_cmd(&self, _: &str) -> bool {
true
}
}

View File

@ -1,2 +0,0 @@
pub(crate) mod basic_host;
pub(crate) mod host;

1140
crates/nu-engine/src/eval.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,204 +0,0 @@
use crate::evaluate::expr::run_expression_block;
use crate::evaluate::internal::run_internal_command;
use crate::evaluation_context::EvaluationContext;
use nu_errors::ShellError;
use nu_parser::ParserScope;
use nu_protocol::hir::{
Block, Call, ClassifiedCommand, Expression, ExternalRedirection, Pipeline, SpannedExpression,
Synthetic,
};
use nu_protocol::{UntaggedValue, Value};
use nu_source::{Span, Tag};
use nu_stream::{InputStream, OutputStream};
use std::sync::atomic::Ordering;
pub fn run_block(
block: &Block,
ctx: &EvaluationContext,
mut input: InputStream,
external_redirection: ExternalRedirection,
) -> Result<OutputStream, ShellError> {
let mut output: Result<InputStream, ShellError> = Ok(OutputStream::empty());
for definition in block.definitions.values() {
ctx.scope.add_definition(definition.clone());
}
let num_groups = block.block.len();
for (group_num, group) in block.block.iter().enumerate() {
let num_pipelines = group.pipelines.len();
for (pipeline_num, pipeline) in group.pipelines.iter().enumerate() {
match output {
Ok(inp) if inp.is_empty() => {}
Ok(inp) => {
// Run autoview on the values we've seen so far
// We may want to make this configurable for other kinds of hosting
if let Some(autoview) = ctx.get_command("autoview") {
let mut output_stream = match ctx.run_command(
autoview,
Tag::unknown(),
Call::new(
Box::new(SpannedExpression::new(
Expression::Synthetic(Synthetic::String("autoview".into())),
Span::unknown(),
)),
Span::unknown(),
),
inp,
) {
Ok(x) => x,
Err(e) => {
return Err(e);
}
};
match output_stream.next() {
Some(Value {
value: UntaggedValue::Error(e),
..
}) => {
return Err(e);
}
Some(_item) => {
if let Some(err) = ctx.get_errors().get(0) {
ctx.clear_errors();
return Err(err.clone());
}
if ctx.ctrl_c().load(Ordering::SeqCst) {
return Ok(InputStream::empty());
}
}
None => {
if let Some(err) = ctx.get_errors().get(0) {
ctx.clear_errors();
return Err(err.clone());
}
}
}
} else {
let _: Vec<_> = inp.collect();
}
}
Err(e) => {
return Err(e);
}
}
output = Ok(OutputStream::empty());
match output {
Ok(inp) if inp.is_empty() => {}
Ok(mut output_stream) => {
match output_stream.next() {
Some(Value {
value: UntaggedValue::Error(e),
..
}) => {
return Err(e);
}
Some(_item) => {
if let Some(err) = ctx.get_errors().get(0) {
ctx.clear_errors();
return Err(err.clone());
}
if ctx.ctrl_c().load(Ordering::SeqCst) {
// This early return doesn't return the result
// we have so far, but breaking out of this loop
// causes lifetime issues. A future contribution
// could attempt to return the current output.
// https://github.com/nushell/nushell/pull/2830#discussion_r550319687
return Ok(InputStream::empty());
}
}
None => {
if let Some(err) = ctx.get_errors().get(0) {
ctx.clear_errors();
return Err(err.clone());
}
}
}
}
Err(e) => {
return Err(e);
}
}
output = if group_num == (num_groups - 1) && pipeline_num == (num_pipelines - 1) {
// we're at the end of the block, so use the given external redirection
run_pipeline(pipeline, ctx, input, external_redirection)
} else {
// otherwise, we're in the middle of the block, so use a default redirection
run_pipeline(pipeline, ctx, input, ExternalRedirection::None)
};
input = OutputStream::empty();
}
}
output
}
fn run_pipeline(
commands: &Pipeline,
ctx: &EvaluationContext,
mut input: InputStream,
external_redirection: ExternalRedirection,
) -> Result<OutputStream, ShellError> {
let num_commands = commands.list.len();
for (command_num, command) in commands.list.iter().enumerate() {
input = match command {
ClassifiedCommand::Dynamic(call) => {
let mut args = vec![];
if let Some(positional) = &call.positional {
for pos in positional {
let result = run_expression_block(pos, ctx)?.into_vec();
args.push(result);
}
}
let block = run_expression_block(&call.head, ctx)?.into_vec();
if block.len() != 1 {
return Err(ShellError::labeled_error(
"Dynamic commands must start with a block",
"needs to be a block",
call.head.span,
));
}
match &block[0].value {
UntaggedValue::Block(captured_block) => {
ctx.scope.enter_scope();
ctx.scope.add_vars(&captured_block.captured.entries);
for (param, value) in
captured_block.block.params.positional.iter().zip(&args)
{
ctx.scope.add_var(param.0.name(), value[0].clone());
}
let result =
run_block(&captured_block.block, ctx, input, external_redirection);
ctx.scope.exit_scope();
result?
}
_ => {
return Err(ShellError::labeled_error("Dynamic commands must start with a block (or variable pointing to a block)", "needs to be a block", call.head.span));
}
}
}
ClassifiedCommand::Expr(expr) => run_expression_block(expr, ctx)?,
ClassifiedCommand::Error(err) => return Err(err.clone().into()),
ClassifiedCommand::Internal(left) => {
if command_num == (num_commands - 1) {
let mut left = left.clone();
left.args.external_redirection = external_redirection;
run_internal_command(&left, ctx, input)?
} else {
run_internal_command(left, ctx, input)?
}
}
};
}
Ok(input)
}

View File

@ -1,50 +0,0 @@
use std::convert::TryFrom;
use nu_errors::ShellError;
use nu_protocol::{SpannedTypeName, Value};
#[derive(Debug, Clone)]
pub enum EnvVar {
Proper(String),
Nothing,
}
impl TryFrom<Value> for EnvVar {
type Error = ShellError;
fn try_from(value: Value) -> Result<Self, Self::Error> {
if value.value.is_none() {
Ok(EnvVar::Nothing)
} else if value.is_primitive() {
Ok(EnvVar::Proper(value.convert_to_string()))
} else {
Err(ShellError::type_error(
"primitive",
value.spanned_type_name(),
))
}
}
}
impl TryFrom<&Value> for EnvVar {
type Error = ShellError;
fn try_from(value: &Value) -> Result<Self, Self::Error> {
if value.value.is_none() {
Ok(EnvVar::Nothing)
} else if value.is_primitive() {
Ok(EnvVar::Proper(value.convert_to_string()))
} else {
Err(ShellError::type_error(
"primitive",
value.spanned_type_name(),
))
}
}
}
impl From<String> for EnvVar {
fn from(string: String) -> Self {
EnvVar::Proper(string)
}
}

View File

@ -1,50 +0,0 @@
// TODO: Temporary redirect
use crate::evaluate::evaluator::evaluate_baseline_expr;
use crate::evaluation_context::EvaluationContext;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{hir, EvaluatedArgs, UntaggedValue, Value};
pub(crate) fn evaluate_args(
call: &hir::Call,
ctx: &EvaluationContext,
) -> Result<EvaluatedArgs, ShellError> {
let mut positional_args: Vec<Value> = vec![];
if let Some(positional) = &call.positional {
for pos in positional {
let result = evaluate_baseline_expr(pos, ctx)?;
positional_args.push(result);
}
}
let positional = if !positional_args.is_empty() {
Some(positional_args)
} else {
None
};
let mut named_args = IndexMap::new();
if let Some(named) = &call.named {
for (name, value) in named {
match value {
hir::NamedValue::PresentSwitch(tag) => {
named_args.insert(name.clone(), UntaggedValue::boolean(true).into_value(tag));
}
hir::NamedValue::Value(_, expr) => {
named_args.insert(name.clone(), evaluate_baseline_expr(expr, ctx)?);
}
_ => {}
};
}
}
let named = if !named_args.is_empty() {
Some(named_args)
} else {
None
};
Ok(EvaluatedArgs::new(positional, named))
}

View File

@ -1,328 +0,0 @@
use crate::evaluate::block::run_block;
use crate::evaluate::operator::apply_operator;
use crate::evaluation_context::EvaluationContext;
use indexmap::IndexMap;
use log::trace;
use nu_errors::{ArgumentError, ShellError};
use nu_protocol::did_you_mean;
use nu_protocol::{
hir::{self, CapturedBlock, Expression, RangeOperator, SpannedExpression},
Dictionary,
};
use nu_protocol::{
ColumnPath, Primitive, RangeInclusion, UnspannedPathMember, UntaggedValue, Value,
};
use nu_source::{Span, SpannedItem, Tag};
use nu_stream::InputStream;
use nu_value_ext::ValueExt;
pub fn evaluate_baseline_expr(
expr: &SpannedExpression,
ctx: &EvaluationContext,
) -> Result<Value, ShellError> {
let tag = Tag {
span: expr.span,
anchor: None,
};
let span = expr.span;
match &expr.expr {
Expression::Literal(literal) => Ok(evaluate_literal(literal, span)),
Expression::ExternalWord => Err(ShellError::argument_error(
"Invalid external word".spanned(tag.span),
ArgumentError::InvalidExternalWord,
)),
Expression::FilePath(path) => Ok(UntaggedValue::filepath(path.clone()).into_value(tag)),
Expression::Synthetic(hir::Synthetic::String(s)) => {
Ok(UntaggedValue::string(s).into_untagged_value())
}
expr @ Expression::Variable(_, _) => evaluate_reference(&Variable::from(expr), ctx, span),
Expression::Command => unimplemented!(),
Expression::Subexpression(block) => evaluate_subexpression(block, ctx),
Expression::ExternalCommand(_) => unimplemented!(),
Expression::Binary(binary) => {
// TODO: If we want to add short-circuiting, we'll need to move these down
let left = evaluate_baseline_expr(&binary.left, ctx)?;
let right = evaluate_baseline_expr(&binary.right, ctx)?;
trace!("left={:?} right={:?}", left.value, right.value);
match binary.op.expr {
Expression::Literal(hir::Literal::Operator(op)) => {
match apply_operator(op, &left, &right) {
Ok(result) => match result {
UntaggedValue::Error(shell_err) => Err(shell_err),
_ => Ok(result.into_value(tag)),
},
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(binary.left.span),
right_type.spanned(binary.right.span),
)),
}
}
_ => Err(ShellError::labeled_error(
"Unknown operator",
"unknown operator",
binary.op.span,
)),
}
}
Expression::Range(range) => {
let left = if let Some(left) = &range.left {
evaluate_baseline_expr(left, ctx)?
} else {
Value::nothing()
};
let right = if let Some(right) = &range.right {
evaluate_baseline_expr(right, ctx)?
} else {
Value::nothing()
};
let left_span = left.tag.span;
let right_span = right.tag.span;
let left = (
left.as_primitive()?.spanned(left_span),
RangeInclusion::Inclusive,
);
let right = (
right.as_primitive()?.spanned(right_span),
match &range.operator.item {
RangeOperator::Inclusive => RangeInclusion::Inclusive,
RangeOperator::RightExclusive => RangeInclusion::Exclusive,
},
);
Ok(UntaggedValue::range(left, right).into_value(tag))
}
Expression::Table(headers, cells) => {
let mut output_headers = vec![];
for expr in headers {
let val = evaluate_baseline_expr(expr, ctx)?;
let header = val.as_string()?;
output_headers.push(header);
}
let mut output_table = vec![];
for row in cells {
if row.len() != headers.len() {
match (row.first(), row.last()) {
(Some(first), Some(last)) => {
return Err(ShellError::labeled_error(
"Cell count doesn't match header count",
format!("expected {} columns", headers.len()),
Span::new(first.span.start(), last.span.end()),
));
}
_ => {
return Err(ShellError::untagged_runtime_error(
"Cell count doesn't match header count",
));
}
}
}
let mut row_output = IndexMap::new();
for cell in output_headers.iter().zip(row) {
let val = evaluate_baseline_expr(cell.1, ctx)?;
row_output.insert(cell.0.clone(), val);
}
output_table.push(UntaggedValue::row(row_output).into_value(tag.clone()));
}
Ok(UntaggedValue::Table(output_table).into_value(tag))
}
Expression::List(list) => {
let mut exprs = vec![];
for expr in list {
let expr = evaluate_baseline_expr(expr, ctx)?;
exprs.push(expr);
}
Ok(UntaggedValue::Table(exprs).into_value(tag))
}
Expression::Block(block) => {
// Capture the current values of all free variables
let mut known_variables = vec![];
let free_variables = block.get_free_variables(&mut known_variables);
let mut captured = Dictionary::new(IndexMap::new());
for free_variable in &free_variables {
if let Some(v) = ctx.scope.get_var(free_variable) {
captured.insert(free_variable.into(), v.clone());
}
}
Ok(
UntaggedValue::Block(Box::new(CapturedBlock::new(block.clone(), captured)))
.into_value(&tag),
)
}
Expression::FullColumnPath(path) => {
let value = evaluate_baseline_expr(&path.head, ctx)?;
let mut item = value;
for member in &path.tail {
let next = item.get_data_by_member(member);
match next {
Err(err) => match &member.unspanned {
UnspannedPathMember::String(_name) => {
let possible_matches = did_you_mean(&item, member.as_string());
match possible_matches {
Some(p) => {
return Err(ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", p[0]),
&member.span,
));
}
None => return Err(err),
}
}
UnspannedPathMember::Int(_row) => {
return Err(ShellError::labeled_error(
"Unknown row",
"unknown row",
&member.span,
));
}
},
Ok(next) => {
item = next.clone().value.into_value(&tag);
}
};
}
if path.tail.is_empty() {
Ok(item)
} else {
Ok(item.value.into_value(tag))
}
}
Expression::Boolean(_boolean) => Ok(UntaggedValue::boolean(*_boolean).into_value(tag)),
Expression::Garbage => unimplemented!(),
}
}
fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
match &literal {
hir::Literal::ColumnPath(path) => {
let members = path.iter().map(|member| member.to_path_member()).collect();
UntaggedValue::Primitive(Primitive::ColumnPath(ColumnPath::new(members)))
.into_value(span)
}
hir::Literal::Number(int) => match int {
nu_protocol::hir::Number::BigInt(i) => {
UntaggedValue::big_int(i.clone()).into_value(span)
}
nu_protocol::hir::Number::Int(i) => UntaggedValue::int(*i).into_value(span),
nu_protocol::hir::Number::Decimal(d) => {
UntaggedValue::decimal(d.clone()).into_value(span)
}
},
hir::Literal::Size(int, unit) => unit.compute(int).into_value(span),
hir::Literal::String(string) => UntaggedValue::string(string).into_value(span),
hir::Literal::GlobPattern(pattern) => UntaggedValue::glob_pattern(pattern).into_value(span),
hir::Literal::Bare(bare) => UntaggedValue::string(bare.clone()).into_value(span),
hir::Literal::Operator(_) => unimplemented!("Not sure what to do with operator yet"),
}
}
pub enum Variable<'a> {
Nu,
Scope,
True,
False,
Nothing,
Other(&'a str),
}
impl<'a> Variable<'a> {
pub fn list() -> Vec<String> {
vec![
String::from("$nu"),
String::from("$scope"),
String::from("$true"),
String::from("$false"),
String::from("$nothing"),
]
}
}
impl<'a> From<&'a Expression> for Variable<'a> {
fn from(expr: &'a Expression) -> Self {
match &expr {
Expression::Variable(name, _) => match name.as_str() {
"$nu" => Self::Nu,
"$scope" => Self::Scope,
"$true" => Self::True,
"$false" => Self::False,
"$nothing" => Self::Nothing,
_ => Self::Other(name),
},
_ => unreachable!(),
}
}
}
pub fn evaluate_reference(
variable: &Variable,
ctx: &EvaluationContext,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
match variable {
Variable::Nu => crate::evaluate::variables::nu(&ctx.scope, ctx),
Variable::Scope => crate::evaluate::variables::scope(
&ctx.scope.get_aliases(),
&ctx.scope.get_commands(),
&ctx.scope.get_vars(),
),
Variable::True => Ok(UntaggedValue::boolean(true).into_untagged_value()),
Variable::False => Ok(UntaggedValue::boolean(false).into_untagged_value()),
Variable::Nothing => Ok(UntaggedValue::nothing().into_untagged_value()),
Variable::Other(name) => match ctx.scope.get_var(name) {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"Variable not in scope",
format!("unknown variable: {}", name),
tag.into(),
)),
},
}
}
fn evaluate_subexpression(
block: &hir::Block,
ctx: &EvaluationContext,
) -> Result<Value, ShellError> {
// FIXME: we should use a real context here
let input = match ctx.scope.get_var("$it") {
Some(it) => InputStream::one(it),
None => InputStream::empty(),
};
let result = run_block(block, ctx, input, hir::ExternalRedirection::Stdout)?;
let output = result.into_vec();
if let Some(e) = ctx.get_errors().get(0) {
return Err(e.clone());
}
match output.len() {
x if x > 1 => {
let tag = output[0].tag.clone();
Ok(UntaggedValue::Table(output).into_value(tag))
}
1 => Ok(output[0].clone()),
_ => Ok(UntaggedValue::nothing().into_value(Tag::unknown())),
}
}

View File

@ -1,29 +0,0 @@
use crate::evaluate_baseline_expr;
use log::{log_enabled, trace};
use crate::evaluation_context::EvaluationContext;
use nu_errors::ShellError;
use nu_protocol::hir::SpannedExpression;
use nu_protocol::{UntaggedValue, Value};
use nu_stream::{InputStream, IntoInputStream};
pub(crate) fn run_expression_block(
expr: &SpannedExpression,
ctx: &EvaluationContext,
) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::expr", "->");
trace!(target: "nu::run::expr", "{:?}", expr);
}
let output = evaluate_baseline_expr(expr, ctx)?;
match output {
Value {
value: UntaggedValue::Table(x),
..
} => Ok(InputStream::from_stream(x.into_iter())),
output => Ok(std::iter::once(Ok(output)).into_input_stream()),
}
}

View File

@ -1,231 +0,0 @@
use crate::call_info::UnevaluatedCallInfo;
use crate::evaluation_context::EvaluationContext;
use crate::filesystem::filesystem_shell::{FilesystemShell, FilesystemShellMode};
use crate::shell::value_shell::ValueShell;
use crate::CommandArgs;
use log::{log_enabled, trace};
use nu_errors::ShellError;
use nu_protocol::hir::{
Expression, ExternalRedirection, InternalCommand, SpannedExpression, Synthetic,
};
use nu_protocol::{CommandAction, ReturnSuccess, UntaggedValue, Value};
use nu_source::{PrettyDebug, Span, Tag};
use nu_stream::{ActionStream, InputStream};
pub(crate) fn run_internal_command(
command: &InternalCommand,
context: &EvaluationContext,
input: InputStream,
) -> Result<InputStream, ShellError> {
if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::internal", "->");
trace!(target: "nu::run::internal", "{}", command.name);
}
let objects: InputStream = input;
let internal_command = context.scope.expect_command(&command.name);
let internal_command = internal_command?;
let result = context.run_command(
internal_command,
Tag::unknown_anchor(command.name_span),
command.args.clone(), // FIXME: this is inefficient
objects,
)?;
Ok(InputStream::from_stream(InternalIteratorSimple {
context: context.clone(),
input: result,
}))
}
struct InternalIteratorSimple {
context: EvaluationContext,
input: InputStream,
}
impl Iterator for InternalIteratorSimple {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
match self.input.next() {
Some(Value {
value: UntaggedValue::Error(err),
..
}) => {
self.context.error(err);
None
}
x => x,
}
}
}
pub struct InternalIterator {
pub context: EvaluationContext,
pub leftovers: InputStream,
pub input: ActionStream,
}
impl Iterator for InternalIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
if let Some(output) = self.leftovers.next() {
return Some(output);
}
while let Some(item) = self.input.next() {
match item {
Ok(ReturnSuccess::Action(action)) => match action {
CommandAction::ChangePath(path) => {
self.context.shell_manager().set_path(path);
}
CommandAction::Exit(code) => std::process::exit(code), // TODO: save history.txt
CommandAction::Error(err) => {
self.context.error(err);
return None;
}
CommandAction::AutoConvert(tagged_contents, extension) => {
let contents_tag = tagged_contents.tag.clone();
let command_name = format!("from {}", extension);
if let Some(converter) = self.context.scope.get_command(&command_name) {
let new_args = CommandArgs {
context: self.context.clone(),
call_info: UnevaluatedCallInfo {
args: nu_protocol::hir::Call {
head: Box::new(SpannedExpression {
expr: Expression::Synthetic(Synthetic::String(
command_name.clone(),
)),
span: tagged_contents.tag().span,
}),
positional: None,
named: None,
span: Span::unknown(),
external_redirection: ExternalRedirection::Stdout,
},
name_tag: tagged_contents.tag(),
},
input: InputStream::one(tagged_contents),
};
let result = converter.run(new_args);
match result {
Ok(mut result) => {
if let Some(x) = result.next() {
self.leftovers =
InputStream::from_stream(result.map(move |x| Value {
value: x.value,
tag: contents_tag.clone(),
}));
return Some(x);
} else {
return None;
}
}
Err(err) => {
self.leftovers = InputStream::empty();
return Some(Value::error(err));
}
}
} else {
return Some(tagged_contents);
}
}
CommandAction::EnterValueShell(value) => {
self.context
.shell_manager()
.insert_at_current(Box::new(ValueShell::new(value)));
}
CommandAction::EnterShell(location) => {
let mode = if self.context.shell_manager().is_interactive() {
FilesystemShellMode::Cli
} else {
FilesystemShellMode::Script
};
self.context.shell_manager().insert_at_current(Box::new(
match FilesystemShell::with_location(location, mode) {
Ok(v) => v,
Err(err) => {
self.context.error(err.into());
break;
}
},
));
}
CommandAction::AddPlugins(path) => {
match crate::plugin::build_plugin::scan(vec![std::path::PathBuf::from(
path,
)]) {
Ok(plugins) => {
self.context.add_commands(
plugins
.into_iter()
.filter(|p| !self.context.is_command_registered(p.name()))
.collect(),
);
}
Err(reason) => {
self.context.error(reason);
}
}
}
CommandAction::PreviousShell => {
self.context.shell_manager().prev();
}
CommandAction::NextShell => {
self.context.shell_manager().next();
}
CommandAction::GotoShell(i) => {
self.context.shell_manager().goto(i);
}
CommandAction::LeaveShell(code) => {
self.context.shell_manager().remove_at_current();
if self.context.shell_manager().is_empty() {
std::process::exit(code); // TODO: save history.txt
}
}
CommandAction::UnloadConfig(cfg_path) => {
self.context.unload_config(&cfg_path);
}
CommandAction::LoadConfig(cfg_path) => {
if let Err(e) = self.context.load_config(&cfg_path) {
return Some(UntaggedValue::Error(e).into_untagged_value());
}
}
},
Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Error(err),
..
})) => {
self.context.error(err);
return None;
}
Ok(ReturnSuccess::Value(v)) => return Some(v),
Ok(ReturnSuccess::DebugValue(v)) => {
let doc = PrettyDebug::pretty_doc(&v);
let mut buffer = termcolor::Buffer::ansi();
let _ = doc.render_raw(
self.context.with_host(|host| host.width() - 5),
&mut nu_source::TermColored::new(&mut buffer),
);
let value = String::from_utf8_lossy(buffer.as_slice());
return Some(UntaggedValue::string(value).into_untagged_value());
}
Err(err) => {
self.context.error(err);
}
}
}
None
}
}

View File

@ -1,209 +0,0 @@
use crate::Scope;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{Dictionary, Signature, UntaggedValue, Value};
use nu_source::Tag;
pub struct Lang;
impl Lang {
pub fn query_commands(scope: &Scope) -> Result<Vec<Value>, ShellError> {
let tag = Tag::unknown();
let full_commands = scope.get_commands_info();
let mut cmd_vec = Vec::new();
for (key, cmd) in full_commands {
let mut indexmap = IndexMap::new();
let mut sig = cmd.signature();
// eprintln!("{}", get_signature(&sig));
indexmap.insert(
"name".to_string(),
UntaggedValue::string(key).into_value(&tag),
);
indexmap.insert(
"usage".to_string(),
UntaggedValue::string(cmd.usage().to_string()).into_value(&tag),
);
// let sig_deser = serde_json::to_string(&sig).unwrap();
// indexmap.insert(
// "signature".to_string(),
// UntaggedValue::string(sig_deser).into_value(&tag),
// );
let signature_table = get_signature(&mut sig, tag.clone());
indexmap.insert(
"signature".to_string(),
UntaggedValue::Table(signature_table).into_value(&tag),
);
indexmap.insert(
"is_filter".to_string(),
UntaggedValue::boolean(sig.is_filter).into_value(&tag),
);
indexmap.insert(
"is_builtin".to_string(),
UntaggedValue::boolean(cmd.is_builtin()).into_value(&tag),
);
indexmap.insert(
"is_sub".to_string(),
UntaggedValue::boolean(cmd.is_sub()).into_value(&tag),
);
indexmap.insert(
"is_plugin".to_string(),
UntaggedValue::boolean(cmd.is_plugin()).into_value(&tag),
);
indexmap.insert(
"is_custom".to_string(),
UntaggedValue::boolean(cmd.is_custom()).into_value(&tag),
);
indexmap.insert(
"is_private".to_string(),
UntaggedValue::boolean(cmd.is_private()).into_value(&tag),
);
indexmap.insert(
"is_binary".to_string(),
UntaggedValue::boolean(cmd.is_binary()).into_value(&tag),
);
indexmap.insert(
"extra_usage".to_string(),
UntaggedValue::string(cmd.extra_usage().to_string()).into_value(&tag),
);
cmd_vec.push(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag));
}
Ok(cmd_vec)
}
}
fn get_signature(sig: &mut Signature, tag: Tag) -> Vec<Value> {
sig.remove_named("help");
let p = &sig.positional;
let r = &sig.rest_positional;
let n = &sig.named;
let name = &sig.name;
let mut sig_vec: Vec<Value> = Vec::new();
for item in p {
let mut indexmap = IndexMap::new();
let (parameter, syntax_shape) = item.0.get_type_description();
let description = &item.1;
// let output = format!(
// "Positional|{}|{}|{}|{}\n",
// name, parameter, syntax_shape, description
// );
// eprintln!("{}", output);
indexmap.insert(
"cmd_name".to_string(),
UntaggedValue::string(name).into_value(&tag),
);
indexmap.insert(
"parameter_name".to_string(),
UntaggedValue::string(parameter).into_value(&tag),
);
indexmap.insert(
"parameter_type".to_string(),
UntaggedValue::string("positional".to_string()).into_value(&tag),
);
indexmap.insert(
"syntax_shape".to_string(),
UntaggedValue::string(syntax_shape).into_value(&tag),
);
indexmap.insert(
"description".to_string(),
UntaggedValue::string(description).into_value(&tag),
);
indexmap.insert(
"flag_name".to_string(),
UntaggedValue::string("".to_string()).into_value(&tag),
);
indexmap.insert(
"flag_type".to_string(),
UntaggedValue::string("".to_string()).into_value(&tag),
);
sig_vec.push(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag));
}
match r {
Some((rest_name, shape, desc)) => {
let mut indexmap = IndexMap::new();
// let output = format!("Rest|{}|{}|{}\n", name, shape.syntax_shape_name(), desc);
// eprintln!("{}", output);
indexmap.insert(
"cmd_name".to_string(),
UntaggedValue::string(name).into_value(&tag),
);
indexmap.insert(
"parameter_name".to_string(),
UntaggedValue::string(rest_name.to_string()).into_value(&tag),
);
indexmap.insert(
"parameter_type".to_string(),
UntaggedValue::string("rest".to_string()).into_value(&tag),
);
indexmap.insert(
"syntax_shape".to_string(),
UntaggedValue::string(shape.syntax_shape_name()).into_value(&tag),
);
indexmap.insert(
"description".to_string(),
UntaggedValue::string(desc).into_value(&tag),
);
indexmap.insert(
"flag_name".to_string(),
UntaggedValue::string("".to_string()).into_value(&tag),
);
indexmap.insert(
"flag_type".to_string(),
UntaggedValue::string("".to_string()).into_value(&tag),
);
sig_vec.push(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag));
}
None => {}
}
for (parameter, (b, description)) in n {
let mut indexmap = IndexMap::new();
let (named_type, flag_name, shape) = b.get_type_description();
// let output = format!(
// "Named|{}|{}|{}|{}|{}|{}\n",
// name, parameter, named_type, flag_name, shape, description
// );
// eprint!("{}", output);
indexmap.insert(
"cmd_name".to_string(),
UntaggedValue::string(name).into_value(&tag),
);
indexmap.insert(
"parameter_name".to_string(),
UntaggedValue::string(parameter).into_value(&tag),
);
indexmap.insert(
"parameter_type".to_string(),
UntaggedValue::string("named".to_string()).into_value(&tag),
);
indexmap.insert(
"syntax_shape".to_string(),
UntaggedValue::string(shape).into_value(&tag),
);
indexmap.insert(
"description".to_string(),
UntaggedValue::string(description).into_value(&tag),
);
indexmap.insert(
"flag_name".to_string(),
UntaggedValue::string(flag_name).into_value(&tag),
);
indexmap.insert(
"flag_type".to_string(),
UntaggedValue::string(named_type).into_value(&tag),
);
sig_vec.push(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag));
}
sig_vec
}

View File

@ -1,10 +0,0 @@
pub(crate) mod block;
pub(crate) mod envvar;
pub(crate) mod evaluate_args;
pub mod evaluator;
pub(crate) mod expr;
pub mod internal;
pub(crate) mod lang;
pub(crate) mod operator;
pub(crate) mod scope;
pub(crate) mod variables;

View File

@ -1,111 +0,0 @@
use nu_data::{value, value::compare_values};
use nu_errors::ShellError;
use nu_protocol::hir::Operator;
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
use std::ops::Not;
#[cfg(feature = "dataframe")]
use nu_protocol::dataframe::{compute_between_dataframes, compute_series_single_value};
pub fn apply_operator(
op: Operator,
left: &Value,
right: &Value,
) -> Result<UntaggedValue, (&'static str, &'static str)> {
#[cfg(feature = "dataframe")]
if let (UntaggedValue::DataFrame(_), UntaggedValue::DataFrame(_)) = (&left.value, &right.value)
{
return compute_between_dataframes(op, left, right);
} else if let (UntaggedValue::DataFrame(_), UntaggedValue::Primitive(_)) =
(&left.value, &right.value)
{
return compute_series_single_value(op, left, right);
}
match op {
Operator::Equal
| Operator::NotEqual
| Operator::LessThan
| Operator::GreaterThan
| Operator::LessThanOrEqual
| Operator::GreaterThanOrEqual => {
value::compare_values(op, left, right).map(UntaggedValue::boolean)
}
Operator::Contains => string_contains(left, right).map(UntaggedValue::boolean),
Operator::NotContains => string_contains(left, right)
.map(Not::not)
.map(UntaggedValue::boolean),
Operator::Plus => value::compute_values(op, left, right),
Operator::Minus => value::compute_values(op, left, right),
Operator::Multiply => value::compute_values(op, left, right),
Operator::Pow => value::compute_values(op, left, right),
Operator::Divide => value::compute_values(op, left, right).map(|res| match res {
UntaggedValue::Error(_) => UntaggedValue::Error(ShellError::labeled_error(
"Evaluation error",
"division by zero",
&right.tag.span,
)),
_ => res,
}),
Operator::Modulo => value::compute_values(op, left, right).map(|res| match res {
UntaggedValue::Error(_) => UntaggedValue::Error(ShellError::labeled_error(
"Evaluation error",
"division by zero",
&right.tag.span,
)),
_ => res,
}),
Operator::In => inside_of(left, right).map(UntaggedValue::boolean),
Operator::NotIn => inside_of(left, right).map(|x| UntaggedValue::boolean(!x)),
Operator::And => match (left.as_bool(), right.as_bool()) {
(Ok(left), Ok(right)) => Ok(UntaggedValue::boolean(left && right)),
_ => Err((left.type_name(), right.type_name())),
},
Operator::Or => match (left.as_bool(), right.as_bool()) {
(Ok(left), Ok(right)) => Ok(UntaggedValue::boolean(left || right)),
_ => Err((left.type_name(), right.type_name())),
},
}
}
fn string_contains(
left: &UntaggedValue,
right: &UntaggedValue,
) -> Result<bool, (&'static str, &'static str)> {
match (left, right) {
(
UntaggedValue::Primitive(Primitive::String(l)),
UntaggedValue::Primitive(Primitive::String(r)),
) => Ok(l.contains(r)),
(
UntaggedValue::Primitive(Primitive::FilePath(l)),
UntaggedValue::Primitive(Primitive::String(r)),
) => Ok(l.as_path().display().to_string().contains(r)),
(
UntaggedValue::Primitive(Primitive::String(l)),
UntaggedValue::Primitive(Primitive::FilePath(r)),
) => Ok(l.contains(&r.as_path().display().to_string())),
_ => Err((left.type_name(), right.type_name())),
}
}
fn inside_of(
left: &UntaggedValue,
right: &UntaggedValue,
) -> Result<bool, (&'static str, &'static str)> {
match (left, right) {
(_, UntaggedValue::Table(values)) => {
Ok(values
.iter()
.any(|x| match compare_values(Operator::Equal, left, &x.value) {
Ok(coerced) => coerced,
_ => false,
}))
}
(
UntaggedValue::Primitive(Primitive::String(lhs)),
UntaggedValue::Primitive(Primitive::String(rhs)),
) => Ok(rhs.contains(lhs)),
_ => Err((left.type_name(), right.type_name())),
}
}

View File

@ -1,508 +0,0 @@
use crate::{
evaluate::envvar::EnvVar,
whole_stream_command::{whole_stream_command, Command},
};
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::ParserScope;
use nu_protocol::{hir::Block, Signature, SignatureRegistry, Value};
use nu_source::Spanned;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Scope {
frames: Arc<parking_lot::Mutex<Vec<ScopeFrame>>>,
}
impl Default for Scope {
fn default() -> Self {
Self::new()
}
}
impl Scope {
pub fn new() -> Scope {
Scope {
frames: Arc::new(parking_lot::Mutex::new(vec![ScopeFrame::new()])),
}
}
pub fn get_command(&self, name: &str) -> Option<Command> {
for frame in self.frames.lock().iter().rev() {
if let Some(command) = frame.get_command(name) {
return Some(command);
}
}
None
}
pub fn get_aliases(&self) -> IndexMap<String, Vec<Spanned<String>>> {
let mut output: IndexMap<String, Vec<Spanned<String>>> = IndexMap::new();
for frame in self.frames.lock().iter().rev() {
for v in &frame.aliases {
if !output.contains_key(v.0) {
output.insert(v.0.clone(), v.1.clone());
}
}
}
output.sorted_by(|k1, _v1, k2, _v2| k1.cmp(k2)).collect()
}
pub fn get_commands(&self) -> IndexMap<String, Signature> {
let mut output: IndexMap<String, Signature> = IndexMap::new();
for frame in self.frames.lock().iter().rev() {
for (name, command) in &frame.commands {
if !output.contains_key(name) {
let mut sig = command.signature();
// don't show --help and -h in the command arguments for $scope.commands
sig.remove_named("help");
output.insert(name.clone(), sig);
}
}
}
output.sorted_by(|k1, _v1, k2, _v2| k1.cmp(k2)).collect()
}
pub fn get_commands_info(&self) -> IndexMap<String, Command> {
let mut output: IndexMap<String, Command> = IndexMap::new();
for frame in self.frames.lock().iter().rev() {
for (name, command) in &frame.commands {
if !output.contains_key(name) {
output.insert(name.clone(), command.clone());
}
}
}
output.sorted_by(|k1, _v1, k2, _v2| k1.cmp(k2)).collect()
}
pub fn get_variable_names(&self) -> Vec<String> {
self.get_vars().iter().map(|(k, _)| k.to_string()).collect()
}
pub fn get_vars(&self) -> IndexMap<String, Value> {
//FIXME: should this be an iterator?
let mut output: IndexMap<String, Value> = IndexMap::new();
for frame in self.frames.lock().iter().rev() {
for v in &frame.vars {
if !output.contains_key(v.0) {
output.insert(v.0.clone(), v.1.clone());
}
}
}
output.sorted_by(|k1, _v1, k2, _v2| k1.cmp(k2)).collect()
}
pub fn get_aliases_with_name(&self, name: &str) -> Option<Vec<Vec<Spanned<String>>>> {
let aliases: Vec<_> = self
.frames
.lock()
.iter()
.rev()
.filter_map(|frame| frame.aliases.get(name).cloned())
.collect();
if aliases.is_empty() {
None
} else {
Some(aliases)
}
}
pub fn get_custom_commands_with_name(&self, name: &str) -> Option<Vec<Arc<Block>>> {
let custom_commands: Vec<_> = self
.frames
.lock()
.iter()
.rev()
.filter_map(|frame| frame.custom_commands.get(name).cloned())
.collect();
if custom_commands.is_empty() {
None
} else {
Some(custom_commands)
}
}
pub fn add_command(&self, name: String, command: Command) {
// Note: this is assumed to always be true, as there is always a global top frame
if let Some(frame) = self.frames.lock().last_mut() {
frame.add_command(name, command)
}
}
pub fn get_alias_names(&self) -> Vec<String> {
let mut names = vec![];
for frame in self.frames.lock().iter() {
let mut frame_command_names = frame.get_alias_names();
names.append(&mut frame_command_names);
}
// Sort needs to happen first because dedup works on consecutive dupes only
names.sort();
names.dedup();
names
}
pub fn get_command_names(&self) -> Vec<String> {
let mut names = vec![];
for frame in self.frames.lock().iter() {
let mut frame_command_names = frame.get_command_names();
frame_command_names.extend(frame.get_alias_names());
frame_command_names.extend(frame.get_custom_command_names());
names.append(&mut frame_command_names);
}
// Sort needs to happen first because dedup works on consecutive dupes only
names.sort();
names.dedup();
names
}
pub fn len(&self) -> usize {
self.frames.lock().len()
}
pub fn is_empty(&self) -> bool {
self.frames.lock().is_empty()
}
fn has_cmd_helper(&self, name: &str, f: fn(&ScopeFrame, &str) -> bool) -> bool {
self.frames.lock().iter().any(|frame| f(frame, name))
}
pub fn has_command(&self, name: &str) -> bool {
self.has_cmd_helper(name, ScopeFrame::has_command)
}
pub fn has_custom_command(&self, name: &str) -> bool {
self.has_cmd_helper(name, ScopeFrame::has_custom_command)
}
pub fn has_alias(&self, name: &str) -> bool {
self.has_cmd_helper(name, ScopeFrame::has_alias)
}
pub fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
if let Some(c) = self.get_command(name) {
Ok(c)
} else {
Err(ShellError::untagged_runtime_error(format!(
"Missing command '{}'",
name
)))
}
}
// This is used for starting processes, keep it string -> string
pub fn get_env_vars(&self) -> IndexMap<String, String> {
//FIXME: should this be an iterator?
let mut output = IndexMap::new();
for frame in self.frames.lock().iter().rev() {
for v in &frame.env {
if !output.contains_key(v.0) {
output.insert(v.0.clone(), v.1.clone());
}
}
}
output
.into_iter()
.filter_map(|(k, v)| match v {
EnvVar::Proper(s) => Some((k, s)),
EnvVar::Nothing => None,
})
.collect()
}
pub fn get_env(&self, name: &str) -> Option<String> {
for frame in self.frames.lock().iter().rev() {
if let Some(v) = frame.env.get(name) {
return match v {
EnvVar::Proper(string) => Some(string.clone()),
EnvVar::Nothing => None,
};
}
}
None
}
pub fn get_var(&self, name: &str) -> Option<Value> {
for frame in self.frames.lock().iter().rev() {
if let Some(v) = frame.vars.get(name) {
return Some(v.clone());
}
}
None
}
pub fn add_var(&self, name: impl Into<String>, value: Value) {
if let Some(frame) = self.frames.lock().last_mut() {
frame.vars.insert(name.into(), value);
}
}
pub fn add_vars(&self, vars: &IndexMap<String, Value>) {
if let Some(frame) = self.frames.lock().last_mut() {
frame
.vars
.extend(vars.iter().map(|(s, v)| (s.clone(), v.clone())))
}
}
pub fn add_env_var(&self, name: impl Into<String>, value: impl Into<EnvVar>) {
if let Some(frame) = self.frames.lock().last_mut() {
frame.env.insert(name.into(), value.into());
}
}
pub fn remove_env_var(&self, name: impl Into<String>) -> Option<String> {
if let Some(frame) = self.frames.lock().last_mut() {
if let Some(val) = frame.env.remove_entry(&name.into()) {
return Some(val.0);
}
}
None
}
pub fn add_env(&self, env_vars: IndexMap<String, EnvVar>) {
if let Some(frame) = self.frames.lock().last_mut() {
frame.env.extend(env_vars)
}
}
pub fn add_env_to_base(&self, env_vars: IndexMap<String, EnvVar>) {
if let Some(frame) = self.frames.lock().first_mut() {
frame.env.extend(env_vars)
}
}
pub fn add_env_var_to_base(&self, name: impl Into<String>, value: impl Into<EnvVar>) {
if let Some(frame) = self.frames.lock().first_mut() {
frame.env.insert(name.into(), value.into());
}
}
pub fn set_exit_scripts(&self, scripts: Vec<String>) {
if let Some(frame) = self.frames.lock().last_mut() {
frame.exitscripts = scripts
}
}
pub fn enter_scope_with_tag(&self, tag: String) {
self.frames.lock().push(ScopeFrame::with_tag(tag));
}
//Removes the scopeframe with tag.
pub fn exit_scope_with_tag(&self, tag: &str) {
let mut frames = self.frames.lock();
let tag = Some(tag);
if let Some(i) = frames.iter().rposition(|f| f.tag.as_deref() == tag) {
frames.remove(i);
}
}
pub fn get_exitscripts_of_frame_with_tag(&self, tag: &str) -> Option<Vec<String>> {
let frames = self.frames.lock();
let tag = Some(tag);
frames.iter().find_map(|f| {
if f.tag.as_deref() == tag {
Some(f.exitscripts.clone())
} else {
None
}
})
}
pub fn get_frame_with_tag(&self, tag: &str) -> Option<ScopeFrame> {
let frames = self.frames.lock();
let tag = Some(tag);
frames.iter().rev().find_map(|f| {
if f.tag.as_deref() == tag {
Some(f.clone())
} else {
None
}
})
}
pub fn update_frame_with_tag(&self, frame: ScopeFrame, tag: &str) -> Result<(), ShellError> {
let mut frames = self.frames.lock();
let tag = Some(tag);
for f in frames.iter_mut().rev() {
if f.tag.as_deref() == tag {
*f = frame;
return Ok(());
}
}
// Frame not found, return err
Err(ShellError::untagged_runtime_error(format!(
"Can't update frame with tag {:?}. No such frame present!",
tag
)))
}
}
impl SignatureRegistry for Scope {
fn names(&self) -> Vec<String> {
self.get_command_names()
}
fn has(&self, name: &str) -> bool {
self.get_signature(name).is_some()
}
fn get(&self, name: &str) -> Option<Signature> {
self.get_signature(name)
}
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
Box::new(self.clone())
}
}
impl ParserScope for Scope {
fn get_signature(&self, name: &str) -> Option<Signature> {
self.get_command(name).map(|x| x.signature())
}
fn has_signature(&self, name: &str) -> bool {
self.get_command(name).is_some()
}
fn add_definition(&self, block: Arc<Block>) {
if let Some(frame) = self.frames.lock().last_mut() {
let name = block.params.name.clone();
frame.custom_commands.insert(name.clone(), block.clone());
frame.commands.insert(name, whole_stream_command(block));
}
}
fn get_definitions(&self) -> Vec<Arc<Block>> {
let mut blocks = vec![];
if let Some(frame) = self.frames.lock().last() {
for (_, custom_command) in &frame.custom_commands {
blocks.push(custom_command.clone());
}
}
blocks
}
fn get_alias(&self, name: &str) -> Option<Vec<Spanned<String>>> {
for frame in self.frames.lock().iter().rev() {
if let Some(x) = frame.aliases.get(name) {
return Some(x.clone());
}
}
None
}
fn add_alias(&self, name: &str, replacement: Vec<Spanned<String>>) {
// Note: this is assumed to always be true, as there is always a global top frame
if let Some(frame) = self.frames.lock().last_mut() {
frame.aliases.insert(name.to_string(), replacement);
}
}
fn remove_alias(&self, name: &str) {
if let Some(frame) = self.frames.lock().last_mut() {
frame.aliases.remove(name);
}
}
fn enter_scope(&self) {
self.frames.lock().push(ScopeFrame::new());
}
fn exit_scope(&self) {
self.frames.lock().pop();
}
}
/// An evaluation scope. Scopes map variable names to Values and aid in evaluating blocks and expressions.
#[derive(Debug, Clone)]
pub struct ScopeFrame {
pub vars: IndexMap<String, Value>,
pub env: IndexMap<String, EnvVar>,
pub commands: IndexMap<String, Command>,
pub custom_commands: IndexMap<String, Arc<Block>>,
pub aliases: IndexMap<String, Vec<Spanned<String>>>,
///Optional tag to better identify this scope frame later
pub tag: Option<String>,
pub exitscripts: Vec<String>,
}
impl Default for ScopeFrame {
fn default() -> Self {
ScopeFrame::new()
}
}
impl ScopeFrame {
pub fn has_command(&self, name: &str) -> bool {
self.commands.contains_key(name)
}
pub fn has_custom_command(&self, name: &str) -> bool {
self.custom_commands.contains_key(name)
}
pub fn has_alias(&self, name: &str) -> bool {
self.aliases.contains_key(name)
}
pub fn get_alias_names(&self) -> Vec<String> {
self.aliases.keys().map(|x| x.to_string()).collect()
}
pub fn get_command_names(&self) -> Vec<String> {
self.commands.keys().map(|x| x.to_string()).collect()
}
pub fn get_custom_command_names(&self) -> Vec<String> {
self.custom_commands.keys().map(|x| x.to_string()).collect()
}
pub fn add_command(&mut self, name: String, command: Command) {
self.commands.insert(name, command);
}
pub fn get_command(&self, name: &str) -> Option<Command> {
self.commands.get(name).cloned()
}
pub fn new() -> ScopeFrame {
ScopeFrame {
vars: IndexMap::new(),
env: IndexMap::new(),
commands: IndexMap::new(),
custom_commands: IndexMap::new(),
aliases: IndexMap::new(),
tag: None,
exitscripts: Vec::new(),
}
}
pub fn with_tag(tag: String) -> ScopeFrame {
let mut scope = ScopeFrame::new();
scope.tag = Some(tag);
scope
}
}

View File

@ -1,159 +0,0 @@
use crate::{
evaluate::{lang, scope::Scope},
EvaluationContext,
};
use indexmap::IndexMap;
use nu_data::config::path::{default_history_path, history_path};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, ShellTypeName, Signature, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::{Spanned, Tag};
pub fn nu(scope: &Scope, ctx: &EvaluationContext) -> Result<Value, ShellError> {
let env = &scope.get_env_vars();
let tag = Tag::unknown();
let mut nu_dict = TaggedDictBuilder::new(&tag);
let mut dict = TaggedDictBuilder::new(&tag);
for v in env {
if v.0 != "PATH" && v.0 != "Path" {
dict.insert_untagged(v.0, UntaggedValue::string(v.1));
}
}
nu_dict.insert_value("env", dict.into_value());
nu_dict.insert_value(
"history-path",
UntaggedValue::filepath(default_history_path()).into_value(&tag),
);
if let Some(global_cfg) = &ctx.configs().lock().global_config {
nu_dict.insert_value(
"config",
UntaggedValue::row(global_cfg.vars.clone()).into_value(&tag),
);
nu_dict.insert_value(
"config-path",
UntaggedValue::filepath(global_cfg.file_path.clone()).into_value(&tag),
);
// overwrite hist-path if present
if let Some(hist_path) = history_path(global_cfg) {
nu_dict.insert_value(
"history-path",
UntaggedValue::filepath(hist_path).into_value(&tag),
);
}
}
// A note about environment variables:
//
// Environment variables in Unix platforms are case-sensitive. On Windows, case-sensitivity is context-dependent.
// In cmd.exe, running `SET` will show you the list of environment variables and their names will be mixed case.
// In PowerShell, running `Get-ChildItem Env:` will show you a list of environment variables, and they will match
// the case in the environment variable section of the user configuration
//
// Rust currently returns the DOS-style, all-uppercase environment variables on Windows (as of 1.52) when running
// std::env::vars(), rather than the case-sensitive Environment.GetEnvironmentVariables() of .NET that PowerShell
// uses.
//
// For now, we work around the discrepancy as best we can by merging the two into what is shown to the user as the
// 'path' column of `$nu`
let mut table = vec![];
for v in env {
if v.0 == "PATH" || v.0 == "Path" {
for path in std::env::split_paths(&v.1) {
table.push(UntaggedValue::filepath(path).into_value(&tag));
}
}
}
nu_dict.insert_value("path", UntaggedValue::table(&table).into_value(&tag));
let path = std::env::current_dir()?;
nu_dict.insert_value("cwd", UntaggedValue::filepath(path).into_value(&tag));
if let Some(home) = crate::filesystem::filesystem_shell::homedir_if_possible() {
nu_dict.insert_value("home-dir", UntaggedValue::filepath(home).into_value(&tag));
}
let temp = std::env::temp_dir();
nu_dict.insert_value("temp-dir", UntaggedValue::filepath(temp).into_value(&tag));
#[cfg(feature = "rustyline-support")]
{
let keybinding_path = nu_data::keybinding::keybinding_path()?;
nu_dict.insert_value(
"keybinding-path",
UntaggedValue::filepath(keybinding_path).into_value(&tag),
);
}
let cmd_info = lang::Lang::query_commands(scope);
match cmd_info {
Ok(cmds) => nu_dict.insert_value("lang", UntaggedValue::table(&cmds).into_value(&tag)),
Err(_) => nu_dict.insert_value("lang", UntaggedValue::string("no commands found")),
}
Ok(nu_dict.into_value())
}
pub fn scope(
aliases: &IndexMap<String, Vec<Spanned<String>>>,
commands: &IndexMap<String, Signature>,
variables: &IndexMap<String, Value>,
) -> Result<Value, ShellError> {
let tag = Tag::unknown();
let mut scope_dict = TaggedDictBuilder::new(&tag);
let mut aliases_dict = TaggedDictBuilder::new(&tag);
for v in aliases {
let values = v.1.clone();
let mut vec = Vec::new();
for k in &values {
vec.push(k.to_string());
}
let alias = vec.join(" ");
aliases_dict.insert_untagged(v.0, UntaggedValue::string(alias));
}
let mut commands_dict = TaggedDictBuilder::new(&tag);
for (name, signature) in commands {
commands_dict.insert_untagged(name, UntaggedValue::string(&signature.allowed().join(" ")))
}
let var_list: Vec<Value> = variables
.iter()
.map(|var| {
let mut entries: IndexMap<String, Value> = IndexMap::new();
let name = var.0.trim_start_matches('$');
entries.insert(
"name".to_string(),
UntaggedValue::string(name).into_value(&tag),
);
entries.insert(
"value".to_string(),
UntaggedValue::string(var.1.convert_to_string()).into_value(&tag),
);
entries.insert(
"type".to_string(),
UntaggedValue::string(ShellTypeName::type_name(&var.1)).into_value(&tag),
);
UntaggedValue::Row(Dictionary { entries }).into_value(&tag)
})
.collect();
scope_dict.insert_value("aliases", aliases_dict.into_value());
scope_dict.insert_value("commands", commands_dict.into_value());
scope_dict.insert_value("variables", UntaggedValue::Table(var_list).into_value(&tag));
Ok(scope_dict.into_value())
}

View File

@ -1,440 +0,0 @@
use crate::evaluate::envvar::EnvVar;
use crate::evaluate::evaluator::Variable;
use crate::evaluate::scope::{Scope, ScopeFrame};
use crate::shell::palette::ThemedPalette;
use crate::shell::shell_manager::ShellManager;
use crate::whole_stream_command::Command;
use crate::{call_info::UnevaluatedCallInfo, config_holder::ConfigHolder};
use crate::{command_args::CommandArgs, script};
use crate::{env::basic_host::BasicHost, Host};
use nu_data::config::{self, Conf, NuConfig};
use nu_errors::ShellError;
use nu_path::expand_path;
use nu_protocol::{hir, ConfigPath, VariableRegistry};
use nu_source::Spanned;
use nu_source::{Span, Tag};
use nu_stream::InputStream;
use nu_test_support::NATIVE_PATH_ENV_VAR;
use indexmap::IndexMap;
use log::trace;
use parking_lot::Mutex;
use std::fs::File;
use std::io::BufReader;
use std::sync::atomic::AtomicBool;
use std::{path::Path, sync::Arc};
#[derive(Clone, Default)]
pub struct EngineState {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub ctrl_c: Arc<AtomicBool>,
pub configs: Arc<Mutex<ConfigHolder>>,
pub shell_manager: ShellManager,
/// Windows-specific: keep track of previous cwd on each drive
pub windows_drives_previous_cwd: Arc<Mutex<std::collections::HashMap<String, String>>>,
}
#[derive(Clone, Default)]
pub struct EvaluationContext {
pub scope: Scope,
pub engine_state: Arc<EngineState>,
}
impl EvaluationContext {
pub fn new(
scope: Scope,
host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
current_errors: Arc<Mutex<Vec<ShellError>>>,
ctrl_c: Arc<AtomicBool>,
configs: Arc<Mutex<ConfigHolder>>,
shell_manager: ShellManager,
windows_drives_previous_cwd: Arc<Mutex<std::collections::HashMap<String, String>>>,
) -> Self {
Self {
scope,
engine_state: Arc::new(EngineState {
host,
current_errors,
ctrl_c,
configs,
shell_manager,
windows_drives_previous_cwd,
}),
}
}
pub fn basic() -> EvaluationContext {
let scope = Scope::new();
let host = BasicHost {};
let env_vars: IndexMap<String, EnvVar> = host
.vars()
.iter()
.cloned()
.map(|(k, v)| (k, v.into()))
.collect();
scope.add_env(env_vars);
EvaluationContext {
scope,
engine_state: Arc::new(EngineState {
host: Arc::new(parking_lot::Mutex::new(Box::new(host))),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: Arc::new(AtomicBool::new(false)),
configs: Arc::new(Mutex::new(ConfigHolder::new())),
shell_manager: ShellManager::basic(),
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
}),
}
}
pub fn error(&self, error: ShellError) {
self.with_errors(|errors| errors.push(error))
}
pub fn host(&self) -> &Arc<parking_lot::Mutex<Box<dyn Host>>> {
&self.engine_state.host
}
pub fn current_errors(&self) -> &Arc<Mutex<Vec<ShellError>>> {
&self.engine_state.current_errors
}
pub fn ctrl_c(&self) -> &Arc<AtomicBool> {
&self.engine_state.ctrl_c
}
pub fn configs(&self) -> &Arc<Mutex<ConfigHolder>> {
&self.engine_state.configs
}
pub fn shell_manager(&self) -> &ShellManager {
&self.engine_state.shell_manager
}
pub fn windows_drives_previous_cwd(
&self,
) -> &Arc<Mutex<std::collections::HashMap<String, String>>> {
&self.engine_state.windows_drives_previous_cwd
}
pub fn clear_errors(&self) {
self.engine_state.current_errors.lock().clear()
}
pub fn get_errors(&self) -> Vec<ShellError> {
self.engine_state.current_errors.lock().clone()
}
pub fn configure<T>(
&mut self,
config: &dyn nu_data::config::Conf,
block: impl FnOnce(&dyn nu_data::config::Conf, &mut Self) -> T,
) {
block(config, &mut *self);
}
pub fn with_host<T>(&self, block: impl FnOnce(&mut dyn Host) -> T) -> T {
let mut host = self.engine_state.host.lock();
block(&mut *host)
}
pub fn with_errors<T>(&self, block: impl FnOnce(&mut Vec<ShellError>) -> T) -> T {
let mut errors = self.engine_state.current_errors.lock();
block(&mut *errors)
}
pub fn add_commands(&self, commands: Vec<Command>) {
for command in commands {
self.scope.add_command(command.name().to_string(), command);
}
}
pub fn sync_path_to_env(&self) {
let env_vars = self.scope.get_env_vars();
for (var, val) in env_vars {
if var == NATIVE_PATH_ENV_VAR {
std::env::set_var(var, expand_path(val));
break;
}
}
}
pub fn get_command(&self, name: &str) -> Option<Command> {
self.scope.get_command(name)
}
pub fn is_command_registered(&self, name: &str) -> bool {
self.scope.has_command(name)
}
pub fn run_command(
&self,
command: Command,
name_tag: Tag,
args: hir::Call,
input: InputStream,
) -> Result<InputStream, ShellError> {
let command_args = self.command_args(args, input, name_tag);
command.run(command_args)
}
fn call_info(&self, args: hir::Call, name_tag: Tag) -> UnevaluatedCallInfo {
UnevaluatedCallInfo { args, name_tag }
}
fn command_args(&self, args: hir::Call, input: InputStream, name_tag: Tag) -> CommandArgs {
CommandArgs {
context: self.clone(),
call_info: self.call_info(args, name_tag),
input,
}
}
/// Loads config under cfg_path.
/// If an error occurs while loading the config:
/// The config is not loaded
/// The error is returned
/// After successful loading of the config the startup scripts are run
/// as normal scripts (Errors are printed out, ...)
/// After executing the startup scripts, true is returned to indicate successful loading
/// of the config
//
// The rational here is that, we should not partially load any config
// that might be damaged. However, startup scripts might fail for various reasons.
// A failure there is not as crucial as wrong config files.
pub fn load_config(&self, cfg_path: &ConfigPath) -> Result<(), ShellError> {
trace!("Loading cfg {:?}", cfg_path);
let cfg = NuConfig::load(Some(cfg_path.get_path().clone()))?;
let exit_scripts = cfg.exit_scripts()?;
let startup_scripts = cfg.startup_scripts()?;
let cfg_paths = cfg.path()?;
let joined_paths = cfg_paths
.map(|mut cfg_paths| {
//existing paths are prepended to path
let env_paths = self.scope.get_env(NATIVE_PATH_ENV_VAR);
if let Some(env_paths) = env_paths {
let mut env_paths = std::env::split_paths(&env_paths).collect::<Vec<_>>();
//No duplicates! Remove env_paths already existing in cfg_paths
env_paths.retain(|env_path| !cfg_paths.contains(env_path));
//env_paths entries are appended at the end
//nu config paths have a higher priority
cfg_paths.extend(env_paths);
}
cfg_paths
})
.map(|paths| {
std::env::join_paths(paths)
.map(|s| s.to_string_lossy().to_string())
.map_err(|e| {
ShellError::labeled_error(
&format!("Error while joining paths from config: {:?}", e),
"Config path error",
Span::unknown(),
)
})
})
.transpose()?;
let tag = config::cfg_path_to_scope_tag(cfg_path.get_path());
self.scope.enter_scope_with_tag(tag);
let config_env = cfg.env_map();
let env_vars = config_env
.into_iter()
.map(|(k, v)| (k, EnvVar::from(v)))
.collect();
self.scope.add_env(env_vars);
if let Some(path) = joined_paths {
self.scope.add_env_var(NATIVE_PATH_ENV_VAR, path);
}
self.scope.set_exit_scripts(exit_scripts);
match cfg_path {
ConfigPath::Global(_) => self.engine_state.configs.lock().set_global_cfg(cfg),
ConfigPath::Local(_) => {
self.engine_state.configs.lock().add_local_cfg(cfg);
}
}
// The syntax_theme is really the file stem of a json file i.e.
// grape.json is the theme file and grape is the file stem and
// the syntax_theme and grape.json would be located in the same
// folder as the config.toml
// Let's open the config
let global_config = self.engine_state.configs.lock().global_config();
// Get the root syntax_theme value
let syntax_theme = global_config.var("syntax_theme");
// If we have a syntax_theme let's process it
if let Some(theme_value) = syntax_theme {
// Append the .json to the syntax_theme to form the file name
let syntax_theme_filename = format!("{}.json", theme_value.convert_to_string());
// Load the syntax config json
let config_file_path = cfg_path.get_path();
// The syntax file should be in the same location as the config.toml
let syntax_file_path = if config_file_path.ends_with("config.toml") {
config_file_path
.display()
.to_string()
.replace("config.toml", &syntax_theme_filename)
} else {
"".to_string()
};
// if we have a syntax_file_path use it otherwise default
if Path::new(&syntax_file_path).exists() {
// eprintln!("Loading syntax file: [{:?}]", syntax_file_path);
let syntax_theme_file = File::open(syntax_file_path)?;
let mut reader = BufReader::new(syntax_theme_file);
let theme = ThemedPalette::new(&mut reader).unwrap_or_default();
// eprintln!("Theme: [{:?}]", theme);
self.engine_state.configs.lock().set_syntax_colors(theme);
} else {
// If the file was missing, use the default
self.engine_state
.configs
.lock()
.set_syntax_colors(ThemedPalette::default())
}
} else {
// if there's no syntax_theme, use the default
self.engine_state
.configs
.lock()
.set_syntax_colors(ThemedPalette::default())
};
if !startup_scripts.is_empty() {
self.run_scripts(startup_scripts, cfg_path.get_path().parent());
}
Ok(())
}
/// Reloads config with a path of cfg_path.
/// If an error occurs while reloading the config:
/// The config is not reloaded
/// The error is returned
pub fn reload_config(&self, cfg: &mut NuConfig) -> Result<(), ShellError> {
trace!("Reloading cfg {:?}", cfg.file_path);
cfg.reload();
let exit_scripts = cfg.exit_scripts()?;
let cfg_paths = cfg.path()?;
let joined_paths = cfg_paths
.map(|mut cfg_paths| {
//existing paths are prepended to path
let env_paths = self.scope.get_env(NATIVE_PATH_ENV_VAR);
if let Some(env_paths) = env_paths {
let mut env_paths = std::env::split_paths(&env_paths).collect::<Vec<_>>();
//No duplicates! Remove env_paths already existing in cfg_paths
env_paths.retain(|env_path| !cfg_paths.contains(env_path));
//env_paths entries are appended at the end
//nu config paths have a higher priority
cfg_paths.extend(env_paths);
}
cfg_paths
})
.map(|paths| {
std::env::join_paths(paths)
.map(|s| s.to_string_lossy().to_string())
.map_err(|e| {
ShellError::labeled_error(
&format!("Error while joining paths from config: {:?}", e),
"Config path error",
Span::unknown(),
)
})
})
.transpose()?;
let tag = config::cfg_path_to_scope_tag(&cfg.file_path);
let mut frame = ScopeFrame::with_tag(tag.clone());
let config_env = cfg.env_map();
let env_vars = config_env
.into_iter()
.map(|(k, v)| (k, EnvVar::from(v)))
.collect();
frame.env = env_vars;
if let Some(path) = joined_paths {
frame
.env
.insert(NATIVE_PATH_ENV_VAR.to_string(), path.into());
}
frame.exitscripts = exit_scripts;
self.scope.update_frame_with_tag(frame, &tag)?;
Ok(())
}
/// Runs all exit_scripts before unloading the config with path of cfg_path
/// If an error occurs while running exit scripts:
/// The error is added to `self.current_errors`
/// If no config with path of `cfg_path` is present, this method does nothing
pub fn unload_config(&self, cfg_path: &ConfigPath) {
trace!("UnLoading cfg {:?}", cfg_path);
let tag = config::cfg_path_to_scope_tag(cfg_path.get_path());
//Run exitscripts with scope frame and cfg still applied
if let Some(scripts) = self.scope.get_exitscripts_of_frame_with_tag(&tag) {
self.run_scripts(scripts, cfg_path.get_path().parent());
}
//Unload config
self.engine_state.configs.lock().remove_cfg(cfg_path);
self.scope.exit_scope_with_tag(&tag);
}
/// Runs scripts with cwd of dir. If dir is None, this method does nothing.
/// Each error is added to `self.current_errors`
pub fn run_scripts(&self, scripts: Vec<String>, dir: Option<&Path>) {
if let Some(dir) = dir {
for script in scripts {
match script::run_script_in_dir(script.clone(), dir, self) {
Ok(_) => {}
Err(e) => {
let err = ShellError::untagged_runtime_error(format!(
"Err while executing exitscript. Err was\n{:?}",
e
));
let text = script.into();
self.engine_state.host.lock().print_err(err, &text);
}
}
}
}
}
}
use itertools::Itertools;
impl VariableRegistry for EvaluationContext {
fn get_variable(&self, name: &Spanned<&str>) -> Option<nu_protocol::Value> {
let span = name.span;
let name = nu_protocol::hir::Expression::variable(name.item.to_string(), name.span);
let var = Variable::from(&name);
crate::evaluate::evaluator::evaluate_reference(&var, self, span).ok()
}
fn variables(&self) -> Vec<String> {
Variable::list()
.into_iter()
.chain(self.scope.get_variable_names())
.unique()
.collect()
}
}

View File

@ -1,7 +0,0 @@
use nu_protocol::Value;
pub struct Example {
pub example: &'static str,
pub description: &'static str,
pub result: Option<Vec<Value>>,
}

View File

@ -1,275 +0,0 @@
use filesize::file_real_size_fast;
use glob::Pattern;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
use nu_source::Tag;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
pub struct DirBuilder {
pub tag: Tag,
pub min: Option<u64>,
pub deref: bool,
pub exclude: Option<Pattern>,
pub all: bool,
}
impl DirBuilder {
pub fn new(
tag: Tag,
min: Option<u64>,
deref: bool,
exclude: Option<Pattern>,
all: bool,
) -> DirBuilder {
DirBuilder {
tag,
min,
deref,
exclude,
all,
}
}
}
pub struct DirInfo {
dirs: Vec<DirInfo>,
files: Vec<FileInfo>,
errors: Vec<ShellError>,
size: u64,
blocks: u64,
path: PathBuf,
tag: Tag,
}
pub struct FileInfo {
path: PathBuf,
size: u64,
blocks: Option<u64>,
tag: Tag,
}
impl FileInfo {
pub fn new(path: impl Into<PathBuf>, deref: bool, tag: Tag) -> Result<Self, ShellError> {
let path = path.into();
let m = if deref {
std::fs::metadata(&path)
} else {
std::fs::symlink_metadata(&path)
};
match m {
Ok(d) => {
let block_size = file_real_size_fast(&path, &d).ok();
Ok(FileInfo {
path,
blocks: block_size,
size: d.len(),
tag,
})
}
Err(e) => Err(e.into()),
}
}
}
impl DirInfo {
pub fn new(
path: impl Into<PathBuf>,
params: &DirBuilder,
depth: Option<u64>,
ctrl_c: Arc<AtomicBool>,
) -> Self {
let path = path.into();
let mut s = Self {
dirs: Vec::new(),
errors: Vec::new(),
files: Vec::new(),
size: 0,
blocks: 0,
tag: params.tag.clone(),
path,
};
match std::fs::metadata(&s.path) {
Ok(d) => {
s.size = d.len(); // dir entry size
s.blocks = file_real_size_fast(&s.path, &d).ok().unwrap_or(0);
}
Err(e) => s = s.add_error(e.into()),
};
match std::fs::read_dir(&s.path) {
Ok(d) => {
for f in d {
if ctrl_c.load(Ordering::SeqCst) {
break;
}
match f {
Ok(i) => match i.file_type() {
Ok(t) if t.is_dir() => {
s = s.add_dir(i.path(), depth, params, ctrl_c.clone())
}
Ok(_t) => s = s.add_file(i.path(), params),
Err(e) => s = s.add_error(e.into()),
},
Err(e) => s = s.add_error(e.into()),
}
}
}
Err(e) => s = s.add_error(e.into()),
}
s
}
fn add_dir(
mut self,
path: impl Into<PathBuf>,
mut depth: Option<u64>,
params: &DirBuilder,
ctrl_c: Arc<AtomicBool>,
) -> Self {
if let Some(current) = depth {
if let Some(new) = current.checked_sub(1) {
depth = Some(new);
} else {
return self;
}
}
let d = DirInfo::new(path, params, depth, ctrl_c);
self.size += d.size;
self.blocks += d.blocks;
self.dirs.push(d);
self
}
fn add_file(mut self, f: impl Into<PathBuf>, params: &DirBuilder) -> Self {
let f = f.into();
let include = params
.exclude
.as_ref()
.map_or(true, |x| !x.matches_path(&f));
if include {
match FileInfo::new(f, params.deref, self.tag.clone()) {
Ok(file) => {
let inc = params.min.map_or(true, |s| file.size >= s);
if inc {
self.size += file.size;
self.blocks += file.blocks.unwrap_or(0);
if params.all {
self.files.push(file);
}
}
}
Err(e) => self = self.add_error(e),
}
}
self
}
fn add_error(mut self, e: ShellError) -> Self {
self.errors.push(e);
self
}
pub fn get_size(&self) -> u64 {
self.size
}
}
impl From<DirInfo> for Value {
fn from(d: DirInfo) -> Self {
let mut r: IndexMap<String, Value> = IndexMap::new();
r.insert(
"path".to_string(),
UntaggedValue::filepath(d.path).into_value(&d.tag),
);
r.insert(
"apparent".to_string(),
UntaggedValue::filesize(d.size).into_value(&d.tag),
);
r.insert(
"physical".to_string(),
UntaggedValue::filesize(d.blocks).into_value(&d.tag),
);
r.insert("directories".to_string(), value_from_vec(d.dirs, &d.tag));
r.insert("files".to_string(), value_from_vec(d.files, &d.tag));
if !d.errors.is_empty() {
let v = UntaggedValue::Table(
d.errors
.into_iter()
.map(move |e| UntaggedValue::Error(e).into_untagged_value())
.collect::<Vec<Value>>(),
)
.into_value(&d.tag);
r.insert("errors".to_string(), v);
}
Value {
value: UntaggedValue::row(r),
tag: d.tag,
}
}
}
impl From<FileInfo> for Value {
fn from(f: FileInfo) -> Self {
let mut r: IndexMap<String, Value> = IndexMap::new();
r.insert(
"path".to_string(),
UntaggedValue::filepath(f.path).into_value(&f.tag),
);
r.insert(
"apparent".to_string(),
UntaggedValue::filesize(f.size).into_value(&f.tag),
);
let b = f
.blocks
.map(UntaggedValue::filesize)
.unwrap_or_else(UntaggedValue::nothing)
.into_value(&f.tag);
r.insert("physical".to_string(), b);
r.insert(
"directories".to_string(),
UntaggedValue::nothing().into_value(&f.tag),
);
r.insert(
"files".to_string(),
UntaggedValue::nothing().into_value(&f.tag),
);
UntaggedValue::row(r).into_value(&f.tag)
}
}
fn value_from_vec<V>(vec: Vec<V>, tag: &Tag) -> Value
where
V: Into<Value>,
{
if vec.is_empty() {
UntaggedValue::nothing()
} else {
let values = vec.into_iter().map(Into::into).collect::<Vec<Value>>();
UntaggedValue::Table(values)
}
.into_value(tag)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
pub(crate) mod dir_info;
pub mod filesystem_shell;
pub(crate) mod utils;

View File

@ -1,210 +0,0 @@
use nu_errors::ShellError;
use nu_path::canonicalize_with;
use std::path::{Path, PathBuf};
#[derive(Default)]
pub struct FileStructure {
pub resources: Vec<Res>,
}
impl FileStructure {
pub fn new() -> FileStructure {
FileStructure {
resources: Vec::<Res>::new(),
}
}
#[allow(dead_code)]
pub fn contains_more_than_one_file(&self) -> bool {
self.resources.len() > 1
}
#[allow(dead_code)]
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.loc), f.at))
.map(to)
.collect()
}
pub fn walk_decorate(&mut self, start_path: &Path) -> Result<(), ShellError> {
self.resources = Vec::<Res>::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(Res {
loc: path.to_path_buf(),
at: lvl,
});
}
} else {
self.resources.push(Res {
loc: source,
at: lvl,
});
}
Ok(())
}
}
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Res {
pub at: usize,
pub loc: PathBuf,
}
impl Res {}
#[cfg(test)]
mod tests {
use super::{FileStructure, Res};
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value, ValueResource, ValueStructure};
use nu_source::Tag;
use nu_test_support::{fs::Stub::EmptyFile, playground::Playground};
use std::path::PathBuf;
fn structured_sample_record(key: &str, value: &str) -> Value {
let mut record = TaggedDictBuilder::new(Tag::unknown());
record.insert_untagged(key, UntaggedValue::string(value));
record.into_value()
}
fn sample_nushell_source_code() -> Value {
/*
src
commands
plugins => "sys.rs"
tests
helpers => "mod.rs"
*/
let mut src = TaggedDictBuilder::new(Tag::unknown());
let mut record = TaggedDictBuilder::new(Tag::unknown());
record.insert_value("commands", structured_sample_record("plugins", "sys.rs"));
record.insert_value("tests", structured_sample_record("helpers", "mod.rs"));
src.insert_value("src", record.into_value());
src.into_value()
}
#[test]
fn prepares_and_decorates_value_filesystemlike_sources() {
let mut res = ValueStructure::new();
res.walk_decorate(&sample_nushell_source_code())
.expect("Can not decorate values traversal.");
assert_eq!(
res.resources,
vec![
ValueResource {
loc: PathBuf::from("src"),
at: 0,
},
ValueResource {
loc: PathBuf::from("commands"),
at: 1,
},
ValueResource {
loc: PathBuf::from("tests"),
at: 1,
},
ValueResource {
loc: PathBuf::from("helpers"),
at: 2,
},
ValueResource {
loc: PathBuf::from("plugins"),
at: 2,
},
]
);
}
#[test]
fn recognizes_if_path_exists_in_value_filesystemlike_sources() {
let mut res = ValueStructure::new();
res.walk_decorate(&sample_nushell_source_code())
.expect("Can not decorate values traversal.");
assert!(res.exists(&PathBuf::from("/")));
assert!(res.exists(&PathBuf::from("src/commands/plugins")));
assert!(res.exists(&PathBuf::from("src/commands")));
assert!(res.exists(&PathBuf::from("src/tests")));
assert!(res.exists(&PathBuf::from("src/tests/helpers")));
assert!(res.exists(&PathBuf::from("src")));
assert!(res.exists(&PathBuf::from("/src/commands/plugins")));
assert!(res.exists(&PathBuf::from("/src/commands")));
assert!(res.exists(&PathBuf::from("/src/tests")));
assert!(res.exists(&PathBuf::from("/src/tests/helpers")));
assert!(res.exists(&PathBuf::from("/src")));
assert!(!res.exists(&PathBuf::from("/not_valid")));
assert!(!res.exists(&PathBuf::from("/src/not_valid")));
}
#[test]
fn prepares_and_decorates_filesystem_source_files() {
Playground::setup("file_structure_test", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("sample.ini"),
EmptyFile("sample.eml"),
EmptyFile("cargo_sample.toml"),
]);
let mut res = FileStructure::new();
res.walk_decorate(dirs.test())
.expect("Can not decorate files traversal.");
assert_eq!(
res.resources,
vec![
Res {
loc: dirs.test().join("cargo_sample.toml"),
at: 0
},
Res {
loc: dirs.test().join("sample.eml"),
at: 0
},
Res {
loc: dirs.test().join("sample.ini"),
at: 0
}
]
);
})
}
}

View File

@ -1,487 +0,0 @@
use std::path::PathBuf;
use bigdecimal::{BigDecimal, ToPrimitive};
use chrono::{DateTime, FixedOffset};
use nu_errors::ShellError;
use nu_path::expand_path;
use nu_protocol::{
hir::CapturedBlock, ColumnPath, Dictionary, Primitive, Range, SpannedTypeName, UntaggedValue,
Value,
};
use nu_source::{Tagged, TaggedItem};
use num_bigint::BigInt;
pub trait FromValue: Sized {
fn from_value(v: &Value) -> Result<Self, ShellError>;
}
impl FromValue for Value {
fn from_value(v: &Value) -> Result<Self, ShellError> {
Ok(v.clone())
}
}
impl FromValue for Tagged<num_bigint::BigInt> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
match v {
Value {
value: UntaggedValue::Primitive(Primitive::Int(i)),
..
} => Ok(BigInt::from(*i).tagged(tag)),
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(i)),
..
} => Ok(BigInt::from(*i).tagged(tag)),
Value {
value: UntaggedValue::Primitive(Primitive::Duration(i)),
..
} => Ok(i.clone().tagged(tag)),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("integer", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("integer", v.spanned_type_name())),
}
}
}
impl FromValue for num_bigint::BigInt {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::Int(i)),
..
} => Ok(BigInt::from(*i)),
Value {
value: UntaggedValue::Primitive(Primitive::Filesize(i)),
..
} => Ok(BigInt::from(*i)),
Value {
value: UntaggedValue::Primitive(Primitive::Duration(i)),
..
} => Ok(i.clone()),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("integer", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("integer", v.spanned_type_name())),
}
}
}
impl FromValue for Tagged<u64> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
v.as_u64().map(|s| s.tagged(tag))
}
}
impl FromValue for u64 {
fn from_value(v: &Value) -> Result<Self, ShellError> {
v.as_u64()
}
}
impl FromValue for i64 {
fn from_value(v: &Value) -> Result<Self, ShellError> {
v.as_i64()
}
}
impl FromValue for Tagged<i64> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
v.as_i64().map(|s| s.tagged(tag))
}
}
impl FromValue for Tagged<u32> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
v.as_u32().map(|s| s.tagged(tag))
}
}
impl FromValue for Tagged<i16> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
v.as_i16().map(|s| s.tagged(tag))
}
}
impl FromValue for Tagged<usize> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
v.as_usize().map(|s| s.tagged(tag))
}
}
impl FromValue for Tagged<char> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
v.as_char().map(|c| c.tagged(tag))
}
}
impl FromValue for usize {
fn from_value(v: &Value) -> Result<Self, ShellError> {
v.as_usize()
}
}
impl FromValue for i32 {
fn from_value(v: &Value) -> Result<Self, ShellError> {
v.as_i32()
}
}
impl FromValue for bigdecimal::BigDecimal {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::Decimal(d)),
..
} => Ok(d.clone()),
Value {
value: UntaggedValue::Primitive(Primitive::Int(i)),
..
} => Ok(BigDecimal::from(*i)),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("decimal", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("decimal", v.spanned_type_name())),
}
}
}
impl FromValue for Tagged<bigdecimal::BigDecimal> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
match &v.value {
UntaggedValue::Primitive(Primitive::Decimal(d)) => Ok(d.clone().tagged(tag)),
UntaggedValue::Primitive(Primitive::Int(i)) => Ok(BigDecimal::from(*i).tagged(tag)),
_ => Err(ShellError::type_error("decimal", v.spanned_type_name())),
}
}
}
impl FromValue for Tagged<f64> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
let decimal: bigdecimal::BigDecimal = FromValue::from_value(v)?;
match decimal.to_f64() {
Some(d) => Ok(d.tagged(tag)),
_ => Err(ShellError::type_error("decimal", v.spanned_type_name())),
}
}
}
impl FromValue for String {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
} => Ok(s.clone()),
Value {
value: UntaggedValue::Primitive(Primitive::GlobPattern(s)),
..
} => Ok(s.clone()),
Value {
value: UntaggedValue::Primitive(Primitive::FilePath(p)),
..
} => Ok(p.to_string_lossy().to_string()),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("string", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("string", v.spanned_type_name())),
}
}
}
impl FromValue for Tagged<String> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
v.as_string().map(|s| s.tagged(tag))
}
}
impl FromValue for PathBuf {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
} => Ok(expand_path(s)),
Value {
value: UntaggedValue::Primitive(Primitive::FilePath(p)),
..
} => Ok(expand_path(p)),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("filepath", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("filepath", v.spanned_type_name())),
}
}
}
impl FromValue for Tagged<PathBuf> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
tag,
} => Ok(expand_path(s).tagged(tag)),
Value {
value: UntaggedValue::Primitive(Primitive::FilePath(p)),
tag,
} => Ok(expand_path(p).tagged(tag)),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("filepath", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("filepath", v.spanned_type_name())),
}
}
}
impl FromValue for ColumnPath {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::ColumnPath(c)),
..
} => Ok(c.clone()),
v => Err(ShellError::type_error("column path", v.spanned_type_name())),
}
}
}
impl FromValue for bool {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
..
} => Ok(*b),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("boolean", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("boolean", v.spanned_type_name())),
}
}
}
impl FromValue for Tagged<bool> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
tag,
} => Ok((*b).tagged(tag)),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("boolean", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("boolean", v.spanned_type_name())),
}
}
}
impl FromValue for DateTime<FixedOffset> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::Date(d)),
..
} => Ok(*d),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("date", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("date", v.spanned_type_name())),
}
}
}
impl FromValue for Range {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::Range(r)),
..
} => Ok((**r).clone()),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("range", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("range", v.spanned_type_name())),
}
}
}
impl FromValue for Tagged<Range> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let tag = v.tag.clone();
match v {
Value {
value: UntaggedValue::Primitive(Primitive::Range(ref range)),
..
} => Ok((*range.clone()).tagged(tag)),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("range", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("range", v.spanned_type_name())),
}
}
}
impl FromValue for Vec<u8> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Primitive(Primitive::Binary(b)),
..
} => Ok(b.clone()),
Value {
value: UntaggedValue::Primitive(Primitive::String(s)),
..
} => Ok(s.bytes().collect()),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("binary data", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("binary data", v.spanned_type_name())),
}
}
}
impl FromValue for Dictionary {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Row(r),
..
} => Ok(r.clone()),
v => Err(ShellError::type_error("row", v.spanned_type_name())),
}
}
}
impl FromValue for CapturedBlock {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Block(b),
..
} => Ok((**b).clone()),
Value {
value: UntaggedValue::Row(_),
..
} => {
let mut shell_error = ShellError::type_error("block", v.spanned_type_name());
shell_error.notes.push(
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
);
Err(shell_error)
}
v => Err(ShellError::type_error("block", v.spanned_type_name())),
}
}
}
impl FromValue for Vec<Value> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value {
value: UntaggedValue::Table(t),
..
} => Ok(t.clone()),
Value {
value: UntaggedValue::Row(_),
..
} => Ok(vec![v.clone()]),
v => Err(ShellError::type_error("table", v.spanned_type_name())),
}
}
}

View File

@ -0,0 +1,121 @@
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::{Component, Path, PathBuf};
use nu_path::{canonicalize_with, expand_path_with};
use nu_protocol::{ShellError, Span, Spanned};
/// This function is like `glob::glob` from the `glob` crate, except it is relative to a given cwd.
///
/// It returns a tuple of two values: the first is an optional prefix that the expanded filenames share.
/// This prefix can be removed from the front of each value to give an approximation of the relative path
/// to the user
///
/// The second of the two values is an iterator over the matching filepaths.
#[allow(clippy::type_complexity)]
pub fn glob_from(
pattern: &Spanned<String>,
cwd: &Path,
span: Span,
) -> Result<
(
Option<PathBuf>,
Box<dyn Iterator<Item = Result<PathBuf, ShellError>> + Send>,
),
ShellError,
> {
let path = PathBuf::from(&pattern.item);
let path = if path.is_relative() {
expand_path_with(path, cwd)
} else {
path
};
let (prefix, pattern) = if path.to_string_lossy().contains('*') {
// Path is a glob pattern => do not check for existence
// Select the longest prefix until the first '*'
let mut p = PathBuf::new();
for c in path.components() {
if let Component::Normal(os) = c {
if os.to_string_lossy().contains('*') {
break;
}
}
p.push(c);
}
(Some(p), path)
} else {
let path = if let Ok(p) = canonicalize_with(path, &cwd) {
p
} else {
return Err(ShellError::DirectoryNotFound(pattern.span));
};
if path.is_dir() {
if permission_denied(&path) {
#[cfg(unix)]
let error_msg = format!(
"The permissions of {:o} do not allow access for this user",
path.metadata()
.expect("this shouldn't be called since we already know there is a dir")
.permissions()
.mode()
& 0o0777
);
#[cfg(not(unix))]
let error_msg = String::from("Permission denied");
return Err(ShellError::SpannedLabeledError(
"Permission denied".into(),
error_msg,
pattern.span,
));
}
if is_empty_dir(&path) {
return Ok((Some(path), Box::new(vec![].into_iter())));
}
(Some(path.clone()), path.join("*"))
} else {
(path.parent().map(|parent| parent.to_path_buf()), path)
}
};
let pattern = pattern.to_string_lossy().to_string();
let glob = glob::glob(&pattern).map_err(|err| {
nu_protocol::ShellError::SpannedLabeledError(
"Error extracting glob pattern".into(),
err.to_string(),
span,
)
})?;
Ok((
prefix,
Box::new(glob.map(move |x| match x {
Ok(v) => Ok(v),
Err(err) => Err(nu_protocol::ShellError::SpannedLabeledError(
"Error extracting glob pattern".into(),
err.to_string(),
span,
)),
})),
))
}
fn permission_denied(dir: impl AsRef<Path>) -> bool {
match dir.as_ref().read_dir() {
Err(e) => matches!(e.kind(), std::io::ErrorKind::PermissionDenied),
Ok(_) => false,
}
}
fn is_empty_dir(dir: impl AsRef<Path>) -> bool {
match dir.as_ref().read_dir() {
Err(_) => true,
Ok(mut s) => s.next().is_none(),
}
}

View File

@ -1,3 +1,4 @@
<<<<<<< HEAD
mod call_info;
mod command_args;
mod config_holder;
@ -38,3 +39,21 @@ pub use crate::shell::palette::{DefaultPalette, Palette};
pub use crate::shell::shell_manager::ShellManager;
pub use crate::shell::value_shell;
pub use crate::whole_stream_command::{whole_stream_command, Command, WholeStreamCommand};
=======
mod call_ext;
pub mod column;
pub mod documentation;
pub mod env;
mod eval;
mod glob_from;
pub use call_ext::CallExt;
pub use column::get_columns;
pub use documentation::{generate_docs, get_brief_help, get_documentation, get_full_help};
pub use env::*;
pub use eval::{
eval_block, eval_block_with_redirect, eval_expression, eval_expression_with_input,
eval_operator, eval_subexpression,
};
pub use glob_from::glob_from;
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce

View File

@ -1,113 +0,0 @@
use std::io::{BufRead, BufReader, Read};
use nu_errors::ShellError;
use encoding_rs::{CoderResult, Decoder, Encoding, UTF_8};
#[cfg(not(test))]
const OUTPUT_BUFFER_SIZE: usize = 8192;
#[cfg(test)]
const OUTPUT_BUFFER_SIZE: usize = 4;
#[derive(Debug, Eq, PartialEq)]
pub enum StringOrBinary {
String(String),
Binary(Vec<u8>),
}
pub struct MaybeTextCodec {
decoder: Decoder,
}
pub struct BufCodecReader<R: Read> {
maybe_text_codec: MaybeTextCodec,
input: BufReader<R>,
}
impl<R: Read> BufCodecReader<R> {
pub fn new(input: BufReader<R>, maybe_text_codec: MaybeTextCodec) -> Self {
BufCodecReader {
maybe_text_codec,
input,
}
}
}
impl<R: Read> Iterator for BufCodecReader<R> {
type Item = Result<StringOrBinary, ShellError>;
fn next(&mut self) -> Option<Self::Item> {
let buffer = self.input.fill_buf();
match buffer {
Ok(s) => {
let result = self.maybe_text_codec.decode(s).transpose();
let buffer_len = s.len();
self.input.consume(buffer_len);
result
}
Err(e) => Some(Err(ShellError::untagged_runtime_error(e.to_string()))),
}
}
}
impl MaybeTextCodec {
// The constructor takes an Option<&'static Encoding>, because an absence of an encoding indicates that we want BOM sniffing enabled
pub fn new(encoding: Option<&'static Encoding>) -> Self {
let decoder = match encoding {
Some(e) => e.new_decoder_with_bom_removal(),
None => UTF_8.new_decoder(),
};
MaybeTextCodec { decoder }
}
}
impl Default for MaybeTextCodec {
fn default() -> Self {
MaybeTextCodec {
decoder: UTF_8.new_decoder(),
}
}
}
impl MaybeTextCodec {
pub fn decode(&mut self, src: &[u8]) -> Result<Option<StringOrBinary>, ShellError> {
if src.is_empty() {
return Ok(None);
}
let mut s = String::with_capacity(OUTPUT_BUFFER_SIZE);
let (res, _read, replacements) = self.decoder.decode_to_string(src, &mut s, false);
let result = if replacements {
// If we had to make replacements when converting to utf8, fall back to binary
StringOrBinary::Binary(src.to_vec())
} else {
// If original buffer size is too small, we continue to allocate new Strings and append
// them to the result until the input buffer is smaller than the allocated String
if let CoderResult::OutputFull = res {
let mut buffer = String::with_capacity(OUTPUT_BUFFER_SIZE);
loop {
let (res, _read, _replacements) =
self.decoder
.decode_to_string(&src[s.len()..], &mut buffer, false);
s.push_str(&buffer);
if let CoderResult::InputEmpty = res {
break;
}
buffer.clear();
}
}
StringOrBinary::String(s)
};
// src.clear();
Ok(Some(result))
}
}

View File

@ -1,172 +0,0 @@
use crate::plugin::run_plugin::PluginCommandBuilder;
use log::trace;
use nu_errors::ShellError;
use nu_path::canonicalize;
use nu_plugin::jsonrpc::JsonRpc;
use nu_protocol::{Signature, Value};
use std::io::{BufRead, BufReader, Write};
use std::process::{Child, Command, Stdio};
use rayon::prelude::*;
pub fn build_plugin_command(
path: &std::path::Path,
) -> Result<Option<PluginCommandBuilder>, ShellError> {
let ext = path.extension();
let ps1_file = match ext {
Some(ext) => ext == "ps1",
None => false,
};
let mut child: Child = if ps1_file {
Command::new("pwsh")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.args([
"-NoLogo",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
&path.to_string_lossy(),
])
.spawn()
.expect("Failed to spawn PowerShell process")
} else {
Command::new(path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to spawn child process")
};
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request = JsonRpc::new("config", Vec::<Value>::new());
let request_raw = serde_json::to_string(&request)?;
trace!(target: "nu::load", "plugin infrastructure config -> path {:#?}, request {:?}", &path, &request_raw);
stdin.write_all(format!("{}\n", request_raw).as_bytes())?;
let path = canonicalize(path)?;
let mut input = String::new();
let result = match reader.read_line(&mut input) {
Ok(count) => {
trace!(target: "nu::load", "plugin infrastructure -> config response for {:#?}", &path);
trace!(target: "nu::load", "plugin infrastructure -> processing response ({} bytes)", count);
trace!(target: "nu::load", "plugin infrastructure -> response: {}", input);
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
match response {
Ok(jrpc) => match jrpc.params {
Ok(params) => {
let fname = path.to_string_lossy();
trace!(target: "nu::load", "plugin infrastructure -> processing {:?}", params);
let name = params.name.clone();
let fname = fname.to_string();
Ok(Some(PluginCommandBuilder::new(&name, &fname, params)))
}
Err(e) => Err(e),
},
Err(e) => {
trace!(target: "nu::load", "plugin infrastructure -> incompatible {:?}", input);
Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}",
e
)))
}
}
}
Err(e) => Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}",
e
))),
};
let _ = child.wait();
result
}
pub fn scan(
paths: Vec<std::path::PathBuf>,
) -> Result<Vec<crate::whole_stream_command::Command>, ShellError> {
let mut plugins = vec![];
let opts = glob::MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
};
for path in paths {
let mut pattern = path.to_path_buf();
pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*"));
let plugs: Vec<_> = glob::glob_with(&pattern.to_string_lossy(), opts)?
.filter_map(|x| x.ok())
.collect();
let plugs: Vec<_> = plugs
.par_iter()
.filter_map(|path| {
let bin_name = {
if let Some(name) = path.file_name() {
name.to_str().unwrap_or("")
} else {
""
}
};
// allow plugins with extensions on all platforms
let is_valid_name = {
bin_name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
};
let is_executable = {
#[cfg(windows)]
{
bin_name.ends_with(".exe")
|| bin_name.ends_with(".bat")
|| bin_name.ends_with(".cmd")
|| bin_name.ends_with(".py")
|| bin_name.ends_with(".ps1")
}
#[cfg(not(windows))]
{
!bin_name.contains('.')
|| (bin_name.ends_with('.')
|| bin_name.ends_with(".py")
|| bin_name.ends_with(".rb")
|| bin_name.ends_with(".sh")
|| bin_name.ends_with(".bash")
|| bin_name.ends_with(".zsh")
|| bin_name.ends_with(".pl")
|| bin_name.ends_with(".awk")
|| bin_name.ends_with(".ps1"))
}
};
if is_valid_name && is_executable {
trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
build_plugin_command(path).unwrap_or(None)
} else {
None
}
}).map(|p| p.build())
.collect::<Vec<crate::whole_stream_command::Command>>();
plugins.extend(plugs);
}
Ok(plugins)
}

View File

@ -1,2 +0,0 @@
pub mod build_plugin;
pub(crate) mod run_plugin;

View File

@ -1,549 +0,0 @@
use crate::{command_args::CommandArgs, evaluate_baseline_expr, UnevaluatedCallInfo};
use crate::{
whole_stream_command::{whole_stream_command, WholeStreamCommand},
EvaluationContext,
};
use derive_new::new;
use indexmap::IndexMap;
use log::trace;
use nu_errors::ShellError;
use nu_plugin::jsonrpc::JsonRpc;
use nu_protocol::{hir, Primitive, ReturnValue, Signature, UntaggedValue, Value};
use nu_source::Tag;
use nu_stream::{ActionStream, InputStream, IntoActionStream};
use serde::{self, Deserialize, Serialize};
use std::collections::VecDeque;
use std::io::prelude::*;
use std::io::BufReader;
use std::io::Write;
use std::path::Path;
use std::process::{Child, Command, Stdio};
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "method")]
#[allow(non_camel_case_types)]
pub enum NuResult {
response {
params: Result<VecDeque<ReturnValue>, ShellError>,
},
}
enum PluginCommand {
Filter(PluginFilter),
Sink(PluginSink),
}
impl PluginCommand {
fn command(self) -> crate::whole_stream_command::Command {
match self {
PluginCommand::Filter(cmd) => whole_stream_command(cmd),
PluginCommand::Sink(cmd) => whole_stream_command(cmd),
}
}
}
enum PluginMode {
Filter,
Sink,
}
pub struct PluginCommandBuilder {
mode: PluginMode,
name: String,
path: String,
config: Signature,
}
impl PluginCommandBuilder {
pub fn new(
name: impl Into<String>,
path: impl Into<String>,
config: impl Into<Signature>,
) -> Self {
let config = config.into();
PluginCommandBuilder {
mode: if config.is_filter {
PluginMode::Filter
} else {
PluginMode::Sink
},
name: name.into(),
path: path.into(),
config,
}
}
pub fn build(&self) -> crate::whole_stream_command::Command {
let mode = &self.mode;
let name = self.name.clone();
let path = self.path.clone();
let config = self.config.clone();
let cmd = match mode {
PluginMode::Filter => PluginCommand::Filter(PluginFilter { name, path, config }),
PluginMode::Sink => PluginCommand::Sink(PluginSink { name, path, config }),
};
cmd.command()
}
}
#[derive(new)]
pub struct PluginFilter {
name: String,
path: String,
config: Signature,
}
impl WholeStreamCommand for PluginFilter {
fn name(&self) -> &str {
&self.name
}
fn signature(&self) -> Signature {
self.config.clone()
}
fn usage(&self) -> &str {
&self.config.usage
}
fn extra_usage(&self) -> &str {
&self.config.extra_usage
}
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
run_filter(self.path.clone(), args)
}
fn is_plugin(&self) -> bool {
true
}
fn is_builtin(&self) -> bool {
false
}
}
fn run_filter(path: String, args: CommandArgs) -> Result<ActionStream, ShellError> {
trace!("filter_plugin :: {}", path);
let bos = vec![UntaggedValue::Primitive(Primitive::BeginningOfStream).into_untagged_value()]
.into_iter();
let eos = [UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()];
let (call_info, input) = evaluate_once(args)?;
let real_path = Path::new(&path);
let ext = real_path.extension();
let ps1_file = match ext {
Some(ext) => ext == "ps1",
None => false,
};
let mut child: Child = if ps1_file {
Command::new("pwsh")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.args([
"-NoLogo",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
&real_path.to_string_lossy(),
])
.spawn()
.expect("Failed to spawn PowerShell process")
} else {
Command::new(path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to spawn child process")
};
trace!("filtering :: {:?}", call_info);
Ok(bos
.chain(input)
.chain(eos)
.flat_map(move |item| {
match item {
Value {
value: UntaggedValue::Primitive(Primitive::BeginningOfStream),
..
} => {
// Beginning of the stream
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request = JsonRpc::new("begin_filter", call_info.clone());
let request_raw = serde_json::to_string(&request);
trace!("begin_filter:request {:?}", &request_raw);
match request_raw {
Err(_) => {
return ActionStream::one(Err(ShellError::labeled_error(
"Could not load json from plugin",
"could not load json from plugin",
&call_info.name_tag,
)));
}
Ok(request_raw) => {
match stdin.write(format!("{}\n", request_raw).as_bytes()) {
Ok(_) => {}
Err(err) => {
return ActionStream::one(Err(ShellError::unexpected(
err.to_string(),
)));
}
}
}
}
let mut input = String::new();
match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
trace!("begin_filter:response {:?}", &response);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => params.into_iter().into_action_stream(),
Err(e) => {
vec![ReturnValue::Err(e)].into_iter().into_action_stream()
}
},
Err(e) => ActionStream::one(Err(
ShellError::untagged_runtime_error(format!(
"Error while processing begin_filter response: {:?} {}",
e, input
)),
)),
}
}
Err(e) => ActionStream::one(Err(ShellError::untagged_runtime_error(
format!("Error while reading begin_filter response: {:?}", e),
))),
}
}
Value {
value: UntaggedValue::Primitive(Primitive::EndOfStream),
..
} => {
// post stream contents
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
let request_raw = serde_json::to_string(&request);
trace!("end_filter:request {:?}", &request_raw);
match request_raw {
Err(_) => {
return ActionStream::one(Err(ShellError::labeled_error(
"Could not load json from plugin",
"could not load json from plugin",
&call_info.name_tag,
)));
}
Ok(request_raw) => {
match stdin.write(format!("{}\n", request_raw).as_bytes()) {
Ok(_) => {}
Err(err) => {
return ActionStream::one(Err(ShellError::unexpected(
err.to_string(),
)));
}
}
}
}
let mut input = String::new();
let stream = match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
trace!("end_filter:response {:?}", &response);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => params.into_iter().into_action_stream(),
Err(e) => {
vec![ReturnValue::Err(e)].into_iter().into_action_stream()
}
},
Err(e) => vec![Err(ShellError::untagged_runtime_error(format!(
"Error while processing end_filter response: {:?} {}",
e, input
)))]
.into_iter()
.into_action_stream(),
}
}
Err(e) => vec![Err(ShellError::untagged_runtime_error(format!(
"Error while reading end_filter response: {:?}",
e
)))]
.into_iter()
.into_action_stream(),
};
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
let request_raw = serde_json::to_string(&request);
trace!("quit:request {:?}", &request_raw);
match request_raw {
Ok(request_raw) => {
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
// TODO: Handle error
}
Err(e) => {
return ActionStream::one(Err(ShellError::untagged_runtime_error(
format!("Error while processing quit response: {:?}", e),
)));
}
}
let _ = child.wait();
stream
}
v => {
// Stream contents
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let request = JsonRpc::new("filter", v);
let request_raw = serde_json::to_string(&request);
trace!("filter:request {:?}", &request_raw);
match request_raw {
Ok(request_raw) => {
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
// TODO: Handle error
}
Err(e) => {
return ActionStream::one(Err(ShellError::untagged_runtime_error(
format!("Error while processing filter response: {:?}", e),
)));
}
}
let mut input = String::new();
match reader.read_line(&mut input) {
Ok(_) => {
let response = serde_json::from_str::<NuResult>(&input);
trace!("filter:response {:?}", &response);
match response {
Ok(NuResult::response { params }) => match params {
Ok(params) => params.into_iter().into_action_stream(),
Err(e) => {
vec![ReturnValue::Err(e)].into_iter().into_action_stream()
}
},
Err(e) => ActionStream::one(Err(
ShellError::untagged_runtime_error(format!(
"Error while processing filter response: {:?}\n== input ==\n{}",
e, input
)),
)),
}
}
Err(e) => ActionStream::one(Err(ShellError::untagged_runtime_error(
format!("Error while reading filter response: {:?}", e),
))),
}
}
}
})
.into_action_stream())
}
#[derive(new)]
pub struct PluginSink {
name: String,
path: String,
config: Signature,
}
impl WholeStreamCommand for PluginSink {
fn name(&self) -> &str {
&self.name
}
fn signature(&self) -> Signature {
self.config.clone()
}
fn usage(&self) -> &str {
&self.config.usage
}
fn extra_usage(&self) -> &str {
&self.config.extra_usage
}
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
run_sink(self.path.clone(), args)
}
fn is_plugin(&self) -> bool {
true
}
fn is_builtin(&self) -> bool {
false
}
}
fn run_sink(path: String, args: CommandArgs) -> Result<ActionStream, ShellError> {
let (call_info, input) = evaluate_once(args)?;
let input: Vec<Value> = input.into_vec();
let request = JsonRpc::new("sink", (call_info, input));
let request_raw = serde_json::to_string(&request);
if let Ok(request_raw) = request_raw {
if let Ok(mut tmpfile) = tempfile::NamedTempFile::new() {
let _ = writeln!(tmpfile, "{}", request_raw);
let _ = tmpfile.flush();
let real_path = Path::new(&path);
let ext = real_path.extension();
let ps1_file = match ext {
Some(ext) => ext == "ps1",
None => false,
};
// TODO: This sink may not work in powershell, trying to find
// an example of what CallInfo would look like in this temp file
let child = if ps1_file {
Command::new("pwsh")
.args([
"-NoLogo",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
&real_path.to_string_lossy(),
tmpfile
.path()
.to_str()
.expect("Failed getting tmpfile path"),
])
.spawn()
} else {
Command::new(path).arg(&tmpfile.path()).spawn()
};
if let Ok(mut child) = child {
let _ = child.wait();
Ok(ActionStream::empty())
} else {
Err(ShellError::untagged_runtime_error(
"Could not create process for sink command",
))
}
} else {
Err(ShellError::untagged_runtime_error(
"Could not open file to send sink command message",
))
}
} else {
Err(ShellError::untagged_runtime_error(
"Could not create message to sink command",
))
}
}
/// Associated information for the call of a command, including the args passed to the command and a tag that spans the name of the command being called
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct CallInfo {
/// The arguments associated with this call
pub args: EvaluatedArgs,
/// The tag (underline-able position) of the name of the call itself
pub name_tag: Tag,
}
/// The set of positional and named arguments, after their values have been evaluated.
///
/// * Positional arguments are those who are given as values, without any associated flag. For example, in `foo arg1 arg2`, both `arg1` and `arg2` are positional arguments.
/// * Named arguments are those associated with a flag. For example, `foo --given bar` the named argument would be name `given` and the value `bar`.
#[derive(Debug, Default, new, Serialize, Deserialize, Clone)]
pub struct EvaluatedArgs {
pub positional: Option<Vec<Value>>,
pub named: Option<IndexMap<String, Value>>,
}
fn evaluate_once(args: CommandArgs) -> Result<(CallInfo, InputStream), ShellError> {
let input = args.input;
let call_info = evaluate_command(args.call_info, args.context)?;
Ok((call_info, input))
}
fn evaluate_command(
args: UnevaluatedCallInfo,
ctx: EvaluationContext,
) -> Result<CallInfo, ShellError> {
let name_tag = args.name_tag.clone();
let args = evaluate_args(&args.args, &ctx)?;
Ok(CallInfo { args, name_tag })
}
fn evaluate_args(call: &hir::Call, ctx: &EvaluationContext) -> Result<EvaluatedArgs, ShellError> {
let mut positional_args: Vec<Value> = vec![];
if let Some(positional) = &call.positional {
for pos in positional {
let result = evaluate_baseline_expr(pos, ctx)?;
positional_args.push(result);
}
}
let positional = if !positional_args.is_empty() {
Some(positional_args)
} else {
None
};
let mut named_args = IndexMap::new();
if let Some(named) = &call.named {
for (name, value) in named {
match value {
hir::NamedValue::PresentSwitch(tag) => {
named_args.insert(name.clone(), UntaggedValue::boolean(true).into_value(tag));
}
hir::NamedValue::Value(_, expr) => {
named_args.insert(name.clone(), evaluate_baseline_expr(expr, ctx)?);
}
_ => {}
};
}
}
let named = if !named_args.is_empty() {
Some(named_args)
} else {
None
};
Ok(EvaluatedArgs::new(positional, named))
}

View File

@ -1,18 +0,0 @@
use nu_source::Text;
use crate::EvaluationContext;
pub fn maybe_print_errors(context: &EvaluationContext, source: Text) -> bool {
let errors = context.current_errors().clone();
let mut errors = errors.lock();
if errors.len() > 0 {
let error = errors[0].clone();
*errors = vec![];
context.host().lock().print_err(error, &source);
true
} else {
false
}
}

View File

@ -1,312 +0,0 @@
use crate::{evaluate::internal::InternalIterator, maybe_print_errors, run_block, shell::CdArgs};
use crate::{BufCodecReader, MaybeTextCodec, StringOrBinary};
use nu_errors::ShellError;
use nu_path::{canonicalize_with, trim_trailing_slash};
use nu_protocol::hir::{
Call, ClassifiedCommand, Expression, ExternalRedirection, InternalCommand, Literal,
NamedArguments, SpannedExpression,
};
use nu_protocol::{Primitive, UntaggedValue, Value};
use nu_stream::{InputStream, IntoInputStream};
use crate::EvaluationContext;
use log::{debug, trace};
use nu_source::{AnchorLocation, Span, Tag, Tagged, Text};
use std::path::{Path, PathBuf};
use std::{error::Error, sync::atomic::Ordering};
use std::{io::BufReader, iter::Iterator};
#[derive(Debug)]
pub enum LineResult {
Success(String),
Error(String, ShellError),
Break,
CtrlC,
CtrlD,
ClearHistory,
}
fn chomp_newline(s: &str) -> &str {
if let Some(s) = s.strip_suffix('\n') {
s
} else {
s
}
}
pub fn run_script_in_dir(
script: String,
dir: &Path,
ctx: &EvaluationContext,
) -> Result<(), Box<dyn Error>> {
//Save path before to switch back to it after executing script
let path_before = ctx.shell_manager().path();
ctx.shell_manager()
.set_path(dir.to_string_lossy().to_string());
run_script_standalone(script, false, ctx, false)?;
ctx.shell_manager().set_path(path_before);
Ok(())
}
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
pub fn process_script(
script_text: &str,
ctx: &EvaluationContext,
redirect_stdin: bool,
span_offset: usize,
cli_mode: bool,
) -> LineResult {
if script_text.trim() == "" {
LineResult::Success(script_text.to_string())
} else {
let line = chomp_newline(script_text);
let (block, err) = nu_parser::parse(line, span_offset, &ctx.scope);
debug!("{:#?}", block);
if let Some(failure) = err {
return LineResult::Error(line.to_string(), failure.into());
}
// There's a special case to check before we process the pipeline:
// If we're giving a path by itself
// ...and it's not a command in the path
// ...and it doesn't have any arguments
// ...and we're in the CLI
// ...then change to this directory
if cli_mode
&& block.block.len() == 1
&& block.block[0].pipelines.len() == 1
&& block.block[0].pipelines[0].list.len() == 1
{
if let ClassifiedCommand::Internal(InternalCommand {
ref name,
ref args,
name_span,
}) = block.block[0].pipelines[0].list[0]
{
let internal_name = name;
let name = args
.positional
.as_ref()
.and_then(|positionals| {
positionals.get(0).map(|e| {
if let Expression::Literal(Literal::String(ref s)) = e.expr {
&s
} else {
""
}
})
})
.unwrap_or("");
ctx.sync_path_to_env();
if internal_name == "run_external"
&& args
.positional
.as_ref()
.map(|v| v.len() == 1)
.unwrap_or(true)
&& args
.named
.as_ref()
.map(NamedArguments::is_empty)
.unwrap_or(true)
&& canonicalize_with(name, ctx.shell_manager().path()).is_ok()
&& Path::new(&name).is_dir()
&& !ctx.host().lock().is_external_cmd(name)
{
let tag = Tag {
anchor: Some(AnchorLocation::Source(line.into())),
span: name_span,
};
let path = {
// Here we work differently if we're in Windows because of the expected Windows behavior
#[cfg(windows)]
{
if name.ends_with(':') {
// This looks like a drive shortcut. We need to a) switch drives and b) go back to the previous directory we were viewing on that drive
// But first, we need to save where we are now
let current_path = ctx.shell_manager().path();
let split_path: Vec<_> = current_path.split(':').collect();
if split_path.len() > 1 {
ctx.windows_drives_previous_cwd()
.lock()
.insert(split_path[0].to_string(), current_path);
}
let name = name.to_uppercase();
let new_drive: Vec<_> = name.split(':').collect();
if let Some(val) =
ctx.windows_drives_previous_cwd().lock().get(new_drive[0])
{
val.to_string()
} else {
format!("{}\\", name)
}
} else {
name.to_string()
}
}
#[cfg(not(windows))]
{
name.to_string()
}
};
let path = trim_trailing_slash(&path);
let cd_args = CdArgs {
path: Some(Tagged {
item: PathBuf::from(path),
tag: tag.clone(),
}),
};
return match ctx.shell_manager().cd(cd_args, tag) {
Err(e) => LineResult::Error(line.to_string(), e),
Ok(stream) => {
let iter = InternalIterator {
context: ctx.clone(),
leftovers: InputStream::empty(),
input: stream,
};
for _ in iter {
//nullopt, commands are run by iterating over iter
}
LineResult::Success(line.to_string())
}
};
}
}
}
let input_stream = if redirect_stdin {
let file = std::io::stdin();
let buf_reader = BufReader::new(file);
let buf_codec = BufCodecReader::new(buf_reader, MaybeTextCodec::default());
let stream = buf_codec.map(|line| {
if let Ok(line) = line {
let primitive = match line {
StringOrBinary::String(s) => Primitive::String(s),
StringOrBinary::Binary(b) => Primitive::Binary(b.into_iter().collect()),
};
Ok(Value {
value: UntaggedValue::Primitive(primitive),
tag: Tag::unknown(),
})
} else {
panic!("Internal error: could not read lines of text from stdin")
}
});
stream.into_input_stream()
} else {
InputStream::empty()
};
trace!("{:#?}", block);
let result = run_block(&block, ctx, input_stream, ExternalRedirection::None);
match result {
Ok(input) => {
// Running a pipeline gives us back a stream that we can then
// work through. At the top level, we just want to pull on the
// values to compute them.
let autoview_cmd = ctx
.get_command("autoview")
.expect("Could not find autoview command");
if let Ok(mut output_stream) = ctx.run_command(
autoview_cmd,
Tag::unknown(),
Call::new(
Box::new(SpannedExpression::new(
Expression::string("autoview".to_string()),
Span::unknown(),
)),
Span::unknown(),
),
input,
) {
loop {
match output_stream.next() {
Some(Value {
value: UntaggedValue::Error(e),
..
}) => return LineResult::Error(line.to_string(), e),
Some(_item) => {
if ctx.ctrl_c().load(Ordering::SeqCst) {
break;
}
}
None => break,
}
}
}
LineResult::Success(line.to_string())
}
Err(err) => LineResult::Error(line.to_string(), err),
}
}
}
pub fn run_script_standalone(
script_text: String,
redirect_stdin: bool,
context: &EvaluationContext,
exit_on_error: bool,
) -> Result<(), ShellError> {
context
.shell_manager()
.enter_script_mode()
.map_err(ShellError::from)?;
let line = process_script(&script_text, context, redirect_stdin, 0, false);
match line {
LineResult::Success(line) => {
let error_code = {
let errors = context.current_errors().clone();
let errors = errors.lock();
if errors.len() > 0 {
1
} else {
0
}
};
maybe_print_errors(context, Text::from(line));
if error_code != 0 && exit_on_error {
std::process::exit(error_code);
}
}
LineResult::Error(line, err) => {
context
.host()
.lock()
.print_err(err, &Text::from(line.clone()));
maybe_print_errors(context, Text::from(line));
if exit_on_error {
std::process::exit(1);
}
}
_ => {}
}
//exit script mode shell
context.shell_manager().remove_at_current();
Ok(())
}

View File

@ -1,54 +0,0 @@
use nu_stream::{ActionStream, OutputStream};
use crate::maybe_text_codec::StringOrBinary;
pub use crate::shell::shell_args::{CdArgs, CopyArgs, LsArgs, MkdirArgs, MvArgs, RemoveArgs};
use crate::CommandArgs;
use encoding_rs::Encoding;
use nu_errors::ShellError;
use nu_source::{Span, Tag};
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
pub(crate) mod painter;
pub(crate) mod palette;
pub(crate) mod shell_args;
pub(crate) mod shell_manager;
pub mod value_shell;
pub trait Shell: std::fmt::Debug {
fn is_interactive(&self) -> bool;
fn name(&self) -> String;
fn homedir(&self) -> Option<PathBuf>;
fn ls(
&self,
args: LsArgs,
name: Tag,
ctrl_c: Arc<AtomicBool>,
) -> Result<ActionStream, ShellError>;
fn cd(&self, args: CdArgs, name: Tag) -> Result<ActionStream, ShellError>;
fn cp(&self, args: CopyArgs, name: Tag, path: &str) -> Result<ActionStream, ShellError>;
fn mkdir(&self, args: MkdirArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
fn mv(&self, args: MvArgs, name: Tag, path: &str) -> Result<ActionStream, ShellError>;
fn rm(&self, args: RemoveArgs, name: Tag, path: &str) -> Result<ActionStream, ShellError>;
fn path(&self) -> String;
fn pwd(&self, args: CommandArgs) -> Result<ActionStream, ShellError>;
fn set_path(&mut self, path: String);
fn open(
&self,
path: &Path,
name: Span,
with_encoding: Option<&'static Encoding>,
) -> Result<
Box<dyn Iterator<Item = Result<StringOrBinary, ShellError>> + Send + Sync>,
ShellError,
>;
fn save(
&mut self,
path: &Path,
contents: &[u8],
name: Span,
append: bool,
) -> Result<OutputStream, ShellError>;
}

View File

@ -1,85 +0,0 @@
use crate::evaluate::scope::Scope;
use crate::shell::palette::Palette;
use nu_ansi_term::{Color, Style};
use nu_parser::ParserScope;
use nu_protocol::hir::FlatShape;
use nu_source::Spanned;
use std::borrow::Cow;
// FIXME: find a good home, as nu-engine may be too core for styling
pub struct Painter {
original: Vec<u8>,
styles: Vec<Style>,
}
impl Painter {
fn new(original: &str) -> Painter {
let bytes: Vec<u8> = original.bytes().collect();
let bytes_count = bytes.len();
Painter {
original: bytes,
styles: vec![Color::White.normal(); bytes_count],
}
}
pub fn paint_string<'l, P: Palette>(line: &'l str, scope: &Scope, palette: &P) -> Cow<'l, str> {
scope.enter_scope();
let (block, _) = nu_parser::parse(line, 0, scope);
scope.exit_scope();
let shapes = nu_parser::shapes(&block);
let mut painter = Painter::new(line);
for shape in shapes {
painter.paint_shape(&shape, palette);
}
Cow::Owned(painter.into_string())
}
fn paint_shape<P: Palette>(&mut self, shape: &Spanned<FlatShape>, palette: &P) {
palette
.styles_for_shape(shape)
.iter()
.for_each(|x| self.paint(x));
}
fn paint(&mut self, styled_span: &Spanned<Style>) {
for pos in styled_span.span.start()..styled_span.span.end() {
if pos < self.styles.len() {
self.styles[pos] = styled_span.item;
}
}
}
fn into_string(self) -> String {
let mut idx_start = 0;
let mut idx_end = 1;
if self.original.is_empty() {
String::new()
} else {
let mut builder = String::new();
let mut current_style = self.styles[0];
while idx_end < self.styles.len() {
if self.styles[idx_end] != current_style {
// Emit, as we changed styles
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&current_style.paint(intermediate).to_string());
current_style = self.styles[idx_end];
idx_start = idx_end;
}
idx_end += 1;
}
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&current_style.paint(intermediate).to_string());
builder
}
}
}

View File

@ -1,412 +0,0 @@
use nu_ansi_term::{Color, Style};
use nu_protocol::hir::FlatShape;
use nu_source::{Span, Spanned};
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
use std::error::Error;
use std::ops::Deref;
use std::str::Bytes;
use std::{fmt, io};
pub trait Palette {
fn styles_for_shape(&self, shape: &Spanned<FlatShape>) -> Vec<Spanned<Style>>;
}
#[derive(Debug, Clone, Default)]
pub struct DefaultPalette {}
impl Palette for DefaultPalette {
fn styles_for_shape(&self, shape: &Spanned<FlatShape>) -> Vec<Spanned<Style>> {
match &shape.item {
FlatShape::BareMember => single_style_span(Color::Yellow.bold(), shape.span),
FlatShape::CloseDelimiter(_) => single_style_span(Color::White.normal(), shape.span),
FlatShape::Comment => single_style_span(Color::Green.bold(), shape.span),
FlatShape::Decimal => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::Dot => single_style_span(Style::new().fg(Color::White), shape.span),
FlatShape::DotDot => single_style_span(Color::Yellow.bold(), shape.span),
FlatShape::DotDotLeftAngleBracket => {
single_style_span(Color::Yellow.bold(), shape.span)
}
FlatShape::ExternalCommand => single_style_span(Color::Cyan.normal(), shape.span),
FlatShape::ExternalWord => single_style_span(Color::Green.bold(), shape.span),
FlatShape::Flag => single_style_span(Color::Blue.bold(), shape.span),
FlatShape::Garbage => {
single_style_span(Style::new().fg(Color::White).on(Color::Red), shape.span)
}
FlatShape::GlobPattern => single_style_span(Color::Cyan.bold(), shape.span),
FlatShape::Identifier => single_style_span(Color::Purple.normal(), shape.span),
FlatShape::Int => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::InternalCommand => single_style_span(Color::Cyan.bold(), shape.span),
FlatShape::ItVariable => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::Keyword => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::OpenDelimiter(_) => single_style_span(Color::White.normal(), shape.span),
FlatShape::Operator => single_style_span(Color::Yellow.normal(), shape.span),
FlatShape::Path => single_style_span(Color::Cyan.normal(), shape.span),
FlatShape::Pipe => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::Separator => single_style_span(Color::White.normal(), shape.span),
FlatShape::ShorthandFlag => single_style_span(Color::Blue.bold(), shape.span),
FlatShape::Size { number, unit } => vec![
Spanned::<Style> {
span: *number,
item: Color::Purple.bold(),
},
Spanned::<Style> {
span: *unit,
item: Color::Cyan.bold(),
},
],
FlatShape::String => single_style_span(Color::Green.normal(), shape.span),
FlatShape::StringMember => single_style_span(Color::Yellow.bold(), shape.span),
FlatShape::Type => single_style_span(Color::Blue.bold(), shape.span),
FlatShape::Variable => single_style_span(Color::Purple.normal(), shape.span),
FlatShape::Whitespace => single_style_span(Color::White.normal(), shape.span),
FlatShape::Word => single_style_span(Color::Green.normal(), shape.span),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ThemedPalette {
theme: Theme,
}
impl ThemedPalette {
pub fn new<R: io::Read>(reader: &mut R) -> Result<ThemedPalette, ThemeError> {
let theme = serde_json::from_reader(reader)?;
Ok(ThemedPalette { theme })
}
pub fn default() -> ThemedPalette {
let theme = Theme::default();
ThemedPalette { theme }
}
}
impl Palette for ThemedPalette {
fn styles_for_shape(&self, shape: &Spanned<FlatShape>) -> Vec<Spanned<Style>> {
match &shape.item {
FlatShape::OpenDelimiter(_) => {
single_style_span(self.theme.open_delimiter.normal(), shape.span)
}
FlatShape::CloseDelimiter(_) => {
single_style_span(self.theme.close_delimiter.normal(), shape.span)
}
FlatShape::ItVariable => single_style_span(self.theme.it_variable.bold(), shape.span),
FlatShape::Keyword => single_style_span(self.theme.keyword.bold(), shape.span),
FlatShape::Variable => single_style_span(self.theme.variable.normal(), shape.span),
FlatShape::Identifier => single_style_span(self.theme.identifier.normal(), shape.span),
FlatShape::Type => single_style_span(self.theme.r#type.bold(), shape.span),
FlatShape::Operator => single_style_span(self.theme.operator.normal(), shape.span),
FlatShape::DotDotLeftAngleBracket => {
single_style_span(self.theme.dot_dot.bold(), shape.span)
}
FlatShape::DotDot => single_style_span(self.theme.dot_dot.bold(), shape.span),
FlatShape::Dot => single_style_span(Style::new().fg(*self.theme.dot), shape.span),
FlatShape::InternalCommand => {
single_style_span(self.theme.internal_command.bold(), shape.span)
}
FlatShape::ExternalCommand => {
single_style_span(self.theme.external_command.normal(), shape.span)
}
FlatShape::ExternalWord => {
single_style_span(self.theme.external_word.bold(), shape.span)
}
FlatShape::BareMember => single_style_span(self.theme.bare_member.bold(), shape.span),
FlatShape::StringMember => {
single_style_span(self.theme.string_member.bold(), shape.span)
}
FlatShape::String => single_style_span(self.theme.string.normal(), shape.span),
FlatShape::Path => single_style_span(self.theme.path.normal(), shape.span),
FlatShape::GlobPattern => single_style_span(self.theme.glob_pattern.bold(), shape.span),
FlatShape::Word => single_style_span(self.theme.word.normal(), shape.span),
FlatShape::Pipe => single_style_span(self.theme.pipe.bold(), shape.span),
FlatShape::Flag => single_style_span(self.theme.flag.bold(), shape.span),
FlatShape::ShorthandFlag => {
single_style_span(self.theme.shorthand_flag.bold(), shape.span)
}
FlatShape::Int => single_style_span(self.theme.int.bold(), shape.span),
FlatShape::Decimal => single_style_span(self.theme.decimal.bold(), shape.span),
FlatShape::Whitespace => single_style_span(self.theme.whitespace.normal(), shape.span),
FlatShape::Separator => single_style_span(self.theme.separator.normal(), shape.span),
FlatShape::Comment => single_style_span(self.theme.comment.bold(), shape.span),
FlatShape::Garbage => single_style_span(
Style::new().fg(*self.theme.garbage).on(Color::Red),
shape.span,
),
FlatShape::Size { number, unit } => vec![
Spanned::<Style> {
span: *number,
item: self.theme.size_number.bold(),
},
Spanned::<Style> {
span: *unit,
item: self.theme.size_unit.bold(),
},
],
}
}
}
#[derive(Debug)]
pub struct ThemeError {
serde_err: serde_json::error::Error,
}
impl fmt::Display for ThemeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "failure to load theme")
}
}
impl Error for ThemeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.serde_err)
}
}
impl From<serde_json::error::Error> for ThemeError {
fn from(serde_err: serde_json::error::Error) -> Self {
ThemeError { serde_err }
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
struct Theme {
bare_member: ThemeColor,
close_delimiter: ThemeColor,
comment: ThemeColor,
decimal: ThemeColor,
dot: ThemeColor,
dot_dot: ThemeColor,
dot_dot_left_angle_bracket: ThemeColor,
external_command: ThemeColor,
external_word: ThemeColor,
flag: ThemeColor,
garbage: ThemeColor,
glob_pattern: ThemeColor,
identifier: ThemeColor,
int: ThemeColor,
internal_command: ThemeColor,
it_variable: ThemeColor,
keyword: ThemeColor,
open_delimiter: ThemeColor,
operator: ThemeColor,
path: ThemeColor,
pipe: ThemeColor,
separator: ThemeColor,
shorthand_flag: ThemeColor,
size_number: ThemeColor,
size_unit: ThemeColor,
string: ThemeColor,
string_member: ThemeColor,
r#type: ThemeColor,
variable: ThemeColor,
whitespace: ThemeColor,
word: ThemeColor,
}
impl Default for Theme {
fn default() -> Self {
Theme {
bare_member: ThemeColor(Color::Yellow),
close_delimiter: ThemeColor(Color::White),
comment: ThemeColor(Color::Green),
decimal: ThemeColor(Color::Purple),
dot: ThemeColor(Color::White),
dot_dot: ThemeColor(Color::Yellow),
dot_dot_left_angle_bracket: ThemeColor(Color::Yellow),
external_command: ThemeColor(Color::Cyan),
external_word: ThemeColor(Color::Green),
flag: ThemeColor(Color::Blue),
garbage: ThemeColor(Color::White),
glob_pattern: ThemeColor(Color::Cyan),
identifier: ThemeColor(Color::Purple),
int: ThemeColor(Color::Purple),
internal_command: ThemeColor(Color::Cyan),
it_variable: ThemeColor(Color::Purple),
keyword: ThemeColor(Color::Purple),
open_delimiter: ThemeColor(Color::White),
operator: ThemeColor(Color::Yellow),
path: ThemeColor(Color::Cyan),
pipe: ThemeColor(Color::Purple),
separator: ThemeColor(Color::Red),
shorthand_flag: ThemeColor(Color::Blue),
size_number: ThemeColor(Color::Purple),
size_unit: ThemeColor(Color::Cyan),
string: ThemeColor(Color::Green),
string_member: ThemeColor(Color::Yellow),
r#type: ThemeColor(Color::Blue),
variable: ThemeColor(Color::Purple),
whitespace: ThemeColor(Color::White),
word: ThemeColor(Color::Green),
// These should really be Styles and not colors
// leave this here for the next change to make
// ThemeColor, ThemeStyle.
// open_delimiter: ThemeColor(Color::White.normal()),
// close_delimiter: ThemeColor(Color::White.normal()),
// it_variable: ThemeColor(Color::Purple.bold()),
// variable: ThemeColor(Color::Purple.normal()),
// r#type: ThemeColor(Color::Blue.bold()),
// identifier: ThemeColor(Color::Purple.normal()),
// operator: ThemeColor(Color::Yellow.normal()),
// dot: ThemeColor(Color::White),
// dot_dot: ThemeColor(Color::Yellow.bold()),
// //missing DotDotLeftAngleBracket
// internal_command: ThemeColor(Color::Cyan.bold()),
// external_command: ThemeColor(Color::Cyan.normal()),
// external_word: ThemeColor(Color::Green.bold()),
// bare_member: ThemeColor(Color::Yellow.bold()),
// string: ThemeColor(Color::Green.normal()),
// string_member: ThemeColor(Color::Yellow.bold()),
// path: ThemeColor(Color::Cyan.normal()),
// glob_pattern: ThemeColor(Color::Cyan.bold()),
// word: ThemeColor(Color::Green.normal()),
// keyword: ThemeColor(Color::Purple.bold()),
// pipe: ThemeColor(Color::Purple.bold()),
// flag: ThemeColor(Color::Blue.bold()),
// shorthand_flag: ThemeColor(Color::Blue.bold()),
// int: ThemeColor(Color::Purple.bold()),
// decimal: ThemeColor(Color::Purple.bold()),
// garbage: ThemeColor(Style::new().fg(Color::White).on(Color::Red)),
// whitespace: ThemeColor(Color::White.normal()),
// separator: ThemeColor(Color::Red),
// comment: ThemeColor(Color::Green.bold()),
// size_number: ThemeColor(Color::Purple.bold()),
// size_unit: ThemeColor(Color::Cyan.bold()),
}
}
}
#[derive(Debug, Clone, Default)]
struct ThemeColor(Color);
impl Deref for ThemeColor {
type Target = Color;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Serialize for ThemeColor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str("TODO: IMPLEMENT SERIALIZATION")
}
}
impl<'de> Deserialize<'de> for ThemeColor {
fn deserialize<D>(deserializer: D) -> Result<ThemeColor, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
ThemeColor::from_str(&s)
}
}
impl ThemeColor {
fn from_str<E>(s: &str) -> Result<ThemeColor, E>
where
E: serde::de::Error,
{
let mut bytes = s.bytes();
let r = ThemeColor::xtoi(&mut bytes)?;
let g = ThemeColor::xtoi(&mut bytes)?;
let b = ThemeColor::xtoi(&mut bytes)?;
Ok(ThemeColor(Color::Rgb(r, g, b)))
}
fn xtoi<E>(b: &mut Bytes) -> Result<u8, E>
where
E: serde::de::Error,
{
let upper = b
.next()
.ok_or_else(|| E::custom("color string too short"))?;
let lower = b
.next()
.ok_or_else(|| E::custom("color string too short"))?;
let mut val = ThemeColor::numerical_value(upper)?;
val = (val << 4) | ThemeColor::numerical_value(lower)?;
Ok(val)
}
fn numerical_value<E>(character: u8) -> Result<u8, E>
where
E: serde::de::Error,
{
match character {
b'0'..=b'9' => Ok(character - b'0'),
b'a'..=b'z' => Ok(character - (b'a' - 10)),
_ => Err(E::custom(format!("invalid character {}", character))),
}
}
}
fn single_style_span(style: Style, span: Span) -> Vec<Spanned<Style>> {
vec![Spanned::<Style> { span, item: style }]
}
#[cfg(test)]
mod tests {
use super::{Palette, ThemedPalette};
use nu_ansi_term::Color;
use nu_protocol::hir::FlatShape;
use nu_source::{Span, Spanned};
use std::io::Cursor;
#[test]
fn create_themed_palette() {
let json = r#"
{
"bare_member": "a359cc",
"close_delimiter": "a359cc",
"comment": "a359cc",
"decimal": "a359cc",
"dot": "a359cc",
"dot_dot": "a359cc",
"dot_dot_left_angle_bracket": "a359cc",
"external_command": "a359cc",
"external_word": "a359cc",
"flag": "a359cc",
"garbage": "a359cc",
"glob_pattern": "a359cc",
"identifier": "a359cc",
"int": "a359cc",
"internal_command": "a359cc",
"it_variable": "a359cc",
"keyword": "a359cc",
"open_delimiter": "a359cc",
"operator": "a359cc",
"path": "a359cc",
"pipe": "a359cc",
"separator": "a359cc",
"shorthand_flag": "a359cc",
"size_number": "a359cc",
"size_unit": "a359cc",
"string": "a359cc",
"string_member": "a359cc",
"type": "a359cc",
"variable": "a359cc",
"whitespace": "a359cc",
"word": "a359cc"
}"#;
let mut json_reader = Cursor::new(json);
let themed_palette = ThemedPalette::new(&mut json_reader).unwrap();
let test_shape = Spanned {
item: FlatShape::Type,
span: Span::new(4, 9),
};
let styled = themed_palette.styles_for_shape(&test_shape);
assert_eq!(styled.len(), 1);
assert_eq!(
styled[0],
Spanned {
item: Color::Rgb(163, 89, 204).bold(),
span: Span::new(4, 9),
},
);
}
}

View File

@ -1,50 +0,0 @@
use nu_source::Tagged;
use serde::{self, Deserialize};
use std::path::PathBuf;
#[derive(Deserialize)]
pub struct CdArgs {
pub path: Option<Tagged<PathBuf>>,
}
#[derive(Deserialize)]
pub struct CopyArgs {
pub src: Tagged<PathBuf>,
pub dst: Tagged<PathBuf>,
pub recursive: bool,
}
#[derive(Deserialize)]
pub struct LsArgs {
pub path: Option<Tagged<PathBuf>>,
pub all: bool,
pub long: bool,
#[serde(rename = "short-names")]
pub short_names: bool,
#[serde(rename = "du")]
pub du: bool,
}
#[derive(Deserialize)]
pub struct MvArgs {
pub src: Tagged<PathBuf>,
pub dst: Tagged<PathBuf>,
}
#[derive(Deserialize)]
pub struct MkdirArgs {
pub rest: Vec<Tagged<PathBuf>>,
#[serde(rename = "show-created-paths")]
pub show_created_paths: bool,
}
#[derive(Deserialize)]
pub struct RemoveArgs {
pub rest: Vec<Tagged<PathBuf>>,
pub recursive: bool,
#[allow(unused)]
pub trash: bool,
#[allow(unused)]
pub permanent: bool,
pub force: bool,
}

View File

@ -1,199 +0,0 @@
use crate::shell::Shell;
use crate::{filesystem::filesystem_shell::FilesystemShellMode, maybe_text_codec::StringOrBinary};
use crate::{CommandArgs, FilesystemShell};
use nu_stream::{ActionStream, OutputStream};
use crate::shell::shell_args::{CdArgs, CopyArgs, LsArgs, MkdirArgs, MvArgs, RemoveArgs};
use encoding_rs::Encoding;
use nu_errors::ShellError;
use nu_source::{Span, Tag};
use parking_lot::Mutex;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Clone, Default, Debug)]
pub struct ShellManager {
pub current_shell: Arc<AtomicUsize>,
pub shells: Arc<Mutex<Vec<Box<dyn Shell + Send>>>>,
}
impl ShellManager {
pub fn basic() -> ShellManager {
ShellManager {
current_shell: Arc::new(AtomicUsize::new(0)),
shells: Arc::new(Mutex::new(vec![Box::new(FilesystemShell::basic(
FilesystemShellMode::Cli,
))])),
}
}
pub fn enter_script_mode(&self) -> Result<(), std::io::Error> {
//New fs_shell starting from current path
let fs_shell = FilesystemShell::with_location(self.path(), FilesystemShellMode::Script)?;
self.insert_at_current(Box::new(fs_shell));
Ok(())
}
pub fn insert_at_current(&self, shell: Box<dyn Shell + Send>) {
self.shells.lock().push(shell);
self.current_shell
.store(self.shells.lock().len() - 1, Ordering::SeqCst);
self.set_path(self.path());
}
pub fn current_shell(&self) -> usize {
self.current_shell.load(Ordering::SeqCst)
}
pub fn remove_at_current(&self) {
{
let mut shells = self.shells.lock();
if shells.len() > 0 {
if self.current_shell() == shells.len() - 1 {
shells.pop();
let new_len = shells.len();
if new_len > 0 {
self.current_shell.store(new_len - 1, Ordering::SeqCst);
} else {
return;
}
} else {
shells.remove(self.current_shell());
}
}
}
self.set_path(self.path())
}
pub fn is_empty(&self) -> bool {
self.shells.lock().is_empty()
}
pub fn is_interactive(&self) -> bool {
self.shells.lock()[self.current_shell()].is_interactive()
}
pub fn path(&self) -> String {
self.shells.lock()[self.current_shell()].path()
}
pub fn pwd(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
let env = self.shells.lock();
env[self.current_shell()].pwd(args)
}
pub fn set_path(&self, path: String) {
self.shells.lock()[self.current_shell()].set_path(path)
}
pub fn open(
&self,
full_path: &Path,
name: Span,
with_encoding: Option<&'static Encoding>,
) -> Result<
Box<dyn Iterator<Item = Result<StringOrBinary, ShellError>> + Send + Sync>,
ShellError,
> {
self.shells.lock()[self.current_shell()].open(full_path, name, with_encoding)
}
pub fn save(
&self,
full_path: &Path,
save_data: &[u8],
name: Span,
append: bool,
) -> Result<OutputStream, ShellError> {
self.shells.lock()[self.current_shell()].save(full_path, save_data, name, append)
}
pub fn next(&self) {
{
let shell_len = self.shells.lock().len();
if self.current_shell() == (shell_len - 1) {
self.current_shell.store(0, Ordering::SeqCst);
} else {
self.current_shell
.store(self.current_shell() + 1, Ordering::SeqCst);
}
}
self.set_path(self.path())
}
pub fn prev(&self) {
{
let shell_len = self.shells.lock().len();
if self.current_shell() == 0 {
self.current_shell.store(shell_len - 1, Ordering::SeqCst);
} else {
self.current_shell
.store(self.current_shell() - 1, Ordering::SeqCst);
}
}
self.set_path(self.path())
}
pub fn goto(&self, i: usize) {
{
let shell_len = self.shells.lock().len();
if i < shell_len {
self.current_shell.store(i, Ordering::SeqCst);
}
}
self.set_path(self.path())
}
pub fn homedir(&self) -> Option<PathBuf> {
let env = self.shells.lock();
env[self.current_shell()].homedir()
}
pub fn ls(
&self,
args: LsArgs,
name: Tag,
ctrl_c: Arc<AtomicBool>,
) -> Result<ActionStream, ShellError> {
let env = self.shells.lock();
env[self.current_shell()].ls(args, name, ctrl_c)
}
pub fn cd(&self, args: CdArgs, name: Tag) -> Result<ActionStream, ShellError> {
let env = self.shells.lock();
env[self.current_shell()].cd(args, name)
}
pub fn cp(&self, args: CopyArgs, name: Tag) -> Result<ActionStream, ShellError> {
let shells = self.shells.lock();
let path = shells[self.current_shell()].path();
shells[self.current_shell()].cp(args, name, &path)
}
pub fn rm(&self, args: RemoveArgs, name: Tag) -> Result<ActionStream, ShellError> {
let shells = self.shells.lock();
let path = shells[self.current_shell()].path();
shells[self.current_shell()].rm(args, name, &path)
}
pub fn mkdir(&self, args: MkdirArgs, name: Tag) -> Result<OutputStream, ShellError> {
let shells = self.shells.lock();
let path = shells[self.current_shell()].path();
shells[self.current_shell()].mkdir(args, name, &path)
}
pub fn mv(&self, args: MvArgs, name: Tag) -> Result<ActionStream, ShellError> {
let shells = self.shells.lock();
let path = shells[self.current_shell()].path();
shells[self.current_shell()].mv(args, name, &path)
}
}

View File

@ -1,262 +0,0 @@
use crate::maybe_text_codec::StringOrBinary;
use crate::shell::shell_args::{CdArgs, CopyArgs, LsArgs, MkdirArgs, MvArgs, RemoveArgs};
use crate::shell::Shell;
use crate::CommandArgs;
use encoding_rs::Encoding;
use nu_errors::ShellError;
use nu_protocol::ValueStructure;
use nu_protocol::{ReturnSuccess, ShellTypeName, UntaggedValue, Value};
use nu_source::SpannedItem;
use nu_source::{Span, Tag, Tagged};
use nu_stream::{ActionStream, OutputStream};
use nu_value_ext::ValueExt;
use std::collections::VecDeque;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
#[derive(Clone)]
pub struct ValueShell {
pub(crate) path: String,
pub(crate) last_path: String,
pub(crate) value: Value,
}
impl std::fmt::Debug for ValueShell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ValueShell @ {}", self.path)
}
}
impl ValueShell {
pub fn new(value: Value) -> ValueShell {
ValueShell {
path: "/".to_string(),
last_path: "/".to_string(),
value,
}
}
pub fn find(&self, path: &Path) -> Option<&Self> {
let mut value_system = ValueStructure::new();
if value_system.walk_decorate(&self.value).is_ok() {
value_system.exists(path).then(|| self)
} else {
None
}
}
pub fn members_under(&self, path: &Path) -> VecDeque<Value> {
let mut shell_entries = VecDeque::new();
let full_path = path.to_path_buf();
let mut viewed = self.value.clone();
let sep_string = std::path::MAIN_SEPARATOR.to_string();
let sep = OsStr::new(&sep_string);
for p in &full_path {
match p {
x if x == sep => {}
step => {
let name: &str = &step.to_string_lossy().to_string();
let value = viewed.get_data_by_key(name.spanned_unknown());
if let Some(v) = value {
viewed = v.clone();
}
}
}
}
match viewed {
Value {
value: UntaggedValue::Table(l),
..
} => {
for item in l {
shell_entries.push_back(item.clone());
}
}
x => {
shell_entries.push_back(x);
}
}
shell_entries
}
}
impl Shell for ValueShell {
fn name(&self) -> String {
let anchor_name = self.value.anchor_name();
match anchor_name {
Some(x) => format!("{{{}}}", x),
None => format!("<{}>", self.value.type_name()),
}
}
fn homedir(&self) -> Option<PathBuf> {
Some(PathBuf::from("/"))
}
fn ls(
&self,
LsArgs { path, .. }: LsArgs,
name_tag: Tag,
_ctrl_c: Arc<AtomicBool>,
) -> Result<ActionStream, ShellError> {
let mut full_path = PathBuf::from(self.path());
if let Some(value) = &path {
full_path.push(&value.item);
}
if self.find(&full_path).is_none() {
if let Some(target) = &path {
return Err(ShellError::labeled_error(
"Can not list entries inside",
"No such path exists",
target.tag(),
));
}
return Err(ShellError::labeled_error(
"Can not list entries inside",
"No such path exists",
name_tag,
));
}
let output = self
.members_under(full_path.as_path())
.into_iter()
.map(ReturnSuccess::value)
.collect::<VecDeque<_>>();
Ok(output.into())
}
fn cd(&self, args: CdArgs, name: Tag) -> Result<ActionStream, ShellError> {
let destination = args.path;
let path = match destination {
None => "/".to_string(),
Some(ref v) => {
let Tagged { item: target, .. } = v;
let mut cwd = PathBuf::from(&self.path);
if target == &PathBuf::from("..") {
cwd.pop();
} else if target == &PathBuf::from("-") {
cwd = PathBuf::from(&self.last_path);
} else {
match target.to_str() {
Some(target) => match target.chars().next() {
Some(x) if x == '/' => cwd = PathBuf::from(target),
_ => cwd.push(target),
},
None => cwd.push(target),
}
}
cwd.to_string_lossy().to_string()
}
};
let mut value_system = ValueStructure::new();
value_system.walk_decorate(&self.value)?;
if !value_system.exists(&PathBuf::from(&path)) {
if let Some(destination) = destination {
return Err(ShellError::labeled_error(
"Can not change to path inside",
"No such path exists",
destination.tag(),
));
}
return Err(ShellError::labeled_error(
"Can not change to path inside",
"No such path exists",
&name,
));
}
Ok(ActionStream::one(ReturnSuccess::change_cwd(path)))
}
fn cp(&self, _args: CopyArgs, name: Tag, _path: &str) -> Result<ActionStream, ShellError> {
Err(ShellError::labeled_error(
"cp not currently supported on values",
"not currently supported",
name,
))
}
fn mv(&self, _args: MvArgs, name: Tag, _path: &str) -> Result<ActionStream, ShellError> {
Err(ShellError::labeled_error(
"mv not currently supported on values",
"not currently supported",
name,
))
}
fn mkdir(&self, _args: MkdirArgs, name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
Err(ShellError::labeled_error(
"mkdir not currently supported on values",
"not currently supported",
name,
))
}
fn rm(&self, _args: RemoveArgs, name: Tag, _path: &str) -> Result<ActionStream, ShellError> {
Err(ShellError::labeled_error(
"rm not currently supported on values",
"not currently supported",
name,
))
}
fn path(&self) -> String {
self.path.clone()
}
fn pwd(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
Ok(ActionStream::one(
UntaggedValue::string(self.path()).into_value(&args.call_info.name_tag),
))
}
fn set_path(&mut self, path: String) {
self.last_path = self.path.clone();
self.path = path;
}
fn open(
&self,
_path: &Path,
_name: Span,
_with_encoding: Option<&'static Encoding>,
) -> Result<
Box<dyn Iterator<Item = Result<StringOrBinary, ShellError>> + Send + Sync>,
ShellError,
> {
Err(ShellError::unimplemented(
"open on help shell is not supported",
))
}
fn save(
&mut self,
_path: &Path,
_contents: &[u8],
_name: Span,
_append: bool,
) -> Result<OutputStream, ShellError> {
Err(ShellError::unimplemented(
"save on help shell is not supported",
))
}
fn is_interactive(&self) -> bool {
//Value shell is always interactive
true
}
}

View File

@ -1 +0,0 @@
pub(crate) mod deduction;

File diff suppressed because it is too large Load Diff

View File

@ -1,325 +0,0 @@
use crate::command_args::CommandArgs;
use crate::documentation::get_full_help;
use crate::evaluate::block::run_block;
use crate::example::Example;
use nu_errors::ShellError;
use nu_parser::ParserScope;
use nu_protocol::hir::Block;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use nu_source::{DbgDocBldr, DebugDocBuilder, PrettyDebugWithSource, Span, Tag};
use nu_stream::{ActionStream, InputStream, IntoOutputStream, OutputStream};
use std::sync::Arc;
pub trait WholeStreamCommand: Send + Sync {
fn name(&self) -> &str;
fn signature(&self) -> Signature {
Signature::new(self.name()).desc(self.usage()).filter()
}
fn usage(&self) -> &str;
fn extra_usage(&self) -> &str {
""
}
fn run_with_actions(&self, _args: CommandArgs) -> Result<ActionStream, ShellError> {
return Err(ShellError::unimplemented(&format!(
"{} does not implement run or run_with_actions",
self.name()
)));
}
fn run(&self, args: CommandArgs) -> Result<InputStream, ShellError> {
let context = args.context.clone();
let stream = self.run_with_actions(args)?;
Ok(Box::new(crate::evaluate::internal::InternalIterator {
context,
input: stream,
leftovers: InputStream::empty(),
})
.into_output_stream())
}
fn is_binary(&self) -> bool {
false
}
// Commands that are not meant to be run by users
fn is_private(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> {
Vec::new()
}
// This is a built-in command
fn is_builtin(&self) -> bool {
true
}
// Is a sub command
fn is_sub(&self) -> bool {
self.name().contains(' ')
}
// Is a plugin command
fn is_plugin(&self) -> bool {
false
}
// Is a custom command i.e. def blah [] { }
fn is_custom(&self) -> bool {
false
}
}
// Custom commands are blocks, so we can use the information in the block to also
// implement a WholeStreamCommand
#[allow(clippy::suspicious_else_formatting)]
impl WholeStreamCommand for Arc<Block> {
fn name(&self) -> &str {
&self.params.name
}
fn signature(&self) -> Signature {
self.params.clone()
}
fn usage(&self) -> &str {
&self.params.usage
}
fn extra_usage(&self) -> &str {
&self.params.extra_usage
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let call_info = args.call_info.clone();
let block = self.clone();
let external_redirection = args.call_info.args.external_redirection;
let ctx = &args.context;
let evaluated = call_info.evaluate(ctx)?;
let input = args.input;
ctx.scope.enter_scope();
if let Some(args) = evaluated.args.positional {
let mut args_iter = args.into_iter().peekable();
let mut params_iter = self.params.positional.iter();
loop {
match (args_iter.peek(), params_iter.next()) {
(Some(_), Some(param)) => {
let name = param.0.name();
// we just checked the peek above, so this should be infallible
if let Some(arg) = args_iter.next() {
if name.starts_with('$') {
ctx.scope.add_var(name.to_string(), arg);
} else {
ctx.scope.add_var(format!("${}", name), arg);
}
}
}
(Some(arg), None) => {
if block.params.rest_positional.is_none() {
ctx.scope.exit_scope();
return Err(ShellError::labeled_error(
"Unexpected argument to command",
"unexpected argument",
arg.tag.span,
));
} else {
break;
}
}
_ => break,
}
}
if let Some(rest_pos) = &block.params.rest_positional {
let elements: Vec<_> = args_iter.collect();
let start = if let Some(first) = elements.first() {
first.tag.span.start()
} else {
0
};
let end = if let Some(last) = elements.last() {
last.tag.span.end()
} else {
0
};
ctx.scope.add_var(
format!("${}", rest_pos.0),
UntaggedValue::Table(elements).into_value(Span::new(start, end)),
);
}
} else if let Some(rest_pos) = &block.params.rest_positional {
//If there is a rest arg, but no args were provided,
//we have to set $rest to an empty table
ctx.scope.add_var(
format!("${}", rest_pos.0),
UntaggedValue::Table(Vec::new()).into_value(Span::new(0, 0)),
);
}
if let Some(args) = evaluated.args.named {
for named in &block.params.named {
let name = named.0;
if let Some(value) = args.get(name) {
if name.starts_with('$') {
ctx.scope.add_var(name, value.clone());
} else {
ctx.scope.add_var(format!("${}", name), value.clone());
}
} else if name.starts_with('$') {
ctx.scope
.add_var(name, UntaggedValue::nothing().into_untagged_value());
} else {
ctx.scope.add_var(
format!("${}", name),
UntaggedValue::nothing().into_untagged_value(),
);
}
}
} else {
for named in &block.params.named {
let name = named.0;
if name.starts_with('$') {
ctx.scope
.add_var(name, UntaggedValue::nothing().into_untagged_value());
} else {
ctx.scope.add_var(
format!("${}", name),
UntaggedValue::nothing().into_untagged_value(),
);
}
}
}
let result = run_block(&block, ctx, input, external_redirection);
ctx.scope.exit_scope();
result
}
fn is_binary(&self) -> bool {
false
}
fn is_private(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> {
vec![]
}
fn is_custom(&self) -> bool {
true
}
fn is_builtin(&self) -> bool {
false
}
}
#[derive(Clone)]
pub struct Command(Arc<dyn WholeStreamCommand>);
impl PrettyDebugWithSource for Command {
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
DbgDocBldr::typed(
"whole stream command",
DbgDocBldr::description(self.name())
+ DbgDocBldr::space()
+ DbgDocBldr::equals()
+ DbgDocBldr::space()
+ self.signature().pretty_debug(source),
)
}
}
impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Command({})", self.name())
}
}
impl Command {
pub fn name(&self) -> &str {
self.0.name()
}
pub fn signature(&self) -> Signature {
self.0.signature()
}
pub fn usage(&self) -> &str {
self.0.usage()
}
pub fn extra_usage(&self) -> &str {
self.0.extra_usage()
}
pub fn examples(&self) -> Vec<Example> {
self.0.examples()
}
pub fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
if args.call_info.switch_present("help") {
let cl = self.0.clone();
Ok(ActionStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_full_help(&*cl, &args.context.scope))
.into_value(Tag::unknown()),
))))
} else {
self.0.run_with_actions(args)
}
}
pub fn run(&self, args: CommandArgs) -> Result<InputStream, ShellError> {
if args.call_info.switch_present("help") {
let cl = self.0.clone();
Ok(InputStream::one(
UntaggedValue::string(get_full_help(&*cl, &args.context.scope))
.into_value(Tag::unknown()),
))
} else {
self.0.run(args)
}
}
pub fn is_binary(&self) -> bool {
self.0.is_binary()
}
pub fn is_private(&self) -> bool {
self.0.is_private()
}
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
&*self.0
}
pub fn is_builtin(&self) -> bool {
self.0.is_builtin()
}
pub fn is_sub(&self) -> bool {
self.0.is_sub()
}
pub fn is_plugin(&self) -> bool {
self.0.is_plugin()
}
pub fn is_custom(&self) -> bool {
self.0.is_custom()
}
}
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
Command(Arc::new(command))
}