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:
Ian Manske
2024-05-18 23:37:31 +00:00
committed by GitHub
parent 580c60bb82
commit cc9f41e553
68 changed files with 224 additions and 217 deletions

View File

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

View File

@ -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>) {

View File

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

View File

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

View File

@ -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)
}
}