mirror of
https://github.com/nushell/nushell.git
synced 2026-10-28 08:54:52 +08:00
Some more cleaning (#18186)
## Description I've been experimenting with additional clippy lints and ran into these while dealing with the warnings. I wanted to separate these from the (eventual) PR for the lints since these changes are a little more significant than the mostly stylistic changes that come with the extra lints.
This commit is contained in:
1 parent
cb757c110a
commit
0d4ae67698
20 files changed
+338
-417
No files matched your search
@@ -4,7 +4,7 @@ id: let-if-let-some
|
||||
language: rust
|
||||
severity: hint
|
||||
message: "This pattern can be confusing due to the repetition of `let` and `=`"
|
||||
note: "It can be rewritten using `.map()` and `unwrap_or`/`unwrap_or_else` methods"
|
||||
note: "It can be rewritten as a match expression or with `.map()` and `unwrap_or`/`unwrap_or_else` methods."
|
||||
|
||||
rule:
|
||||
kind: let_declaration
|
||||
@@ -17,7 +17,13 @@ rule:
|
||||
|
||||
# Multiple fix suggestions are supported after ast-grep 38.4
|
||||
fix:
|
||||
- title: Lazy
|
||||
- title: Rewrite as a match expression
|
||||
template: |
|
||||
let $$$PAT = match $$$EXPR {
|
||||
Some($VAR) => { $$$THEN }
|
||||
None => { $$$ELSE }
|
||||
};
|
||||
- title: Rewrite with .map & .unwrap_or_else
|
||||
template: |
|
||||
let $$$PAT = $$$EXPR.map(|$VAR| {
|
||||
$$$THEN
|
||||
@@ -25,5 +31,5 @@ fix:
|
||||
.unwrap_or_else(|| {
|
||||
$$$ELSE
|
||||
});
|
||||
- title: Simple
|
||||
- title: Rewrite with .map & .unwrap_or
|
||||
template: "let $$$PAT = $$$EXPR.map(|$VAR| { $$$THEN }).unwrap_or($$$ELSE);"
|
||||
@@ -382,14 +382,17 @@ impl<'a> Completer for CommandWideCompletion<'a> {
|
||||
span: args_span,
|
||||
} = get_command_arguments(working_set, self.expression);
|
||||
|
||||
let mut new_span = span;
|
||||
// strip the placeholder
|
||||
if self.strip
|
||||
let new_span = if self.strip
|
||||
&& let Some(last) = args.last_mut()
|
||||
{
|
||||
last.item.pop();
|
||||
new_span = Span::new(span.start, span.end.saturating_sub(1));
|
||||
}
|
||||
if let Some(popped) = last.item.pop() {
|
||||
last.span.end = last.span.end.saturating_sub(popped.len_utf8())
|
||||
}
|
||||
last.span
|
||||
} else {
|
||||
span
|
||||
};
|
||||
|
||||
let mut block = working_set.get_block(self.block_id).clone();
|
||||
|
||||
|
||||
@@ -83,19 +83,19 @@ impl NuHelpCompleter {
|
||||
let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc);
|
||||
}
|
||||
for positional in &sig.optional_positional {
|
||||
let opt_suffix = if let Some(value) = &positional.default_value {
|
||||
format!(
|
||||
" (optional, default: {})",
|
||||
&value.to_parsable_string(", ", &self.config),
|
||||
)
|
||||
} else {
|
||||
(" (optional)").to_string()
|
||||
};
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" (optional) {}: {}{}\r\n",
|
||||
positional.name, positional.desc, opt_suffix
|
||||
" {}: {} (optional",
|
||||
positional.name, positional.desc
|
||||
);
|
||||
if let Some(value) = &positional.default_value {
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
", default: {}",
|
||||
&value.to_parsable_string(", ", &self.config),
|
||||
);
|
||||
}
|
||||
let _ = write!(long_desc, ")\r\n");
|
||||
}
|
||||
|
||||
if let Some(rest_positional) = &sig.rest_positional {
|
||||
|
||||
@@ -54,45 +54,39 @@ fn get_prompt_string(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Option<String> {
|
||||
stack
|
||||
.get_env_var(engine_state, prompt)
|
||||
.and_then(|v| match v {
|
||||
Value::Closure { val, .. } => {
|
||||
let result = ClosureEvalOnce::new(engine_state, stack, val.as_ref().clone())
|
||||
.run_with_input(PipelineData::empty());
|
||||
let mut output = match stack.get_env_var(engine_state, prompt)? {
|
||||
Value::String { val, .. } => val.clone(),
|
||||
Value::Closure { val, .. } => {
|
||||
let result = ClosureEvalOnce::new(engine_state, stack, val.as_ref().clone())
|
||||
.run_with_input(PipelineData::empty());
|
||||
|
||||
trace!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
trace!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
|
||||
result
|
||||
.map_err(|err| {
|
||||
report_shell_error(None, engine_state, &err);
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
Value::String { .. } => Some(PipelineData::value(v.clone(), None)),
|
||||
_ => None,
|
||||
})
|
||||
.and_then(|pipeline_data| {
|
||||
let output = pipeline_data.collect_string("", config).ok();
|
||||
let ansi_output = output.map(|mut x| {
|
||||
// Always reset the color at the start of the right prompt
|
||||
// to ensure there is no ansi bleed over
|
||||
if x.is_empty() && prompt == PROMPT_COMMAND_RIGHT {
|
||||
x.insert_str(0, "\x1b[0m")
|
||||
};
|
||||
let result_string = result
|
||||
.map_err(|err| report_shell_error(None, engine_state, &err))
|
||||
.ok()
|
||||
.and_then(|pd| pd.collect_string("", config).ok());
|
||||
|
||||
x
|
||||
});
|
||||
// Let's keep this for debugging purposes with nu --log-level warn
|
||||
warn!("{}:{}:{} {:?}", file!(), line!(), column!(), ansi_output);
|
||||
result_string?
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
ansi_output
|
||||
})
|
||||
// Always reset the color at the start of the right prompt
|
||||
// to ensure there is no ansi bleed over
|
||||
if output.is_empty() && prompt == PROMPT_COMMAND_RIGHT {
|
||||
output.insert_str(0, "\x1b[0m")
|
||||
};
|
||||
|
||||
// Let's keep this for debugging purposes with nu --log-level warn
|
||||
warn!("{}:{}:{} {:?}", file!(), line!(), column!(), output);
|
||||
|
||||
Some(output)
|
||||
}
|
||||
|
||||
pub fn update_prompt(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use nu_cmd_base::input_handler::{CmdArgument, operate};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use nu_protocol::FromValue;
|
||||
use print_positions::print_positions;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -19,14 +20,28 @@ impl CmdArgument for Arguments {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
enum FillAlignment {
|
||||
#[default]
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
MiddleRight,
|
||||
}
|
||||
|
||||
impl FromValue for FillAlignment {
|
||||
fn from_value(v: Value) -> Result<Self, ShellError> {
|
||||
Ok(match String::from_value(v)?.to_ascii_lowercase().as_str() {
|
||||
"l" | "left" => Self::Left,
|
||||
"r" | "right" => Self::Right,
|
||||
"c" | "center" | "m" | "middle" => Self::Middle,
|
||||
"cr" | "centerright" | "mr" | "middleright" => Self::MiddleRight,
|
||||
// TODO: This should probably be an error
|
||||
_ => Self::Left,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for Fill {
|
||||
fn name(&self) -> &str {
|
||||
"fill"
|
||||
@@ -152,23 +167,13 @@ fn fill(
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let width_arg: Option<usize> = call.get_flag(engine_state, stack, "width")?;
|
||||
let alignment_arg: Option<String> = call.get_flag(engine_state, stack, "alignment")?;
|
||||
let alignment = call
|
||||
.get_flag::<FillAlignment>(engine_state, stack, "alignment")?
|
||||
.unwrap_or_default();
|
||||
let character_arg: Option<String> = call.get_flag(engine_state, stack, "character")?;
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
|
||||
let alignment = if let Some(arg) = alignment_arg {
|
||||
match arg.to_ascii_lowercase().as_str() {
|
||||
"l" | "left" => FillAlignment::Left,
|
||||
"r" | "right" => FillAlignment::Right,
|
||||
"c" | "center" | "m" | "middle" => FillAlignment::Middle,
|
||||
"cr" | "centerright" | "mr" | "middleright" => FillAlignment::MiddleRight,
|
||||
_ => FillAlignment::Left,
|
||||
}
|
||||
} else {
|
||||
FillAlignment::Left
|
||||
};
|
||||
|
||||
let width = width_arg.unwrap_or(1);
|
||||
|
||||
let character = character_arg.unwrap_or_else(|| " ".to_string());
|
||||
|
||||
@@ -42,19 +42,9 @@ impl Command for DropColumn {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let input = input.into_stream_or_original(engine_state);
|
||||
// the number of columns to drop
|
||||
let columns: Option<Spanned<i64>> = call.opt(engine_state, stack, 0)?;
|
||||
let columns = call.opt::<usize>(engine_state, stack, 0)?.unwrap_or(1);
|
||||
let from_left = call.has_flag(engine_state, stack, "left")?;
|
||||
|
||||
let columns = if let Some(columns) = columns {
|
||||
if columns.item < 0 {
|
||||
return Err(ShellError::NeedsPositiveValue { span: columns.span });
|
||||
} else {
|
||||
columns.item as usize
|
||||
}
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
drop_cols(engine_state, input, call.head, columns, from_left)
|
||||
}
|
||||
|
||||
|
||||
@@ -78,20 +78,10 @@ impl Command for Drop {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let metadata = input.take_metadata();
|
||||
let rows: Option<Spanned<i64>> = call.opt(engine_state, stack, 0)?;
|
||||
let rows = call.opt::<usize>(engine_state, stack, 0)?.unwrap_or(1);
|
||||
let mut values = input.into_iter_strict(head)?.collect::<Vec<_>>();
|
||||
|
||||
let rows_to_drop = if let Some(rows) = rows {
|
||||
if rows.item < 0 {
|
||||
return Err(ShellError::NeedsPositiveValue { span: rows.span });
|
||||
} else {
|
||||
rows.item as usize
|
||||
}
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
values.truncate(values.len().saturating_sub(rows_to_drop));
|
||||
values.truncate(values.len().saturating_sub(rows));
|
||||
Ok(Value::list(values, head).into_pipeline_data_with_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,64 +339,62 @@ fn get_match_pattern_from_arguments(
|
||||
let highlight_style =
|
||||
style_computer.compute("search_result", &Value::string("search result", span));
|
||||
|
||||
let (regex_str, search_terms) = if let Some(regex) = regex {
|
||||
if !terms.is_empty() {
|
||||
let (regex_str, search_terms) = match (regex, terms.as_slice()) {
|
||||
(Some(_), [_, ..]) => {
|
||||
return Err(ShellError::IncompatibleParametersSingle {
|
||||
msg: "Cannot use a `--regex` parameter with additional search terms".into(),
|
||||
span: call.get_flag_span(stack, "regex").expect("has flag"),
|
||||
});
|
||||
}
|
||||
(Some(regex), []) => {
|
||||
let flags = match (ignore_case, dotall) {
|
||||
(false, false) => "",
|
||||
(true, false) => "(?i)", // case insensitive
|
||||
(false, true) => "(?s)", // allow . to match \n
|
||||
(true, true) => "(?is)", // case insensitive and allow . to match \n
|
||||
};
|
||||
|
||||
let flags = match (ignore_case, dotall) {
|
||||
(false, false) => "",
|
||||
(true, false) => "(?i)", // case insensitive
|
||||
(false, true) => "(?s)", // allow . to match \n
|
||||
(true, true) => "(?is)", // case insensitive and allow . to match \n
|
||||
};
|
||||
|
||||
(flags.to_string() + regex.as_str(), Vec::new())
|
||||
} else {
|
||||
if dotall {
|
||||
(flags.to_string() + regex.as_str(), Vec::new())
|
||||
}
|
||||
(None, _) if dotall => {
|
||||
return Err(ShellError::IncompatibleParametersSingle {
|
||||
msg: "Flag --dotall only works for regex search".into(),
|
||||
span: call.get_flag_span(stack, "dotall").expect("has flag"),
|
||||
});
|
||||
}
|
||||
// NOTE: Should this be an error? It doesn't make sense to call `find` with no arguments.
|
||||
// (None, []) => {}
|
||||
(None, terms) => {
|
||||
let mut regex = String::new();
|
||||
|
||||
let mut regex = String::new();
|
||||
if ignore_case {
|
||||
regex += "(?i)";
|
||||
}
|
||||
|
||||
if ignore_case {
|
||||
regex += "(?i)";
|
||||
}
|
||||
let search_terms = terms
|
||||
.iter()
|
||||
.map(|v| {
|
||||
if ignore_case {
|
||||
v.to_expanded_string("", &config).to_lowercase()
|
||||
} else {
|
||||
v.to_expanded_string("", &config)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let search_terms = terms
|
||||
.iter()
|
||||
.map(|v| {
|
||||
if ignore_case {
|
||||
v.to_expanded_string("", &config).to_lowercase()
|
||||
} else {
|
||||
v.to_expanded_string("", &config)
|
||||
if let [first, rest @ ..] = search_terms.as_slice() {
|
||||
regex.push_str(escape(first).as_ref());
|
||||
for term in rest {
|
||||
regex.push('|');
|
||||
regex.push_str(escape(term).as_ref());
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
}
|
||||
|
||||
let escaped_terms = search_terms
|
||||
.iter()
|
||||
.map(|v| escape(v).into())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
if let Some(term) = escaped_terms.first() {
|
||||
regex += term;
|
||||
(regex, search_terms)
|
||||
}
|
||||
|
||||
for term in escaped_terms.iter().skip(1) {
|
||||
regex += "|";
|
||||
regex += term;
|
||||
}
|
||||
|
||||
(regex, search_terms)
|
||||
};
|
||||
|
||||
// TODO: Should be InvalidValue
|
||||
let regex = Regex::new(regex_str.as_str()).map_err(|e| ShellError::TypeMismatch {
|
||||
err_message: format!("invalid regex: {e}"),
|
||||
span,
|
||||
@@ -454,15 +452,11 @@ fn highlight_all_matches(pattern: &MatchPattern, text: &str) -> String {
|
||||
let mut highlighted = String::new();
|
||||
|
||||
for cap in pattern.regex.captures_iter(text) {
|
||||
let capture = match cap {
|
||||
Ok(capture) => capture,
|
||||
Err(_) => return pattern.string_style.paint(text).to_string(),
|
||||
let Ok(capture) = cap else {
|
||||
return pattern.string_style.paint(text).to_string();
|
||||
};
|
||||
|
||||
let m = match capture.get(0) {
|
||||
Some(m) => m,
|
||||
None => continue,
|
||||
};
|
||||
let Some(m) = capture.get(0) else { continue };
|
||||
|
||||
highlighted.push_str(
|
||||
&pattern
|
||||
|
||||
@@ -89,7 +89,7 @@ fn first_helper(
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let rows: Option<Spanned<i64>> = call.opt(engine_state, stack, 0)?;
|
||||
let rows: Option<usize> = call.opt(engine_state, stack, 0)?;
|
||||
let strict_mode = call.has_flag(engine_state, stack, "strict")?;
|
||||
|
||||
// FIXME: for backwards compatibility reasons, if `rows` is not specified we
|
||||
@@ -97,15 +97,7 @@ fn first_helper(
|
||||
// remove `rows` so that `first` always returns a single element; getting a list of
|
||||
// the first N elements is covered by `take`
|
||||
let return_single_element = rows.is_none();
|
||||
let rows = if let Some(rows) = rows {
|
||||
if rows.item < 0 {
|
||||
return Err(ShellError::NeedsPositiveValue { span: rows.span });
|
||||
} else {
|
||||
rows.item as usize
|
||||
}
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let rows = rows.unwrap_or(1);
|
||||
|
||||
let mut input = input;
|
||||
let input_meta = input.take_metadata();
|
||||
|
||||
@@ -80,7 +80,7 @@ fn extract_headers(
|
||||
span: Span,
|
||||
config: &Config,
|
||||
) -> Result<(Vec<String>, Vec<String>), ShellError> {
|
||||
table
|
||||
let record = table
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
ShellError::Generic(GenericError::new(
|
||||
@@ -88,46 +88,38 @@ fn extract_headers(
|
||||
"unable to extract headers",
|
||||
span,
|
||||
))
|
||||
})
|
||||
.and_then(Value::as_record)
|
||||
.and_then(|record| {
|
||||
for v in record.values() {
|
||||
if !is_valid_header(v) {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: "needs compatible type: Null, String, Bool, Float, Int"
|
||||
.to_string(),
|
||||
span: v.span(),
|
||||
});
|
||||
}
|
||||
}
|
||||
})?
|
||||
.as_record()?;
|
||||
|
||||
let old_headers = record.columns().cloned().collect();
|
||||
let new_headers = record
|
||||
.values()
|
||||
.enumerate()
|
||||
.map(|(idx, value)| {
|
||||
let col = value.to_expanded_string("", config);
|
||||
if col.is_empty() {
|
||||
format!("column{idx}")
|
||||
} else {
|
||||
col
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let new_headers = record
|
||||
.values()
|
||||
.enumerate()
|
||||
.map(|(idx, value)| make_header_string(config, idx, value))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok((old_headers, new_headers))
|
||||
})
|
||||
let old_headers = record.columns().cloned().collect();
|
||||
|
||||
Ok((old_headers, new_headers))
|
||||
}
|
||||
|
||||
fn is_valid_header(value: &Value) -> bool {
|
||||
matches!(
|
||||
value,
|
||||
fn make_header_string(config: &Config, idx: usize, value: &Value) -> Result<String, ShellError> {
|
||||
match value {
|
||||
Value::Nothing { .. }
|
||||
| Value::String { val: _, .. }
|
||||
| Value::Bool { val: _, .. }
|
||||
| Value::Float { val: _, .. }
|
||||
| Value::Int { val: _, .. }
|
||||
)
|
||||
| Value::String { .. }
|
||||
| Value::Bool { .. }
|
||||
| Value::Float { .. }
|
||||
| Value::Int { .. } => {
|
||||
let col = value.to_expanded_string("", config);
|
||||
Ok(match col.is_empty() {
|
||||
true => format!("column{idx}"),
|
||||
false => col,
|
||||
})
|
||||
}
|
||||
_ => Err(ShellError::TypeMismatch {
|
||||
err_message: "needs compatible type: Null, String, Bool, Float, Int".to_string(),
|
||||
span: value.span(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_headers(
|
||||
|
||||
@@ -79,21 +79,13 @@ impl Command for Last {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let rows: Option<Spanned<i64>> = call.opt(engine_state, stack, 0)?;
|
||||
let rows = call.opt::<usize>(engine_state, stack, 0)?;
|
||||
let strict_mode = call.has_flag(engine_state, stack, "strict")?;
|
||||
|
||||
// FIXME: Please read the FIXME message in `first.rs`'s `first_helper` implementation.
|
||||
// It has the same issue.
|
||||
let return_single_element = rows.is_none();
|
||||
let rows = if let Some(rows) = rows {
|
||||
if rows.item < 0 {
|
||||
return Err(ShellError::NeedsPositiveValue { span: rows.span });
|
||||
} else {
|
||||
rows.item as usize
|
||||
}
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let rows = rows.unwrap_or(1);
|
||||
|
||||
let mut input = input;
|
||||
let metadata = input.take_metadata();
|
||||
|
||||
@@ -83,37 +83,23 @@ impl Command for Window {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let input = input.into_stream_or_original(engine_state);
|
||||
let head = call.head;
|
||||
let window_size: Value = call.req(engine_state, stack, 0)?;
|
||||
let stride: Option<Value> = call.get_flag(engine_state, stack, "stride")?;
|
||||
let remainder = call.has_flag(engine_state, stack, "remainder")?;
|
||||
|
||||
let size =
|
||||
usize::try_from(window_size.as_int()?).map_err(|_| ShellError::NeedsPositiveValue {
|
||||
span: window_size.span(),
|
||||
})?;
|
||||
|
||||
let size = NonZeroUsize::try_from(size).map_err(|_| ShellError::IncorrectValue {
|
||||
msg: "`window_size` cannot be zero".into(),
|
||||
val_span: window_size.span(),
|
||||
call_span: head,
|
||||
})?;
|
||||
|
||||
let stride = if let Some(stride_val) = stride {
|
||||
let stride = usize::try_from(stride_val.as_int()?).map_err(|_| {
|
||||
ShellError::NeedsPositiveValue {
|
||||
span: stride_val.span(),
|
||||
}
|
||||
})?;
|
||||
|
||||
NonZeroUsize::try_from(stride).map_err(|_| ShellError::IncorrectValue {
|
||||
msg: "`stride` cannot be zero".into(),
|
||||
val_span: stride_val.span(),
|
||||
call_span: head,
|
||||
})?
|
||||
} else {
|
||||
NonZeroUsize::MIN
|
||||
let fix_call_span = |err: ShellError| match err {
|
||||
ShellError::IncorrectValue { msg, val_span, .. } => ShellError::IncorrectValue {
|
||||
msg,
|
||||
val_span,
|
||||
call_span: call.head,
|
||||
},
|
||||
_ => err,
|
||||
};
|
||||
|
||||
let size: NonZeroUsize = call.req(engine_state, stack, 0).map_err(fix_call_span)?;
|
||||
let stride: NonZeroUsize = call
|
||||
.get_flag(engine_state, stack, "stride")
|
||||
.map_err(fix_call_span)?
|
||||
.unwrap_or(NonZeroUsize::MIN);
|
||||
let remainder = call.has_flag(engine_state, stack, "remainder")?;
|
||||
|
||||
if remainder && size == stride {
|
||||
super::chunks::chunks(engine_state, input, size, head)
|
||||
} else if stride >= size {
|
||||
|
||||
@@ -1258,14 +1258,18 @@ pub(crate) fn request_error_to_shell_error(span: Span, e: ShellErrorOrRequestErr
|
||||
}
|
||||
|
||||
fn retrieve_http_proxy_from_env(engine_state: &EngineState, stack: &mut Stack) -> Option<String> {
|
||||
stack
|
||||
.get_env_var(engine_state, "http_proxy")
|
||||
.or(stack.get_env_var(engine_state, "HTTP_PROXY"))
|
||||
.or(stack.get_env_var(engine_state, "https_proxy"))
|
||||
.or(stack.get_env_var(engine_state, "HTTPS_PROXY"))
|
||||
.or(stack.get_env_var(engine_state, "ALL_PROXY"))
|
||||
.cloned()
|
||||
.and_then(|proxy| proxy.coerce_into_string().ok())
|
||||
[
|
||||
"http_proxy",
|
||||
"HTTP_PROXY",
|
||||
"https_proxy",
|
||||
"HTTPS_PROXY",
|
||||
"ALL_PROXY",
|
||||
]
|
||||
.iter()
|
||||
.find_map(|name| stack.get_env_var(engine_state, name))?
|
||||
.coerce_str()
|
||||
.map(|s| s.into_owned())
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn proxy_builder_from_env(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::FromValue;
|
||||
use uuid::{Timestamp, Uuid};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -18,8 +19,8 @@ impl Command for RandomUuid {
|
||||
.short('v')
|
||||
.arg(SyntaxShape::Int)
|
||||
.desc(
|
||||
"The UUID version to generate (1, 3, 4, 5, 7). Defaults to 4 if not \
|
||||
specified.",
|
||||
"The UUID version to generate (1, 3, 4, 5, 7). \
|
||||
Defaults to 4 if not specified.",
|
||||
)
|
||||
.completion(Completion::new_list(&["1", "3", "4", "5", "7"])),
|
||||
)
|
||||
@@ -28,8 +29,8 @@ impl Command for RandomUuid {
|
||||
.short('n')
|
||||
.arg(SyntaxShape::String)
|
||||
.desc(
|
||||
"The namespace for v3 and v5 UUIDs (dns, url, oid, x500). Required for v3 \
|
||||
and v5.",
|
||||
"The namespace for v3 and v5 UUIDs (dns, url, oid, x500). \
|
||||
Required for v3 and v5.",
|
||||
)
|
||||
.completion(Completion::new_list(&["dns", "url", "oid", "x500"])),
|
||||
)
|
||||
@@ -63,7 +64,15 @@ impl Command for RandomUuid {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
uuid(engine_state, stack, call)
|
||||
let fix_call_span = |err: ShellError| match err {
|
||||
ShellError::IncorrectValue { msg, val_span, .. } => ShellError::IncorrectValue {
|
||||
msg,
|
||||
val_span,
|
||||
call_span: call.head,
|
||||
},
|
||||
_ => err,
|
||||
};
|
||||
uuid(engine_state, stack, call).map_err(fix_call_span)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example<'_>> {
|
||||
@@ -117,7 +126,12 @@ fn uuid(
|
||||
let uuid_str = match version {
|
||||
1 => {
|
||||
let ts = Timestamp::now(uuid::timestamp::context::NoContext);
|
||||
let node_id = get_mac_address(engine_state, stack, call, span)?;
|
||||
let MacAddr(node_id) = call
|
||||
.get_flag::<MacAddr>(engine_state, stack, "mac")?
|
||||
.ok_or_else(|| ShellError::MissingParameter {
|
||||
param_name: "mac".to_string(),
|
||||
span,
|
||||
})?;
|
||||
let uuid = Uuid::new_v1(ts, &node_id);
|
||||
uuid.hyphenated().to_string()
|
||||
}
|
||||
@@ -235,50 +249,64 @@ fn validate_flags(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_mac_address(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
span: Span,
|
||||
) -> Result<[u8; 6], ShellError> {
|
||||
let mac_str: Option<String> = call.get_flag(engine_state, stack, "mac")?;
|
||||
struct MacAddr([u8; 6]);
|
||||
|
||||
let mac_str = match mac_str {
|
||||
Some(mac) => mac,
|
||||
None => {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "mac".to_string(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
};
|
||||
impl FromValue for MacAddr {
|
||||
fn from_value(v: Value) -> Result<Self, ShellError> {
|
||||
let span = v.span();
|
||||
let s = v.into_string()?;
|
||||
|
||||
let mac_parts = mac_str.split(':').collect::<Vec<&str>>();
|
||||
if mac_parts.len() != 6 {
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: "MAC address must be in the format XX:XX:XX:XX:XX:XX".to_string(),
|
||||
val_span: span,
|
||||
call_span: span,
|
||||
let mut buf = [0_u8; 6];
|
||||
let mut mac_parts = s.split(':').map(|x| u8::from_str_radix(x, 0x10));
|
||||
|
||||
let has_6_hexadecimal_parts = buf.iter_mut().all(|ele| match mac_parts.next() {
|
||||
Some(Ok(n)) => {
|
||||
*ele = n;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
if has_6_hexadecimal_parts && mac_parts.next().is_none() {
|
||||
Ok(Self(buf))
|
||||
} else {
|
||||
Err(ShellError::IncorrectValue {
|
||||
msg: "MAC address must be in the format XX:XX:XX:XX:XX:XX".to_string(),
|
||||
val_span: span,
|
||||
call_span: span,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mac: [u8; 6] = mac_parts
|
||||
.iter()
|
||||
.map(|x| u8::from_str_radix(x, 16))
|
||||
.collect::<Result<Vec<u8>, _>>()
|
||||
.map_err(|_| ShellError::IncorrectValue {
|
||||
msg: "MAC address must be in the format XX:XX:XX:XX:XX:XX".to_string(),
|
||||
val_span: span,
|
||||
call_span: span,
|
||||
})?
|
||||
.try_into()
|
||||
.map_err(|_| ShellError::IncorrectValue {
|
||||
msg: "MAC address must be in the format XX:XX:XX:XX:XX:XX".to_string(),
|
||||
val_span: span,
|
||||
call_span: span,
|
||||
})?;
|
||||
struct NameSpace(Uuid);
|
||||
|
||||
Ok(mac)
|
||||
impl FromValue for NameSpace {
|
||||
fn from_value(v: Value) -> Result<Self, ShellError> {
|
||||
let span = v.span();
|
||||
let s = v.into_string()?;
|
||||
|
||||
let namespace = match s.to_lowercase().as_str() {
|
||||
"dns" => Uuid::NAMESPACE_DNS,
|
||||
"url" => Uuid::NAMESPACE_URL,
|
||||
"oid" => Uuid::NAMESPACE_OID,
|
||||
"x500" => Uuid::NAMESPACE_X500,
|
||||
_ => match Uuid::parse_str(&s) {
|
||||
Ok(uuid) => uuid,
|
||||
Err(_) => {
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg:
|
||||
"Namespace must be one of: dns, url, oid, x500, or a valid UUID string"
|
||||
.to_string(),
|
||||
val_span: span,
|
||||
call_span: span,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Self(namespace))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_namespace_and_name(
|
||||
@@ -287,46 +315,19 @@ fn get_namespace_and_name(
|
||||
call: &Call,
|
||||
span: Span,
|
||||
) -> Result<(Uuid, String), ShellError> {
|
||||
let namespace_str: Option<String> = call.get_flag(engine_state, stack, "namespace")?;
|
||||
let name: Option<String> = call.get_flag(engine_state, stack, "name")?;
|
||||
let NameSpace(namespace) = call
|
||||
.get_flag::<NameSpace>(engine_state, stack, "namespace")?
|
||||
.ok_or_else(|| ShellError::MissingParameter {
|
||||
param_name: "namespace".to_string(),
|
||||
span,
|
||||
})?;
|
||||
|
||||
let namespace_str = match namespace_str {
|
||||
Some(ns) => ns,
|
||||
None => {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "namespace".to_string(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let name = match name {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "name".to_string(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let namespace = match namespace_str.to_lowercase().as_str() {
|
||||
"dns" => Uuid::NAMESPACE_DNS,
|
||||
"url" => Uuid::NAMESPACE_URL,
|
||||
"oid" => Uuid::NAMESPACE_OID,
|
||||
"x500" => Uuid::NAMESPACE_X500,
|
||||
_ => match Uuid::parse_str(&namespace_str) {
|
||||
Ok(uuid) => uuid,
|
||||
Err(_) => {
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: "Namespace must be one of: dns, url, oid, x500, or a valid UUID string"
|
||||
.to_string(),
|
||||
val_span: span,
|
||||
call_span: span,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
let name = call
|
||||
.get_flag::<String>(engine_state, stack, "name")?
|
||||
.ok_or_else(|| ShellError::MissingParameter {
|
||||
param_name: "name".to_string(),
|
||||
span,
|
||||
})?;
|
||||
|
||||
Ok((namespace, name))
|
||||
}
|
||||
|
||||
@@ -100,28 +100,7 @@ impl Command for FormatDate {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let list = call.has_flag(engine_state, stack, "list")?;
|
||||
let format = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
|
||||
|
||||
// env var preference is documented at https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
|
||||
// LC_ALL overrides LC_TIME, LC_TIME overrides LANG
|
||||
|
||||
// get the locale first so we can use the proper get_env_var functions since this is a const command
|
||||
// we can override the locale by setting $env.NU_TEST_LOCALE_OVERRIDE or $env.LC_TIME
|
||||
let locale = if let Some(loc) = stack
|
||||
.get_env_var(engine_state, LOCALE_OVERRIDE_ENV_VAR)
|
||||
.or_else(|| stack.get_env_var(engine_state, "LC_ALL"))
|
||||
.or_else(|| stack.get_env_var(engine_state, "LC_TIME"))
|
||||
.or_else(|| stack.get_env_var(engine_state, "LANG"))
|
||||
{
|
||||
let locale_str = loc.as_str()?.split('.').next().unwrap_or("en_US");
|
||||
locale_str.try_into().unwrap_or(Locale::en_US)
|
||||
} else {
|
||||
get_system_locale_string()
|
||||
.map(|l| l.replace('-', "_"))
|
||||
.unwrap_or_else(|| String::from("en_US"))
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap_or(Locale::en_US)
|
||||
};
|
||||
let locale = get_locale(|name| stack.get_env_var(engine_state, name))?;
|
||||
|
||||
run(engine_state, call, input, list, format, locale)
|
||||
}
|
||||
@@ -134,33 +113,40 @@ impl Command for FormatDate {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let list = call.has_flag_const(working_set, "list")?;
|
||||
let format = call.opt_const::<Spanned<String>>(working_set, 0)?;
|
||||
|
||||
// env var preference is documented at https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
|
||||
// LC_ALL overrides LC_TIME, LC_TIME overrides LANG
|
||||
|
||||
// get the locale first so we can use the proper get_env_var functions since this is a const command
|
||||
// we can override the locale by setting $env.NU_TEST_LOCALE_OVERRIDE or $env.LC_TIME
|
||||
let locale = if let Some(loc) = working_set
|
||||
.get_env_var(LOCALE_OVERRIDE_ENV_VAR)
|
||||
.or_else(|| working_set.get_env_var("LC_ALL"))
|
||||
.or_else(|| working_set.get_env_var("LC_TIME"))
|
||||
.or_else(|| working_set.get_env_var("LANG"))
|
||||
{
|
||||
let locale_str = loc.as_str()?.split('.').next().unwrap_or("en_US");
|
||||
locale_str.try_into().unwrap_or(Locale::en_US)
|
||||
} else {
|
||||
get_system_locale_string()
|
||||
.map(|l| l.replace('-', "_"))
|
||||
.unwrap_or_else(|| String::from("en_US"))
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap_or(Locale::en_US)
|
||||
};
|
||||
let locale = get_locale(|name| working_set.get_env_var(name))?;
|
||||
|
||||
run(working_set.permanent(), call, input, list, format, locale)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_locale<'a, F>(env_getter: F) -> Result<Locale, ShellError>
|
||||
where
|
||||
F: Fn(&str) -> Option<&'a Value> + 'a,
|
||||
{
|
||||
// env var preference is documented at https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
|
||||
// LC_ALL overrides LC_TIME, LC_TIME overrides LANG
|
||||
static LOCALE_VAR_NAMES: &[&str] = &[LOCALE_OVERRIDE_ENV_VAR, "LC_ALL", "LC_TIME", "LANG"];
|
||||
|
||||
// get the locale first so we can use the proper get_env_var functions since this is a const command
|
||||
// we can override the locale by setting $env.NU_TEST_LOCALE_OVERRIDE or $env.LC_TIME
|
||||
let locale_var = LOCALE_VAR_NAMES
|
||||
.iter()
|
||||
.find_map(|name| env_getter(name))
|
||||
.map(Value::as_str)
|
||||
.transpose()?;
|
||||
|
||||
let locale = match locale_var {
|
||||
Some(loc) => (loc.split('.').next())
|
||||
.and_then(|s| Locale::try_from(s).ok())
|
||||
.unwrap_or(Locale::en_US),
|
||||
None => get_system_locale_string()
|
||||
.map(|l| l.replace('-', "_"))
|
||||
.and_then(|s| Locale::try_from(s.as_str()).ok())
|
||||
.unwrap_or(Locale::en_US),
|
||||
};
|
||||
Ok(locale)
|
||||
}
|
||||
|
||||
fn run(
|
||||
engine_state: &EngineState,
|
||||
call: &Call,
|
||||
|
||||
@@ -12,7 +12,7 @@ fn window_size_zero() -> Result {
|
||||
let err = test().run("[0 1 2] | window 0").expect_shell_error()?;
|
||||
match err {
|
||||
ShellError::IncorrectValue { msg, .. } => {
|
||||
assert_eq!(msg, "`window_size` cannot be zero");
|
||||
assert_eq!(msg, "use a value other than 0");
|
||||
Ok(())
|
||||
}
|
||||
err => Err(err.into()),
|
||||
@@ -42,7 +42,7 @@ fn stride_zero() -> Result {
|
||||
let err = test().run("[0 1 2] | window 1 -s 0").expect_shell_error()?;
|
||||
match err {
|
||||
ShellError::IncorrectValue { msg, .. } => {
|
||||
assert_eq!(msg, "`stride` cannot be zero");
|
||||
assert_eq!(msg, "use a value other than 0");
|
||||
Ok(())
|
||||
}
|
||||
err => Err(err.into()),
|
||||
|
||||
@@ -2097,9 +2097,7 @@ pub fn parse_range(working_set: &mut StateWorkingSet, span: Span) -> Option<Expr
|
||||
|
||||
let contents = working_set.get_span_contents(span);
|
||||
|
||||
let token = if let Ok(s) = String::from_utf8(contents.into()) {
|
||||
s
|
||||
} else {
|
||||
let Ok(token) = String::from_utf8(contents.into()) else {
|
||||
working_set.error(ParseError::NonUtf8(span));
|
||||
return None;
|
||||
};
|
||||
@@ -2122,12 +2120,10 @@ pub fn parse_range(working_set: &mut StateWorkingSet, span: Span) -> Option<Expr
|
||||
.filter_map(|(pos, _)| {
|
||||
// paren_depth = count of unclosed parens prior to pos
|
||||
let before = &token[..pos];
|
||||
let paren_depth = before
|
||||
.chars()
|
||||
.filter(|&c| c == '(')
|
||||
.count()
|
||||
.checked_sub(before.chars().filter(|&c| c == ')').count());
|
||||
paren_depth.and_then(|d| (d == 0).then_some(pos))
|
||||
let paren_opened = before.chars().filter(|&c| c == '(').count();
|
||||
let paren_closed = before.chars().filter(|&c| c == ')').count();
|
||||
let paren_depth = paren_opened.checked_sub(paren_closed)?;
|
||||
(paren_depth == 0).then_some(pos)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet, hash_map::Entry},
|
||||
sync::{
|
||||
Arc, Mutex,
|
||||
mpsc::{Receiver, RecvTimeoutError, Sender, TryRecvError},
|
||||
@@ -84,7 +84,7 @@ impl Jobs {
|
||||
pub fn add_job_with_id(&mut self, id: JobId, job: Job) -> Result<(), &'static str> {
|
||||
self.assign_last_frozen_id_if_frozen(id, &job);
|
||||
|
||||
if let std::collections::hash_map::Entry::Vacant(e) = self.jobs.entry(id) {
|
||||
if let Entry::Vacant(e) = self.jobs.entry(id) {
|
||||
e.insert(job);
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -383,16 +383,12 @@ impl IgnoredMail {
|
||||
fn pop_oldest(&mut self) -> Option<PipelineData> {
|
||||
let (id, (tag, value)) = self.messages.pop_first()?;
|
||||
|
||||
if let Some(tag) = tag {
|
||||
let needs_cleanup = if let Some(ids) = self.by_tag.get_mut(&tag) {
|
||||
ids.remove(&id);
|
||||
ids.is_empty()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if needs_cleanup {
|
||||
self.by_tag.remove(&tag);
|
||||
if let Some(tag) = tag
|
||||
&& let Entry::Occupied(mut occupied_entry) = self.by_tag.entry(tag)
|
||||
{
|
||||
occupied_entry.get_mut().remove(&id);
|
||||
if occupied_entry.get().is_empty() {
|
||||
occupied_entry.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -285,16 +285,17 @@ impl ChildProcess {
|
||||
span: Span,
|
||||
callback: Option<PostWaitCallback>,
|
||||
) -> Result<Self, ShellError> {
|
||||
let (stdout, stderr) = if let Some(combined) = reader {
|
||||
(Some(combined), None)
|
||||
} else {
|
||||
let stdout = child.as_mut().stdout.take().map(convert_file);
|
||||
let stderr = child.as_mut().stderr.take().map(convert_file);
|
||||
let (stdout, stderr) = match reader {
|
||||
Some(combined) => (Some(combined), None),
|
||||
None => {
|
||||
let stdout = child.as_mut().stdout.take().map(convert_file);
|
||||
let stderr = child.as_mut().stderr.take().map(convert_file);
|
||||
|
||||
if swap {
|
||||
(stderr, stdout)
|
||||
} else {
|
||||
(stdout, stderr)
|
||||
if swap {
|
||||
(stderr, stdout)
|
||||
} else {
|
||||
(stdout, stderr)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -382,11 +383,11 @@ impl ChildProcess {
|
||||
)));
|
||||
}
|
||||
|
||||
let bytes = if let Some(stdout) = self.stdout {
|
||||
collect_bytes(stdout).map_err(|err| IoError::new(err, self.span, None))?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let bytes = (self.stdout)
|
||||
.map(collect_bytes)
|
||||
.transpose()
|
||||
.map_err(|err| IoError::new(err, self.span, None))?
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut exit_status = self
|
||||
.exit_status
|
||||
@@ -462,39 +463,32 @@ impl ChildProcess {
|
||||
|
||||
pub fn wait_with_output(self) -> Result<ProcessOutput, ShellError> {
|
||||
let from_io_error = IoError::factory(self.span, None);
|
||||
let (stdout, stderr) = if let Some(stdout) = self.stdout {
|
||||
let stderr = self
|
||||
.stderr
|
||||
.map(|stderr| thread::Builder::new().spawn(move || collect_bytes(stderr)))
|
||||
.transpose()
|
||||
.map_err(&from_io_error)?;
|
||||
|
||||
let stdout = collect_bytes(stdout).map_err(&from_io_error)?;
|
||||
let (stdout, stderr) = match (self.stdout, self.stderr) {
|
||||
(None, None) => (None, None),
|
||||
(None, Some(stderr)) => (None, Some(collect_bytes(stderr).map_err(&from_io_error)?)),
|
||||
(Some(stdout), None) => (Some(collect_bytes(stdout).map_err(&from_io_error)?), None),
|
||||
(Some(stdout), Some(stderr)) => {
|
||||
let stderr = thread::Builder::new()
|
||||
.spawn(move || collect_bytes(stderr))
|
||||
.map_err(&from_io_error)?;
|
||||
|
||||
let stderr = stderr
|
||||
.map(|handle| {
|
||||
handle.join().map_err(|e| match e.downcast::<io::Error>() {
|
||||
let stdout = collect_bytes(stdout).map_err(&from_io_error)?;
|
||||
|
||||
let stderr = stderr
|
||||
.join()
|
||||
.map_err(|e| match e.downcast::<io::Error>() {
|
||||
Ok(io) => from_io_error(*io).into(),
|
||||
Err(err) => ShellError::Generic(GenericError::new(
|
||||
"Unknown error",
|
||||
format!("{err:?}"),
|
||||
self.span,
|
||||
)),
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.transpose()
|
||||
.map_err(&from_io_error)?;
|
||||
})?
|
||||
.map_err(&from_io_error)?;
|
||||
|
||||
(Some(stdout), stderr)
|
||||
} else {
|
||||
let stderr = self
|
||||
.stderr
|
||||
.map(collect_bytes)
|
||||
.transpose()
|
||||
.map_err(&from_io_error)?;
|
||||
|
||||
(None, stderr)
|
||||
(Some(stdout), Some(stderr))
|
||||
}
|
||||
};
|
||||
|
||||
let mut exit_status = self
|
||||
|
||||
@@ -375,7 +375,7 @@ macro_rules! impl_from_value_for_uint {
|
||||
let span = v.span();
|
||||
const MAX: i64 = $max;
|
||||
match v {
|
||||
Value::Int { val, .. } | Value::Duration { val, .. } => {
|
||||
Value::Int { val, .. } => {
|
||||
match val {
|
||||
i64::MIN..=-1 => Err(ShellError::NeedsPositiveValue { span }),
|
||||
0..=MAX => Ok(val as $type),
|
||||
|
||||
Reference in new issue
Block a user