mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 11:05:40 +02:00
REFACTOR: move the 0% commands to nu-cmd-extra
(#9404)
requires - https://github.com/nushell/nushell/pull/9455 # ⚙️ Description in this PR i move the commands we've all agreed, in the core team, to move out of the core Nushell to the `extra` feature. > **Warning** > in the first commits here, i've > - moved the implementations to `nu-cmd-extra` > - removed the declaration of all the commands below from `nu-command` > - made sure the commands were not available anymore with `cargo run -- -n` ## the list of commands to move with the current command table downloaded as `commands.csv`, i've run ```bash let commands = ( open commands.csv | where is_plugin == "FALSE" and category != "deprecated" | select name category "approv. %" | rename name category approval | insert treated {|it| ( ($it.approval == 100) or # all the core team agreed on them ($it.name | str starts-with "bits") or # see https://github.com/nushell/nushell/pull/9241 ($it.name | str starts-with "dfr") # see https://github.com/nushell/nushell/pull/9327 )} ) ``` to preprocess them and then ```bash $commands | where {|it| (not $it.treated) and ($it.approval == 0)} ``` to get all untreated commands with no approval, which gives ``` ╭────┬───────────────┬─────────┬─────────────┬──────────╮ │ # │ name │ treated │ category │ approval │ ├────┼───────────────┼─────────┼─────────────┼──────────┤ │ 0 │ fmt │ false │ conversions │ 0 │ │ 1 │ each while │ false │ filters │ 0 │ │ 2 │ roll │ false │ filters │ 0 │ │ 3 │ roll down │ false │ filters │ 0 │ │ 4 │ roll left │ false │ filters │ 0 │ │ 5 │ roll right │ false │ filters │ 0 │ │ 6 │ roll up │ false │ filters │ 0 │ │ 7 │ rotate │ false │ filters │ 0 │ │ 8 │ update cells │ false │ filters │ 0 │ │ 9 │ decode hex │ false │ formats │ 0 │ │ 10 │ encode hex │ false │ formats │ 0 │ │ 11 │ from url │ false │ formats │ 0 │ │ 12 │ to html │ false │ formats │ 0 │ │ 13 │ ansi gradient │ false │ platform │ 0 │ │ 14 │ ansi link │ false │ platform │ 0 │ │ 15 │ format │ false │ strings │ 0 │ ╰────┴───────────────┴─────────┴─────────────┴──────────╯ ``` # 🖌️ User-Facing Changes ``` $nothing ``` # 🧪 Tests + Formatting - ⚫ `toolkit fmt` - ⚫ `toolkit clippy` - ⚫ `toolkit test` - ⚫ `toolkit test stdlib` # 📖 After Submitting ``` $nothing ``` # 🔍 For reviewers ```bash $commands | where {|it| (not $it.treated) and ($it.approval == 0)} | each {|command| try { help $command.name | ignore } catch {|e| $"($command.name): ($e.msg)" } } ``` should give no output in `cargo run --features extra -- -n` and a table with 16 lines in `cargo run -- -n`
This commit is contained in:
@ -1,179 +0,0 @@
|
||||
use nu_cmd_base::input_handler::{operate, CellPathOnlyArgs};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Fmt;
|
||||
|
||||
impl Command for Fmt {
|
||||
fn name(&self) -> &str {
|
||||
"fmt"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Format a number."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("fmt")
|
||||
.input_output_types(vec![(Type::Number, Type::Record(vec![]))])
|
||||
.category(Category::Conversions)
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["display", "render", "format"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get a record containing multiple formats for the number 42",
|
||||
example: "42 | fmt",
|
||||
result: Some(Value::Record {
|
||||
cols: vec![
|
||||
"binary".into(),
|
||||
"debug".into(),
|
||||
"display".into(),
|
||||
"lowerexp".into(),
|
||||
"lowerhex".into(),
|
||||
"octal".into(),
|
||||
"upperexp".into(),
|
||||
"upperhex".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::test_string("0b101010"),
|
||||
Value::test_string("42"),
|
||||
Value::test_string("42"),
|
||||
Value::test_string("4.2e1"),
|
||||
Value::test_string("0x2a"),
|
||||
Value::test_string("0o52"),
|
||||
Value::test_string("4.2E1"),
|
||||
Value::test_string("0x2A"),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
fmt(engine_state, stack, call, input)
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt(
|
||||
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 args = CellPathOnlyArgs::from(cell_paths);
|
||||
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
match input {
|
||||
Value::Float { val, .. } => fmt_it_64(*val, span),
|
||||
Value::Int { val, .. } => fmt_it(*val, span),
|
||||
Value::Filesize { val, .. } => fmt_it(*val, span),
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "float , integer or filesize".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: other.expect_span(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_it(num: i64, span: Span) -> Value {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
cols.push("binary".into());
|
||||
vals.push(Value::string(format!("{num:#b}"), span));
|
||||
|
||||
cols.push("debug".into());
|
||||
vals.push(Value::string(format!("{num:#?}"), span));
|
||||
|
||||
cols.push("display".into());
|
||||
vals.push(Value::string(format!("{num}"), span));
|
||||
|
||||
cols.push("lowerexp".into());
|
||||
vals.push(Value::string(format!("{num:#e}"), span));
|
||||
|
||||
cols.push("lowerhex".into());
|
||||
vals.push(Value::string(format!("{num:#x}"), span));
|
||||
|
||||
cols.push("octal".into());
|
||||
vals.push(Value::string(format!("{num:#o}"), span));
|
||||
|
||||
// cols.push("pointer".into());
|
||||
// vals.push(Value::string(format!("{:#p}", &num), span));
|
||||
|
||||
cols.push("upperexp".into());
|
||||
vals.push(Value::string(format!("{num:#E}"), span));
|
||||
|
||||
cols.push("upperhex".into());
|
||||
vals.push(Value::string(format!("{num:#X}"), span));
|
||||
|
||||
Value::Record { cols, vals, span }
|
||||
}
|
||||
|
||||
fn fmt_it_64(num: f64, span: Span) -> Value {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
cols.push("binary".into());
|
||||
vals.push(Value::string(format!("{:b}", num.to_bits()), span));
|
||||
|
||||
cols.push("debug".into());
|
||||
vals.push(Value::string(format!("{num:#?}"), span));
|
||||
|
||||
cols.push("display".into());
|
||||
vals.push(Value::string(format!("{num}"), span));
|
||||
|
||||
cols.push("lowerexp".into());
|
||||
vals.push(Value::string(format!("{num:#e}"), span));
|
||||
|
||||
cols.push("lowerhex".into());
|
||||
vals.push(Value::string(format!("{:0x}", num.to_bits()), span));
|
||||
|
||||
cols.push("octal".into());
|
||||
vals.push(Value::string(format!("{:0o}", num.to_bits()), span));
|
||||
|
||||
// cols.push("pointer".into());
|
||||
// vals.push(Value::string(format!("{:#p}", &num), span));
|
||||
|
||||
cols.push("upperexp".into());
|
||||
vals.push(Value::string(format!("{num:#E}"), span));
|
||||
|
||||
cols.push("upperhex".into());
|
||||
vals.push(Value::string(format!("{:0X}", num.to_bits()), span));
|
||||
|
||||
Value::Record { cols, vals, span }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Fmt {})
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
mod fill;
|
||||
mod fmt;
|
||||
pub(crate) mod into;
|
||||
|
||||
pub use fill::Fill;
|
||||
pub use fmt::Fmt;
|
||||
pub use into::*;
|
||||
|
@ -41,7 +41,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
DropColumn,
|
||||
DropNth,
|
||||
Each,
|
||||
EachWhile,
|
||||
Empty,
|
||||
Enumerate,
|
||||
Every,
|
||||
@ -72,12 +71,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
Reject,
|
||||
Rename,
|
||||
Reverse,
|
||||
Roll,
|
||||
RollDown,
|
||||
RollUp,
|
||||
RollLeft,
|
||||
RollRight,
|
||||
Rotate,
|
||||
Select,
|
||||
Shuffle,
|
||||
Skip,
|
||||
@ -91,7 +84,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
UniqBy,
|
||||
Upsert,
|
||||
Update,
|
||||
UpdateCells,
|
||||
Values,
|
||||
Where,
|
||||
Window,
|
||||
@ -176,11 +168,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
Encode,
|
||||
DecodeBase64,
|
||||
EncodeBase64,
|
||||
DecodeHex,
|
||||
EncodeHex,
|
||||
DetectColumns,
|
||||
Format,
|
||||
FileSize,
|
||||
Parse,
|
||||
Size,
|
||||
Split,
|
||||
@ -231,9 +219,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
// Platform
|
||||
bind_command! {
|
||||
Ansi,
|
||||
AnsiGradient,
|
||||
AnsiStrip,
|
||||
AnsiLink,
|
||||
Clear,
|
||||
Du,
|
||||
Input,
|
||||
@ -271,14 +257,12 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
FromSsv,
|
||||
FromToml,
|
||||
FromTsv,
|
||||
FromUrl,
|
||||
FromXlsx,
|
||||
FromXml,
|
||||
FromYaml,
|
||||
FromYml,
|
||||
To,
|
||||
ToCsv,
|
||||
ToHtml,
|
||||
ToJson,
|
||||
ToMd,
|
||||
ToNuon,
|
||||
@ -301,7 +285,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
// Conversions
|
||||
bind_command! {
|
||||
Fill,
|
||||
Fmt,
|
||||
Into,
|
||||
IntoBool,
|
||||
IntoBinary,
|
||||
|
@ -1,214 +0,0 @@
|
||||
use nu_engine::{eval_block_with_early_return, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
|
||||
Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EachWhile;
|
||||
|
||||
impl Command for EachWhile {
|
||||
fn name(&self) -> &str {
|
||||
"each while"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block on each row of the input list until a null is found, then create a new list with the results."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["for", "loop", "iterate"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::Any)),
|
||||
),
|
||||
(Type::Table(vec![]), Type::List(Box::new(Type::Any))),
|
||||
])
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
|
||||
"the closure to run",
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
let stream_test_1 = vec![Value::test_int(2), Value::test_int(4)];
|
||||
let stream_test_2 = vec![
|
||||
Value::test_string("Output: 1"),
|
||||
Value::test_string("Output: 2"),
|
||||
];
|
||||
|
||||
vec![
|
||||
Example {
|
||||
example: "[1 2 3 2 1] | each while {|e| if $e < 3 { $e * 2 } }",
|
||||
description: "Produces a list of each element before the 3, doubled",
|
||||
result: Some(Value::List {
|
||||
vals: stream_test_1,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
example: r#"[1 2 stop 3 4] | each while {|e| if $e != 'stop' { $"Output: ($e)" } }"#,
|
||||
description: "Output elements until reaching 'stop'",
|
||||
result: Some(Value::List {
|
||||
vals: stream_test_2,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
example: r#"[1 2 3] | enumerate | each while {|e| if $e.item < 2 { $"value ($e.item) at ($e.index)!"} }"#,
|
||||
description: "Iterate over each element, printing the matching value and its index",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_string("value 1 at 0!")],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let capture_block: Closure = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let metadata = input.metadata();
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
let block = engine_state.get_block(capture_block.block_id).clone();
|
||||
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
let span = call.head;
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Value(Value::Range { .. }, ..)
|
||||
| PipelineData::Value(Value::List { .. }, ..)
|
||||
| PipelineData::ListStream { .. } => Ok(input
|
||||
// TODO: Could this be changed to .into_interruptible_iter(ctrlc) ?
|
||||
.into_iter()
|
||||
.map_while(move |x| {
|
||||
// with_env() is used here to ensure that each iteration uses
|
||||
// a different set of environment variables.
|
||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block_with_early_return(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
x.into_pipeline_data(),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
) {
|
||||
Ok(v) => {
|
||||
let value = v.into_value(span);
|
||||
if value.is_nothing() {
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
.fuse()
|
||||
.into_pipeline_data(ctrlc)),
|
||||
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::empty()),
|
||||
PipelineData::ExternalStream {
|
||||
stdout: Some(stream),
|
||||
..
|
||||
} => Ok(stream
|
||||
.into_iter()
|
||||
.map_while(move |x| {
|
||||
// with_env() is used here to ensure that each iteration uses
|
||||
// a different set of environment variables.
|
||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
let x = match x {
|
||||
Ok(x) => x,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block_with_early_return(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
x.into_pipeline_data(),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
) {
|
||||
Ok(v) => {
|
||||
let value = v.into_value(span);
|
||||
if value.is_nothing() {
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
.fuse()
|
||||
.into_pipeline_data(ctrlc)),
|
||||
// This match allows non-iterables to be accepted,
|
||||
// which is currently considered undesirable (Nov 2022).
|
||||
PipelineData::Value(x, ..) => {
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
}
|
||||
}
|
||||
|
||||
eval_block_with_early_return(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
x.into_pipeline_data(),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)
|
||||
}
|
||||
}
|
||||
.map(|x| x.set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(EachWhile {})
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ mod compact;
|
||||
mod default;
|
||||
mod drop;
|
||||
mod each;
|
||||
mod each_while;
|
||||
mod empty;
|
||||
mod enumerate;
|
||||
mod every;
|
||||
@ -33,8 +32,6 @@ mod reduce;
|
||||
mod reject;
|
||||
mod rename;
|
||||
mod reverse;
|
||||
mod roll;
|
||||
mod rotate;
|
||||
mod select;
|
||||
mod shuffle;
|
||||
mod skip;
|
||||
@ -46,7 +43,6 @@ mod transpose;
|
||||
mod uniq;
|
||||
mod uniq_by;
|
||||
mod update;
|
||||
mod update_cells;
|
||||
mod upsert;
|
||||
mod utils;
|
||||
mod values;
|
||||
@ -63,7 +59,6 @@ pub use compact::Compact;
|
||||
pub use default::Default;
|
||||
pub use drop::*;
|
||||
pub use each::Each;
|
||||
pub use each_while::EachWhile;
|
||||
pub use empty::Empty;
|
||||
pub use enumerate::Enumerate;
|
||||
pub use every::Every;
|
||||
@ -90,8 +85,6 @@ pub use reduce::Reduce;
|
||||
pub use reject::Reject;
|
||||
pub use rename::Rename;
|
||||
pub use reverse::Reverse;
|
||||
pub use roll::*;
|
||||
pub use rotate::Rotate;
|
||||
pub use select::Select;
|
||||
pub use shuffle::Shuffle;
|
||||
pub use skip::*;
|
||||
@ -103,7 +96,6 @@ pub use transpose::Transpose;
|
||||
pub use uniq::*;
|
||||
pub use uniq_by::UniqBy;
|
||||
pub use update::Update;
|
||||
pub use update_cells::UpdateCells;
|
||||
pub use upsert::Upsert;
|
||||
pub use values::Values;
|
||||
pub use where_::Where;
|
||||
|
@ -1,104 +0,0 @@
|
||||
mod roll_;
|
||||
mod roll_down;
|
||||
mod roll_left;
|
||||
mod roll_right;
|
||||
mod roll_up;
|
||||
|
||||
use nu_protocol::{ShellError, Value};
|
||||
pub use roll_::Roll;
|
||||
pub use roll_down::RollDown;
|
||||
pub use roll_left::RollLeft;
|
||||
pub use roll_right::RollRight;
|
||||
pub use roll_up::RollUp;
|
||||
|
||||
enum VerticalDirection {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
fn vertical_rotate_value(
|
||||
value: Value,
|
||||
by: Option<usize>,
|
||||
direction: VerticalDirection,
|
||||
) -> Result<Value, ShellError> {
|
||||
match value {
|
||||
Value::List { mut vals, span } => {
|
||||
let rotations = by.map(|n| n % vals.len()).unwrap_or(1);
|
||||
let values = vals.as_mut_slice();
|
||||
|
||||
match direction {
|
||||
VerticalDirection::Up => values.rotate_left(rotations),
|
||||
VerticalDirection::Down => values.rotate_right(rotations),
|
||||
}
|
||||
|
||||
Ok(Value::List {
|
||||
vals: values.to_owned(),
|
||||
span,
|
||||
})
|
||||
}
|
||||
_ => Err(ShellError::TypeMismatch {
|
||||
err_message: "list".to_string(),
|
||||
span: value.span()?,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
enum HorizontalDirection {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
fn horizontal_rotate_value(
|
||||
value: Value,
|
||||
by: &Option<usize>,
|
||||
cells_only: bool,
|
||||
direction: &HorizontalDirection,
|
||||
) -> Result<Value, ShellError> {
|
||||
match value {
|
||||
Value::Record {
|
||||
mut cols,
|
||||
mut vals,
|
||||
span,
|
||||
} => {
|
||||
let rotations = by.map(|n| n % vals.len()).unwrap_or(1);
|
||||
|
||||
let columns = if cells_only {
|
||||
cols
|
||||
} else {
|
||||
let columns = cols.as_mut_slice();
|
||||
|
||||
match direction {
|
||||
HorizontalDirection::Right => columns.rotate_right(rotations),
|
||||
HorizontalDirection::Left => columns.rotate_left(rotations),
|
||||
}
|
||||
|
||||
columns.to_owned()
|
||||
};
|
||||
|
||||
let values = vals.as_mut_slice();
|
||||
|
||||
match direction {
|
||||
HorizontalDirection::Right => values.rotate_right(rotations),
|
||||
HorizontalDirection::Left => values.rotate_left(rotations),
|
||||
}
|
||||
|
||||
Ok(Value::Record {
|
||||
cols: columns,
|
||||
vals: values.to_owned(),
|
||||
span,
|
||||
})
|
||||
}
|
||||
Value::List { vals, span } => {
|
||||
let values = vals
|
||||
.into_iter()
|
||||
.map(|value| horizontal_rotate_value(value, by, cells_only, direction))
|
||||
.collect::<Result<Vec<Value>, ShellError>>()?;
|
||||
|
||||
Ok(Value::List { vals: values, span })
|
||||
}
|
||||
_ => Err(ShellError::TypeMismatch {
|
||||
err_message: "record".to_string(),
|
||||
span: value.span()?,
|
||||
}),
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use nu_engine::get_full_help;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Roll;
|
||||
|
||||
impl Command for Roll {
|
||||
fn name(&self) -> &str {
|
||||
"roll"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.category(Category::Filters)
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Rolling commands for tables."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(
|
||||
&Roll.signature(),
|
||||
&Roll.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
use super::{vertical_rotate_value, VerticalDirection};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RollDown;
|
||||
|
||||
impl Command for RollDown {
|
||||
fn name(&self) -> &str {
|
||||
"roll down"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move", "row"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
// TODO: It also operates on List
|
||||
.input_output_types(vec![(Type::Table(vec![]), Type::Table(vec![]))])
|
||||
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Roll table rows down."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
let columns = vec!["a".to_string(), "b".to_string()];
|
||||
vec![Example {
|
||||
description: "Rolls rows down of a table",
|
||||
example: "[[a b]; [1 2] [3 4] [5 6]] | roll down",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: columns.clone(),
|
||||
vals: vec![Value::test_int(5), Value::test_int(6)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: columns.clone(),
|
||||
vals: vec![Value::test_int(1), Value::test_int(2)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: columns,
|
||||
vals: vec![Value::test_int(3), Value::test_int(4)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let by: Option<usize> = call.get_flag(engine_state, stack, "by")?;
|
||||
let metadata = input.metadata();
|
||||
|
||||
let value = input.into_value(call.head);
|
||||
let rotated_value = vertical_rotate_value(value, by, VerticalDirection::Down)?;
|
||||
|
||||
Ok(rotated_value.into_pipeline_data().set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(RollDown {})
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
use super::{horizontal_rotate_value, HorizontalDirection};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RollLeft;
|
||||
|
||||
impl Command for RollLeft {
|
||||
fn name(&self) -> &str {
|
||||
"roll left"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move", "column"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_types(vec![
|
||||
(Type::Record(vec![]), Type::Record(vec![])),
|
||||
(Type::Table(vec![]), Type::Table(vec![])),
|
||||
])
|
||||
.named(
|
||||
"by",
|
||||
SyntaxShape::Int,
|
||||
"Number of columns to roll",
|
||||
Some('b'),
|
||||
)
|
||||
.switch(
|
||||
"cells-only",
|
||||
"rotates columns leaving headers fixed",
|
||||
Some('c'),
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Roll record or table columns left."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
let columns = vec!["a".to_string(), "b".to_string(), "c".to_string()];
|
||||
let rotated_columns = vec!["b".to_string(), "c".to_string(), "a".to_string()];
|
||||
vec![
|
||||
Example {
|
||||
description: "Rolls columns of a record to the left",
|
||||
example: "{a:1 b:2 c:3} | roll left",
|
||||
result: Some(Value::Record {
|
||||
cols: rotated_columns.clone(),
|
||||
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(1)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rolls columns of a table to the left",
|
||||
example: "[[a b c]; [1 2 3] [4 5 6]] | roll left",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: rotated_columns.clone(),
|
||||
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(1)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: rotated_columns,
|
||||
vals: vec![Value::test_int(5), Value::test_int(6), Value::test_int(4)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rolls columns to the left without changing column names",
|
||||
example: "[[a b c]; [1 2 3] [4 5 6]] | roll left --cells-only",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: columns.clone(),
|
||||
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(1)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: columns,
|
||||
vals: vec![Value::test_int(5), Value::test_int(6), Value::test_int(4)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let by: Option<usize> = call.get_flag(engine_state, stack, "by")?;
|
||||
let metadata = input.metadata();
|
||||
|
||||
let cells_only = call.has_flag("cells-only");
|
||||
let value = input.into_value(call.head);
|
||||
let rotated_value =
|
||||
horizontal_rotate_value(value, &by, cells_only, &HorizontalDirection::Left)?;
|
||||
|
||||
Ok(rotated_value.into_pipeline_data().set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(RollLeft {})
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
use super::{horizontal_rotate_value, HorizontalDirection};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RollRight;
|
||||
|
||||
impl Command for RollRight {
|
||||
fn name(&self) -> &str {
|
||||
"roll right"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move", "column"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_types(vec![
|
||||
(Type::Record(vec![]), Type::Record(vec![])),
|
||||
(Type::Table(vec![]), Type::Table(vec![])),
|
||||
])
|
||||
.named(
|
||||
"by",
|
||||
SyntaxShape::Int,
|
||||
"Number of columns to roll",
|
||||
Some('b'),
|
||||
)
|
||||
.switch(
|
||||
"cells-only",
|
||||
"rotates columns leaving headers fixed",
|
||||
Some('c'),
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Roll table columns right."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
let columns = vec!["a".to_string(), "b".to_string(), "c".to_string()];
|
||||
let rotated_columns = vec!["c".to_string(), "a".to_string(), "b".to_string()];
|
||||
vec![
|
||||
Example {
|
||||
description: "Rolls columns of a record to the right",
|
||||
example: "{a:1 b:2 c:3} | roll right",
|
||||
result: Some(Value::Record {
|
||||
cols: rotated_columns.clone(),
|
||||
vals: vec![Value::test_int(3), Value::test_int(1), Value::test_int(2)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rolls columns to the right",
|
||||
example: "[[a b c]; [1 2 3] [4 5 6]] | roll right",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: rotated_columns.clone(),
|
||||
vals: vec![Value::test_int(3), Value::test_int(1), Value::test_int(2)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: rotated_columns,
|
||||
vals: vec![Value::test_int(6), Value::test_int(4), Value::test_int(5)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rolls columns to the right with fixed headers",
|
||||
example: "[[a b c]; [1 2 3] [4 5 6]] | roll right --cells-only",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: columns.clone(),
|
||||
vals: vec![Value::test_int(3), Value::test_int(1), Value::test_int(2)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: columns,
|
||||
vals: vec![Value::test_int(6), Value::test_int(4), Value::test_int(5)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let by: Option<usize> = call.get_flag(engine_state, stack, "by")?;
|
||||
let metadata = input.metadata();
|
||||
|
||||
let cells_only = call.has_flag("cells-only");
|
||||
let value = input.into_value(call.head);
|
||||
let rotated_value =
|
||||
horizontal_rotate_value(value, &by, cells_only, &HorizontalDirection::Right)?;
|
||||
|
||||
Ok(rotated_value.into_pipeline_data().set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(RollRight {})
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
use super::{vertical_rotate_value, VerticalDirection};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RollUp;
|
||||
|
||||
impl Command for RollUp {
|
||||
fn name(&self) -> &str {
|
||||
"roll up"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate", "shift", "move", "row"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
// TODO: It also operates on List
|
||||
.input_output_types(vec![(Type::Table(vec![]), Type::Table(vec![]))])
|
||||
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Roll table rows up."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
let columns = vec!["a".to_string(), "b".to_string()];
|
||||
vec![Example {
|
||||
description: "Rolls rows up",
|
||||
example: "[[a b]; [1 2] [3 4] [5 6]] | roll up",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: columns.clone(),
|
||||
vals: vec![Value::test_int(3), Value::test_int(4)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: columns.clone(),
|
||||
vals: vec![Value::test_int(5), Value::test_int(6)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: columns,
|
||||
vals: vec![Value::test_int(1), Value::test_int(2)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let by: Option<usize> = call.get_flag(engine_state, stack, "by")?;
|
||||
let metadata = input.metadata();
|
||||
|
||||
let value = input.into_value(call.head);
|
||||
let rotated_value = vertical_rotate_value(value, by, VerticalDirection::Up)?;
|
||||
|
||||
Ok(rotated_value.into_pipeline_data().set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(RollUp {})
|
||||
}
|
||||
}
|
@ -1,361 +0,0 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Rotate;
|
||||
|
||||
impl Command for Rotate {
|
||||
fn name(&self) -> &str {
|
||||
"rotate"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("rotate")
|
||||
.input_output_types(vec![
|
||||
(Type::Record(vec![]), Type::Table(vec![])),
|
||||
(Type::Table(vec![]), Type::Table(vec![])),
|
||||
])
|
||||
.switch("ccw", "rotate counter clockwise", None)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the names to give columns once rotated",
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Rotates a table or record clockwise (default) or counter-clockwise (use --ccw flag)."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Rotate a record clockwise, producing a table (like `transpose` but with column order reversed)",
|
||||
example: "{a:1, b:2} | rotate",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["column0".to_string(), "column1".to_string()],
|
||||
vals: vec![Value::test_int(1), Value::test_string("a")],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["column0".to_string(), "column1".to_string()],
|
||||
vals: vec![Value::test_int(2), Value::test_string("b")],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rotate 2x3 table clockwise",
|
||||
example: "[[a b]; [1 2] [3 4] [5 6]] | rotate",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec![
|
||||
"column0".to_string(),
|
||||
"column1".to_string(),
|
||||
"column2".to_string(),
|
||||
"column3".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::test_int(5),
|
||||
Value::test_int(3),
|
||||
Value::test_int(1),
|
||||
Value::test_string("a"),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec![
|
||||
"column0".to_string(),
|
||||
"column1".to_string(),
|
||||
"column2".to_string(),
|
||||
"column3".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::test_int(6),
|
||||
Value::test_int(4),
|
||||
Value::test_int(2),
|
||||
Value::test_string("b"),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rotate table clockwise and change columns names",
|
||||
example: "[[a b]; [1 2]] | rotate col_a col_b",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["col_a".to_string(), "col_b".to_string()],
|
||||
vals: vec![Value::test_int(1), Value::test_string("a")],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["col_a".to_string(), "col_b".to_string()],
|
||||
vals: vec![Value::test_int(2), Value::test_string("b")],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rotate table counter clockwise",
|
||||
example: "[[a b]; [1 2]] | rotate --ccw",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["column0".to_string(), "column1".to_string()],
|
||||
vals: vec![Value::test_string("b"), Value::test_int(2)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["column0".to_string(), "column1".to_string()],
|
||||
vals: vec![Value::test_string("a"), Value::test_int(1)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rotate table counter-clockwise",
|
||||
example: "[[a b]; [1 2] [3 4] [5 6]] | rotate --ccw",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec![
|
||||
"column0".to_string(),
|
||||
"column1".to_string(),
|
||||
"column2".to_string(),
|
||||
"column3".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::test_string("b"),
|
||||
Value::test_int(2),
|
||||
Value::test_int(4),
|
||||
Value::test_int(6),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec![
|
||||
"column0".to_string(),
|
||||
"column1".to_string(),
|
||||
"column2".to_string(),
|
||||
"column3".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::test_string("a"),
|
||||
Value::test_int(1),
|
||||
Value::test_int(3),
|
||||
Value::test_int(5),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rotate table counter-clockwise and change columns names",
|
||||
example: "[[a b]; [1 2]] | rotate --ccw col_a col_b",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["col_a".to_string(), "col_b".to_string()],
|
||||
vals: vec![Value::test_string("b"), Value::test_int(2)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["col_a".to_string(), "col_b".to_string()],
|
||||
vals: vec![Value::test_string("a"), Value::test_int(1)],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
rotate(engine_state, stack, call, input)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let metadata = input.metadata();
|
||||
let col_given_names: Vec<String> = call.rest(engine_state, stack, 0)?;
|
||||
let span = input.span();
|
||||
let mut values = input.into_iter().collect::<Vec<_>>();
|
||||
let mut old_column_names = vec![];
|
||||
let mut new_values = vec![];
|
||||
let mut not_a_record = false;
|
||||
let total_rows = &mut values.len();
|
||||
let ccw: bool = call.has_flag("ccw");
|
||||
|
||||
if !ccw {
|
||||
values.reverse();
|
||||
}
|
||||
|
||||
if !values.is_empty() {
|
||||
for val in values.into_iter() {
|
||||
match val {
|
||||
Value::Record { cols, vals, .. } => {
|
||||
old_column_names = cols;
|
||||
for v in vals {
|
||||
new_values.push(v)
|
||||
}
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
not_a_record = true;
|
||||
for v in vals {
|
||||
new_values.push(v);
|
||||
}
|
||||
}
|
||||
Value::String { val, span } => {
|
||||
not_a_record = true;
|
||||
new_values.push(Value::String { val, span })
|
||||
}
|
||||
x => {
|
||||
not_a_record = true;
|
||||
new_values.push(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"list input is empty".to_string(),
|
||||
"value originates from here".into(),
|
||||
call.head,
|
||||
// TODO: Maybe make all Pipelines have spans, so that this doesn't need to be unwrapped.
|
||||
span.unwrap_or(call.head),
|
||||
));
|
||||
}
|
||||
|
||||
let total_columns = &old_column_names.len();
|
||||
|
||||
// we use this for building columns names, but for non-records we get an extra row so we remove it
|
||||
if *total_columns == 0 {
|
||||
*total_rows -= 1;
|
||||
}
|
||||
|
||||
// holder for the new column names, particularly if none are provided by the user we create names as column0, column1, etc.
|
||||
let mut new_column_names = {
|
||||
let mut res = vec![];
|
||||
for idx in 0..(*total_rows + 1) {
|
||||
res.push(format!("column{idx}"));
|
||||
}
|
||||
res.to_vec()
|
||||
};
|
||||
|
||||
// we got new names for columns from the input, so we need to swap those we already made
|
||||
if !col_given_names.is_empty() {
|
||||
for (idx, val) in col_given_names.into_iter().enumerate() {
|
||||
if idx > new_column_names.len() - 1 {
|
||||
break;
|
||||
}
|
||||
new_column_names[idx] = val;
|
||||
}
|
||||
}
|
||||
|
||||
if not_a_record {
|
||||
return Ok(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: new_column_names,
|
||||
vals: new_values,
|
||||
span: call.head,
|
||||
}],
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data()
|
||||
.set_metadata(metadata));
|
||||
}
|
||||
|
||||
// holder for the new records
|
||||
let mut final_values = vec![];
|
||||
|
||||
// the number of initial columns will be our number of rows, so we iterate through that to get the new number of rows that we need to make
|
||||
// for counter clockwise, we're iterating from right to left and have a pair of (index, value)
|
||||
let columns_iter = if ccw {
|
||||
old_column_names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
// as we're rotating clockwise, we're iterating from left to right and have a pair of (index, value)
|
||||
old_column_names.iter().enumerate().collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
for (idx, val) in columns_iter {
|
||||
// when rotating counter clockwise, the old columns names become the first column's values
|
||||
let mut res = if ccw {
|
||||
vec![Value::string(val, call.head)]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let new_vals = {
|
||||
// move through the array with a step, which is every new_values size / total rows, starting from our old column's index
|
||||
// so if initial data was like this [[a b]; [1 2] [3 4]] - we basically iterate on this [3 4 1 2] array, so we pick 3, then 1, and then when idx increases, we pick 4 and 2
|
||||
for i in (idx..new_values.len()).step_by(new_values.len() / *total_rows) {
|
||||
res.push(new_values[i].clone());
|
||||
}
|
||||
// when rotating clockwise, the old column names become the last column's values
|
||||
if !ccw {
|
||||
res.push(Value::string(val, call.head));
|
||||
}
|
||||
res.to_vec()
|
||||
};
|
||||
final_values.push(Value::Record {
|
||||
cols: new_column_names.clone(),
|
||||
vals: new_vals,
|
||||
span: call.head,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Value::List {
|
||||
vals: final_values,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data()
|
||||
.set_metadata(metadata))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Rotate)
|
||||
}
|
||||
}
|
@ -1,270 +0,0 @@
|
||||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::{Block, Call};
|
||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||
PipelineIterator, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UpdateCells;
|
||||
|
||||
impl Command for UpdateCells {
|
||||
fn name(&self) -> &str {
|
||||
"update cells"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("update cells")
|
||||
.input_output_types(vec![(Type::Table(vec![]), Type::Table(vec![]))])
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
"the closure to run an update for each cell",
|
||||
)
|
||||
.named(
|
||||
"columns",
|
||||
SyntaxShape::Table,
|
||||
"list of columns to update",
|
||||
Some('c'),
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Update the table cells."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Update the zero value cells to empty strings.",
|
||||
example: r#"[
|
||||
["2021-04-16", "2021-06-10", "2021-09-18", "2021-10-15", "2021-11-16", "2021-11-17", "2021-11-18"];
|
||||
[ 37, 0, 0, 0, 37, 0, 0]
|
||||
] | update cells { |value|
|
||||
if $value == 0 {
|
||||
""
|
||||
} else {
|
||||
$value
|
||||
}
|
||||
}"#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec![
|
||||
"2021-04-16".into(),
|
||||
"2021-06-10".into(),
|
||||
"2021-09-18".into(),
|
||||
"2021-10-15".into(),
|
||||
"2021-11-16".into(),
|
||||
"2021-11-17".into(),
|
||||
"2021-11-18".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::test_int(37),
|
||||
Value::test_string(""),
|
||||
Value::test_string(""),
|
||||
Value::test_string(""),
|
||||
Value::test_int(37),
|
||||
Value::test_string(""),
|
||||
Value::test_string(""),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Update the zero value cells to empty strings in 2 last columns.",
|
||||
example: r#"[
|
||||
["2021-04-16", "2021-06-10", "2021-09-18", "2021-10-15", "2021-11-16", "2021-11-17", "2021-11-18"];
|
||||
[ 37, 0, 0, 0, 37, 0, 0]
|
||||
] | update cells -c ["2021-11-18", "2021-11-17"] { |value|
|
||||
if $value == 0 {
|
||||
""
|
||||
} else {
|
||||
$value
|
||||
}
|
||||
}"#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec![
|
||||
"2021-04-16".into(),
|
||||
"2021-06-10".into(),
|
||||
"2021-09-18".into(),
|
||||
"2021-10-15".into(),
|
||||
"2021-11-16".into(),
|
||||
"2021-11-17".into(),
|
||||
"2021-11-18".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::test_int(37),
|
||||
Value::test_int(0),
|
||||
Value::test_int(0),
|
||||
Value::test_int(0),
|
||||
Value::test_int(37),
|
||||
Value::test_string(""),
|
||||
Value::test_string(""),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// the block to run on each cell
|
||||
let engine_state = engine_state.clone();
|
||||
let block: Closure = call.req(&engine_state, stack, 0)?;
|
||||
let mut stack = stack.captures_to_stack(&block.captures);
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
|
||||
let metadata = input.metadata();
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let block: Block = engine_state.get_block(block.block_id).clone();
|
||||
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
|
||||
let span = call.head;
|
||||
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
// the columns to update
|
||||
let columns: Option<Value> = call.get_flag(&engine_state, &mut stack, "columns")?;
|
||||
let columns: Option<HashSet<String>> = match columns {
|
||||
Some(val) => {
|
||||
let cols = val
|
||||
.as_list()?
|
||||
.iter()
|
||||
.map(|val| val.as_string())
|
||||
.collect::<Result<Vec<String>, ShellError>>()?;
|
||||
Some(HashSet::from_iter(cols.into_iter()))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(UpdateCellIterator {
|
||||
input: input.into_iter(),
|
||||
engine_state,
|
||||
stack,
|
||||
block,
|
||||
columns,
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
span,
|
||||
}
|
||||
.into_pipeline_data(ctrlc)
|
||||
.set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
struct UpdateCellIterator {
|
||||
input: PipelineIterator,
|
||||
columns: Option<HashSet<String>>,
|
||||
engine_state: EngineState,
|
||||
stack: Stack,
|
||||
block: Block,
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl Iterator for UpdateCellIterator {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.input.next() {
|
||||
Some(val) => {
|
||||
if let Some(ref cols) = self.columns {
|
||||
if !val.columns().iter().any(|c| cols.contains(c)) {
|
||||
return Some(val);
|
||||
}
|
||||
}
|
||||
|
||||
match val {
|
||||
Value::Record { vals, cols, span } => Some(Value::Record {
|
||||
vals: cols
|
||||
.iter()
|
||||
.zip(vals.into_iter())
|
||||
.map(|(col, val)| match &self.columns {
|
||||
Some(cols) if !cols.contains(col) => val,
|
||||
_ => process_cell(
|
||||
val,
|
||||
&self.engine_state,
|
||||
&mut self.stack,
|
||||
&self.block,
|
||||
self.redirect_stdout,
|
||||
self.redirect_stderr,
|
||||
span,
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
cols,
|
||||
span,
|
||||
}),
|
||||
val => Some(process_cell(
|
||||
val,
|
||||
&self.engine_state,
|
||||
&mut self.stack,
|
||||
&self.block,
|
||||
self.redirect_stdout,
|
||||
self.redirect_stderr,
|
||||
self.span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_cell(
|
||||
val: Value,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block: &Block,
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, val.clone());
|
||||
}
|
||||
}
|
||||
match eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block,
|
||||
val.into_pipeline_data(),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
) {
|
||||
Ok(pd) => pd.into_value(span),
|
||||
Err(e) => Value::Error { error: Box::new(e) },
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(UpdateCells {})
|
||||
}
|
||||
}
|
@ -7,14 +7,12 @@ mod ods;
|
||||
mod ssv;
|
||||
mod toml;
|
||||
mod tsv;
|
||||
mod url;
|
||||
mod xlsx;
|
||||
mod xml;
|
||||
mod yaml;
|
||||
|
||||
pub use self::csv::FromCsv;
|
||||
pub use self::toml::FromToml;
|
||||
pub use self::url::FromUrl;
|
||||
pub use command::From;
|
||||
pub use json::FromJson;
|
||||
pub use nuon::FromNuon;
|
||||
|
@ -1,99 +0,0 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FromUrl;
|
||||
|
||||
impl Command for FromUrl {
|
||||
fn name(&self) -> &str {
|
||||
"from url"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from url")
|
||||
.input_output_types(vec![(Type::String, Type::Record(vec![]))])
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse url-encoded string as a record."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
from_url(input, head)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "'bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter' | from url",
|
||||
description: "Convert url encoded string into a record",
|
||||
result: Some(Value::Record {
|
||||
cols: vec![
|
||||
"bread".to_string(),
|
||||
"cheese".to_string(),
|
||||
"meat".to_string(),
|
||||
"fat".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::test_string("baguette"),
|
||||
Value::test_string("comté"),
|
||||
Value::test_string("ham"),
|
||||
Value::test_string("butter"),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
|
||||
let (concat_string, span, metadata) = input.collect_string_strict(head)?;
|
||||
|
||||
let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string);
|
||||
|
||||
match result {
|
||||
Ok(result) => {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
for (k, v) in result {
|
||||
cols.push(k);
|
||||
vals.push(Value::String { val: v, span: head })
|
||||
}
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: head,
|
||||
},
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedInput(
|
||||
"String not compatible with URL encoding".to_string(),
|
||||
"value originates from here".into(),
|
||||
head,
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(FromUrl {})
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use csv::{Writer, WriterBuilder};
|
||||
use indexmap::{indexset, IndexSet};
|
||||
use nu_cmd_base::formats::to::delimited::merge_descriptors;
|
||||
use nu_protocol::{Config, IntoPipelineData, PipelineData, ShellError, Span, Value};
|
||||
use std::collections::VecDeque;
|
||||
use std::error::Error;
|
||||
@ -142,24 +142,6 @@ pub fn find_non_record(values: &[Value]) -> Option<&Value> {
|
||||
.find(|val| !matches!(val, Value::Record { .. }))
|
||||
}
|
||||
|
||||
pub fn merge_descriptors(values: &[Value]) -> Vec<String> {
|
||||
let mut ret: Vec<String> = vec![];
|
||||
let mut seen: IndexSet<String> = indexset! {};
|
||||
for value in values {
|
||||
let data_descriptors = match value {
|
||||
Value::Record { cols, .. } => cols.to_owned(),
|
||||
_ => vec!["".to_string()],
|
||||
};
|
||||
for desc in data_descriptors {
|
||||
if !desc.is_empty() && !seen.contains(&desc) {
|
||||
seen.insert(desc.to_string());
|
||||
ret.push(desc.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn to_delimited_data(
|
||||
noheaders: bool,
|
||||
sep: char,
|
||||
|
@ -1,733 +0,0 @@
|
||||
use crate::formats::to::delimited::merge_descriptors;
|
||||
use fancy_regex::Regex;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Config, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata,
|
||||
ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use rust_embed::RustEmbed;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct HtmlThemes {
|
||||
themes: Vec<HtmlTheme>,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct HtmlTheme {
|
||||
name: String,
|
||||
black: String,
|
||||
red: String,
|
||||
green: String,
|
||||
yellow: String,
|
||||
blue: String,
|
||||
purple: String,
|
||||
cyan: String,
|
||||
white: String,
|
||||
brightBlack: String,
|
||||
brightRed: String,
|
||||
brightGreen: String,
|
||||
brightYellow: String,
|
||||
brightBlue: String,
|
||||
brightPurple: String,
|
||||
brightCyan: String,
|
||||
brightWhite: String,
|
||||
background: String,
|
||||
foreground: String,
|
||||
}
|
||||
|
||||
impl Default for HtmlThemes {
|
||||
fn default() -> Self {
|
||||
HtmlThemes {
|
||||
themes: vec![HtmlTheme::default()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HtmlTheme {
|
||||
fn default() -> Self {
|
||||
HtmlTheme {
|
||||
name: "nu_default".to_string(),
|
||||
black: "black".to_string(),
|
||||
red: "red".to_string(),
|
||||
green: "green".to_string(),
|
||||
yellow: "#717100".to_string(),
|
||||
blue: "blue".to_string(),
|
||||
purple: "#c800c8".to_string(),
|
||||
cyan: "#037979".to_string(),
|
||||
white: "white".to_string(),
|
||||
brightBlack: "black".to_string(),
|
||||
brightRed: "red".to_string(),
|
||||
brightGreen: "green".to_string(),
|
||||
brightYellow: "#717100".to_string(),
|
||||
brightBlue: "blue".to_string(),
|
||||
brightPurple: "#c800c8".to_string(),
|
||||
brightCyan: "#037979".to_string(),
|
||||
brightWhite: "white".to_string(),
|
||||
background: "white".to_string(),
|
||||
foreground: "black".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "assets/"]
|
||||
struct Assets;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToHtml;
|
||||
|
||||
impl Command for ToHtml {
|
||||
fn name(&self) -> &str {
|
||||
"to html"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("to html")
|
||||
.input_output_types(vec![(Type::Any, Type::String)])
|
||||
.switch("html-color", "change ansi colors to html colors", Some('c'))
|
||||
.switch("no-color", "remove all ansi colors in output", Some('n'))
|
||||
.switch(
|
||||
"dark",
|
||||
"indicate your background color is a darker color",
|
||||
Some('d'),
|
||||
)
|
||||
.switch(
|
||||
"partial",
|
||||
"only output the html for the content itself",
|
||||
Some('p'),
|
||||
)
|
||||
.named(
|
||||
"theme",
|
||||
SyntaxShape::String,
|
||||
"the name of the theme to use (github, blulocolight, ...)",
|
||||
Some('t'),
|
||||
)
|
||||
.switch(
|
||||
"list",
|
||||
"produce a color table of all available themes",
|
||||
Some('l'),
|
||||
)
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Outputs an HTML string representing the contents of this table",
|
||||
example: "[[foo bar]; [1 2]] | to html",
|
||||
result: Some(Value::test_string(
|
||||
r#"<html><style>body { background-color:white;color:black; }</style><body><table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table></body></html>"#,
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Optionally, only output the html for the content itself",
|
||||
example: "[[foo bar]; [1 2]] | to html --partial",
|
||||
result: Some(Value::test_string(
|
||||
r#"<div style="background-color:white;color:black;"><table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table></div>"#,
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Optionally, output the string with a dark background",
|
||||
example: "[[foo bar]; [1 2]] | to html --dark",
|
||||
result: Some(Value::test_string(
|
||||
r#"<html><style>body { background-color:black;color:white; }</style><body><table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table></body></html>"#,
|
||||
)),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert table into simple HTML."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Screenshots of the themes can be browsed here: https://github.com/mbadolato/iTerm2-Color-Schemes."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
to_html(input, call, engine_state, stack)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_theme_from_asset_file(
|
||||
is_dark: bool,
|
||||
theme: &Option<Spanned<String>>,
|
||||
) -> Result<HashMap<&'static str, String>, ShellError> {
|
||||
let theme_name = match theme {
|
||||
Some(s) => &s.item,
|
||||
None => "default", // There is no theme named "default" so this will be HtmlTheme::default(), which is "nu_default".
|
||||
};
|
||||
|
||||
// 228 themes come from
|
||||
// https://github.com/mbadolato/iTerm2-Color-Schemes/tree/master/windowsterminal
|
||||
// we should find a hit on any name in there
|
||||
let asset = get_html_themes("228_themes.json").unwrap_or_default();
|
||||
|
||||
// Find the theme by theme name
|
||||
let th = asset
|
||||
.themes
|
||||
.into_iter()
|
||||
.find(|n| n.name.to_lowercase() == theme_name.to_lowercase()) // case insensitive search
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(convert_html_theme_to_hash_map(is_dark, &th))
|
||||
}
|
||||
|
||||
fn convert_html_theme_to_hash_map(
|
||||
is_dark: bool,
|
||||
theme: &HtmlTheme,
|
||||
) -> HashMap<&'static str, String> {
|
||||
let mut hm: HashMap<&str, String> = HashMap::with_capacity(18);
|
||||
|
||||
hm.insert("bold_black", theme.brightBlack[..].to_string());
|
||||
hm.insert("bold_red", theme.brightRed[..].to_string());
|
||||
hm.insert("bold_green", theme.brightGreen[..].to_string());
|
||||
hm.insert("bold_yellow", theme.brightYellow[..].to_string());
|
||||
hm.insert("bold_blue", theme.brightBlue[..].to_string());
|
||||
hm.insert("bold_magenta", theme.brightPurple[..].to_string());
|
||||
hm.insert("bold_cyan", theme.brightCyan[..].to_string());
|
||||
hm.insert("bold_white", theme.brightWhite[..].to_string());
|
||||
|
||||
hm.insert("black", theme.black[..].to_string());
|
||||
hm.insert("red", theme.red[..].to_string());
|
||||
hm.insert("green", theme.green[..].to_string());
|
||||
hm.insert("yellow", theme.yellow[..].to_string());
|
||||
hm.insert("blue", theme.blue[..].to_string());
|
||||
hm.insert("magenta", theme.purple[..].to_string());
|
||||
hm.insert("cyan", theme.cyan[..].to_string());
|
||||
hm.insert("white", theme.white[..].to_string());
|
||||
|
||||
// Try to make theme work with light or dark but
|
||||
// flipping the foreground and background but leave
|
||||
// the other colors the same.
|
||||
if is_dark {
|
||||
hm.insert("background", theme.black[..].to_string());
|
||||
hm.insert("foreground", theme.white[..].to_string());
|
||||
} else {
|
||||
hm.insert("background", theme.white[..].to_string());
|
||||
hm.insert("foreground", theme.black[..].to_string());
|
||||
}
|
||||
|
||||
hm
|
||||
}
|
||||
|
||||
fn get_html_themes(json_name: &str) -> Result<HtmlThemes, Box<dyn Error>> {
|
||||
match Assets::get(json_name) {
|
||||
Some(content) => Ok(nu_json::from_slice(&content.data)?),
|
||||
None => Ok(HtmlThemes::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_html(
|
||||
input: PipelineData,
|
||||
call: &Call,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let html_color = call.has_flag("html-color");
|
||||
let no_color = call.has_flag("no-color");
|
||||
let dark = call.has_flag("dark");
|
||||
let partial = call.has_flag("partial");
|
||||
let list = call.has_flag("list");
|
||||
let theme: Option<Spanned<String>> = call.get_flag(engine_state, stack, "theme")?;
|
||||
let config = engine_state.get_config();
|
||||
|
||||
let vec_of_values = input.into_iter().collect::<Vec<Value>>();
|
||||
let headers = merge_descriptors(&vec_of_values);
|
||||
let headers = Some(headers)
|
||||
.filter(|headers| !headers.is_empty() && (headers.len() > 1 || !headers[0].is_empty()));
|
||||
let mut output_string = String::new();
|
||||
let mut regex_hm: HashMap<u32, (&str, String)> = HashMap::with_capacity(17);
|
||||
|
||||
// Being essentially a 'help' option, this can afford to be relatively unoptimised
|
||||
if list {
|
||||
// If asset doesn't work, make sure to return the default theme
|
||||
let html_themes = get_html_themes("228_themes.json").unwrap_or_default();
|
||||
|
||||
let cols = vec![
|
||||
"name".into(),
|
||||
"black".into(),
|
||||
"red".into(),
|
||||
"green".into(),
|
||||
"yellow".into(),
|
||||
"blue".into(),
|
||||
"purple".into(),
|
||||
"cyan".into(),
|
||||
"white".into(),
|
||||
"brightBlack".into(),
|
||||
"brightRed".into(),
|
||||
"brightGreen".into(),
|
||||
"brightYellow".into(),
|
||||
"brightBlue".into(),
|
||||
"brightPurple".into(),
|
||||
"brightCyan".into(),
|
||||
"brightWhite".into(),
|
||||
"background".into(),
|
||||
"foreground".into(),
|
||||
];
|
||||
|
||||
let result: Vec<Value> = html_themes
|
||||
.themes
|
||||
.into_iter()
|
||||
.map(|n| {
|
||||
let vals = vec![
|
||||
n.name,
|
||||
n.black,
|
||||
n.red,
|
||||
n.green,
|
||||
n.yellow,
|
||||
n.blue,
|
||||
n.purple,
|
||||
n.cyan,
|
||||
n.white,
|
||||
n.brightBlack,
|
||||
n.brightRed,
|
||||
n.brightGreen,
|
||||
n.brightYellow,
|
||||
n.brightBlue,
|
||||
n.brightPurple,
|
||||
n.brightCyan,
|
||||
n.brightWhite,
|
||||
n.background,
|
||||
n.foreground,
|
||||
]
|
||||
.into_iter()
|
||||
.map(|val| Value::String { val, span: head })
|
||||
.collect();
|
||||
|
||||
Value::Record {
|
||||
cols: cols.clone(),
|
||||
vals,
|
||||
span: head,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
return Ok(Value::List {
|
||||
vals: result,
|
||||
span: head,
|
||||
}
|
||||
.into_pipeline_data_with_metadata(Box::new(PipelineMetadata {
|
||||
data_source: DataSource::HtmlThemes,
|
||||
})));
|
||||
} else {
|
||||
let theme_span = match &theme {
|
||||
Some(v) => v.span,
|
||||
None => head,
|
||||
};
|
||||
|
||||
let color_hm = get_theme_from_asset_file(dark, &theme);
|
||||
let color_hm = match color_hm {
|
||||
Ok(c) => c,
|
||||
_ => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Error finding theme name".to_string(),
|
||||
"Error finding theme name".to_string(),
|
||||
Some(theme_span),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// change the color of the page
|
||||
if !partial {
|
||||
write!(
|
||||
&mut output_string,
|
||||
r"<html><style>body {{ background-color:{};color:{}; }}</style><body>",
|
||||
color_hm
|
||||
.get("background")
|
||||
.expect("Error getting background color"),
|
||||
color_hm
|
||||
.get("foreground")
|
||||
.expect("Error getting foreground color")
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
write!(
|
||||
&mut output_string,
|
||||
"<div style=\"background-color:{};color:{};\">",
|
||||
color_hm
|
||||
.get("background")
|
||||
.expect("Error getting background color"),
|
||||
color_hm
|
||||
.get("foreground")
|
||||
.expect("Error getting foreground color")
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let inner_value = match vec_of_values.len() {
|
||||
0 => String::default(),
|
||||
1 => match headers {
|
||||
Some(headers) => html_table(vec_of_values, headers, config),
|
||||
None => {
|
||||
let value = &vec_of_values[0];
|
||||
html_value(value.clone(), config)
|
||||
}
|
||||
},
|
||||
_ => match headers {
|
||||
Some(headers) => html_table(vec_of_values, headers, config),
|
||||
None => html_list(vec_of_values, config),
|
||||
},
|
||||
};
|
||||
|
||||
output_string.push_str(&inner_value);
|
||||
|
||||
if !partial {
|
||||
output_string.push_str("</body></html>");
|
||||
} else {
|
||||
output_string.push_str("</div>")
|
||||
}
|
||||
|
||||
// Check to see if we want to remove all color or change ansi to html colors
|
||||
if html_color {
|
||||
setup_html_color_regexes(&mut regex_hm, &color_hm);
|
||||
output_string = run_regexes(®ex_hm, &output_string);
|
||||
} else if no_color {
|
||||
setup_no_color_regexes(&mut regex_hm);
|
||||
output_string = run_regexes(®ex_hm, &output_string);
|
||||
}
|
||||
}
|
||||
Ok(Value::string(output_string, head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn html_list(list: Vec<Value>, config: &Config) -> String {
|
||||
let mut output_string = String::new();
|
||||
output_string.push_str("<ol>");
|
||||
for value in list {
|
||||
output_string.push_str("<li>");
|
||||
output_string.push_str(&html_value(value, config));
|
||||
output_string.push_str("</li>");
|
||||
}
|
||||
output_string.push_str("</ol>");
|
||||
output_string
|
||||
}
|
||||
|
||||
fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> String {
|
||||
let mut output_string = String::new();
|
||||
|
||||
output_string.push_str("<table>");
|
||||
|
||||
output_string.push_str("<thead><tr>");
|
||||
for header in &headers {
|
||||
output_string.push_str("<th>");
|
||||
output_string.push_str(&htmlescape::encode_minimal(header));
|
||||
output_string.push_str("</th>");
|
||||
}
|
||||
output_string.push_str("</tr></thead><tbody>");
|
||||
|
||||
for row in table {
|
||||
if let Value::Record { span, .. } = row {
|
||||
output_string.push_str("<tr>");
|
||||
for header in &headers {
|
||||
let data = row.get_data_by_key(header);
|
||||
output_string.push_str("<td>");
|
||||
output_string.push_str(&html_value(
|
||||
data.unwrap_or_else(|| Value::nothing(span)),
|
||||
config,
|
||||
));
|
||||
output_string.push_str("</td>");
|
||||
}
|
||||
output_string.push_str("</tr>");
|
||||
}
|
||||
}
|
||||
output_string.push_str("</tbody></table>");
|
||||
|
||||
output_string
|
||||
}
|
||||
|
||||
fn html_value(value: Value, config: &Config) -> String {
|
||||
let mut output_string = String::new();
|
||||
match value {
|
||||
Value::Binary { val, .. } => {
|
||||
let output = nu_pretty_hex::pretty_hex(&val);
|
||||
output_string.push_str("<pre>");
|
||||
output_string.push_str(&output);
|
||||
output_string.push_str("</pre>");
|
||||
}
|
||||
other => output_string.push_str(
|
||||
&htmlescape::encode_minimal(&other.into_abbreviated_string(config))
|
||||
.replace('\n', "<br>"),
|
||||
),
|
||||
}
|
||||
output_string
|
||||
}
|
||||
|
||||
fn setup_html_color_regexes(
|
||||
hash: &mut HashMap<u32, (&'static str, String)>,
|
||||
color_hm: &HashMap<&str, String>,
|
||||
) {
|
||||
// All the bold colors
|
||||
hash.insert(
|
||||
0,
|
||||
(
|
||||
r"(?P<reset>\[0m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
// Reset the text color, normal weight font
|
||||
format!(
|
||||
r"<span style='color:{};font-weight:normal;'>$word</span>",
|
||||
color_hm
|
||||
.get("foreground")
|
||||
.expect("Error getting reset text color")
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
1,
|
||||
(
|
||||
// Bold Black
|
||||
r"(?P<bb>\[1;30m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||
color_hm
|
||||
.get("foreground")
|
||||
.expect("Error getting bold black text color")
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
2,
|
||||
(
|
||||
// Bold Red
|
||||
r"(?P<br>\[1;31m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||
color_hm
|
||||
.get("bold_red")
|
||||
.expect("Error getting bold red text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
3,
|
||||
(
|
||||
// Bold Green
|
||||
r"(?P<bg>\[1;32m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||
color_hm
|
||||
.get("bold_green")
|
||||
.expect("Error getting bold green text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
4,
|
||||
(
|
||||
// Bold Yellow
|
||||
r"(?P<by>\[1;33m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||
color_hm
|
||||
.get("bold_yellow")
|
||||
.expect("Error getting bold yellow text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
5,
|
||||
(
|
||||
// Bold Blue
|
||||
r"(?P<bu>\[1;34m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||
color_hm
|
||||
.get("bold_blue")
|
||||
.expect("Error getting bold blue text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
6,
|
||||
(
|
||||
// Bold Magenta
|
||||
r"(?P<bm>\[1;35m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||
color_hm
|
||||
.get("bold_magenta")
|
||||
.expect("Error getting bold magenta text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
7,
|
||||
(
|
||||
// Bold Cyan
|
||||
r"(?P<bc>\[1;36m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||
color_hm
|
||||
.get("bold_cyan")
|
||||
.expect("Error getting bold cyan text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
8,
|
||||
(
|
||||
// Bold White
|
||||
// Let's change this to black since the html background
|
||||
// is white. White on white = no bueno.
|
||||
r"(?P<bw>\[1;37m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||
color_hm
|
||||
.get("foreground")
|
||||
.expect("Error getting bold bold white text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
// All the normal colors
|
||||
hash.insert(
|
||||
9,
|
||||
(
|
||||
// Black
|
||||
r"(?P<b>\[30m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};'>$word</span>",
|
||||
color_hm
|
||||
.get("foreground")
|
||||
.expect("Error getting black text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
10,
|
||||
(
|
||||
// Red
|
||||
r"(?P<r>\[31m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};'>$word</span>",
|
||||
color_hm.get("red").expect("Error getting red text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
11,
|
||||
(
|
||||
// Green
|
||||
r"(?P<g>\[32m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};'>$word</span>",
|
||||
color_hm
|
||||
.get("green")
|
||||
.expect("Error getting green text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
12,
|
||||
(
|
||||
// Yellow
|
||||
r"(?P<y>\[33m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};'>$word</span>",
|
||||
color_hm
|
||||
.get("yellow")
|
||||
.expect("Error getting yellow text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
13,
|
||||
(
|
||||
// Blue
|
||||
r"(?P<u>\[34m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};'>$word</span>",
|
||||
color_hm.get("blue").expect("Error getting blue text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
14,
|
||||
(
|
||||
// Magenta
|
||||
r"(?P<m>\[35m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};'>$word</span>",
|
||||
color_hm
|
||||
.get("magenta")
|
||||
.expect("Error getting magenta text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
15,
|
||||
(
|
||||
// Cyan
|
||||
r"(?P<c>\[36m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};'>$word</span>",
|
||||
color_hm.get("cyan").expect("Error getting cyan text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
hash.insert(
|
||||
16,
|
||||
(
|
||||
// White
|
||||
// Let's change this to black since the html background
|
||||
// is white. White on white = no bueno.
|
||||
r"(?P<w>\[37m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||
format!(
|
||||
r"<span style='color:{};'>$word</span>",
|
||||
color_hm
|
||||
.get("foreground")
|
||||
.expect("Error getting white text color"),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn setup_no_color_regexes(hash: &mut HashMap<u32, (&'static str, String)>) {
|
||||
// We can just use one regex here because we're just removing ansi sequences
|
||||
// and not replacing them with html colors.
|
||||
// attribution: https://stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python
|
||||
hash.insert(
|
||||
0,
|
||||
(
|
||||
r"(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])",
|
||||
r"$name_group_doesnt_exist".to_string(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn run_regexes(hash: &HashMap<u32, (&'static str, String)>, contents: &str) -> String {
|
||||
let mut working_string = contents.to_owned();
|
||||
let hash_count: u32 = hash.len() as u32;
|
||||
for n in 0..hash_count {
|
||||
let value = hash.get(&n).expect("error getting hash at index");
|
||||
//println!("{},{}", value.0, value.1);
|
||||
let re = Regex::new(value.0).expect("problem with color regex");
|
||||
let after = re.replace_all(&working_string, &value.1[..]).to_string();
|
||||
working_string = after.clone();
|
||||
}
|
||||
working_string
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(ToHtml {})
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::formats::to::delimited::merge_descriptors;
|
||||
use indexmap::map::IndexMap;
|
||||
use nu_cmd_base::formats::to::delimited::merge_descriptors;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
|
@ -1,7 +1,6 @@
|
||||
mod command;
|
||||
mod csv;
|
||||
mod delimited;
|
||||
mod html;
|
||||
mod json;
|
||||
mod md;
|
||||
mod nuon;
|
||||
@ -14,7 +13,6 @@ mod yaml;
|
||||
pub use self::csv::ToCsv;
|
||||
pub use self::toml::ToToml;
|
||||
pub use command::To;
|
||||
pub use html::ToHtml;
|
||||
pub use json::ToJson;
|
||||
pub use md::ToMd;
|
||||
pub use nuon::value_to_string;
|
||||
|
@ -1,329 +0,0 @@
|
||||
use nu_ansi_term::{build_all_gradient_text, gradient::TargetGround, Gradient, Rgb};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call, ast::CellPath, engine::Command, engine::EngineState, engine::Stack, Category,
|
||||
Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"ansi gradient"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi gradient")
|
||||
.named(
|
||||
"fgstart",
|
||||
SyntaxShape::String,
|
||||
"foreground gradient start color in hex (0x123456)",
|
||||
Some('a'),
|
||||
)
|
||||
.named(
|
||||
"fgend",
|
||||
SyntaxShape::String,
|
||||
"foreground gradient end color in hex",
|
||||
Some('b'),
|
||||
)
|
||||
.named(
|
||||
"bgstart",
|
||||
SyntaxShape::String,
|
||||
"background gradient start color in hex",
|
||||
Some('c'),
|
||||
)
|
||||
.named(
|
||||
"bgend",
|
||||
SyntaxShape::String,
|
||||
"background gradient end color in hex",
|
||||
Some('d'),
|
||||
)
|
||||
.rest(
|
||||
"cell path",
|
||||
SyntaxShape::CellPath,
|
||||
"for a data structure input, add a gradient to strings at the given cell paths",
|
||||
)
|
||||
.input_output_types(vec![
|
||||
(Type::String, Type::String),
|
||||
(Type::Table(vec![]), Type::Table(vec![])),
|
||||
])
|
||||
.vectorizes_over_list(true)
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Platform)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Add a color gradient (using ANSI color codes) to the given string."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
operate(engine_state, stack, call, input)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "draw text in a gradient with foreground start and end colors",
|
||||
example:
|
||||
"'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart '0x40c9ff' --fgend '0xe81cff'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "draw text in a gradient with foreground start and end colors and background start and end colors",
|
||||
example:
|
||||
"'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart '0x40c9ff' --fgend '0xe81cff' --bgstart '0xe81cff' --bgend '0x40c9ff'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "draw text in a gradient by specifying foreground start color - end color is assumed to be black",
|
||||
example:
|
||||
"'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart '0x40c9ff'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "draw text in a gradient by specifying foreground end color - start color is assumed to be black",
|
||||
example:
|
||||
"'Hello, Nushell! This is a gradient.' | ansi gradient --fgend '0xe81cff'",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn value_to_color(v: Option<Value>) -> Result<Option<Rgb>, ShellError> {
|
||||
let s = match v {
|
||||
None => return Ok(None),
|
||||
Some(x) => x.as_string()?,
|
||||
};
|
||||
Ok(Some(Rgb::from_hex_string(s)))
|
||||
}
|
||||
|
||||
fn operate(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let fgstart: Option<Value> = call.get_flag(engine_state, stack, "fgstart")?;
|
||||
let fgend: Option<Value> = call.get_flag(engine_state, stack, "fgend")?;
|
||||
let bgstart: Option<Value> = call.get_flag(engine_state, stack, "bgstart")?;
|
||||
let bgend: Option<Value> = call.get_flag(engine_state, stack, "bgend")?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
let fgs_hex = value_to_color(fgstart)?;
|
||||
let fge_hex = value_to_color(fgend)?;
|
||||
let bgs_hex = value_to_color(bgstart)?;
|
||||
let bge_hex = value_to_color(bgend)?;
|
||||
let head = call.head;
|
||||
input.map(
|
||||
move |v| {
|
||||
if column_paths.is_empty() {
|
||||
action(&v, fgs_hex, fge_hex, bgs_hex, bge_hex, &head)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
for path in &column_paths {
|
||||
let r = ret.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| action(old, fgs_hex, fge_hex, bgs_hex, bge_hex, &head)),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error {
|
||||
error: Box::new(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
},
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
fg_start: Option<Rgb>,
|
||||
fg_end: Option<Rgb>,
|
||||
bg_start: Option<Rgb>,
|
||||
bg_end: Option<Rgb>,
|
||||
command_span: &Span,
|
||||
) -> Value {
|
||||
match input {
|
||||
Value::String { val, span } => {
|
||||
match (fg_start, fg_end, bg_start, bg_end) {
|
||||
(None, None, None, None) => {
|
||||
// Error - no colors
|
||||
Value::Error {
|
||||
error: Box::new(ShellError::MissingParameter {
|
||||
param_name:
|
||||
"please supply foreground and/or background color parameters".into(),
|
||||
span: *command_span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
(None, None, None, Some(bg_end)) => {
|
||||
// Error - missing bg_start, so assume black
|
||||
let bg_start = Rgb::new(0, 0, 0);
|
||||
let gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Background);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(None, None, Some(bg_start), None) => {
|
||||
// Error - missing bg_end, so assume black
|
||||
let bg_end = Rgb::new(0, 0, 0);
|
||||
let gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Background);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(None, None, Some(bg_start), Some(bg_end)) => {
|
||||
// Background Only
|
||||
let gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Background);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(None, Some(fg_end), None, None) => {
|
||||
// Error - missing fg_start, so assume black
|
||||
let fg_start = Rgb::new(0, 0, 0);
|
||||
let gradient = Gradient::new(fg_start, fg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Foreground);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(None, Some(fg_end), None, Some(bg_end)) => {
|
||||
// missing fg_start and bg_start, so assume black
|
||||
let fg_start = Rgb::new(0, 0, 0);
|
||||
let bg_start = Rgb::new(0, 0, 0);
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(None, Some(fg_end), Some(bg_start), None) => {
|
||||
// Error - missing fg_start and bg_end
|
||||
let fg_start = Rgb::new(0, 0, 0);
|
||||
let bg_end = Rgb::new(0, 0, 0);
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(None, Some(fg_end), Some(bg_start), Some(bg_end)) => {
|
||||
// Error - missing fg_start, so assume black
|
||||
let fg_start = Rgb::new(0, 0, 0);
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(Some(fg_start), None, None, None) => {
|
||||
// Error - missing fg_end, so assume black
|
||||
let fg_end = Rgb::new(0, 0, 0);
|
||||
let gradient = Gradient::new(fg_start, fg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Foreground);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(Some(fg_start), None, None, Some(bg_end)) => {
|
||||
// Error - missing fg_end, bg_start, so assume black
|
||||
let fg_end = Rgb::new(0, 0, 0);
|
||||
let bg_start = Rgb::new(0, 0, 0);
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(Some(fg_start), None, Some(bg_start), None) => {
|
||||
// Error - missing fg_end, bg_end, so assume black
|
||||
let fg_end = Rgb::new(0, 0, 0);
|
||||
let bg_end = Rgb::new(0, 0, 0);
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(Some(fg_start), None, Some(bg_start), Some(bg_end)) => {
|
||||
// Error - missing fg_end, so assume black
|
||||
let fg_end = Rgb::new(0, 0, 0);
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(Some(fg_start), Some(fg_end), None, None) => {
|
||||
// Foreground Only
|
||||
let gradient = Gradient::new(fg_start, fg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Foreground);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(Some(fg_start), Some(fg_end), None, Some(bg_end)) => {
|
||||
// Error - missing bg_start, so assume black
|
||||
let bg_start = Rgb::new(0, 0, 0);
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(Some(fg_start), Some(fg_end), Some(bg_start), None) => {
|
||||
// Error - missing bg_end, so assume black
|
||||
let bg_end = Rgb::new(0, 0, 0);
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
(Some(fg_start), Some(fg_end), Some(bg_start), Some(bg_end)) => {
|
||||
// Foreground and Background Gradient
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
let got = format!("value is {}, not string", other.get_type());
|
||||
|
||||
Value::Error {
|
||||
error: Box::new(ShellError::TypeMismatch {
|
||||
err_message: got,
|
||||
span: other.span().unwrap_or(*command_span),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, SubCommand};
|
||||
use nu_ansi_term::Rgb;
|
||||
use nu_protocol::{Span, Value};
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fg_gradient() {
|
||||
let input_string = Value::test_string("Hello, World!");
|
||||
let expected = Value::test_string("\u{1b}[38;2;64;201;255mH\u{1b}[38;2;76;187;254me\u{1b}[38;2;89;174;254ml\u{1b}[38;2;102;160;254ml\u{1b}[38;2;115;147;254mo\u{1b}[38;2;128;133;254m,\u{1b}[38;2;141;120;254m \u{1b}[38;2;153;107;254mW\u{1b}[38;2;166;94;254mo\u{1b}[38;2;179;80;254mr\u{1b}[38;2;192;67;254ml\u{1b}[38;2;205;53;254md\u{1b}[38;2;218;40;254m!\u{1b}[0m");
|
||||
let fg_start = Rgb::from_hex_string("0x40c9ff".to_string());
|
||||
let fg_end = Rgb::from_hex_string("0xe81cff".to_string());
|
||||
let actual = action(
|
||||
&input_string,
|
||||
Some(fg_start),
|
||||
Some(fg_end),
|
||||
None,
|
||||
None,
|
||||
&Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::Command,
|
||||
engine::EngineState,
|
||||
engine::Stack,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"ansi link"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi link")
|
||||
.input_output_types(vec![
|
||||
(Type::String, Type::String),
|
||||
(
|
||||
Type::List(Box::new(Type::String)),
|
||||
Type::List(Box::new(Type::String)),
|
||||
),
|
||||
(Type::Table(vec![]), Type::Table(vec![])),
|
||||
(Type::Record(vec![]), Type::Record(vec![])),
|
||||
])
|
||||
.named(
|
||||
"text",
|
||||
SyntaxShape::String,
|
||||
"Link text. Uses uri as text if absent. In case of
|
||||
tables, records and lists applies this text to all elements",
|
||||
Some('t'),
|
||||
)
|
||||
.rest(
|
||||
"cell path",
|
||||
SyntaxShape::CellPath,
|
||||
"for a data structure input, add links to all strings at the given cell paths",
|
||||
)
|
||||
.vectorizes_over_list(true)
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Platform)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Add a link (using OSC 8 escape sequence) to the given string."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
operate(engine_state, stack, call, input)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create a link to open some file",
|
||||
example: "'file:///file.txt' | ansi link --text 'Open Me!'",
|
||||
result: Some(Value::string(
|
||||
"\u{1b}]8;;file:///file.txt\u{1b}\\Open Me!\u{1b}]8;;\u{1b}\\",
|
||||
Span::unknown(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Create a link without text",
|
||||
example: "'https://www.nushell.sh/' | ansi link",
|
||||
result: Some(Value::string(
|
||||
"\u{1b}]8;;https://www.nushell.sh/\u{1b}\\https://www.nushell.sh/\u{1b}]8;;\u{1b}\\",
|
||||
Span::unknown(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Format a table column into links",
|
||||
example: "[[url text]; [https://example.com Text]] | ansi link url",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let text: Option<Spanned<String>> = call.get_flag(engine_state, stack, "text")?;
|
||||
let text = text.map(|e| e.item);
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
let command_span = call.head;
|
||||
|
||||
if column_paths.is_empty() {
|
||||
input.map(
|
||||
move |v| process_value(&v, &text, &command_span),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
} else {
|
||||
input.map(
|
||||
move |v| process_each_path(v, &column_paths, &text, &command_span),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn process_each_path(
|
||||
mut value: Value,
|
||||
column_paths: &Vec<CellPath>,
|
||||
text: &Option<String>,
|
||||
command_span: &Span,
|
||||
) -> Value {
|
||||
for path in column_paths {
|
||||
let ret = value.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(|v| process_value(v, text, command_span)),
|
||||
);
|
||||
if let Err(error) = ret {
|
||||
return Value::Error {
|
||||
error: Box::new(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
fn process_value(value: &Value, text: &Option<String>, command_span: &Span) -> Value {
|
||||
match value {
|
||||
Value::String { val, span } => {
|
||||
let text = text.as_deref().unwrap_or(val.as_str());
|
||||
let result = add_osc_link(text, val.as_str());
|
||||
Value::string(result, *span)
|
||||
}
|
||||
other => {
|
||||
let got = format!("value is {}, not string", other.get_type());
|
||||
|
||||
Value::Error {
|
||||
error: Box::new(ShellError::TypeMismatch {
|
||||
err_message: got,
|
||||
span: other.span().unwrap_or(*command_span),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_osc_link(text: &str, link: &str) -> String {
|
||||
format!("\u{1b}]8;;{link}\u{1b}\\{text}\u{1b}]8;;\u{1b}\\")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,9 +1,5 @@
|
||||
mod ansi_;
|
||||
mod gradient;
|
||||
mod link;
|
||||
mod strip;
|
||||
|
||||
pub use ansi_::AnsiCommand as Ansi;
|
||||
pub use gradient::SubCommand as AnsiGradient;
|
||||
pub use link::SubCommand as AnsiLink;
|
||||
pub use strip::SubCommand as AnsiStrip;
|
||||
|
@ -7,7 +7,7 @@ mod kill;
|
||||
mod sleep;
|
||||
mod term_size;
|
||||
|
||||
pub use ansi::{Ansi, AnsiGradient, AnsiLink, AnsiStrip};
|
||||
pub use ansi::{Ansi, AnsiStrip};
|
||||
pub use clear::Clear;
|
||||
pub use dir_info::{DirBuilder, DirInfo, FileInfo};
|
||||
pub use du::Du;
|
||||
|
@ -1,72 +0,0 @@
|
||||
use super::hex::{operate, ActionType};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DecodeHex;
|
||||
|
||||
impl Command for DecodeHex {
|
||||
fn name(&self) -> &str {
|
||||
"decode hex"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("decode hex")
|
||||
.input_output_types(vec![(Type::String, Type::Binary)])
|
||||
.vectorizes_over_list(true)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"For a data structure input, decode data at the given cell paths",
|
||||
)
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Hex decode a value."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Hex decode a value and output as binary",
|
||||
example: "'0102030A0a0B' | decode hex",
|
||||
result: Some(Value::binary(
|
||||
[0x01, 0x02, 0x03, 0x0A, 0x0A, 0x0B],
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Whitespaces are allowed to be between hex digits",
|
||||
example: "'01 02 03 0A 0a 0B' | decode hex",
|
||||
result: Some(Value::binary(
|
||||
[0x01, 0x02, 0x03, 0x0A, 0x0A, 0x0B],
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
operate(ActionType::Decode, engine_state, stack, call, input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
crate::test_examples(DecodeHex)
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
use super::hex::{operate, ActionType};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EncodeHex;
|
||||
|
||||
impl Command for EncodeHex {
|
||||
fn name(&self) -> &str {
|
||||
"encode hex"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("encode hex")
|
||||
.input_output_types(vec![(Type::Binary, Type::String)])
|
||||
.vectorizes_over_list(true)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"For a data structure input, encode data at the given cell paths",
|
||||
)
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Encode a binary value using hex."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Encode binary data",
|
||||
example: "0x[09 F9 11 02 9D 74 E3 5B D8 41 56 C5 63 56 88 C0] | encode hex",
|
||||
result: Some(Value::test_string("09F911029D74E35BD84156C5635688C0")),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
operate(ActionType::Encode, engine_state, stack, call, input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
crate::test_examples(EncodeHex)
|
||||
}
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::{Call, CellPath};
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::{PipelineData, ShellError, Span, Value};
|
||||
|
||||
enum HexDecodingError {
|
||||
InvalidLength(usize),
|
||||
InvalidDigit(usize, char),
|
||||
}
|
||||
|
||||
fn hex_decode(value: &str) -> Result<Vec<u8>, HexDecodingError> {
|
||||
let mut digits = value
|
||||
.chars()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| !c.is_whitespace());
|
||||
|
||||
let mut res = Vec::with_capacity(value.len() / 2);
|
||||
loop {
|
||||
let c1 = match digits.next() {
|
||||
Some((ind, c)) => match c.to_digit(16) {
|
||||
Some(d) => d,
|
||||
None => return Err(HexDecodingError::InvalidDigit(ind, c)),
|
||||
},
|
||||
None => return Ok(res),
|
||||
};
|
||||
let c2 = match digits.next() {
|
||||
Some((ind, c)) => match c.to_digit(16) {
|
||||
Some(d) => d,
|
||||
None => return Err(HexDecodingError::InvalidDigit(ind, c)),
|
||||
},
|
||||
None => {
|
||||
return Err(HexDecodingError::InvalidLength(value.len()));
|
||||
}
|
||||
};
|
||||
res.push((c1 << 4 | c2) as u8);
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_digit(num: u8) -> char {
|
||||
match num {
|
||||
0..=9 => (num + b'0') as char,
|
||||
10..=15 => (num - 10 + b'A') as char,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_encode(bytes: &[u8]) -> String {
|
||||
let mut res = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
res.push(hex_digit(byte >> 4));
|
||||
res.push(hex_digit(byte & 0b1111));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HexConfig {
|
||||
pub action_type: ActionType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ActionType {
|
||||
Encode,
|
||||
Decode,
|
||||
}
|
||||
|
||||
struct Arguments {
|
||||
cell_paths: Option<Vec<CellPath>>,
|
||||
encoding_config: HexConfig,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.cell_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operate(
|
||||
action_type: ActionType,
|
||||
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 {
|
||||
encoding_config: HexConfig { action_type },
|
||||
cell_paths,
|
||||
};
|
||||
|
||||
general_operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
// only used for `decode` action
|
||||
args: &Arguments,
|
||||
command_span: Span,
|
||||
) -> Value {
|
||||
let hex_config = &args.encoding_config;
|
||||
|
||||
match input {
|
||||
// Propagate existing errors.
|
||||
Value::Error { .. } => input.clone(),
|
||||
Value::Binary { val, .. } => match hex_config.action_type {
|
||||
ActionType::Encode => Value::string(hex_encode(val.as_ref()), command_span),
|
||||
ActionType::Decode => Value::Error {
|
||||
error: Box::new(ShellError::UnsupportedInput(
|
||||
"Binary data can only be encoded".to_string(),
|
||||
"value originates from here".into(),
|
||||
command_span,
|
||||
// This line requires the Value::Error {} match above.
|
||||
input.expect_span(),
|
||||
)),
|
||||
},
|
||||
},
|
||||
Value::String { val, .. } => {
|
||||
match hex_config.action_type {
|
||||
ActionType::Encode => Value::Error {
|
||||
error: Box::new(ShellError::UnsupportedInput(
|
||||
"String value can only be decoded".to_string(),
|
||||
"value originates from here".into(),
|
||||
command_span,
|
||||
// This line requires the Value::Error {} match above.
|
||||
input.expect_span(),
|
||||
)),
|
||||
},
|
||||
|
||||
ActionType::Decode => match hex_decode(val.as_ref()) {
|
||||
Ok(decoded_value) => Value::binary(decoded_value, command_span),
|
||||
Err(HexDecodingError::InvalidLength(len)) => Value::Error {
|
||||
error: Box::new(ShellError::GenericError(
|
||||
"value could not be hex decoded".to_string(),
|
||||
format!("invalid hex input length: {len}. The length should be even"),
|
||||
Some(command_span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
},
|
||||
Err(HexDecodingError::InvalidDigit(index, digit)) => Value::Error {
|
||||
error: Box::new(ShellError::GenericError(
|
||||
"value could not be hex decoded".to_string(),
|
||||
format!("invalid hex digit: '{digit}' at index {index}. Only 0-9, A-F, a-f are allowed in hex encoding"),
|
||||
Some(command_span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
other => Value::Error {
|
||||
error: Box::new(ShellError::TypeMismatch {
|
||||
err_message: format!("string or binary, not {}", other.get_type()),
|
||||
span: other.span().unwrap_or(command_span),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, ActionType, Arguments, HexConfig};
|
||||
use nu_protocol::{Span, Value};
|
||||
|
||||
#[test]
|
||||
fn hex_encode() {
|
||||
let word = Value::binary([77, 97, 110], Span::test_data());
|
||||
let expected = Value::test_string("4D616E");
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: HexConfig {
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_decode() {
|
||||
let word = Value::test_string("4D 61\r\n\n6E");
|
||||
let expected = Value::binary([77, 97, 110], Span::test_data());
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: HexConfig {
|
||||
action_type: ActionType::Decode,
|
||||
},
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -1,16 +1,11 @@
|
||||
mod base64;
|
||||
mod decode;
|
||||
mod decode_base64;
|
||||
mod decode_hex;
|
||||
mod encode;
|
||||
mod encode_base64;
|
||||
mod encode_hex;
|
||||
mod encoding;
|
||||
mod hex;
|
||||
|
||||
pub use self::decode::Decode;
|
||||
pub use self::decode_base64::DecodeBase64;
|
||||
pub use self::decode_hex::DecodeHex;
|
||||
pub use self::encode::Encode;
|
||||
pub use self::encode_base64::EncodeBase64;
|
||||
pub use self::encode_hex::EncodeHex;
|
||||
|
@ -1,322 +0,0 @@
|
||||
use nu_engine::{eval_expression, CallExt};
|
||||
use nu_parser::parse_expression;
|
||||
use nu_protocol::ast::{Call, PathMember};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
|
||||
use nu_protocol::{
|
||||
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Format;
|
||||
|
||||
impl Command for Format {
|
||||
fn name(&self) -> &str {
|
||||
"format"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("format")
|
||||
.input_output_types(vec![(
|
||||
Type::Table(vec![]),
|
||||
Type::List(Box::new(Type::String)),
|
||||
)])
|
||||
.required(
|
||||
"pattern",
|
||||
SyntaxShape::String,
|
||||
"the pattern to output. e.g.) \"{foo}: {bar}\"",
|
||||
)
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Format columns into a string using a simple pattern."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let specified_pattern: Result<Value, ShellError> = call.req(engine_state, stack, 0);
|
||||
let input_val = input.into_value(call.head);
|
||||
// add '$it' variable to support format like this: $it.column1.column2.
|
||||
let it_id = working_set.add_variable(b"$it".to_vec(), call.head, Type::Any, false);
|
||||
stack.add_var(it_id, input_val.clone());
|
||||
|
||||
match specified_pattern {
|
||||
Err(e) => Err(e),
|
||||
Ok(pattern) => {
|
||||
let string_pattern = pattern.as_string()?;
|
||||
let string_span = pattern.span()?;
|
||||
// the string span is start as `"`, we don't need the character
|
||||
// to generate proper span for sub expression.
|
||||
let ops = extract_formatting_operations(
|
||||
string_pattern,
|
||||
call.head,
|
||||
string_span.start + 1,
|
||||
)?;
|
||||
|
||||
format(
|
||||
input_val,
|
||||
&ops,
|
||||
engine_state,
|
||||
&mut working_set,
|
||||
stack,
|
||||
call.head,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Print filenames with their sizes",
|
||||
example: "ls | format '{name}: {size}'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Print elements from some columns of a table",
|
||||
example: "[[col1, col2]; [v1, v2] [v3, v4]] | format '{col2}'",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_string("v2"), Value::test_string("v4")],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: The reason to split {column1.column2} and {$it.column1.column2}:
|
||||
// for {column1.column2}, we just need to follow given record or list.
|
||||
// for {$it.column1.column2} or {$variable}, we need to manually evaluate the expression.
|
||||
//
|
||||
// Have thought about converting from {column1.column2} to {$it.column1.column2}, but that
|
||||
// will extend input relative span, finally make `nu` panic out with message: span missing in file
|
||||
// contents cache.
|
||||
#[derive(Debug)]
|
||||
enum FormatOperation {
|
||||
FixedText(String),
|
||||
// raw input is something like {column1.column2}
|
||||
ValueFromColumn(String, Span),
|
||||
// raw input is something like {$it.column1.column2} or {$var}.
|
||||
ValueNeedEval(String, Span),
|
||||
}
|
||||
|
||||
/// Given a pattern that is fed into the Format command, we can process it and subdivide it
|
||||
/// in two kind of operations.
|
||||
/// FormatOperation::FixedText contains a portion of the pattern that has to be placed
|
||||
/// there without any further processing.
|
||||
/// FormatOperation::ValueFromColumn contains the name of a column whose values will be
|
||||
/// formatted according to the input pattern.
|
||||
/// FormatOperation::ValueNeedEval contains expression which need to eval, it has the following form:
|
||||
/// "$it.column1.column2" or "$variable"
|
||||
fn extract_formatting_operations(
|
||||
input: String,
|
||||
error_span: Span,
|
||||
span_start: usize,
|
||||
) -> Result<Vec<FormatOperation>, ShellError> {
|
||||
let mut output = vec![];
|
||||
|
||||
let mut characters = input.char_indices();
|
||||
|
||||
let mut column_span_start = 0;
|
||||
let mut column_span_end = 0;
|
||||
loop {
|
||||
let mut before_bracket = String::new();
|
||||
|
||||
for (index, ch) in &mut characters {
|
||||
if ch == '{' {
|
||||
column_span_start = index + 1; // not include '{' character.
|
||||
break;
|
||||
}
|
||||
before_bracket.push(ch);
|
||||
}
|
||||
|
||||
if !before_bracket.is_empty() {
|
||||
output.push(FormatOperation::FixedText(before_bracket.to_string()));
|
||||
}
|
||||
|
||||
let mut column_name = String::new();
|
||||
let mut column_need_eval = false;
|
||||
for (index, ch) in &mut characters {
|
||||
if ch == '$' {
|
||||
column_need_eval = true;
|
||||
}
|
||||
|
||||
if ch == '}' {
|
||||
column_span_end = index; // not include '}' character.
|
||||
break;
|
||||
}
|
||||
column_name.push(ch);
|
||||
}
|
||||
|
||||
if column_span_end < column_span_start {
|
||||
return Err(ShellError::DelimiterError {
|
||||
msg: "there are unmatched curly braces".to_string(),
|
||||
span: error_span,
|
||||
});
|
||||
}
|
||||
|
||||
if !column_name.is_empty() {
|
||||
if column_need_eval {
|
||||
output.push(FormatOperation::ValueNeedEval(
|
||||
column_name.clone(),
|
||||
Span::new(span_start + column_span_start, span_start + column_span_end),
|
||||
));
|
||||
} else {
|
||||
output.push(FormatOperation::ValueFromColumn(
|
||||
column_name.clone(),
|
||||
Span::new(span_start + column_span_start, span_start + column_span_end),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if before_bracket.is_empty() && column_name.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Format the incoming PipelineData according to the pattern
|
||||
fn format(
|
||||
input_data: Value,
|
||||
format_operations: &[FormatOperation],
|
||||
engine_state: &EngineState,
|
||||
working_set: &mut StateWorkingSet,
|
||||
stack: &mut Stack,
|
||||
head_span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let data_as_value = input_data;
|
||||
|
||||
// We can only handle a Record or a List of Records
|
||||
match data_as_value {
|
||||
Value::Record { .. } => {
|
||||
match format_record(
|
||||
format_operations,
|
||||
&data_as_value,
|
||||
engine_state,
|
||||
working_set,
|
||||
stack,
|
||||
) {
|
||||
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
|
||||
Err(value) => Err(value),
|
||||
}
|
||||
}
|
||||
|
||||
Value::List { vals, .. } => {
|
||||
let mut list = vec![];
|
||||
for val in vals.iter() {
|
||||
match val {
|
||||
Value::Record { .. } => {
|
||||
match format_record(
|
||||
format_operations,
|
||||
val,
|
||||
engine_state,
|
||||
working_set,
|
||||
stack,
|
||||
) {
|
||||
Ok(value) => {
|
||||
list.push(Value::string(value, head_span));
|
||||
}
|
||||
Err(value) => {
|
||||
return Err(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Error { error } => return Err(*error.clone()),
|
||||
_ => {
|
||||
return Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "record".to_string(),
|
||||
wrong_type: val.get_type().to_string(),
|
||||
dst_span: head_span,
|
||||
src_span: val.expect_span(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::ListStream(
|
||||
ListStream::from_stream(list.into_iter(), None),
|
||||
None,
|
||||
))
|
||||
}
|
||||
// Unwrapping this ShellError is a bit unfortunate.
|
||||
// Ideally, its Span would be preserved.
|
||||
Value::Error { error } => Err(*error),
|
||||
_ => Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "record".to_string(),
|
||||
wrong_type: data_as_value.get_type().to_string(),
|
||||
dst_span: head_span,
|
||||
src_span: data_as_value.expect_span(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_record(
|
||||
format_operations: &[FormatOperation],
|
||||
data_as_value: &Value,
|
||||
engine_state: &EngineState,
|
||||
working_set: &mut StateWorkingSet,
|
||||
stack: &mut Stack,
|
||||
) -> Result<String, ShellError> {
|
||||
let config = engine_state.get_config();
|
||||
let mut output = String::new();
|
||||
|
||||
for op in format_operations {
|
||||
match op {
|
||||
FormatOperation::FixedText(s) => output.push_str(s.as_str()),
|
||||
FormatOperation::ValueFromColumn(col_name, span) => {
|
||||
// path member should split by '.' to handle for nested structure.
|
||||
let path_members: Vec<PathMember> = col_name
|
||||
.split('.')
|
||||
.map(|path| PathMember::String {
|
||||
val: path.to_string(),
|
||||
span: *span,
|
||||
optional: false,
|
||||
})
|
||||
.collect();
|
||||
match data_as_value.clone().follow_cell_path(&path_members, false) {
|
||||
Ok(value_at_column) => {
|
||||
output.push_str(value_at_column.into_string(", ", config).as_str())
|
||||
}
|
||||
Err(se) => return Err(se),
|
||||
}
|
||||
}
|
||||
FormatOperation::ValueNeedEval(_col_name, span) => {
|
||||
let exp = parse_expression(working_set, &[*span], false);
|
||||
match working_set.parse_errors.first() {
|
||||
None => {
|
||||
let parsed_result = eval_expression(engine_state, stack, &exp);
|
||||
if let Ok(val) = parsed_result {
|
||||
output.push_str(&val.into_abbreviated_string(config))
|
||||
}
|
||||
}
|
||||
Some(err) => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: format!("expression is invalid, detail message: {err:?}"),
|
||||
span: *span,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::Format;
|
||||
use crate::test_examples;
|
||||
test_examples(Format {})
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::{Call, CellPath};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
format_filesize, Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
format_value: 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 FileSize;
|
||||
|
||||
impl Command for FileSize {
|
||||
fn name(&self) -> &str {
|
||||
"format filesize"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("format filesize")
|
||||
.input_output_types(vec![(Type::Filesize, Type::String)])
|
||||
.required(
|
||||
"format value",
|
||||
SyntaxShape::String,
|
||||
"the format into which convert the file sizes",
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"For a data structure input, format filesizes at the given cell paths",
|
||||
)
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Converts a column of filesizes to some specified format."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "display", "pattern", "human readable"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let format_value = call
|
||||
.req::<Value>(engine_state, stack, 0)?
|
||||
.as_string()?
|
||||
.to_ascii_lowercase();
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
let arg = Arguments {
|
||||
format_value,
|
||||
cell_paths,
|
||||
};
|
||||
operate(
|
||||
format_value_impl,
|
||||
arg,
|
||||
input,
|
||||
call.head,
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert the size column to KB",
|
||||
example: "ls | format filesize KB size",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert the apparent column to B",
|
||||
example: "du | format filesize B apparent",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert the size data to MB",
|
||||
example: "4Gb | format filesize MB",
|
||||
result: Some(Value::test_string("4000.0 MB")),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn format_value_impl(val: &Value, arg: &Arguments, span: Span) -> Value {
|
||||
match val {
|
||||
Value::Filesize { val, span } => Value::String {
|
||||
// don't need to concern about metric, we just format units by what user input.
|
||||
val: format_filesize(*val, &arg.format_value, None),
|
||||
span: *span,
|
||||
},
|
||||
Value::Error { .. } => val.clone(),
|
||||
_ => Value::Error {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "filesize".into(),
|
||||
wrong_type: val.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: val.expect_span(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(FileSize)
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
mod command;
|
||||
mod filesize;
|
||||
|
||||
pub use self::filesize::FileSize;
|
||||
pub use command::Format;
|
@ -1,7 +1,6 @@
|
||||
mod char_;
|
||||
mod detect_columns;
|
||||
mod encode_decode;
|
||||
mod format;
|
||||
mod parse;
|
||||
mod size;
|
||||
mod split;
|
||||
@ -10,7 +9,6 @@ mod str_;
|
||||
pub use char_::Char;
|
||||
pub use detect_columns::*;
|
||||
pub use encode_decode::*;
|
||||
pub use format::*;
|
||||
pub use parse::*;
|
||||
pub use size::Size;
|
||||
pub use split::*;
|
||||
|
Reference in New Issue
Block a user