nushell/crates/nu-engine/src/eval.rs
nibon7 772ad896c8
Get $nu.config-path and $nu.env-path from EngineState (#6366)
* Get `$nu.config-path` and `$nu.env-path` from `EngineState`

Signed-off-by: nibon7 <nibon7@163.com>

* replace tuple with hashmap

Signed-off-by: nibon7 <nibon7@163.com>

* refactor set_config_path

Signed-off-by: nibon7 <nibon7@163.com>

Signed-off-by: nibon7 <nibon7@163.com>
2022-08-22 19:30:09 +03:00

1604 lines
55 KiB
Rust

use crate::{current_dir_str, get_full_help};
use nu_path::expand_path_with;
use nu_protocol::{
ast::{Block, Call, Expr, Expression, Operator},
engine::{EngineState, Stack, Visibility},
Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
PipelineData, Range, ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId,
ENV_VARIABLE_ID,
};
use nu_utils::stdout_write_all_and_flush;
use std::cmp::Ordering;
use std::collections::HashMap;
use sysinfo::SystemExt;
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
match op {
Expression {
expr: Expr::Operator(operator),
..
} => Ok(operator.clone()),
Expression { span, expr, .. } => {
Err(ShellError::UnknownOperator(format!("{:?}", expr), *span))
}
}
}
pub fn eval_call(
engine_state: &EngineState,
caller_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
if let Some(ctrlc) = &engine_state.ctrlc {
if ctrlc.load(core::sync::atomic::Ordering::SeqCst) {
return Ok(Value::Nothing { span: call.head }.into_pipeline_data());
}
}
let decl = engine_state.get_decl(call.decl_id);
if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
let mut signature = decl.signature();
signature.usage = decl.usage().to_string();
signature.extra_usage = decl.extra_usage().to_string();
let full_help = get_full_help(&signature, &decl.examples(), engine_state, caller_stack);
Ok(Value::String {
val: full_help,
span: call.head,
}
.into_pipeline_data())
} else if let Some(block_id) = decl.get_block_id() {
let block = engine_state.get_block(block_id);
let mut callee_stack = caller_stack.gather_captures(&block.captures);
for (param_idx, param) in decl
.signature()
.required_positional
.iter()
.chain(decl.signature().optional_positional.iter())
.enumerate()
{
let var_id = param
.var_id
.expect("internal error: all custom parameters must have var_ids");
if let Some(arg) = call.positional_nth(param_idx) {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else if let Some(arg) = &param.default_value {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else {
callee_stack.add_var(var_id, Value::nothing(call.head));
}
}
if let Some(rest_positional) = decl.signature().rest_positional {
let mut rest_items = vec![];
for arg in call.positional_iter().skip(
decl.signature().required_positional.len()
+ decl.signature().optional_positional.len(),
) {
let result = eval_expression(engine_state, caller_stack, arg)?;
rest_items.push(result);
}
let span = if let Some(rest_item) = rest_items.first() {
rest_item.span()?
} else {
call.head
};
callee_stack.add_var(
rest_positional
.var_id
.expect("Internal error: rest positional parameter lacks var_id"),
Value::List {
vals: rest_items,
span,
},
)
}
for named in decl.signature().named {
if let Some(var_id) = named.var_id {
let mut found = false;
for call_named in call.named_iter() {
if call_named.0.item == named.long {
if let Some(arg) = &call_named.2 {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else if let Some(arg) = &named.default_value {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else {
callee_stack.add_var(
var_id,
Value::Bool {
val: true,
span: call.head,
},
)
}
found = true;
}
}
if !found {
if named.arg.is_none() {
callee_stack.add_var(
var_id,
Value::Bool {
val: false,
span: call.head,
},
)
} else if let Some(arg) = &named.default_value {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else {
callee_stack.add_var(var_id, Value::Nothing { span: call.head })
}
}
}
}
let result = eval_block(
engine_state,
&mut callee_stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
);
if block.redirect_env {
redirect_env(engine_state, caller_stack, &callee_stack);
}
result
} else {
// We pass caller_stack here with the knowledge that internal commands
// are going to be specifically looking for global state in the stack
// rather than any local state.
decl.run(engine_state, caller_stack, call, input)
}
}
/// Redirect the environment from callee to the caller.
pub fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
let caller_env_vars = caller_stack.get_env_var_names(engine_state);
// remove env vars that are present in the caller but not in the callee
// (the callee hid them)
for var in caller_env_vars.iter() {
if !callee_stack.has_env_var(engine_state, var) {
caller_stack.remove_env_var(engine_state, var);
}
}
// add new env vars from callee to caller
for (var, value) in callee_stack.get_stack_env_vars() {
caller_stack.add_env_var(var, value);
}
}
/// Eval extarnal expression
///
/// It returns PipelineData with a boolean flag, indicate that if the external runs to failed.
fn eval_external(
engine_state: &EngineState,
stack: &mut Stack,
head: &Expression,
args: &[Expression],
input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> {
let decl_id = engine_state
.find_decl("run-external".as_bytes(), &[])
.ok_or(ShellError::ExternalNotSupported(head.span))?;
let command = engine_state.get_decl(decl_id);
let mut call = Call::new(head.span);
call.add_positional(head.clone());
for arg in args {
call.add_positional(arg.clone())
}
if redirect_stdout {
call.add_named((
Spanned {
item: "redirect-stdout".into(),
span: head.span,
},
None,
None,
))
}
if redirect_stderr {
call.add_named((
Spanned {
item: "redirect-stderr".into(),
span: head.span,
},
None,
None,
))
}
// when the external command doesn't redirect output, we eagerly check the result
// and find if the command runs to failed.
let mut runs_to_failed = false;
let result = command.run(engine_state, stack, &call, input)?;
if let PipelineData::ExternalStream {
stdout: None,
stderr,
mut exit_code,
span,
metadata,
} = result
{
let exit_code = exit_code.take();
match exit_code {
Some(exit_code_stream) => {
let ctrlc = exit_code_stream.ctrlc.clone();
let exit_code: Vec<Value> = exit_code_stream
.into_iter()
.map(|(value, _)| value)
.collect();
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
runs_to_failed = true;
}
}
Ok((
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: Some(ListStream::from_stream(exit_code.into_iter(), ctrlc)),
span,
metadata,
},
runs_to_failed,
))
}
None => Ok((
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: None,
span,
metadata,
},
runs_to_failed,
)),
}
} else {
Ok((result, runs_to_failed))
}
}
pub fn eval_expression(
engine_state: &EngineState,
stack: &mut Stack,
expr: &Expression,
) -> Result<Value, ShellError> {
match &expr.expr {
Expr::Bool(b) => Ok(Value::Bool {
val: *b,
span: expr.span,
}),
Expr::Int(i) => Ok(Value::Int {
val: *i,
span: expr.span,
}),
Expr::Float(f) => Ok(Value::Float {
val: *f,
span: expr.span,
}),
Expr::Binary(b) => Ok(Value::Binary {
val: b.clone(),
span: expr.span,
}),
Expr::ValueWithUnit(e, unit) => match eval_expression(engine_state, stack, e)? {
Value::Int { val, .. } => Ok(compute(val, unit.item, unit.span)),
x => Err(ShellError::CantConvert(
"unit value".into(),
x.get_type().to_string(),
e.span,
None,
)),
},
Expr::Range(from, next, to, operator) => {
let from = if let Some(f) = from {
eval_expression(engine_state, stack, f)?
} else {
Value::Nothing { span: expr.span }
};
let next = if let Some(s) = next {
eval_expression(engine_state, stack, s)?
} else {
Value::Nothing { span: expr.span }
};
let to = if let Some(t) = to {
eval_expression(engine_state, stack, t)?
} else {
Value::Nothing { span: expr.span }
};
Ok(Value::Range {
val: Box::new(Range::new(expr.span, from, next, to, operator)?),
span: expr.span,
})
}
Expr::Var(var_id) => eval_variable(engine_state, stack, *var_id, expr.span),
Expr::VarDecl(_) => Ok(Value::Nothing { span: expr.span }),
Expr::CellPath(cell_path) => Ok(Value::CellPath {
val: cell_path.clone(),
span: expr.span,
}),
Expr::FullCellPath(cell_path) => {
let value = eval_expression(engine_state, stack, &cell_path.head)?;
value.follow_cell_path(&cell_path.tail, false)
}
Expr::ImportPattern(_) => Ok(Value::Nothing { span: expr.span }),
Expr::Call(call) => {
// FIXME: protect this collect with ctrl-c
Ok(
eval_call(engine_state, stack, call, PipelineData::new(call.head))?
.into_value(call.head),
)
}
Expr::ExternalCall(head, args) => {
let span = head.span;
// FIXME: protect this collect with ctrl-c
Ok(eval_external(
engine_state,
stack,
head,
args,
PipelineData::new(span),
false,
false,
)?
.0
.into_value(span))
}
Expr::DateTime(dt) => Ok(Value::Date {
val: *dt,
span: expr.span,
}),
Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }),
Expr::UnaryNot(expr) => {
let lhs = eval_expression(engine_state, stack, expr)?;
match lhs {
Value::Bool { val, .. } => Ok(Value::Bool {
val: !val,
span: expr.span,
}),
_ => Err(ShellError::TypeMismatch("bool".to_string(), expr.span)),
}
}
Expr::BinaryOp(lhs, op, rhs) => {
let op_span = op.span;
let lhs = eval_expression(engine_state, stack, lhs)?;
let op = eval_operator(op)?;
match op {
Operator::And => {
if !lhs.is_true() {
Ok(Value::Bool {
val: false,
span: expr.span,
})
} else {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.and(op_span, &rhs, expr.span)
}
}
Operator::Or => {
if lhs.is_true() {
Ok(Value::Bool {
val: true,
span: expr.span,
})
} else {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.or(op_span, &rhs, expr.span)
}
}
Operator::Plus => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.add(op_span, &rhs, expr.span)
}
Operator::Minus => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.sub(op_span, &rhs, expr.span)
}
Operator::Multiply => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.mul(op_span, &rhs, expr.span)
}
Operator::Divide => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.div(op_span, &rhs, expr.span)
}
Operator::LessThan => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.lt(op_span, &rhs, expr.span)
}
Operator::LessThanOrEqual => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.lte(op_span, &rhs, expr.span)
}
Operator::GreaterThan => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.gt(op_span, &rhs, expr.span)
}
Operator::GreaterThanOrEqual => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.gte(op_span, &rhs, expr.span)
}
Operator::Equal => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.eq(op_span, &rhs, expr.span)
}
Operator::NotEqual => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.ne(op_span, &rhs, expr.span)
}
Operator::In => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.r#in(op_span, &rhs, expr.span)
}
Operator::NotIn => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.not_in(op_span, &rhs, expr.span)
}
Operator::RegexMatch => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.regex_match(op_span, &rhs, false, expr.span)
}
Operator::NotRegexMatch => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.regex_match(op_span, &rhs, true, expr.span)
}
Operator::Modulo => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.modulo(op_span, &rhs, expr.span)
}
Operator::FloorDivision => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.floor_div(op_span, &rhs, expr.span)
}
Operator::Pow => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.pow(op_span, &rhs, expr.span)
}
Operator::StartsWith => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.starts_with(op_span, &rhs, expr.span)
}
Operator::EndsWith => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.ends_with(op_span, &rhs, expr.span)
}
Operator::BitOr => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_or(op_span, &rhs, expr.span)
}
Operator::BitXor => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_xor(op_span, &rhs, expr.span)
}
Operator::BitAnd => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_and(op_span, &rhs, expr.span)
}
Operator::ShiftRight => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_shr(op_span, &rhs, expr.span)
}
Operator::ShiftLeft => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_shl(op_span, &rhs, expr.span)
}
}
}
Expr::Subexpression(block_id) => {
let block = engine_state.get_block(*block_id);
// FIXME: protect this collect with ctrl-c
Ok(
eval_subexpression(engine_state, stack, block, PipelineData::new(expr.span))?
.into_value(expr.span),
)
}
Expr::RowCondition(block_id) | Expr::Block(block_id) => {
let mut captures = HashMap::new();
let block = engine_state.get_block(*block_id);
for var_id in &block.captures {
captures.insert(*var_id, stack.get_var(*var_id, expr.span)?);
}
Ok(Value::Block {
val: *block_id,
captures,
span: expr.span,
})
}
Expr::List(x) => {
let mut output = vec![];
for expr in x {
output.push(eval_expression(engine_state, stack, expr)?);
}
Ok(Value::List {
vals: output,
span: expr.span,
})
}
Expr::Record(fields) => {
let mut cols = vec![];
let mut vals = vec![];
for (col, val) in fields {
// avoid duplicate cols.
let col_name = eval_expression(engine_state, stack, col)?.as_string()?;
let pos = cols.iter().position(|c| c == &col_name);
match pos {
Some(index) => {
vals[index] = eval_expression(engine_state, stack, val)?;
}
None => {
cols.push(col_name);
vals.push(eval_expression(engine_state, stack, val)?);
}
}
}
Ok(Value::Record {
cols,
vals,
span: expr.span,
})
}
Expr::Table(headers, vals) => {
let mut output_headers = vec![];
for expr in headers {
output_headers.push(eval_expression(engine_state, stack, expr)?.as_string()?);
}
let mut output_rows = vec![];
for val in vals {
let mut row = vec![];
for expr in val {
row.push(eval_expression(engine_state, stack, expr)?);
}
output_rows.push(Value::Record {
cols: output_headers.clone(),
vals: row,
span: expr.span,
});
}
Ok(Value::List {
vals: output_rows,
span: expr.span,
})
}
Expr::Keyword(_, _, expr) => eval_expression(engine_state, stack, expr),
Expr::StringInterpolation(exprs) => {
let mut parts = vec![];
for expr in exprs {
parts.push(eval_expression(engine_state, stack, expr)?);
}
let config = engine_state.get_config();
parts
.into_iter()
.into_pipeline_data(None)
.collect_string("", config)
.map(|x| Value::String {
val: x,
span: expr.span,
})
}
Expr::String(s) => Ok(Value::String {
val: s.clone(),
span: expr.span,
}),
Expr::Filepath(s) => {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(s, cwd);
Ok(Value::String {
val: path.to_string_lossy().to_string(),
span: expr.span,
})
}
Expr::Directory(s) => {
if s == "-" {
Ok(Value::String {
val: "-".to_string(),
span: expr.span,
})
} else {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(s, cwd);
Ok(Value::String {
val: path.to_string_lossy().to_string(),
span: expr.span,
})
}
}
Expr::GlobPattern(s) => {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(s, cwd);
Ok(Value::String {
val: path.to_string_lossy().to_string(),
span: expr.span,
})
}
Expr::Signature(_) => Ok(Value::Nothing { span: expr.span }),
Expr::Garbage => Ok(Value::Nothing { span: expr.span }),
Expr::Nothing => Ok(Value::Nothing { span: expr.span }),
}
}
/// Checks the expression to see if it's a internal or external call. If so, passes the input
/// into the call and gets out the result
/// Otherwise, invokes the expression
///
/// It returns PipelineData with a boolean flag, indicate that if the external runs to failed.
/// The boolean flag **may only be true** for external calls, for internal calls, it always to be false.
pub fn eval_expression_with_input(
engine_state: &EngineState,
stack: &mut Stack,
expr: &Expression,
mut input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> {
let mut external_failed = false;
match expr {
Expression {
expr: Expr::Call(call),
..
} => {
if !redirect_stdout || redirect_stderr {
// we're doing something different than the defaults
let mut call = call.clone();
call.redirect_stdout = redirect_stdout;
call.redirect_stderr = redirect_stderr;
input = eval_call(engine_state, stack, &call, input)?;
} else {
input = eval_call(engine_state, stack, call, input)?;
}
}
Expression {
expr: Expr::ExternalCall(head, args),
..
} => {
let external_result = eval_external(
engine_state,
stack,
head,
args,
input,
redirect_stdout,
redirect_stderr,
)?;
input = external_result.0;
external_failed = external_result.1
}
Expression {
expr: Expr::Subexpression(block_id),
..
} => {
let block = engine_state.get_block(*block_id);
// FIXME: protect this collect with ctrl-c
input = eval_subexpression(engine_state, stack, block, input)?;
}
elem => {
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
}
}
Ok((input, external_failed))
}
pub fn eval_block(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
mut input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<PipelineData, ShellError> {
let num_pipelines = block.len();
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
for (i, elem) in pipeline.expressions.iter().enumerate() {
// if eval internal command failed, it can just make early return with `Err(ShellError)`.
let eval_result = eval_expression_with_input(
engine_state,
stack,
elem,
input,
redirect_stdout || (i != pipeline.expressions.len() - 1),
redirect_stderr,
)?;
input = eval_result.0;
// external command may runs to failed
// make early return so remaining commands will not be executed.
// don't return `Err(ShellError)`, so nushell wouldn't show extra error message.
if eval_result.1 {
return Ok(input);
}
}
if pipeline_idx < (num_pipelines) - 1 {
match input {
PipelineData::Value(Value::Nothing { .. }, ..) => {}
PipelineData::ExternalStream {
ref mut exit_code, ..
} => {
let exit_code = exit_code.take();
// Drain the input to the screen via tabular output
let config = engine_state.get_config();
match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
input,
)?;
print_or_return(table, config)?;
}
None => {
print_or_return(input, config)?;
}
};
if let Some(exit_code) = exit_code {
let mut v: Vec<_> = exit_code.map(|(value, _)| value).collect();
if let Some(v) = v.pop() {
stack.add_env_var("LAST_EXIT_CODE".into(), v);
}
}
}
_ => {
// Drain the input to the screen via tabular output
let config = engine_state.get_config();
match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
input,
)?;
print_or_return(table, config)?;
}
None => {
print_or_return(input, config)?;
}
};
}
}
input = PipelineData::new(Span { start: 0, end: 0 })
}
}
Ok(input)
}
fn print_or_return(pipeline_data: PipelineData, config: &Config) -> Result<(), ShellError> {
for item in pipeline_data {
if let Value::Error { error } = item {
return Err(error);
}
let mut out = item.into_string("\n", config);
out.push('\n');
stdout_write_all_and_flush(out)?;
}
Ok(())
}
pub fn eval_subexpression(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
mut input: PipelineData,
) -> Result<PipelineData, ShellError> {
for pipeline in block.pipelines.iter() {
for expr in pipeline.expressions.iter() {
input = eval_expression_with_input(engine_state, stack, expr, input, true, false)?.0
}
}
Ok(input)
}
fn extract_custom_completion_from_arg(engine_state: &EngineState, shape: &SyntaxShape) -> String {
return match shape {
SyntaxShape::Custom(_, custom_completion_decl_id) => {
let custom_completion_command = engine_state.get_decl(*custom_completion_decl_id);
let custom_completion_command_name: &str = custom_completion_command.name();
custom_completion_command_name.to_string()
}
_ => "".to_string(),
};
}
pub fn create_scope(
engine_state: &EngineState,
stack: &Stack,
span: Span,
) -> Result<Value, ShellError> {
let mut output_cols = vec![];
let mut output_vals = vec![];
let mut vars = vec![];
let mut commands = vec![];
let mut aliases = vec![];
let mut modules = vec![];
let mut vars_map = HashMap::new();
let mut commands_map = HashMap::new();
let mut aliases_map = HashMap::new();
let mut modules_map = HashMap::new();
let mut visibility = Visibility::new();
for overlay_frame in engine_state.active_overlays(&[]) {
vars_map.extend(&overlay_frame.vars);
commands_map.extend(&overlay_frame.decls);
aliases_map.extend(&overlay_frame.aliases);
modules_map.extend(&overlay_frame.modules);
visibility.merge_with(overlay_frame.visibility.clone());
}
for var in vars_map {
let var_name = Value::string(String::from_utf8_lossy(var.0).to_string(), span);
let var_type = Value::string(engine_state.get_var(*var.1).ty.to_string(), span);
let var_value = if let Ok(val) = stack.get_var(*var.1, span) {
val
} else {
Value::nothing(span)
};
vars.push(Value::Record {
cols: vec!["name".to_string(), "type".to_string(), "value".to_string()],
vals: vec![var_name, var_type, var_value],
span,
})
}
for ((command_name, _), decl_id) in commands_map {
if visibility.is_decl_id_visible(decl_id) {
let mut cols = vec![];
let mut vals = vec![];
let mut module_commands = vec![];
for module in &modules_map {
let module_name = String::from_utf8_lossy(module.0).to_string();
let module_id = engine_state.find_module(module.0, &[]);
if let Some(module_id) = module_id {
let module = engine_state.get_module(module_id);
if module.has_decl(command_name) {
module_commands.push(module_name);
}
}
}
cols.push("command".into());
vals.push(Value::String {
val: String::from_utf8_lossy(command_name).to_string(),
span,
});
cols.push("module_name".into());
vals.push(Value::string(module_commands.join(", "), span));
let decl = engine_state.get_decl(*decl_id);
let signature = decl.signature();
cols.push("category".to_string());
vals.push(Value::String {
val: signature.category.to_string(),
span,
});
// signature
let mut sig_records = vec![];
{
let sig_cols = vec![
"command".to_string(),
"parameter_name".to_string(),
"parameter_type".to_string(),
"syntax_shape".to_string(),
"is_optional".to_string(),
"short_flag".to_string(),
"description".to_string(),
"custom_completion".to_string(),
];
// required_positional
for req in signature.required_positional {
let sig_vals = vec![
Value::string(&signature.name, span),
Value::string(req.name, span),
Value::string("positional", span),
Value::string(req.shape.to_string(), span),
Value::boolean(false, span),
Value::nothing(span),
Value::string(req.desc, span),
Value::string(
extract_custom_completion_from_arg(engine_state, &req.shape),
span,
),
];
sig_records.push(Value::Record {
cols: sig_cols.clone(),
vals: sig_vals,
span,
});
}
// optional_positional
for opt in signature.optional_positional {
let sig_vals = vec![
Value::string(&signature.name, span),
Value::string(opt.name, span),
Value::string("positional", span),
Value::string(opt.shape.to_string(), span),
Value::boolean(true, span),
Value::nothing(span),
Value::string(opt.desc, span),
Value::string(
extract_custom_completion_from_arg(engine_state, &opt.shape),
span,
),
];
sig_records.push(Value::Record {
cols: sig_cols.clone(),
vals: sig_vals,
span,
});
}
{
// rest_positional
if let Some(rest) = signature.rest_positional {
let sig_vals = vec![
Value::string(&signature.name, span),
Value::string(rest.name, span),
Value::string("rest", span),
Value::string(rest.shape.to_string(), span),
Value::boolean(true, span),
Value::nothing(span),
Value::string(rest.desc, span),
Value::string(
extract_custom_completion_from_arg(engine_state, &rest.shape),
span,
),
];
sig_records.push(Value::Record {
cols: sig_cols.clone(),
vals: sig_vals,
span,
});
}
}
// named flags
for named in signature.named {
let flag_type;
// Skip the help flag
if named.long == "help" {
continue;
}
let mut custom_completion_command_name: String = "".to_string();
let shape = if let Some(arg) = named.arg {
flag_type = Value::string("named", span);
custom_completion_command_name =
extract_custom_completion_from_arg(engine_state, &arg);
Value::string(arg.to_string(), span)
} else {
flag_type = Value::string("switch", span);
Value::nothing(span)
};
let short_flag = if let Some(c) = named.short {
Value::string(c, span)
} else {
Value::nothing(span)
};
let sig_vals = vec![
Value::string(&signature.name, span),
Value::string(named.long, span),
flag_type,
shape,
Value::boolean(!named.required, span),
short_flag,
Value::string(named.desc, span),
Value::string(custom_completion_command_name, span),
];
sig_records.push(Value::Record {
cols: sig_cols.clone(),
vals: sig_vals,
span,
});
}
}
cols.push("signature".to_string());
vals.push(Value::List {
vals: sig_records,
span,
});
cols.push("usage".to_string());
vals.push(Value::String {
val: decl.usage().into(),
span,
});
cols.push("examples".to_string());
vals.push(Value::List {
vals: decl
.examples()
.into_iter()
.map(|x| Value::Record {
cols: vec!["description".into(), "example".into()],
vals: vec![
Value::String {
val: x.description.to_string(),
span,
},
Value::String {
val: x.example.to_string(),
span,
},
],
span,
})
.collect(),
span,
});
cols.push("is_builtin".to_string());
// we can only be a is_builtin or is_custom, not both
vals.push(Value::Bool {
val: !decl.is_custom_command(),
span,
});
cols.push("is_sub".to_string());
vals.push(Value::Bool {
val: decl.is_sub(),
span,
});
cols.push("is_plugin".to_string());
vals.push(Value::Bool {
val: decl.is_plugin().is_some(),
span,
});
cols.push("is_custom".to_string());
vals.push(Value::Bool {
val: decl.is_custom_command(),
span,
});
cols.push("is_keyword".into());
vals.push(Value::Bool {
val: decl.is_parser_keyword(),
span,
});
cols.push("is_extern".to_string());
vals.push(Value::Bool {
val: decl.is_known_external(),
span,
});
cols.push("creates_scope".to_string());
vals.push(Value::Bool {
val: signature.creates_scope,
span,
});
cols.push("extra_usage".to_string());
vals.push(Value::String {
val: decl.extra_usage().into(),
span,
});
let search_terms = decl.search_terms();
cols.push("search_terms".to_string());
vals.push(if search_terms.is_empty() {
Value::nothing(span)
} else {
Value::String {
val: search_terms.join(", "),
span,
}
});
commands.push(Value::Record { cols, vals, span })
}
}
for (alias_name, alias_id) in aliases_map {
if visibility.is_alias_id_visible(alias_id) {
let alias = engine_state.get_alias(*alias_id);
let mut alias_text = String::new();
for span in alias {
let contents = engine_state.get_span_contents(span);
if !alias_text.is_empty() {
alias_text.push(' ');
}
alias_text.push_str(&String::from_utf8_lossy(contents));
}
aliases.push((
Value::String {
val: String::from_utf8_lossy(alias_name).to_string(),
span,
},
Value::string(alias_text, span),
));
}
}
for module in modules_map {
modules.push(Value::String {
val: String::from_utf8_lossy(module.0).to_string(),
span,
});
}
output_cols.push("vars".to_string());
output_vals.push(Value::List { vals: vars, span });
commands.sort_by(|a, b| match (a, b) {
(Value::Record { vals: rec_a, .. }, Value::Record { vals: rec_b, .. }) => {
// Comparing the first value from the record
// It is expected that the first value is the name of the column
// The names of the commands should be a value string
match (rec_a.get(0), rec_b.get(0)) {
(Some(val_a), Some(val_b)) => match (val_a, val_b) {
(Value::String { val: str_a, .. }, Value::String { val: str_b, .. }) => {
str_a.cmp(str_b)
}
_ => Ordering::Equal,
},
_ => Ordering::Equal,
}
}
_ => Ordering::Equal,
});
output_cols.push("commands".to_string());
output_vals.push(Value::List {
vals: commands,
span,
});
aliases.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
output_cols.push("aliases".to_string());
output_vals.push(Value::List {
vals: aliases
.into_iter()
.map(|(alias, value)| Value::Record {
cols: vec!["alias".into(), "expansion".into()],
vals: vec![alias, value],
span,
})
.collect(),
span,
});
modules.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
output_cols.push("modules".to_string());
output_vals.push(Value::List {
vals: modules,
span,
});
let engine_state_cols = vec![
"source_bytes".to_string(),
"num_vars".to_string(),
"num_commands".to_string(),
"num_aliases".to_string(),
"num_blocks".to_string(),
"num_modules".to_string(),
"num_env_vars".to_string(),
];
let engine_state_vals = vec![
Value::int(engine_state.next_span_start() as i64, span),
Value::int(engine_state.num_vars() as i64, span),
Value::int(engine_state.num_decls() as i64, span),
Value::int(engine_state.num_aliases() as i64, span),
Value::int(engine_state.num_blocks() as i64, span),
Value::int(engine_state.num_modules() as i64, span),
Value::int(
engine_state
.env_vars
.values()
.map(|overlay| overlay.len() as i64)
.sum(),
span,
),
];
output_cols.push("engine_state".to_string());
output_vals.push(Value::Record {
cols: engine_state_cols,
vals: engine_state_vals,
span,
});
Ok(Value::Record {
cols: output_cols,
vals: output_vals,
span,
})
}
pub fn eval_variable(
engine_state: &EngineState,
stack: &Stack,
var_id: VarId,
span: Span,
) -> Result<Value, ShellError> {
match var_id {
nu_protocol::NU_VARIABLE_ID => {
// $nu
let mut output_cols = vec![];
let mut output_vals = vec![];
if let Some(path) = engine_state.get_config_path("config-path") {
output_cols.push("config-path".into());
output_vals.push(Value::String {
val: path.to_string_lossy().to_string(),
span,
});
}
if let Some(path) = engine_state.get_config_path("env-path") {
output_cols.push("env-path".into());
output_vals.push(Value::String {
val: path.to_string_lossy().to_string(),
span,
});
}
if let Some(mut config_path) = nu_path::config_dir() {
config_path.push("nushell");
let mut env_config_path = config_path.clone();
let mut loginshell_path = config_path.clone();
let mut history_path = config_path.clone();
match engine_state.config.history_file_format {
HistoryFileFormat::Sqlite => {
history_path.push("history.sqlite3");
}
HistoryFileFormat::PlainText => {
history_path.push("history.txt");
}
}
// let mut history_path = config_files::get_history_path(); // todo: this should use the get_history_path method but idk where to put that function
output_cols.push("history-path".into());
output_vals.push(Value::String {
val: history_path.to_string_lossy().to_string(),
span,
});
if engine_state.get_config_path("config-path").is_none() {
config_path.push("config.nu");
output_cols.push("config-path".into());
output_vals.push(Value::String {
val: config_path.to_string_lossy().to_string(),
span,
});
}
if engine_state.get_config_path("env-path").is_none() {
env_config_path.push("env.nu");
output_cols.push("env-path".into());
output_vals.push(Value::String {
val: env_config_path.to_string_lossy().to_string(),
span,
});
}
loginshell_path.push("login.nu");
output_cols.push("loginshell-path".into());
output_vals.push(Value::String {
val: loginshell_path.to_string_lossy().to_string(),
span,
});
}
#[cfg(feature = "plugin")]
if let Some(path) = &engine_state.plugin_signatures {
if let Some(path_str) = path.to_str() {
output_cols.push("plugin-path".into());
output_vals.push(Value::String {
val: path_str.into(),
span,
});
}
}
output_cols.push("scope".into());
output_vals.push(create_scope(engine_state, stack, span)?);
if let Some(home_path) = nu_path::home_dir() {
if let Some(home_path_str) = home_path.to_str() {
output_cols.push("home-path".into());
output_vals.push(Value::String {
val: home_path_str.into(),
span,
})
}
}
let temp = std::env::temp_dir();
if let Some(temp_path) = temp.to_str() {
output_cols.push("temp-path".into());
output_vals.push(Value::String {
val: temp_path.into(),
span,
})
}
let pid = std::process::id();
output_cols.push("pid".into());
output_vals.push(Value::int(pid as i64, span));
let sys = sysinfo::System::new();
let ver = match sys.kernel_version() {
Some(v) => v,
None => "unknown".into(),
};
let os_record = Value::Record {
cols: vec![
"name".into(),
"arch".into(),
"family".into(),
"kernel_version".into(),
],
vals: vec![
Value::string(std::env::consts::OS, span),
Value::string(std::env::consts::ARCH, span),
Value::string(std::env::consts::FAMILY, span),
Value::string(ver, span),
],
span,
};
output_cols.push("os-info".into());
output_vals.push(os_record);
Ok(Value::Record {
cols: output_cols,
vals: output_vals,
span,
})
}
ENV_VARIABLE_ID => {
let env_vars = stack.get_env_vars(engine_state);
let env_columns = env_vars.keys();
let env_values = env_vars.values();
let mut pairs = env_columns
.map(|x| x.to_string())
.zip(env_values.cloned())
.collect::<Vec<(String, Value)>>();
pairs.sort_by(|a, b| a.0.cmp(&b.0));
let (env_columns, env_values) = pairs.into_iter().unzip();
Ok(Value::Record {
cols: env_columns,
vals: env_values,
span,
})
}
var_id => stack.get_var(var_id, span),
}
}
fn compute(size: i64, unit: Unit, span: Span) -> Value {
match unit {
Unit::Byte => Value::Filesize { val: size, span },
Unit::Kilobyte => Value::Filesize {
val: size * 1000,
span,
},
Unit::Megabyte => Value::Filesize {
val: size * 1000 * 1000,
span,
},
Unit::Gigabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000,
span,
},
Unit::Terabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000,
span,
},
Unit::Petabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000 * 1000,
span,
},
Unit::Exabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
span,
},
Unit::Zettabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
span,
},
Unit::Kibibyte => Value::Filesize {
val: size * 1024,
span,
},
Unit::Mebibyte => Value::Filesize {
val: size * 1024 * 1024,
span,
},
Unit::Gibibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024,
span,
},
Unit::Tebibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024,
span,
},
Unit::Pebibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024 * 1024,
span,
},
Unit::Exbibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
span,
},
Unit::Zebibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
span,
},
Unit::Nanosecond => Value::Duration { val: size, span },
Unit::Microsecond => Value::Duration {
val: size * 1000,
span,
},
Unit::Millisecond => Value::Duration {
val: size * 1000 * 1000,
span,
},
Unit::Second => Value::Duration {
val: size * 1000 * 1000 * 1000,
span,
},
Unit::Minute => Value::Duration {
val: size * 1000 * 1000 * 1000 * 60,
span,
},
Unit::Hour => Value::Duration {
val: size * 1000 * 1000 * 1000 * 60 * 60,
span,
},
Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"day duration too large".into(),
"day duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
},
Unit::Week => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 7) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"week duration too large".into(),
"week duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
},
Unit::Month => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 30) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"month duration too large".into(),
"month duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
},
Unit::Year => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"year duration too large".into(),
"year duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
},
Unit::Decade => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365 * 10) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"decade duration too large".into(),
"decade duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
},
}
}