mirror of
https://github.com/nushell/nushell.git
synced 2025-06-20 09:58:15 +02:00
add attr category
@category
to custom command attributes (#15137)
# Description This PR adds the `@category` attribute to nushell for use with custom commands. ### Example Code ```nushell # Some example with category @category "math" @search-terms "addition" @example "add two numbers together" { blah 5 6 } --result 11 def blah [ a: int # First number to add b: int # Second number to add ] { $a + $b } ``` #### Source & Help ```nushell ❯ source blah.nu ❯ help blah Some example with category Search terms: addition Usage: > blah <a> <b> Flags: -h, --help: Display the help message for this command Parameters: a <int>: First number to add b <int>: Second number to add Input/output types: ╭─#─┬─input─┬─output─╮ │ 0 │ any │ any │ ╰───┴───────┴────────╯ Examples: add two numbers together > blah 5 6 11 ``` #### Show the category ```nushell ❯ help commands | where name == blah ╭─#─┬─name─┬─category─┬─command_type─┬────────description─────────┬─────params─────┬──input_output──┬─search_terms─┬─is_const─╮ │ 0 │ blah │ math │ custom │ Some example with category │ [table 3 rows] │ [list 0 items] │ addition │ false │ ╰───┴──────┴──────────┴──────────────┴────────────────────────────┴────────────────┴────────────────┴──────────────┴──────────╯ ``` # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. --> /cc @Bahex
This commit is contained in:
parent
5d1e2b1df1
commit
7636963732
@ -1317,7 +1317,7 @@ fn attribute_completions() {
|
|||||||
let suggestions = completer.complete("@", 1);
|
let suggestions = completer.complete("@", 1);
|
||||||
|
|
||||||
// Only checking for the builtins and not the std attributes
|
// Only checking for the builtins and not the std attributes
|
||||||
let expected: Vec<String> = vec!["example".into(), "search-terms".into()];
|
let expected: Vec<String> = vec!["category".into(), "example".into(), "search-terms".into()];
|
||||||
|
|
||||||
// Match results
|
// Match results
|
||||||
match_suggestions(&expected, &suggestions);
|
match_suggestions(&expected, &suggestions);
|
||||||
|
61
crates/nu-cmd-lang/src/core_commands/attr/category.rs
Normal file
61
crates/nu-cmd-lang/src/core_commands/attr/category.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AttrCategory;
|
||||||
|
|
||||||
|
impl Command for AttrCategory {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"attr category"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("attr category")
|
||||||
|
.input_output_type(Type::Nothing, Type::list(Type::String))
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.required(
|
||||||
|
"category",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Category of the custom command.",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Attribute for adding a category to custom commands."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let arg: String = call.req(engine_state, stack, 0)?;
|
||||||
|
Ok(Value::string(arg, call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let arg: String = call.req_const(working_set, 0)?;
|
||||||
|
Ok(Value::string(arg, call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Add a category to a custom command",
|
||||||
|
example: r###"# Double numbers
|
||||||
|
@category math
|
||||||
|
def double []: [number -> number] { $in * 2 }"###,
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
mod category;
|
||||||
mod example;
|
mod example;
|
||||||
mod search_terms;
|
mod search_terms;
|
||||||
|
|
||||||
|
pub use category::AttrCategory;
|
||||||
pub use example::AttrExample;
|
pub use example::AttrExample;
|
||||||
pub use search_terms::AttrSearchTerms;
|
pub use search_terms::AttrSearchTerms;
|
||||||
|
@ -16,6 +16,7 @@ pub fn create_default_context() -> EngineState {
|
|||||||
// Core
|
// Core
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Alias,
|
Alias,
|
||||||
|
AttrCategory,
|
||||||
AttrExample,
|
AttrExample,
|
||||||
AttrSearchTerms,
|
AttrSearchTerms,
|
||||||
Break,
|
Break,
|
||||||
|
@ -1027,7 +1027,7 @@ mod tests {
|
|||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
assert!(hover_text.contains("SLEEP"));
|
assert!(hover_text.contains("SLEEP"));
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
assert!(hover_text.starts_with("NAME\r\n Start-Sleep"));
|
assert!(hover_text.contains("Start-Sleep"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -12,6 +12,7 @@ use nu_protocol::{
|
|||||||
Argument, AttributeBlock, Block, Call, Expr, Expression, ImportPattern, ImportPatternHead,
|
Argument, AttributeBlock, Block, Call, Expr, Expression, ImportPattern, ImportPatternHead,
|
||||||
ImportPatternMember, Pipeline, PipelineElement,
|
ImportPatternMember, Pipeline, PipelineElement,
|
||||||
},
|
},
|
||||||
|
category_from_string,
|
||||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||||
eval_const::eval_constant,
|
eval_const::eval_constant,
|
||||||
parser_path::ParserPath,
|
parser_path::ParserPath,
|
||||||
@ -520,7 +521,7 @@ fn parse_def_inner(
|
|||||||
|
|
||||||
let (desc, extra_desc) = working_set.build_desc(&lite_command.comments);
|
let (desc, extra_desc) = working_set.build_desc(&lite_command.comments);
|
||||||
|
|
||||||
let (attribute_vals, examples, search_terms) =
|
let (attribute_vals, examples, search_terms, category) =
|
||||||
handle_special_attributes(attributes, working_set);
|
handle_special_attributes(attributes, working_set);
|
||||||
|
|
||||||
// Checking that the function is used with the correct name
|
// Checking that the function is used with the correct name
|
||||||
@ -733,6 +734,7 @@ fn parse_def_inner(
|
|||||||
signature.extra_description = extra_desc;
|
signature.extra_description = extra_desc;
|
||||||
signature.allows_unknown_args = has_wrapped;
|
signature.allows_unknown_args = has_wrapped;
|
||||||
signature.search_terms = search_terms;
|
signature.search_terms = search_terms;
|
||||||
|
signature.category = category_from_string(&category);
|
||||||
|
|
||||||
*declaration = signature
|
*declaration = signature
|
||||||
.clone()
|
.clone()
|
||||||
@ -786,7 +788,7 @@ fn parse_extern_inner(
|
|||||||
|
|
||||||
let (description, extra_description) = working_set.build_desc(&lite_command.comments);
|
let (description, extra_description) = working_set.build_desc(&lite_command.comments);
|
||||||
|
|
||||||
let (attribute_vals, examples, search_terms) =
|
let (attribute_vals, examples, search_terms, category) =
|
||||||
handle_special_attributes(attributes, working_set);
|
handle_special_attributes(attributes, working_set);
|
||||||
|
|
||||||
// Checking that the function is used with the correct name
|
// Checking that the function is used with the correct name
|
||||||
@ -891,6 +893,7 @@ fn parse_extern_inner(
|
|||||||
signature.extra_description = extra_description;
|
signature.extra_description = extra_description;
|
||||||
signature.search_terms = search_terms;
|
signature.search_terms = search_terms;
|
||||||
signature.allows_unknown_args = true;
|
signature.allows_unknown_args = true;
|
||||||
|
signature.category = category_from_string(&category);
|
||||||
|
|
||||||
if let Some(block_id) = body.and_then(|x| x.as_block()) {
|
if let Some(block_id) = body.and_then(|x| x.as_block()) {
|
||||||
if signature.rest_positional.is_none() {
|
if signature.rest_positional.is_none() {
|
||||||
@ -947,13 +950,20 @@ fn parse_extern_inner(
|
|||||||
Expression::new(working_set, Expr::Call(call), call_span, Type::Any)
|
Expression::new(working_set, Expr::Call(call), call_span, Type::Any)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn handle_special_attributes(
|
fn handle_special_attributes(
|
||||||
attributes: Vec<(String, Value)>,
|
attributes: Vec<(String, Value)>,
|
||||||
working_set: &mut StateWorkingSet<'_>,
|
working_set: &mut StateWorkingSet<'_>,
|
||||||
) -> (Vec<(String, Value)>, Vec<CustomExample>, Vec<String>) {
|
) -> (
|
||||||
|
Vec<(String, Value)>,
|
||||||
|
Vec<CustomExample>,
|
||||||
|
Vec<String>,
|
||||||
|
String,
|
||||||
|
) {
|
||||||
let mut attribute_vals = vec![];
|
let mut attribute_vals = vec![];
|
||||||
let mut examples = vec![];
|
let mut examples = vec![];
|
||||||
let mut search_terms = vec![];
|
let mut search_terms = vec![];
|
||||||
|
let mut category = String::new();
|
||||||
|
|
||||||
for (name, value) in attributes {
|
for (name, value) in attributes {
|
||||||
let val_span = value.span();
|
let val_span = value.span();
|
||||||
@ -986,12 +996,27 @@ fn handle_special_attributes(
|
|||||||
working_set.error(e.wrap(working_set, val_span));
|
working_set.error(e.wrap(working_set, val_span));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"category" => match <String>::from_value(value) {
|
||||||
|
Ok(term) => {
|
||||||
|
category.push_str(&term);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
let e = ShellError::GenericError {
|
||||||
|
error: "nu::shell::invalid_category".into(),
|
||||||
|
msg: "Value couldn't be converted to category".into(),
|
||||||
|
span: Some(val_span),
|
||||||
|
help: Some("Is `attr category` shadowed?".into()),
|
||||||
|
inner: vec![],
|
||||||
|
};
|
||||||
|
working_set.error(e.wrap(working_set, val_span));
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
attribute_vals.push((name, value));
|
attribute_vals.push((name, value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(attribute_vals, examples, search_terms)
|
(attribute_vals, examples, search_terms, category)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_alias_name<'a>(working_set: &mut StateWorkingSet, spans: &'a [Span]) -> Option<&'a Span> {
|
fn check_alias_name<'a>(working_set: &mut StateWorkingSet, spans: &'a [Span]) -> Option<&'a Span> {
|
||||||
|
@ -113,6 +113,43 @@ impl std::fmt::Display for Category {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn category_from_string(category: &str) -> Category {
|
||||||
|
match category {
|
||||||
|
"bits" => Category::Bits,
|
||||||
|
"bytes" => Category::Bytes,
|
||||||
|
"chart" => Category::Chart,
|
||||||
|
"conversions" => Category::Conversions,
|
||||||
|
// Let's protect our own "core" commands by preventing scripts from having this category.
|
||||||
|
"core" => Category::Custom("custom_core".to_string()),
|
||||||
|
"database" => Category::Database,
|
||||||
|
"date" => Category::Date,
|
||||||
|
"debug" => Category::Debug,
|
||||||
|
"default" => Category::Default,
|
||||||
|
"deprecated" => Category::Deprecated,
|
||||||
|
"removed" => Category::Removed,
|
||||||
|
"env" => Category::Env,
|
||||||
|
"experimental" => Category::Experimental,
|
||||||
|
"filesystem" => Category::FileSystem,
|
||||||
|
"filter" => Category::Filters,
|
||||||
|
"formats" => Category::Formats,
|
||||||
|
"generators" => Category::Generators,
|
||||||
|
"hash" => Category::Hash,
|
||||||
|
"history" => Category::History,
|
||||||
|
"math" => Category::Math,
|
||||||
|
"misc" => Category::Misc,
|
||||||
|
"network" => Category::Network,
|
||||||
|
"path" => Category::Path,
|
||||||
|
"platform" => Category::Platform,
|
||||||
|
"plugin" => Category::Plugin,
|
||||||
|
"random" => Category::Random,
|
||||||
|
"shells" => Category::Shells,
|
||||||
|
"strings" => Category::Strings,
|
||||||
|
"system" => Category::System,
|
||||||
|
"viewers" => Category::Viewers,
|
||||||
|
_ => Category::Custom(category.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Signature information of a [`Command`]
|
/// Signature information of a [`Command`]
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Signature {
|
pub struct Signature {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user