forked from extern/nushell
A fill
command to replace str lpad
and str rpad
(#7846)
# Description The point of this command is to allow you to be able to format ints, floats, filesizes, and strings with an alignment, padding, and a fill character, as strings. It's meant to take the place of `str lpad` and `str rpad`. ``` > help fill Fill and Align Search terms: display, render, format, pad, align Usage: > fill {flags} Flags: -h, --help - Display the help message for this command -w, --width <Int> - The width of the output. Defaults to 1 -a, --alignment <String> - The alignment of the output. Defaults to Left (Left(l), Right(r), Center(c/m), MiddleRight(cr/mr)) -c, --character <String> - The character to fill with. Defaults to ' ' (space) Signatures: <number> | fill -> <string> <string> | fill -> <string> Examples: Fill a string on the left side to a width of 15 with the character '─' > 'nushell' | fill -a l -c '─' -w 15 Fill a string on the right side to a width of 15 with the character '─' > 'nushell' | fill -a r -c '─' -w 15 Fill a string on both sides to a width of 15 with the character '─' > 'nushell' | fill -a m -c '─' -w 15 Fill a number on the left side to a width of 5 with the character '0' > 1 | fill --alignment right --character 0 --width 5 Fill a filesize on the left side to a width of 5 with the character '0' > 1kib | fill --alignment middle --character 0 --width 10 ```  # User-Facing Changes Deprecated `str lpad` and `str rpad`. # 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 -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # 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.
This commit is contained in:
parent
c31225fdcf
commit
00601f1835
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -2804,10 +2804,11 @@ dependencies = [
|
|||||||
"terminal_size 0.2.1",
|
"terminal_size 0.2.1",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"titlecase",
|
"titlecase",
|
||||||
"toml 0.7.1",
|
"toml 0.7.2",
|
||||||
"trash",
|
"trash",
|
||||||
"umask",
|
"umask",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
"url",
|
"url",
|
||||||
"users 0.11.0",
|
"users 0.11.0",
|
||||||
"uuid",
|
"uuid",
|
||||||
@ -5288,9 +5289,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.7.1"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "772c1426ab886e7362aedf4abc9c0d1348a979517efedfc25862944d10137af0"
|
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
@ -5309,9 +5310,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.19.1"
|
version = "0.19.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90a238ee2e6ede22fb95350acc78e21dc40da00bb66c0334bde83de4ed89424e"
|
checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"nom8",
|
"nom8",
|
||||||
|
@ -86,8 +86,8 @@ sysinfo = "0.27.7"
|
|||||||
terminal_size = "0.2.1"
|
terminal_size = "0.2.1"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
titlecase = "2.0.0"
|
titlecase = "2.0.0"
|
||||||
|
unicode-segmentation = "1.10.0"
|
||||||
toml = "0.7.1"
|
toml = "0.7.1"
|
||||||
unicode-segmentation = "1.8.0"
|
|
||||||
url = "2.2.1"
|
url = "2.2.1"
|
||||||
percent-encoding = "2.2.0"
|
percent-encoding = "2.2.0"
|
||||||
uuid = { version = "1.2.2", features = ["v4"] }
|
uuid = { version = "1.2.2", features = ["v4"] }
|
||||||
@ -96,6 +96,7 @@ reedline = { version = "0.15.0", features = ["bashisms", "sqlite"] }
|
|||||||
wax = { version = "0.5.0" }
|
wax = { version = "0.5.0" }
|
||||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||||
sqlparser = { version = "0.30.0", features = ["serde"], optional = true }
|
sqlparser = { version = "0.30.0", features = ["serde"], optional = true }
|
||||||
|
unicode-width = "0.1.10"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.10.1"
|
winreg = "0.10.1"
|
||||||
|
268
crates/nu-command/src/conversions/fill.rs
Normal file
268
crates/nu-command/src/conversions/fill.rs
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Call, CellPath},
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Fill;
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
width: usize,
|
||||||
|
alignment: FillAlignment,
|
||||||
|
character: String,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum FillAlignment {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Middle,
|
||||||
|
MiddleRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command for Fill {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"fill"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Fill and Align"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("fill")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Int, Type::String),
|
||||||
|
(Type::Float, Type::String),
|
||||||
|
(Type::String, Type::String),
|
||||||
|
(Type::Filesize, Type::String),
|
||||||
|
])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.named(
|
||||||
|
"width",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"The width of the output. Defaults to 1",
|
||||||
|
Some('w'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"alignment",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"The alignment of the output. Defaults to Left (Left(l), Right(r), Center(c/m), MiddleRight(cr/mr))",
|
||||||
|
Some('a'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"character",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"The character to fill with. Defaults to ' ' (space)",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
|
.category(Category::Conversions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["display", "render", "format", "pad", "align"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Fill a string on the left side to a width of 15 with the character '─'",
|
||||||
|
example: "'nushell' | fill -a l -c '─' -w 15",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "nushell────────".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Fill a string on the right side to a width of 15 with the character '─'",
|
||||||
|
example: "'nushell' | fill -a r -c '─' -w 15",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "────────nushell".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Fill a string on both sides to a width of 15 with the character '─'",
|
||||||
|
example: "'nushell' | fill -a m -c '─' -w 15",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "────nushell────".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Fill a number on the left side to a width of 5 with the character '0'",
|
||||||
|
example: "1 | fill --alignment right --character 0 --width 5",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "00001".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Fill a number on both sides to a width of 5 with the character '0'",
|
||||||
|
example: "1.1 | fill --alignment center --character 0 --width 5",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "01.10".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Fill a filesize on the left side to a width of 5 with the character '0'",
|
||||||
|
example: "1kib | fill --alignment middle --character 0 --width 10",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "0001024000".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
fill(engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
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 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_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 = if let Some(arg) = width_arg { arg } else { 1 };
|
||||||
|
|
||||||
|
let character = if let Some(arg) = character_arg {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
" ".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let arg = Arguments {
|
||||||
|
width,
|
||||||
|
alignment,
|
||||||
|
character,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(action, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match input {
|
||||||
|
Value::Int { val, .. } => fill_int(*val, args, span),
|
||||||
|
Value::Filesize { val, .. } => fill_int(*val, args, span),
|
||||||
|
Value::Float { val, .. } => fill_float(*val, args, span),
|
||||||
|
Value::String { val, .. } => fill_string(val, args, span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => input.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"int, filesize, float, string".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_float(num: f64, args: &Arguments, span: Span) -> Value {
|
||||||
|
let s = num.to_string();
|
||||||
|
let out_str = pad(&s, args.width, &args.character, args.alignment, false);
|
||||||
|
|
||||||
|
Value::String { val: out_str, span }
|
||||||
|
}
|
||||||
|
fn fill_int(num: i64, args: &Arguments, span: Span) -> Value {
|
||||||
|
let s = num.to_string();
|
||||||
|
let out_str = pad(&s, args.width, &args.character, args.alignment, false);
|
||||||
|
|
||||||
|
Value::String { val: out_str, span }
|
||||||
|
}
|
||||||
|
fn fill_string(s: &str, args: &Arguments, span: Span) -> Value {
|
||||||
|
let out_str = pad(s, args.width, &args.character, args.alignment, false);
|
||||||
|
|
||||||
|
Value::String { val: out_str, span }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad(s: &str, width: usize, pad_char: &str, alignment: FillAlignment, truncate: bool) -> String {
|
||||||
|
// Attribution: Most of this function was taken from https://github.com/ogham/rust-pad and tweaked. Thank you!
|
||||||
|
// Use width instead of len for graphical display
|
||||||
|
let cols = UnicodeWidthStr::width(s);
|
||||||
|
|
||||||
|
if cols >= width {
|
||||||
|
if truncate {
|
||||||
|
return s[..width].to_string();
|
||||||
|
} else {
|
||||||
|
return s.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let diff = width - cols;
|
||||||
|
|
||||||
|
let (left_pad, right_pad) = match alignment {
|
||||||
|
FillAlignment::Left => (0, diff),
|
||||||
|
FillAlignment::Right => (diff, 0),
|
||||||
|
FillAlignment::Middle => (diff / 2, diff - diff / 2),
|
||||||
|
FillAlignment::MiddleRight => (diff - diff / 2, diff / 2),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_str = String::new();
|
||||||
|
for _ in 0..left_pad {
|
||||||
|
new_str.push_str(pad_char)
|
||||||
|
}
|
||||||
|
new_str.push_str(s);
|
||||||
|
for _ in 0..right_pad {
|
||||||
|
new_str.push_str(pad_char)
|
||||||
|
}
|
||||||
|
new_str
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Fill {})
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
mod fill;
|
||||||
mod fmt;
|
mod fmt;
|
||||||
pub(crate) mod into;
|
pub(crate) mod into;
|
||||||
|
|
||||||
|
pub use fill::Fill;
|
||||||
pub use fmt::Fmt;
|
pub use fmt::Fmt;
|
||||||
pub use into::*;
|
pub use into::*;
|
||||||
|
@ -227,10 +227,8 @@ pub fn create_default_context() -> EngineState {
|
|||||||
StrIndexOf,
|
StrIndexOf,
|
||||||
StrKebabCase,
|
StrKebabCase,
|
||||||
StrLength,
|
StrLength,
|
||||||
StrLpad,
|
|
||||||
StrPascalCase,
|
StrPascalCase,
|
||||||
StrReverse,
|
StrReverse,
|
||||||
StrRpad,
|
|
||||||
StrScreamingSnakeCase,
|
StrScreamingSnakeCase,
|
||||||
StrSnakeCase,
|
StrSnakeCase,
|
||||||
StrStartsWith,
|
StrStartsWith,
|
||||||
@ -370,6 +368,7 @@ pub fn create_default_context() -> EngineState {
|
|||||||
|
|
||||||
// Conversions
|
// Conversions
|
||||||
bind_command! {
|
bind_command! {
|
||||||
|
Fill,
|
||||||
Fmt,
|
Fmt,
|
||||||
Into,
|
Into,
|
||||||
IntoBool,
|
IntoBool,
|
||||||
@ -484,6 +483,8 @@ pub fn create_default_context() -> EngineState {
|
|||||||
// Deprecated
|
// Deprecated
|
||||||
bind_command! {
|
bind_command! {
|
||||||
HashBase64,
|
HashBase64,
|
||||||
|
LPadDeprecated,
|
||||||
|
RPadDeprecated,
|
||||||
Source,
|
Source,
|
||||||
StrDatetimeDeprecated,
|
StrDatetimeDeprecated,
|
||||||
StrDecimalDeprecated,
|
StrDecimalDeprecated,
|
||||||
|
@ -20,5 +20,7 @@ pub fn deprecated_commands() -> HashMap<String, String> {
|
|||||||
),
|
),
|
||||||
("fetch".to_string(), "http get".to_string()),
|
("fetch".to_string(), "http get".to_string()),
|
||||||
("post".to_string(), "http post".to_string()),
|
("post".to_string(), "http post".to_string()),
|
||||||
|
("str lpad".to_string(), "fill".to_string()),
|
||||||
|
("str rpad".to_string(), "fill".to_string()),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
35
crates/nu-command/src/deprecated/lpad.rs
Normal file
35
crates/nu-command/src/deprecated/lpad.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{PipelineData, ShellError, Signature};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LPadDeprecated;
|
||||||
|
|
||||||
|
impl Command for LPadDeprecated {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"str lpad"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name()).category(Category::Deprecated)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Deprecated command"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_: &EngineState,
|
||||||
|
_: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Err(nu_protocol::ShellError::DeprecatedCommand(
|
||||||
|
self.name().to_string(),
|
||||||
|
"fill".to_owned(),
|
||||||
|
call.head,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
mod deprecated_commands;
|
mod deprecated_commands;
|
||||||
mod hash_base64;
|
mod hash_base64;
|
||||||
|
mod lpad;
|
||||||
mod math_eval;
|
mod math_eval;
|
||||||
|
mod rpad;
|
||||||
mod source;
|
mod source;
|
||||||
mod str_datetime;
|
mod str_datetime;
|
||||||
mod str_decimal;
|
mod str_decimal;
|
||||||
@ -9,7 +11,9 @@ mod str_int;
|
|||||||
|
|
||||||
pub use deprecated_commands::*;
|
pub use deprecated_commands::*;
|
||||||
pub use hash_base64::HashBase64;
|
pub use hash_base64::HashBase64;
|
||||||
|
pub use lpad::LPadDeprecated;
|
||||||
pub use math_eval::SubCommand as MathEvalDeprecated;
|
pub use math_eval::SubCommand as MathEvalDeprecated;
|
||||||
|
pub use rpad::RPadDeprecated;
|
||||||
pub use source::Source;
|
pub use source::Source;
|
||||||
pub use str_datetime::StrDatetimeDeprecated;
|
pub use str_datetime::StrDatetimeDeprecated;
|
||||||
pub use str_decimal::StrDecimalDeprecated;
|
pub use str_decimal::StrDecimalDeprecated;
|
||||||
|
35
crates/nu-command/src/deprecated/rpad.rs
Normal file
35
crates/nu-command/src/deprecated/rpad.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{PipelineData, ShellError, Signature};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RPadDeprecated;
|
||||||
|
|
||||||
|
impl Command for RPadDeprecated {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"str rpad"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name()).category(Category::Deprecated)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Deprecated command"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_: &EngineState,
|
||||||
|
_: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Err(nu_protocol::ShellError::DeprecatedCommand(
|
||||||
|
self.name().to_string(),
|
||||||
|
"fill".to_owned(),
|
||||||
|
call.head,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -1,165 +0,0 @@
|
|||||||
use crate::input_handler::{operate, CmdArgument};
|
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::ast::CellPath;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::Category;
|
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
|
||||||
|
|
||||||
struct Arguments {
|
|
||||||
length: Option<i64>,
|
|
||||||
character: Option<String>,
|
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CmdArgument for Arguments {
|
|
||||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
|
||||||
self.cell_paths.take()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SubCommand;
|
|
||||||
|
|
||||||
impl Command for SubCommand {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"str lpad"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("str lpad")
|
|
||||||
.input_output_types(vec![(Type::String, Type::String)])
|
|
||||||
.vectorizes_over_list(true)
|
|
||||||
.required_named("length", SyntaxShape::Int, "length to pad to", Some('l'))
|
|
||||||
.required_named(
|
|
||||||
"character",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"character to pad with",
|
|
||||||
Some('c'),
|
|
||||||
)
|
|
||||||
.rest(
|
|
||||||
"rest",
|
|
||||||
SyntaxShape::CellPath,
|
|
||||||
"For a data structure input, pad strings at the given cell paths",
|
|
||||||
)
|
|
||||||
.category(Category::Strings)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Left-pad a string to a specific length"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["append", "truncate", "padding"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
|
||||||
let args = Arguments {
|
|
||||||
length: call.get_flag(engine_state, stack, "length")?,
|
|
||||||
character: call.get_flag(engine_state, stack, "character")?,
|
|
||||||
cell_paths,
|
|
||||||
};
|
|
||||||
|
|
||||||
if args.length.expect("this exists") < 0 {
|
|
||||||
return Err(ShellError::TypeMismatch(
|
|
||||||
String::from("The length of the string cannot be negative"),
|
|
||||||
call.head,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Left-pad a string with asterisks until it's 10 characters wide",
|
|
||||||
example: "'nushell' | str lpad -l 10 -c '*'",
|
|
||||||
result: Some(Value::test_string("***nushell")),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Left-pad a string with zeroes until it's 10 character wide",
|
|
||||||
example: "'123' | str lpad -l 10 -c '0'",
|
|
||||||
result: Some(Value::test_string("0000000123")),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Use lpad to truncate a string to its last three characters",
|
|
||||||
example: "'123456789' | str lpad -l 3 -c '0'",
|
|
||||||
result: Some(Value::test_string("789")),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Use lpad to pad Unicode",
|
|
||||||
example: "'▉' | str lpad -l 10 -c '▉'",
|
|
||||||
result: Some(Value::test_string("▉▉▉▉▉▉▉▉▉▉")),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action(
|
|
||||||
input: &Value,
|
|
||||||
Arguments {
|
|
||||||
character, length, ..
|
|
||||||
}: &Arguments,
|
|
||||||
head: Span,
|
|
||||||
) -> Value {
|
|
||||||
match &input {
|
|
||||||
Value::String { val, .. } => match length {
|
|
||||||
Some(x) => {
|
|
||||||
let s = *x as usize;
|
|
||||||
if s < val.len() {
|
|
||||||
Value::String {
|
|
||||||
val: val
|
|
||||||
.chars()
|
|
||||||
.rev()
|
|
||||||
.take(s)
|
|
||||||
.collect::<String>()
|
|
||||||
.chars()
|
|
||||||
.rev()
|
|
||||||
.collect::<String>(),
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let c = character.as_ref().expect("we already know this flag needs to exist because the command is type checked before we call the action function");
|
|
||||||
let mut res = c.repeat(s - val.chars().count());
|
|
||||||
res += val;
|
|
||||||
Value::String {
|
|
||||||
val: res,
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Value::Error {
|
|
||||||
error: ShellError::TypeMismatch(String::from("Length argument is missing"), head),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Value::Error { .. } => input.clone(),
|
|
||||||
_ => Value::Error {
|
|
||||||
error: ShellError::OnlySupportsThisInputType(
|
|
||||||
"string".into(),
|
|
||||||
input.get_type().to_string(),
|
|
||||||
head,
|
|
||||||
input.expect_span(),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,10 +6,8 @@ mod ends_with;
|
|||||||
mod index_of;
|
mod index_of;
|
||||||
mod join;
|
mod join;
|
||||||
mod length;
|
mod length;
|
||||||
mod lpad;
|
|
||||||
mod replace;
|
mod replace;
|
||||||
mod reverse;
|
mod reverse;
|
||||||
mod rpad;
|
|
||||||
mod starts_with;
|
mod starts_with;
|
||||||
mod substring;
|
mod substring;
|
||||||
mod trim;
|
mod trim;
|
||||||
@ -22,10 +20,8 @@ pub use ends_with::SubCommand as StrEndswith;
|
|||||||
pub use index_of::SubCommand as StrIndexOf;
|
pub use index_of::SubCommand as StrIndexOf;
|
||||||
pub use join::*;
|
pub use join::*;
|
||||||
pub use length::SubCommand as StrLength;
|
pub use length::SubCommand as StrLength;
|
||||||
pub use lpad::SubCommand as StrLpad;
|
|
||||||
pub use replace::SubCommand as StrReplace;
|
pub use replace::SubCommand as StrReplace;
|
||||||
pub use reverse::SubCommand as StrReverse;
|
pub use reverse::SubCommand as StrReverse;
|
||||||
pub use rpad::SubCommand as StrRpad;
|
|
||||||
pub use starts_with::SubCommand as StrStartsWith;
|
pub use starts_with::SubCommand as StrStartsWith;
|
||||||
pub use substring::SubCommand as StrSubstring;
|
pub use substring::SubCommand as StrSubstring;
|
||||||
pub use trim::Trim as StrTrim;
|
pub use trim::Trim as StrTrim;
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
use crate::input_handler::{operate, CmdArgument};
|
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::ast::CellPath;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::Category;
|
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
|
||||||
|
|
||||||
struct Arguments {
|
|
||||||
length: Option<i64>,
|
|
||||||
character: Option<String>,
|
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CmdArgument for Arguments {
|
|
||||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
|
||||||
self.cell_paths.take()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SubCommand;
|
|
||||||
|
|
||||||
impl Command for SubCommand {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"str rpad"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("str rpad")
|
|
||||||
.input_output_types(vec![(Type::String, Type::String)])
|
|
||||||
.vectorizes_over_list(true)
|
|
||||||
.required_named("length", SyntaxShape::Int, "length to pad to", Some('l'))
|
|
||||||
.required_named(
|
|
||||||
"character",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"character to pad with",
|
|
||||||
Some('c'),
|
|
||||||
)
|
|
||||||
.rest(
|
|
||||||
"rest",
|
|
||||||
SyntaxShape::CellPath,
|
|
||||||
"For a data structure input, pad strings at the given cell paths",
|
|
||||||
)
|
|
||||||
.category(Category::Strings)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Right-pad a string to a specific length"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["append", "truncate", "padding"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
|
||||||
let args = Arguments {
|
|
||||||
length: call.get_flag(engine_state, stack, "length")?,
|
|
||||||
character: call.get_flag(engine_state, stack, "character")?,
|
|
||||||
cell_paths,
|
|
||||||
};
|
|
||||||
|
|
||||||
if args.length.expect("this exists") < 0 {
|
|
||||||
return Err(ShellError::TypeMismatch(
|
|
||||||
String::from("The length of the string cannot be negative"),
|
|
||||||
call.head,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Right-pad a string with asterisks until it's 10 characters wide",
|
|
||||||
example: "'nushell' | str rpad -l 10 -c '*'",
|
|
||||||
result: Some(Value::test_string("nushell***")),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Right-pad a string with zeroes until it's 10 characters wide",
|
|
||||||
example: "'123' | str rpad -l 10 -c '0'",
|
|
||||||
result: Some(Value::test_string("1230000000")),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Use rpad to truncate a string to its first three characters",
|
|
||||||
example: "'123456789' | str rpad -l 3 -c '0'",
|
|
||||||
result: Some(Value::test_string("123")),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Use rpad to pad Unicode",
|
|
||||||
example: "'▉' | str rpad -l 10 -c '▉'",
|
|
||||||
result: Some(Value::test_string("▉▉▉▉▉▉▉▉▉▉")),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action(
|
|
||||||
input: &Value,
|
|
||||||
Arguments {
|
|
||||||
character, length, ..
|
|
||||||
}: &Arguments,
|
|
||||||
head: Span,
|
|
||||||
) -> Value {
|
|
||||||
match &input {
|
|
||||||
Value::String { val, .. } => match length {
|
|
||||||
Some(x) => {
|
|
||||||
let s = *x as usize;
|
|
||||||
if s < val.len() {
|
|
||||||
Value::String {
|
|
||||||
val: val.chars().take(s).collect::<String>(),
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut res = val.to_string();
|
|
||||||
res += &character.as_ref().expect("we already know this flag needs to exist because the command is type checked before we call the action function").repeat(s - val.chars().count());
|
|
||||||
Value::String {
|
|
||||||
val: res,
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Value::Error {
|
|
||||||
error: ShellError::TypeMismatch(String::from("Length argument is missing"), head),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Value::Error { .. } => input.clone(),
|
|
||||||
_ => Value::Error {
|
|
||||||
error: ShellError::OnlySupportsThisInputType(
|
|
||||||
"string".into(),
|
|
||||||
input.get_type().to_string(),
|
|
||||||
head,
|
|
||||||
input.expect_span(),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -224,7 +224,7 @@ fn commands_have_usage() -> TestResult {
|
|||||||
#[test]
|
#[test]
|
||||||
fn equals_separates_long_flag() -> TestResult {
|
fn equals_separates_long_flag() -> TestResult {
|
||||||
run_test(
|
run_test(
|
||||||
r#"'nushell' | str lpad --length=10 --character='-'"#,
|
r#"'nushell' | fill --alignment right --width=10 --character='-'"#,
|
||||||
"---nushell",
|
"---nushell",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user