mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 11:35:43 +02:00
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:
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 {})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user