Add `command_prelude` module (#12291)
# Description
When implementing a `Command`, one must also import all the types
present in the function signatures for `Command`. This makes it so that
we often import the same set of types in each command implementation
file. E.g., something like this:
```rust
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, Type, Value,
};
```
This PR adds the `nu_engine::command_prelude` module which contains the
necessary and commonly used types to implement a `Command`:
```rust
// command_prelude.rs
pub use crate::CallExt;
pub use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned,
PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
```
This should reduce the boilerplate needed to implement a command and
also gives us a place to track the breadth of the `Command` API. I tried
to be conservative with what went into the prelude modules, since it
might be hard/annoying to remove items from the prelude in the future.
Let me know if something should be included or excluded.
2024-03-26 21:17:30 +00:00
|
|
|
|
use indexmap::{indexmap, IndexMap};
|
|
|
|
|
use nu_engine::command_prelude::*;
|
2024-06-05 19:21:52 +00:00
|
|
|
|
|
2022-12-17 18:30:04 +00:00
|
|
|
|
use once_cell::sync::Lazy;
|
2024-02-07 22:29:00 +00:00
|
|
|
|
use std::sync::{atomic::AtomicBool, Arc};
|
2021-12-15 23:08:12 +00:00
|
|
|
|
|
|
|
|
|
// Character used to separate directories in a Path Environment variable on windows is ";"
|
|
|
|
|
#[cfg(target_family = "windows")]
|
|
|
|
|
const ENV_PATH_SEPARATOR_CHAR: char = ';';
|
|
|
|
|
// Character used to separate directories in a Path Environment variable on linux/mac/unix is ":"
|
|
|
|
|
#[cfg(not(target_family = "windows"))]
|
|
|
|
|
const ENV_PATH_SEPARATOR_CHAR: char = ':';
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct Char;
|
|
|
|
|
|
2022-12-17 18:30:04 +00:00
|
|
|
|
static CHAR_MAP: Lazy<IndexMap<&'static str, String>> = Lazy::new(|| {
|
|
|
|
|
indexmap! {
|
2021-12-15 23:08:12 +00:00
|
|
|
|
// These are some regular characters that either can't be used or
|
|
|
|
|
// it's just easier to use them like this.
|
|
|
|
|
|
2024-06-26 23:49:44 +00:00
|
|
|
|
"nul" => '\x00'.to_string(), // nul character, 0x00
|
|
|
|
|
"null_byte" => '\x00'.to_string(), // nul character, 0x00
|
|
|
|
|
"zero_byte" => '\x00'.to_string(), // nul character, 0x00
|
2021-12-15 23:08:12 +00:00
|
|
|
|
// This are the "normal" characters section
|
|
|
|
|
"newline" => '\n'.to_string(),
|
|
|
|
|
"enter" => '\n'.to_string(),
|
|
|
|
|
"nl" => '\n'.to_string(),
|
2022-01-06 18:52:43 +00:00
|
|
|
|
"line_feed" => '\n'.to_string(),
|
|
|
|
|
"lf" => '\n'.to_string(),
|
|
|
|
|
"carriage_return" => '\r'.to_string(),
|
|
|
|
|
"cr" => '\r'.to_string(),
|
|
|
|
|
"crlf" => "\r\n".to_string(),
|
2021-12-15 23:08:12 +00:00
|
|
|
|
"tab" => '\t'.to_string(),
|
|
|
|
|
"sp" => ' '.to_string(),
|
|
|
|
|
"space" => ' '.to_string(),
|
|
|
|
|
"pipe" => '|'.to_string(),
|
|
|
|
|
"left_brace" => '{'.to_string(),
|
|
|
|
|
"lbrace" => '{'.to_string(),
|
|
|
|
|
"right_brace" => '}'.to_string(),
|
|
|
|
|
"rbrace" => '}'.to_string(),
|
|
|
|
|
"left_paren" => '('.to_string(),
|
2021-12-18 18:13:10 +00:00
|
|
|
|
"lp" => '('.to_string(),
|
2021-12-15 23:08:12 +00:00
|
|
|
|
"lparen" => '('.to_string(),
|
|
|
|
|
"right_paren" => ')'.to_string(),
|
|
|
|
|
"rparen" => ')'.to_string(),
|
2021-12-18 18:13:10 +00:00
|
|
|
|
"rp" => ')'.to_string(),
|
2021-12-15 23:08:12 +00:00
|
|
|
|
"left_bracket" => '['.to_string(),
|
|
|
|
|
"lbracket" => '['.to_string(),
|
|
|
|
|
"right_bracket" => ']'.to_string(),
|
|
|
|
|
"rbracket" => ']'.to_string(),
|
|
|
|
|
"single_quote" => '\''.to_string(),
|
|
|
|
|
"squote" => '\''.to_string(),
|
|
|
|
|
"sq" => '\''.to_string(),
|
|
|
|
|
"double_quote" => '\"'.to_string(),
|
|
|
|
|
"dquote" => '\"'.to_string(),
|
|
|
|
|
"dq" => '\"'.to_string(),
|
|
|
|
|
"path_sep" => std::path::MAIN_SEPARATOR.to_string(),
|
|
|
|
|
"psep" => std::path::MAIN_SEPARATOR.to_string(),
|
|
|
|
|
"separator" => std::path::MAIN_SEPARATOR.to_string(),
|
|
|
|
|
"esep" => ENV_PATH_SEPARATOR_CHAR.to_string(),
|
|
|
|
|
"env_sep" => ENV_PATH_SEPARATOR_CHAR.to_string(),
|
|
|
|
|
"tilde" => '~'.to_string(), // ~
|
|
|
|
|
"twiddle" => '~'.to_string(), // ~
|
|
|
|
|
"squiggly" => '~'.to_string(), // ~
|
|
|
|
|
"home" => '~'.to_string(), // ~
|
|
|
|
|
"hash" => '#'.to_string(), // #
|
|
|
|
|
"hashtag" => '#'.to_string(), // #
|
|
|
|
|
"pound_sign" => '#'.to_string(), // #
|
|
|
|
|
"sharp" => '#'.to_string(), // #
|
|
|
|
|
"root" => '#'.to_string(), // #
|
|
|
|
|
|
|
|
|
|
// This is the unicode section
|
|
|
|
|
// Unicode names came from https://www.compart.com/en/unicode
|
|
|
|
|
// Private Use Area (U+E000-U+F8FF)
|
|
|
|
|
// Unicode can't be mixed with Ansi or it will break width calculation
|
2022-02-18 10:52:48 +00:00
|
|
|
|
"nf_branch" => '\u{e0a0}'.to_string(), //
|
|
|
|
|
"nf_segment" => '\u{e0b0}'.to_string(), //
|
|
|
|
|
"nf_left_segment" => '\u{e0b0}'.to_string(), //
|
|
|
|
|
"nf_left_segment_thin" => '\u{e0b1}'.to_string(), //
|
|
|
|
|
"nf_right_segment" => '\u{e0b2}'.to_string(), //
|
|
|
|
|
"nf_right_segment_thin" => '\u{e0b3}'.to_string(), //
|
|
|
|
|
"nf_git" => '\u{f1d3}'.to_string(), //
|
|
|
|
|
"nf_git_branch" => "\u{e709}\u{e0a0}".to_string(), //
|
|
|
|
|
"nf_folder1" => '\u{f07c}'.to_string(), //
|
|
|
|
|
"nf_folder2" => '\u{f115}'.to_string(), //
|
|
|
|
|
"nf_house1" => '\u{f015}'.to_string(), //
|
|
|
|
|
"nf_house2" => '\u{f7db}'.to_string(), //
|
2021-12-15 23:08:12 +00:00
|
|
|
|
|
|
|
|
|
"identical_to" => '\u{2261}'.to_string(), // ≡
|
|
|
|
|
"hamburger" => '\u{2261}'.to_string(), // ≡
|
|
|
|
|
"not_identical_to" => '\u{2262}'.to_string(), // ≢
|
|
|
|
|
"branch_untracked" => '\u{2262}'.to_string(), // ≢
|
|
|
|
|
"strictly_equivalent_to" => '\u{2263}'.to_string(), // ≣
|
|
|
|
|
"branch_identical" => '\u{2263}'.to_string(), // ≣
|
|
|
|
|
|
|
|
|
|
"upwards_arrow" => '\u{2191}'.to_string(), // ↑
|
|
|
|
|
"branch_ahead" => '\u{2191}'.to_string(), // ↑
|
|
|
|
|
"downwards_arrow" => '\u{2193}'.to_string(), // ↓
|
|
|
|
|
"branch_behind" => '\u{2193}'.to_string(), // ↓
|
|
|
|
|
"up_down_arrow" => '\u{2195}'.to_string(), // ↕
|
|
|
|
|
"branch_ahead_behind" => '\u{2195}'.to_string(), // ↕
|
|
|
|
|
|
|
|
|
|
"black_right_pointing_triangle" => '\u{25b6}'.to_string(), // ▶
|
|
|
|
|
"prompt" => '\u{25b6}'.to_string(), // ▶
|
|
|
|
|
"vector_or_cross_product" => '\u{2a2f}'.to_string(), // ⨯
|
|
|
|
|
"failed" => '\u{2a2f}'.to_string(), // ⨯
|
|
|
|
|
"high_voltage_sign" => '\u{26a1}'.to_string(), // ⚡
|
|
|
|
|
"elevated" => '\u{26a1}'.to_string(), // ⚡
|
|
|
|
|
|
|
|
|
|
// This is the emoji section
|
|
|
|
|
// Weather symbols
|
2022-01-08 13:19:51 +00:00
|
|
|
|
// https://www.babelstone.co.uk/Unicode/whatisit.html
|
|
|
|
|
"sun" => "☀️".to_string(), //2600 + fe0f
|
|
|
|
|
"sunny" => "☀️".to_string(), //2600 + fe0f
|
|
|
|
|
"sunrise" => "☀️".to_string(), //2600 + fe0f
|
|
|
|
|
"moon" => "🌛".to_string(), //1f31b
|
|
|
|
|
"cloudy" => "☁️".to_string(), //2601 + fe0f
|
|
|
|
|
"cloud" => "☁️".to_string(), //2601 + fe0f
|
|
|
|
|
"clouds" => "☁️".to_string(), //2601 + fe0f
|
|
|
|
|
"rainy" => "🌦️".to_string(), //1f326 + fe0f
|
|
|
|
|
"rain" => "🌦️".to_string(), //1f326 + fe0f
|
|
|
|
|
"foggy" => "🌫️".to_string(), //1f32b + fe0f
|
|
|
|
|
"fog" => "🌫️".to_string(), //1f32b + fe0f
|
|
|
|
|
"mist" => '\u{2591}'.to_string(), //2591
|
|
|
|
|
"haze" => '\u{2591}'.to_string(), //2591
|
|
|
|
|
"snowy" => "❄️".to_string(), //2744 + fe0f
|
|
|
|
|
"snow" => "❄️".to_string(), //2744 + fe0f
|
|
|
|
|
"thunderstorm" => "🌩️".to_string(),//1f329 + fe0f
|
|
|
|
|
"thunder" => "🌩️".to_string(), //1f329 + fe0f
|
2021-12-15 23:08:12 +00:00
|
|
|
|
|
|
|
|
|
// This is the "other" section
|
|
|
|
|
"bel" => '\x07'.to_string(), // Terminal Bell
|
|
|
|
|
"backspace" => '\x08'.to_string(), // Backspace
|
2022-02-27 22:03:21 +00:00
|
|
|
|
|
|
|
|
|
// separators
|
|
|
|
|
"file_separator" => '\x1c'.to_string(),
|
|
|
|
|
"file_sep" => '\x1c'.to_string(),
|
|
|
|
|
"fs" => '\x1c'.to_string(),
|
|
|
|
|
"group_separator" => '\x1d'.to_string(),
|
|
|
|
|
"group_sep" => '\x1d'.to_string(),
|
|
|
|
|
"gs" => '\x1d'.to_string(),
|
|
|
|
|
"record_separator" => '\x1e'.to_string(),
|
|
|
|
|
"record_sep" => '\x1e'.to_string(),
|
|
|
|
|
"rs" => '\x1e'.to_string(),
|
|
|
|
|
"unit_separator" => '\x1f'.to_string(),
|
|
|
|
|
"unit_sep" => '\x1f'.to_string(),
|
|
|
|
|
"us" => '\x1f'.to_string(),
|
2022-12-17 18:30:04 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
2021-12-15 23:08:12 +00:00
|
|
|
|
|
|
|
|
|
impl Command for Char {
|
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
|
"char"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn signature(&self) -> Signature {
|
|
|
|
|
Signature::build("char")
|
2024-01-15 08:58:26 +00:00
|
|
|
|
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
2021-12-15 23:08:12 +00:00
|
|
|
|
.optional(
|
|
|
|
|
"character",
|
|
|
|
|
SyntaxShape::Any,
|
2023-12-15 06:32:37 +00:00
|
|
|
|
"The name of the character to output.",
|
2021-12-15 23:08:12 +00:00
|
|
|
|
)
|
2023-12-15 06:32:37 +00:00
|
|
|
|
.rest("rest", SyntaxShape::Any, "Multiple Unicode bytes.")
|
2021-12-15 23:08:12 +00:00
|
|
|
|
.switch("list", "List all supported character names", Some('l'))
|
|
|
|
|
.switch("unicode", "Unicode string i.e. 1f378", Some('u'))
|
2022-04-14 13:34:02 +00:00
|
|
|
|
.switch("integer", "Create a codepoint from an integer", Some('i'))
|
2023-08-02 13:42:13 +00:00
|
|
|
|
.allow_variants_without_examples(true)
|
2021-12-16 18:36:07 +00:00
|
|
|
|
.category(Category::Strings)
|
2021-12-15 23:08:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-07 22:29:00 +00:00
|
|
|
|
fn is_const(&self) -> bool {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-15 23:08:12 +00:00
|
|
|
|
fn usage(&self) -> &str {
|
|
|
|
|
"Output special characters (e.g., 'newline')."
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-05 12:01:21 +00:00
|
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
|
|
|
vec!["line break", "newline", "Unicode"]
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-15 23:08:12 +00:00
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
|
|
|
vec![
|
|
|
|
|
Example {
|
|
|
|
|
description: "Output newline",
|
|
|
|
|
example: r#"char newline"#,
|
|
|
|
|
result: Some(Value::test_string("\n")),
|
|
|
|
|
},
|
2023-03-17 09:15:41 +00:00
|
|
|
|
Example {
|
|
|
|
|
description: "List available characters",
|
|
|
|
|
example: r#"char --list"#,
|
|
|
|
|
result: None,
|
|
|
|
|
},
|
2021-12-15 23:08:12 +00:00
|
|
|
|
Example {
|
2022-10-26 16:36:42 +00:00
|
|
|
|
description: "Output prompt character, newline and a hamburger menu character",
|
|
|
|
|
example: r#"(char prompt) + (char newline) + (char hamburger)"#,
|
2021-12-15 23:08:12 +00:00
|
|
|
|
result: Some(Value::test_string("\u{25b6}\n\u{2261}")),
|
|
|
|
|
},
|
|
|
|
|
Example {
|
|
|
|
|
description: "Output Unicode character",
|
2023-10-19 20:08:09 +00:00
|
|
|
|
example: r#"char --unicode 1f378"#,
|
2021-12-15 23:08:12 +00:00
|
|
|
|
result: Some(Value::test_string("\u{1f378}")),
|
|
|
|
|
},
|
2022-04-13 10:33:08 +00:00
|
|
|
|
Example {
|
2022-04-14 13:34:02 +00:00
|
|
|
|
description: "Create Unicode from integer codepoint values",
|
2023-10-19 20:08:09 +00:00
|
|
|
|
example: r#"char --integer (0x60 + 1) (0x60 + 2)"#,
|
2022-04-14 13:34:02 +00:00
|
|
|
|
result: Some(Value::test_string("ab")),
|
2022-04-13 10:33:08 +00:00
|
|
|
|
},
|
2021-12-15 23:08:12 +00:00
|
|
|
|
Example {
|
|
|
|
|
description: "Output multi-byte Unicode character",
|
2023-10-19 20:08:09 +00:00
|
|
|
|
example: r#"char --unicode 1F468 200D 1F466 200D 1F466"#,
|
2021-12-15 23:08:12 +00:00
|
|
|
|
result: Some(Value::test_string(
|
|
|
|
|
"\u{1F468}\u{200D}\u{1F466}\u{200D}\u{1F466}",
|
|
|
|
|
)),
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-07 22:29:00 +00:00
|
|
|
|
fn run_const(
|
|
|
|
|
&self,
|
|
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
|
call: &Call,
|
|
|
|
|
_input: PipelineData,
|
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
|
|
|
|
let call_span = call.head;
|
|
|
|
|
let list = call.has_flag_const(working_set, "list")?;
|
|
|
|
|
let integer = call.has_flag_const(working_set, "integer")?;
|
|
|
|
|
let unicode = call.has_flag_const(working_set, "unicode")?;
|
|
|
|
|
let ctrlc = working_set.permanent().ctrlc.clone();
|
|
|
|
|
|
|
|
|
|
// handle -l flag
|
|
|
|
|
if list {
|
2024-05-05 16:00:59 +00:00
|
|
|
|
return Ok(generate_character_list(ctrlc, call.head));
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handle -i flag
|
|
|
|
|
if integer {
|
2024-05-14 21:10:06 +00:00
|
|
|
|
let int_args = call.rest_const(working_set, 0)?;
|
|
|
|
|
handle_integer_flag(int_args, call_span)
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
// handle -u flag
|
|
|
|
|
else if unicode {
|
2024-05-14 21:10:06 +00:00
|
|
|
|
let string_args = call.rest_const(working_set, 0)?;
|
|
|
|
|
handle_unicode_flag(string_args, call_span)
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
// handle the rest
|
|
|
|
|
else {
|
2024-05-14 21:10:06 +00:00
|
|
|
|
let string_args = call.rest_const(working_set, 0)?;
|
|
|
|
|
handle_the_rest(string_args, call_span)
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-15 23:08:12 +00:00
|
|
|
|
fn run(
|
|
|
|
|
&self,
|
2023-02-05 21:17:46 +00:00
|
|
|
|
engine_state: &EngineState,
|
|
|
|
|
stack: &mut Stack,
|
2021-12-15 23:08:12 +00:00
|
|
|
|
call: &Call,
|
|
|
|
|
_input: PipelineData,
|
2023-02-05 21:17:46 +00:00
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2021-12-15 23:08:12 +00:00
|
|
|
|
let call_span = call.head;
|
2024-02-07 22:29:00 +00:00
|
|
|
|
let list = call.has_flag(engine_state, stack, "list")?;
|
|
|
|
|
let integer = call.has_flag(engine_state, stack, "integer")?;
|
|
|
|
|
let unicode = call.has_flag(engine_state, stack, "unicode")?;
|
|
|
|
|
let ctrlc = engine_state.ctrlc.clone();
|
|
|
|
|
|
2021-12-15 23:08:12 +00:00
|
|
|
|
// handle -l flag
|
2024-02-07 22:29:00 +00:00
|
|
|
|
if list {
|
2024-05-05 16:00:59 +00:00
|
|
|
|
return Ok(generate_character_list(ctrlc, call_span));
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
|
|
|
|
|
2024-02-07 22:29:00 +00:00
|
|
|
|
// handle -i flag
|
|
|
|
|
if integer {
|
2024-05-14 21:10:06 +00:00
|
|
|
|
let int_args = call.rest(engine_state, stack, 0)?;
|
|
|
|
|
handle_integer_flag(int_args, call_span)
|
2021-12-15 23:08:12 +00:00
|
|
|
|
}
|
|
|
|
|
// handle -u flag
|
2024-02-07 22:29:00 +00:00
|
|
|
|
else if unicode {
|
2024-05-14 21:10:06 +00:00
|
|
|
|
let string_args = call.rest(engine_state, stack, 0)?;
|
|
|
|
|
handle_unicode_flag(string_args, call_span)
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
// handle the rest
|
|
|
|
|
else {
|
2024-05-14 21:10:06 +00:00
|
|
|
|
let string_args = call.rest(engine_state, stack, 0)?;
|
|
|
|
|
handle_the_rest(string_args, call_span)
|
2021-12-15 23:08:12 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-05 16:00:59 +00:00
|
|
|
|
fn generate_character_list(ctrlc: Option<Arc<AtomicBool>>, call_span: Span) -> PipelineData {
|
|
|
|
|
CHAR_MAP
|
2024-02-07 22:29:00 +00:00
|
|
|
|
.iter()
|
|
|
|
|
.map(move |(name, s)| {
|
|
|
|
|
let unicode = Value::string(
|
|
|
|
|
s.chars()
|
|
|
|
|
.map(|c| format!("{:x}", c as u32))
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join(" "),
|
|
|
|
|
call_span,
|
|
|
|
|
);
|
|
|
|
|
let record = record! {
|
|
|
|
|
"name" => Value::string(*name, call_span),
|
|
|
|
|
"character" => Value::string(s, call_span),
|
|
|
|
|
"unicode" => unicode,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Value::record(record, call_span)
|
|
|
|
|
})
|
2024-05-05 16:00:59 +00:00
|
|
|
|
.into_pipeline_data(call_span, ctrlc)
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_integer_flag(
|
2024-05-14 21:10:06 +00:00
|
|
|
|
int_args: Vec<Spanned<i64>>,
|
2024-02-07 22:29:00 +00:00
|
|
|
|
call_span: Span,
|
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
|
|
|
|
if int_args.is_empty() {
|
|
|
|
|
return Err(ShellError::MissingParameter {
|
|
|
|
|
param_name: "missing at least one unicode character".into(),
|
|
|
|
|
span: call_span,
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-05-14 21:10:06 +00:00
|
|
|
|
|
|
|
|
|
let str = int_args
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(integer_to_unicode_char)
|
|
|
|
|
.collect::<Result<String, _>>()?;
|
|
|
|
|
|
|
|
|
|
Ok(Value::string(str, call_span).into_pipeline_data())
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_unicode_flag(
|
2024-05-14 21:10:06 +00:00
|
|
|
|
string_args: Vec<Spanned<String>>,
|
2024-02-07 22:29:00 +00:00
|
|
|
|
call_span: Span,
|
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
|
|
|
|
if string_args.is_empty() {
|
|
|
|
|
return Err(ShellError::MissingParameter {
|
|
|
|
|
param_name: "missing at least one unicode character".into(),
|
|
|
|
|
span: call_span,
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-05-14 21:10:06 +00:00
|
|
|
|
|
|
|
|
|
let str = string_args
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(string_to_unicode_char)
|
|
|
|
|
.collect::<Result<String, _>>()?;
|
|
|
|
|
|
|
|
|
|
Ok(Value::string(str, call_span).into_pipeline_data())
|
2024-02-07 22:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_the_rest(
|
2024-05-14 21:10:06 +00:00
|
|
|
|
string_args: Vec<Spanned<String>>,
|
2024-02-07 22:29:00 +00:00
|
|
|
|
call_span: Span,
|
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2024-05-14 21:10:06 +00:00
|
|
|
|
let Some(s) = string_args.first() else {
|
2024-02-07 22:29:00 +00:00
|
|
|
|
return Err(ShellError::MissingParameter {
|
|
|
|
|
param_name: "missing name of the character".into(),
|
|
|
|
|
span: call_span,
|
|
|
|
|
});
|
2024-05-14 21:10:06 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let special_character = str_to_character(&s.item);
|
|
|
|
|
|
2024-02-07 22:29:00 +00:00
|
|
|
|
if let Some(output) = special_character {
|
|
|
|
|
Ok(Value::string(output, call_span).into_pipeline_data())
|
|
|
|
|
} else {
|
|
|
|
|
Err(ShellError::TypeMismatch {
|
|
|
|
|
err_message: "error finding named character".into(),
|
2024-05-14 21:10:06 +00:00
|
|
|
|
span: s.span,
|
2024-02-07 22:29:00 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
2021-12-15 23:08:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-14 21:10:06 +00:00
|
|
|
|
fn integer_to_unicode_char(value: Spanned<i64>) -> Result<char, ShellError> {
|
|
|
|
|
let decoded_char = value.item.try_into().ok().and_then(std::char::from_u32);
|
2022-04-14 13:34:02 +00:00
|
|
|
|
|
|
|
|
|
if let Some(ch) = decoded_char {
|
|
|
|
|
Ok(ch)
|
|
|
|
|
} else {
|
2023-03-06 10:31:07 +00:00
|
|
|
|
Err(ShellError::TypeMismatch {
|
|
|
|
|
err_message: "not a valid Unicode codepoint".into(),
|
2024-05-14 21:10:06 +00:00
|
|
|
|
span: value.span,
|
2023-03-06 10:31:07 +00:00
|
|
|
|
})
|
2022-04-14 13:34:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-14 21:10:06 +00:00
|
|
|
|
fn string_to_unicode_char(s: Spanned<String>) -> Result<char, ShellError> {
|
|
|
|
|
let decoded_char = u32::from_str_radix(&s.item, 16)
|
2022-04-14 13:34:02 +00:00
|
|
|
|
.ok()
|
|
|
|
|
.and_then(std::char::from_u32);
|
2021-12-15 23:08:12 +00:00
|
|
|
|
|
|
|
|
|
if let Some(ch) = decoded_char {
|
|
|
|
|
Ok(ch)
|
|
|
|
|
} else {
|
2023-03-06 10:31:07 +00:00
|
|
|
|
Err(ShellError::TypeMismatch {
|
|
|
|
|
err_message: "error decoding Unicode character".into(),
|
2024-05-14 21:10:06 +00:00
|
|
|
|
span: s.span,
|
2023-03-06 10:31:07 +00:00
|
|
|
|
})
|
2021-12-15 23:08:12 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn str_to_character(s: &str) -> Option<String> {
|
|
|
|
|
CHAR_MAP.get(s).map(|s| s.into())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::Char;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn examples_work_as_expected() {
|
|
|
|
|
use crate::test_examples;
|
|
|
|
|
|
|
|
|
|
test_examples(Char {})
|
|
|
|
|
}
|
|
|
|
|
}
|