mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 00:13:21 +01:00
Use CommandType
in more places (#12832)
# Description Kind of a vague title, but this PR does two main things: 1. Rather than overriding functions like `Command::is_parser_keyword`, this PR instead changes commands to override `Command::command_type`. The `CommandType` returned by `Command::command_type` is then used to automatically determine whether `Command::is_parser_keyword` and the other `is_{type}` functions should return true. These changes allow us to remove the `CommandType::Other` case and should also guarantee than only one of the `is_{type}` functions on `Command` will return true. 2. Uses the new, reworked `Command::command_type` function in the `scope commands` and `which` commands. # User-Facing Changes - Breaking change for `scope commands`: multiple columns (`is_builtin`, `is_keyword`, `is_plugin`, etc.) have been merged into the `type` column. - Breaking change: the `which` command can now report `plugin` or `keyword` instead of `built-in` in the `type` column. It may also now report `external` instead of `custom` in the `type` column for known `extern`s.
This commit is contained in:
parent
580c60bb82
commit
cc9f41e553
@ -42,7 +42,7 @@ For more information on input and keybindings, check:
|
||||
&Keybindings.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -18,7 +18,7 @@ impl NuHelpCompleter {
|
||||
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
||||
let mut commands = full_commands
|
||||
.iter()
|
||||
.filter(|(sig, _, _, _, _)| {
|
||||
.filter(|(sig, _, _)| {
|
||||
sig.name.to_folded_case().contains(&folded_line)
|
||||
|| sig.usage.to_folded_case().contains(&folded_line)
|
||||
|| sig
|
||||
@ -29,7 +29,7 @@ impl NuHelpCompleter {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
commands.sort_by(|(a, _, _, _, _), (b, _, _, _, _)| {
|
||||
commands.sort_by(|(a, _, _), (b, _, _)| {
|
||||
let a_distance = levenshtein_distance(line, &a.name);
|
||||
let b_distance = levenshtein_distance(line, &b.name);
|
||||
a_distance.cmp(&b_distance)
|
||||
@ -37,7 +37,7 @@ impl NuHelpCompleter {
|
||||
|
||||
commands
|
||||
.into_iter()
|
||||
.map(|(sig, examples, _, _, _)| {
|
||||
.map(|(sig, examples, _)| {
|
||||
let mut long_desc = String::new();
|
||||
|
||||
let usage = &sig.usage;
|
||||
|
@ -35,7 +35,7 @@ impl Command for Dfr {
|
||||
&Dfr.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for Bits {
|
||||
&Bits.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -39,7 +39,7 @@ impl Command for Roll {
|
||||
&Roll.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for Str {
|
||||
&Str.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Alias;
|
||||
@ -29,8 +30,8 @@ impl Command for Alias {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Const;
|
||||
@ -30,8 +31,8 @@ impl Command for Const {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Def;
|
||||
@ -28,8 +29,8 @@ impl Command for Def {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportCommand;
|
||||
@ -23,8 +24,8 @@ impl Command for ExportCommand {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -40,7 +41,7 @@ impl Command for ExportCommand {
|
||||
&ExportCommand.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportAlias;
|
||||
@ -29,8 +30,8 @@ impl Command for ExportAlias {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportConst;
|
||||
@ -30,8 +31,8 @@ impl Command for ExportConst {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportDef;
|
||||
@ -28,8 +29,8 @@ impl Command for ExportDef {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportExtern;
|
||||
@ -25,8 +26,8 @@ impl Command for ExportExtern {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportModule;
|
||||
@ -30,8 +31,8 @@ impl Command for ExportModule {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportUse;
|
||||
@ -29,8 +30,8 @@ impl Command for ExportUse {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Extern;
|
||||
@ -25,8 +26,8 @@ impl Command for Extern {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct For;
|
||||
@ -41,8 +42,8 @@ impl Command for For {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Hide;
|
||||
@ -31,8 +32,8 @@ This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::{command_prelude::*, get_eval_block};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Let;
|
||||
@ -30,8 +31,8 @@ impl Command for Let {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Module;
|
||||
@ -30,8 +31,8 @@ impl Command for Module {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::{command_prelude::*, get_eval_block};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Mut;
|
||||
@ -30,8 +31,8 @@ impl Command for Mut {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Overlay;
|
||||
@ -25,8 +26,8 @@ impl Command for Overlay {
|
||||
You must use one of the following subcommands. Using this command as-is will only produce this help message."#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -42,7 +43,7 @@ impl Command for Overlay {
|
||||
&[],
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayHide;
|
||||
@ -35,8 +36,8 @@ impl Command for OverlayHide {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayNew;
|
||||
@ -33,8 +34,8 @@ This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -2,7 +2,7 @@ use nu_engine::{
|
||||
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block, redirect_env,
|
||||
};
|
||||
use nu_parser::trim_quotes_str;
|
||||
use nu_protocol::ast::Expr;
|
||||
use nu_protocol::{ast::Expr, engine::CommandType};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
@ -50,8 +50,8 @@ impl Command for OverlayUse {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Return;
|
||||
@ -28,8 +29,8 @@ impl Command for Return {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Scope;
|
||||
@ -19,8 +20,8 @@ impl Command for Scope {
|
||||
"Commands for getting info about what is in scope."
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -36,7 +37,7 @@ impl Command for Scope {
|
||||
&[],
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -1,7 +1,10 @@
|
||||
use nu_engine::{
|
||||
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block, redirect_env,
|
||||
};
|
||||
use nu_protocol::ast::{Expr, Expression};
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
engine::CommandType,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Use;
|
||||
@ -40,8 +43,8 @@ This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -43,7 +43,7 @@ impl Command for PluginCommand {
|
||||
&PluginCommand.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PluginUse;
|
||||
@ -52,8 +53,8 @@ it was already previously registered with `plugin add`.
|
||||
vec!["add", "register", "scope"]
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Register;
|
||||
@ -48,8 +49,8 @@ This command is a parser keyword. For details, check:
|
||||
vec!["add"]
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -35,7 +35,7 @@ impl Command for Bytes {
|
||||
&Bytes.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for Into {
|
||||
&[],
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for View {
|
||||
&View.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -55,7 +55,7 @@ impl Command for ViewSource {
|
||||
}
|
||||
}
|
||||
// gets vector of positionals.
|
||||
else if let Some(block_id) = decl.get_block_id() {
|
||||
else if let Some(block_id) = decl.block_id() {
|
||||
let block = engine_state.get_block(block_id);
|
||||
if let Some(block_span) = block.span {
|
||||
let contents = engine_state.get_span_contents(block_span);
|
||||
|
2
crates/nu-command/src/env/config/config_.rs
vendored
2
crates/nu-command/src/env/config/config_.rs
vendored
@ -35,7 +35,7 @@ impl Command for ConfigMeta {
|
||||
&ConfigMeta.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -172,7 +172,7 @@ impl Command for Open {
|
||||
match converter {
|
||||
Some((converter_id, ext)) => {
|
||||
let decl = engine_state.get_decl(converter_id);
|
||||
let command_output = if let Some(block_id) = decl.get_block_id() {
|
||||
let command_output = if let Some(block_id) = decl.block_id() {
|
||||
let block = engine_state.get_block(block_id);
|
||||
eval_block(engine_state, stack, block, stream)
|
||||
} else {
|
||||
|
@ -393,7 +393,7 @@ fn convert_to_extension(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if let Some(decl_id) = engine_state.find_decl(format!("to {extension}").as_bytes(), &[]) {
|
||||
let decl = engine_state.get_decl(decl_id);
|
||||
if let Some(block_id) = decl.get_block_id() {
|
||||
if let Some(block_id) = decl.block_id() {
|
||||
let block = engine_state.get_block(block_id);
|
||||
let eval_block = get_eval_block(engine_state);
|
||||
eval_block(engine_state, stack, block, input)
|
||||
|
@ -35,7 +35,7 @@ impl Command for From {
|
||||
&From.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for To {
|
||||
&To.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for Hash {
|
||||
&Self.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpCommands;
|
||||
@ -90,9 +91,15 @@ pub fn help_commands(
|
||||
let output = engine_state
|
||||
.get_signatures_with_examples(false)
|
||||
.iter()
|
||||
.filter(|(signature, _, _, _, _)| signature.name == name)
|
||||
.map(|(signature, examples, _, _, is_parser_keyword)| {
|
||||
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
|
||||
.filter(|(signature, _, _)| signature.name == name)
|
||||
.map(|(signature, examples, cmd_type)| {
|
||||
get_full_help(
|
||||
signature,
|
||||
examples,
|
||||
engine_state,
|
||||
stack,
|
||||
cmd_type == &CommandType::Keyword,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{command_prelude::*, get_full_help, scope::ScopeData};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpExterns;
|
||||
@ -110,9 +111,15 @@ pub fn help_externs(
|
||||
let output = engine_state
|
||||
.get_signatures_with_examples(false)
|
||||
.iter()
|
||||
.filter(|(signature, _, _, _, _)| signature.name == name)
|
||||
.map(|(signature, examples, _, _, is_parser_keyword)| {
|
||||
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
|
||||
.filter(|(signature, _, _)| signature.name == name)
|
||||
.map(|(signature, examples, cmd_type)| {
|
||||
get_full_help(
|
||||
signature,
|
||||
examples,
|
||||
engine_state,
|
||||
stack,
|
||||
cmd_type == &CommandType::Keyword,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
|
@ -35,7 +35,7 @@ impl Command for MathCommand {
|
||||
&MathCommand.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
/// Source a file for environment variables.
|
||||
#[derive(Clone)]
|
||||
@ -29,8 +30,8 @@ impl Command for Source {
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -41,7 +41,7 @@ impl Command for Http {
|
||||
&Http.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -39,7 +39,7 @@ impl Command for Url {
|
||||
&Url.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -48,7 +48,7 @@ the path literal."#
|
||||
&PathCommand.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -39,7 +39,7 @@ impl Command for RandomCommand {
|
||||
&RandomCommand.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for Stor {
|
||||
&Stor.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for Format {
|
||||
&Format.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for SplitCommand {
|
||||
&SplitCommand.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ impl Command for Str {
|
||||
&Str.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
self.is_keyword(),
|
||||
),
|
||||
call.head,
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
use log::trace;
|
||||
use nu_engine::{command_prelude::*, env};
|
||||
use nu_protocol::engine::CommandType;
|
||||
use std::{ffi::OsStr, path::Path};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -51,14 +51,14 @@ impl Command for Which {
|
||||
fn entry(
|
||||
arg: impl Into<String>,
|
||||
path: impl Into<String>,
|
||||
cmd_type: impl Into<String>,
|
||||
cmd_type: CommandType,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
Value::record(
|
||||
record! {
|
||||
"command" => Value::string(arg.into(), span),
|
||||
"path" => Value::string(path.into(), span),
|
||||
"type" => Value::string(cmd_type.into(), span),
|
||||
"command" => Value::string(arg, span),
|
||||
"path" => Value::string(path, span),
|
||||
"type" => Value::string(cmd_type.to_string(), span),
|
||||
},
|
||||
span,
|
||||
)
|
||||
@ -66,17 +66,8 @@ fn entry(
|
||||
|
||||
fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> {
|
||||
if let Some(decl_id) = engine_state.find_decl(name.as_bytes(), &[]) {
|
||||
let cmd_type = if engine_state.get_decl(decl_id).is_custom_command() {
|
||||
"custom"
|
||||
} else if engine_state.get_decl(decl_id).is_alias() {
|
||||
"alias"
|
||||
} else {
|
||||
"built-in"
|
||||
};
|
||||
|
||||
trace!("Found command: {}", name);
|
||||
|
||||
Some(entry(name, "", cmd_type, span))
|
||||
let decl = engine_state.get_decl(decl_id);
|
||||
Some(entry(name, "", decl.command_type(), span))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -109,7 +100,7 @@ fn get_first_entry_in_path(
|
||||
paths: impl AsRef<OsStr>,
|
||||
) -> Option<Value> {
|
||||
which::which_in(item, Some(paths), cwd)
|
||||
.map(|path| entry(item, path.to_string_lossy().to_string(), "external", span))
|
||||
.map(|path| entry(item, path.to_string_lossy(), CommandType::External, span))
|
||||
.ok()
|
||||
}
|
||||
|
||||
@ -132,7 +123,7 @@ fn get_all_entries_in_path(
|
||||
) -> Vec<Value> {
|
||||
which::which_in_all(&item, Some(paths), cwd)
|
||||
.map(|iter| {
|
||||
iter.map(|path| entry(item, path.to_string_lossy().to_string(), "external", span))
|
||||
iter.map(|path| entry(item, path.to_string_lossy(), CommandType::External, span))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
|
@ -36,10 +36,10 @@ pub fn eval_call<D: DebugContext>(
|
||||
&decl.examples(),
|
||||
engine_state,
|
||||
caller_stack,
|
||||
decl.is_parser_keyword(),
|
||||
decl.is_keyword(),
|
||||
);
|
||||
Ok(Value::string(full_help, call.head).into_pipeline_data())
|
||||
} else if let Some(block_id) = decl.get_block_id() {
|
||||
} else if let Some(block_id) = decl.block_id() {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
|
||||
|
@ -111,13 +111,8 @@ impl<'e, 's> ScopeData<'e, 's> {
|
||||
"signatures" => self.collect_signatures(&signature, span),
|
||||
"usage" => Value::string(decl.usage(), span),
|
||||
"examples" => Value::list(examples, span),
|
||||
// we can only be a is_builtin or is_custom, not both
|
||||
"is_builtin" => Value::bool(!decl.is_custom_command(), span),
|
||||
"type" => Value::string(decl.command_type().to_string(), span),
|
||||
"is_sub" => Value::bool(decl.is_sub(), span),
|
||||
"is_plugin" => Value::bool(decl.is_plugin(), span),
|
||||
"is_custom" => Value::bool(decl.is_custom_command(), span),
|
||||
"is_keyword" => Value::bool(decl.is_parser_keyword(), span),
|
||||
"is_extern" => Value::bool(decl.is_known_external(), span),
|
||||
"creates_scope" => Value::bool(signature.creates_scope, span),
|
||||
"extra_usage" => Value::string(decl.extra_usage(), span),
|
||||
"search_terms" => Value::string(decl.search_terms().join(", "), span),
|
||||
|
@ -279,7 +279,7 @@ impl LanguageServer {
|
||||
|
||||
match id {
|
||||
Id::Declaration(decl_id) => {
|
||||
if let Some(block_id) = working_set.get_decl(decl_id).get_block_id() {
|
||||
if let Some(block_id) = working_set.get_decl(decl_id).block_id() {
|
||||
let block = working_set.get_block(block_id);
|
||||
if let Some(span) = &block.span {
|
||||
for cached_file in working_set.files() {
|
||||
|
@ -1,5 +1,8 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::ast::{Argument, Expr, Expression};
|
||||
use nu_protocol::{
|
||||
ast::{Argument, Expr, Expression},
|
||||
engine::CommandType,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct KnownExternal {
|
||||
@ -22,12 +25,8 @@ impl Command for KnownExternal {
|
||||
&self.usage
|
||||
}
|
||||
|
||||
fn is_known_external(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_builtin(&self) -> bool {
|
||||
false
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::External
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1020,7 +1020,7 @@ pub fn parse_alias(
|
||||
} => {
|
||||
let cmd = working_set.get_decl(rhs_call.decl_id);
|
||||
|
||||
if cmd.is_parser_keyword()
|
||||
if cmd.is_keyword()
|
||||
&& !ALIASABLE_PARSER_KEYWORDS.contains(&cmd.name().as_bytes())
|
||||
{
|
||||
working_set.error(ParseError::CantAliasKeyword(
|
||||
|
@ -5966,7 +5966,7 @@ pub fn discover_captures_in_expr(
|
||||
Expr::Bool(_) => {}
|
||||
Expr::Call(call) => {
|
||||
let decl = working_set.get_decl(call.decl_id);
|
||||
if let Some(block_id) = decl.get_block_id() {
|
||||
if let Some(block_id) = decl.block_id() {
|
||||
match seen_blocks.get(&block_id) {
|
||||
Some(capture_list) => {
|
||||
// Push captures onto the outer closure that aren't created by that outer closure
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_engine::{command_prelude::*, get_eval_expression};
|
||||
use nu_plugin_protocol::{CallInfo, EvaluatedCall};
|
||||
use nu_protocol::{PluginIdentity, PluginSignature};
|
||||
use nu_protocol::{engine::CommandType, PluginIdentity, PluginSignature};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{GetPlugin, PluginExecutionCommandContext, PluginSource};
|
||||
@ -116,8 +116,8 @@ impl Command for PluginDeclaration {
|
||||
)
|
||||
}
|
||||
|
||||
fn is_plugin(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Plugin
|
||||
}
|
||||
|
||||
fn plugin_identity(&self) -> Option<&PluginIdentity> {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
ast::{Call, Expression},
|
||||
engine::{Command, EngineState, Stack},
|
||||
engine::{Command, CommandType, EngineState, Stack},
|
||||
PipelineData, ShellError, Signature,
|
||||
};
|
||||
|
||||
@ -48,8 +48,8 @@ impl Command for Alias {
|
||||
})
|
||||
}
|
||||
|
||||
fn is_alias(&self) -> bool {
|
||||
true
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Alias
|
||||
}
|
||||
|
||||
fn as_alias(&self) -> Option<&Alias> {
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::{ast::Call, Alias, BlockId, Example, OutDest, PipelineData, ShellError, Signature};
|
||||
|
||||
use super::{EngineState, Stack, StateWorkingSet};
|
||||
use crate::{ast::Call, Alias, BlockId, Example, OutDest, PipelineData, ShellError, Signature};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CommandType {
|
||||
Builtin,
|
||||
Custom,
|
||||
@ -10,7 +10,20 @@ pub enum CommandType {
|
||||
External,
|
||||
Alias,
|
||||
Plugin,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl Display for CommandType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = match self {
|
||||
CommandType::Builtin => "built-in",
|
||||
CommandType::Custom => "custom",
|
||||
CommandType::Keyword => "keyword",
|
||||
CommandType::External => "external",
|
||||
CommandType::Alias => "alias",
|
||||
CommandType::Plugin => "plugin",
|
||||
};
|
||||
write!(f, "{str}")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Command: Send + Sync + CommandClone {
|
||||
@ -49,49 +62,29 @@ pub trait Command: Send + Sync + CommandClone {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
// This is a built-in command
|
||||
fn is_builtin(&self) -> bool {
|
||||
true
|
||||
// Related terms to help with command search
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
// This is a signature for a known external command
|
||||
fn is_known_external(&self) -> bool {
|
||||
// Whether can run in const evaluation in the parser
|
||||
fn is_const(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// This is an alias of another command
|
||||
fn is_alias(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// Return reference to the command as Alias
|
||||
fn as_alias(&self) -> Option<&Alias> {
|
||||
None
|
||||
}
|
||||
|
||||
// This is an enhanced method to determine if a command is custom command or not
|
||||
// since extern "foo" [] and def "foo" [] behaves differently
|
||||
fn is_custom_command(&self) -> bool {
|
||||
if self.get_block_id().is_some() {
|
||||
true
|
||||
} else {
|
||||
self.is_known_external()
|
||||
}
|
||||
}
|
||||
|
||||
// Is a sub command
|
||||
fn is_sub(&self) -> bool {
|
||||
self.name().contains(' ')
|
||||
}
|
||||
|
||||
// Is a parser keyword (source, def, etc.)
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
false
|
||||
// If command is a block i.e. def blah [] { }, get the block id
|
||||
fn block_id(&self) -> Option<BlockId> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Is a plugin command
|
||||
fn is_plugin(&self) -> bool {
|
||||
false
|
||||
// Return reference to the command as Alias
|
||||
fn as_alias(&self) -> Option<&Alias> {
|
||||
None
|
||||
}
|
||||
|
||||
/// The identity of the plugin, if this is a plugin command
|
||||
@ -100,38 +93,32 @@ pub trait Command: Send + Sync + CommandClone {
|
||||
None
|
||||
}
|
||||
|
||||
// Whether can run in const evaluation in the parser
|
||||
fn is_const(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// If command is a block i.e. def blah [] { }, get the block id
|
||||
fn get_block_id(&self) -> Option<BlockId> {
|
||||
None
|
||||
}
|
||||
|
||||
// Related terms to help with command search
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
match (
|
||||
self.is_builtin(),
|
||||
self.is_custom_command(),
|
||||
self.is_parser_keyword(),
|
||||
self.is_known_external(),
|
||||
self.is_alias(),
|
||||
self.is_plugin(),
|
||||
) {
|
||||
(true, false, false, false, false, false) => CommandType::Builtin,
|
||||
(true, true, false, false, false, false) => CommandType::Custom,
|
||||
(true, false, true, false, false, false) => CommandType::Keyword,
|
||||
(false, true, false, true, false, false) => CommandType::External,
|
||||
(_, _, _, _, true, _) => CommandType::Alias,
|
||||
(true, false, false, false, false, true) => CommandType::Plugin,
|
||||
_ => CommandType::Other,
|
||||
CommandType::Builtin
|
||||
}
|
||||
|
||||
fn is_builtin(&self) -> bool {
|
||||
self.command_type() == CommandType::Builtin
|
||||
}
|
||||
|
||||
fn is_custom(&self) -> bool {
|
||||
self.command_type() == CommandType::Custom
|
||||
}
|
||||
|
||||
fn is_keyword(&self) -> bool {
|
||||
self.command_type() == CommandType::Keyword
|
||||
}
|
||||
|
||||
fn is_known_external(&self) -> bool {
|
||||
self.command_type() == CommandType::External
|
||||
}
|
||||
|
||||
fn is_alias(&self) -> bool {
|
||||
self.command_type() == CommandType::Alias
|
||||
}
|
||||
|
||||
fn is_plugin(&self) -> bool {
|
||||
self.command_type() == CommandType::Plugin
|
||||
}
|
||||
|
||||
fn pipe_redirection(&self) -> (Option<OutDest>, Option<OutDest>) {
|
||||
|
@ -794,7 +794,7 @@ impl EngineState {
|
||||
}
|
||||
|
||||
pub fn get_signature(&self, decl: &dyn Command) -> Signature {
|
||||
if let Some(block_id) = decl.get_block_id() {
|
||||
if let Some(block_id) = decl.block_id() {
|
||||
*self.blocks[block_id].signature.clone()
|
||||
} else {
|
||||
decl.signature()
|
||||
@ -814,26 +814,16 @@ impl EngineState {
|
||||
|
||||
/// Get signatures of all commands within scope.
|
||||
///
|
||||
/// In addition to signatures, it returns whether each command is:
|
||||
/// a) a plugin
|
||||
/// b) custom
|
||||
/// In addition to signatures, it returns each command's examples and type.
|
||||
pub fn get_signatures_with_examples(
|
||||
&self,
|
||||
include_hidden: bool,
|
||||
) -> Vec<(Signature, Vec<Example>, bool, bool, bool)> {
|
||||
) -> Vec<(Signature, Vec<Example>, CommandType)> {
|
||||
self.get_decls_sorted(include_hidden)
|
||||
.map(|(_, id)| {
|
||||
let decl = self.get_decl(id);
|
||||
|
||||
let signature = self.get_signature(decl).update_from_command(decl);
|
||||
|
||||
(
|
||||
signature,
|
||||
decl.examples(),
|
||||
decl.is_plugin(),
|
||||
decl.get_block_id().is_some(),
|
||||
decl.is_parser_keyword(),
|
||||
)
|
||||
(signature, decl.examples(), decl.command_type())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -550,7 +550,7 @@ impl PipelineData {
|
||||
// to create the table value that will be printed in the terminal
|
||||
if let Some(decl_id) = engine_state.table_decl_id {
|
||||
let command = engine_state.get_decl(decl_id);
|
||||
if command.get_block_id().is_some() {
|
||||
if command.block_id().is_some() {
|
||||
self.write_all_and_flush(engine_state, no_newline, to_stderr)
|
||||
} else {
|
||||
let call = Call::new(Span::new(0, 0));
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
engine::{Command, CommandType, EngineState, Stack},
|
||||
BlockId, PipelineData, ShellError, SyntaxShape, Type, Value, VarId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -703,7 +703,11 @@ impl Command for BlockCommand {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_block_id(&self) -> Option<BlockId> {
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Custom
|
||||
}
|
||||
|
||||
fn block_id(&self) -> Option<BlockId> {
|
||||
Some(self.block_id)
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ pub fn goto_def(engine_state: &mut EngineState, file_path: &str, location: &Valu
|
||||
match find_id(&mut working_set, file_path, &file, location) {
|
||||
Some((Id::Declaration(decl_id), ..)) => {
|
||||
let result = working_set.get_decl(decl_id);
|
||||
if let Some(block_id) = result.get_block_id() {
|
||||
if let Some(block_id) = result.block_id() {
|
||||
let block = working_set.get_block(block_id);
|
||||
if let Some(span) = &block.span {
|
||||
for file in working_set.files() {
|
||||
|
Loading…
Reference in New Issue
Block a user