engine-q merge

This commit is contained in:
Fernando Herrera 2022-02-07 19:11:34 +00:00
commit fdce6c49ab
1965 changed files with 119062 additions and 20 deletions

11
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,11 @@
# Description
(description of your pull request here)
# Tests
Make sure you've run and fixed any issues with these commands:
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- [ ] `cargo clippy --all --all-features -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- [ ] `cargo build; cargo test --all --all-features` to check that all the tests pass

42
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,42 @@
on: [pull_request]
name: Continuous integration
jobs:
ci:
strategy:
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
rust:
- stable
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
with:
command: build
- uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --all --all-features -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect

7
.gitignore vendored
View File

@ -1,3 +1,4 @@
<<<<<<< HEAD
/target
/scratch
**/*.rs.bk
@ -20,3 +21,9 @@ debian/nu/
# VSCode's IDE items
.vscode/*
=======
history.txt
/target
/.vscode
.DS_Store
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce

2290
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
[package]
<<<<<<< HEAD
authors = ["The Nu Project Contributors"]
default-run = "nu"
description = "A new type of shell"
@ -148,10 +149,117 @@ required-features = ["textview"]
[[bin]]
name = "nu_plugin_core_inc"
=======
name = "nu"
version = "0.1.0"
edition = "2021"
default-run = "nu"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = [
"crates/nu-cli",
"crates/nu-engine",
"crates/nu-parser",
"crates/nu-system",
"crates/nu-command",
"crates/nu-protocol",
"crates/nu-plugin",
"crates/nu_plugin_inc",
"crates/nu_plugin_gstat",
"crates/nu_plugin_example",
"crates/nu_plugin_query",
]
[dependencies]
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
crossterm = "0.22.*"
nu-cli = { path="./crates/nu-cli" }
nu-command = { path="./crates/nu-command" }
nu-engine = { path="./crates/nu-engine" }
nu-json = { path="./crates/nu-json" }
nu-parser = { path="./crates/nu-parser" }
nu-path = { path="./crates/nu-path" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex" }
nu-protocol = { path = "./crates/nu-protocol" }
nu-plugin = { path = "./crates/nu-plugin", optional = true }
nu-system = { path = "./crates/nu-system"}
nu-table = { path = "./crates/nu-table" }
nu-term-grid = { path = "./crates/nu-term-grid" }
nu-ansi-term = "0.42.0"
nu-color-config = { path = "./crates/nu-color-config" }
miette = "3.0.0"
ctrlc = "3.2.1"
crossterm_winapi = "0.9.0"
log = "0.4"
pretty_env_logger = "0.4.0"
# mimalloc = { version = "*", default-features = false }
nu_plugin_inc = { version = "0.1.0", path = "./crates/nu_plugin_inc", optional = true }
nu_plugin_example = { version = "0.1.0", path = "./crates/nu_plugin_example", optional = true }
nu_plugin_gstat = { version = "0.1.0", path = "./crates/nu_plugin_gstat", optional = true }
nu_plugin_query = { version = "0.1.0", path = "./crates/nu_plugin_query", optional = true }
[dev-dependencies]
nu-test-support = { path="./crates/nu-test-support" }
tempfile = "3.2.0"
assert_cmd = "2.0.2"
pretty_assertions = "1.0.0"
serial_test = "0.5.1"
hamcrest2 = "0.3.0"
rstest = "0.12.0"
itertools = "0.10.3"
[features]
plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
default = [
"plugin",
"inc",
"example",
"which"
]
stable = ["default"]
extra = [
"default",
"dataframe",
"gstat",
"zip-support",
"query",
]
wasi = ["inc"]
# Stable (Default)
inc = ["nu_plugin_inc"]
example = ["nu_plugin_example"]
which = ["nu-command/which"]
# Extra
gstat = ["nu_plugin_gstat"]
zip-support = ["nu-command/zip"]
query = ["nu_plugin_query"]
# Dataframe feature for nushell
dataframe = ["nu-command/dataframe"]
[profile.release]
opt-level = "s" # Optimize for size
# Build plugins
[[bin]]
name = "nu_plugin_inc"
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
path = "src/plugins/nu_plugin_core_inc.rs"
required-features = ["inc"]
[[bin]]
<<<<<<< HEAD
name = "nu_plugin_core_match"
path = "src/plugins/nu_plugin_core_match.rs"
required-features = ["match"]
@ -222,6 +330,22 @@ required-features = ["sqlite"]
name = "nu_plugin_extra_to_sqlite"
path = "src/plugins/nu_plugin_extra_to_sqlite.rs"
required-features = ["sqlite"]
=======
name = "nu_plugin_example"
path = "src/plugins/nu_plugin_core_example.rs"
required-features = ["example"]
# Extra plugins
[[bin]]
name = "nu_plugin_gstat"
path = "src/plugins/nu_plugin_extra_gstat.rs"
required-features = ["gstat"]
[[bin]]
name = "nu_plugin_query"
path = "src/plugins/nu_plugin_extra_query.rs"
required-features = ["query"]
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
# Main nu binary
[[bin]]

View File

@ -1,6 +1,10 @@
MIT License
<<<<<<< HEAD
Copyright (c) 2019 - 2021 Nushell Project
=======
Copyright (c) 2021 Nushell Project
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,3 +1,4 @@
<<<<<<< HEAD
# README
[![Crates.io](https://img.shields.io/crates/v/nu.svg)](https://crates.io/crates/nu)
@ -281,3 +282,8 @@ Thanks to all the people who already contributed!
## License
The project is made available under the MIT license. See the `LICENSE` file for more information.
=======
# NOTE: Engine-q is merged into Nushell
Please use https://github.com/nushell/nushell
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce

View File

@ -1,4 +1,5 @@
[package]
<<<<<<< HEAD
authors = ["The Nu Project Contributors"]
description = "CLI for nushell"
edition = "2018"
@ -41,3 +42,24 @@ shadow-rs = "0.8.1"
default = ["shadow-rs"]
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
stable = []
=======
name = "nu-cli"
version = "0.1.0"
edition = "2021"
[dependencies]
nu-engine = { path = "../nu-engine" }
nu-path = { path = "../nu-path" }
nu-parser = { path = "../nu-parser" }
nu-protocol = { path = "../nu-protocol" }
# nu-ansi-term = { path = "../nu-ansi-term" }
nu-ansi-term = "0.42.0"
nu-color-config = { path = "../nu-color-config" }
miette = { version = "3.0.0", features = ["fancy"] }
thiserror = "1.0.29"
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
log = "0.4"
is_executable = "1.0.1"
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce

View File

@ -0,0 +1,442 @@
use nu_engine::eval_block;
use nu_parser::{flatten_expression, parse};
use nu_protocol::{
ast::{Expr, Statement},
engine::{EngineState, Stack, StateWorkingSet},
PipelineData, Span,
};
use reedline::Completer;
const SEP: char = std::path::MAIN_SEPARATOR;
#[derive(Clone)]
pub struct NuCompleter {
engine_state: EngineState,
}
impl NuCompleter {
pub fn new(engine_state: EngineState) -> Self {
Self { engine_state }
}
fn external_command_completion(&self, prefix: &str) -> Vec<String> {
let mut executables = vec![];
let paths;
paths = self.engine_state.env_vars.get("PATH");
if let Some(paths) = paths {
if let Ok(paths) = paths.as_list() {
for path in paths {
let path = path.as_string().unwrap_or_default();
if let Ok(mut contents) = std::fs::read_dir(path) {
while let Some(Ok(item)) = contents.next() {
if !executables.contains(
&item
.path()
.file_name()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or_default(),
) && matches!(
item.path()
.file_name()
.map(|x| x.to_string_lossy().starts_with(prefix)),
Some(true)
) && is_executable::is_executable(&item.path())
{
if let Ok(name) = item.file_name().into_string() {
executables.push(name);
}
}
}
}
}
}
}
executables
}
fn complete_variables(
&self,
working_set: &StateWorkingSet,
prefix: &[u8],
span: Span,
offset: usize,
) -> Vec<(reedline::Span, String)> {
let mut output = vec![];
let builtins = ["$nu", "$scope", "$in", "$config", "$env"];
for builtin in builtins {
if builtin.as_bytes().starts_with(prefix) {
output.push((
reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
builtin.to_string(),
));
}
}
for scope in &working_set.delta.scope {
for v in &scope.vars {
if v.0.starts_with(prefix) {
output.push((
reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
String::from_utf8_lossy(v.0).to_string(),
));
}
}
}
for scope in &self.engine_state.scope {
for v in &scope.vars {
if v.0.starts_with(prefix) {
output.push((
reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
String::from_utf8_lossy(v.0).to_string(),
));
}
}
}
output.dedup();
output
}
fn complete_commands(
&self,
working_set: &StateWorkingSet,
span: Span,
offset: usize,
) -> Vec<(reedline::Span, String)> {
let prefix = working_set.get_span_contents(span);
let results = working_set
.find_commands_by_prefix(prefix)
.into_iter()
.map(move |x| {
(
reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
String::from_utf8_lossy(&x).to_string(),
)
});
let prefix = working_set.get_span_contents(span);
let prefix = String::from_utf8_lossy(prefix).to_string();
let results_external =
self.external_command_completion(&prefix)
.into_iter()
.map(move |x| {
(
reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
x,
)
});
results
.into_iter()
.chain(results_external.into_iter())
.collect()
}
fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> {
let mut working_set = StateWorkingSet::new(&self.engine_state);
let offset = working_set.next_span_start();
let pos = offset + pos;
let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
for stmt in output.stmts.into_iter() {
if let Statement::Pipeline(pipeline) = stmt {
for expr in pipeline.expressions {
let flattened = flatten_expression(&working_set, &expr);
for flat in flattened {
if pos >= flat.0.start && pos <= flat.0.end {
let prefix = working_set.get_span_contents(flat.0);
if prefix.starts_with(b"$") {
return self.complete_variables(
&working_set,
prefix,
flat.0,
offset,
);
}
if prefix.starts_with(b"-") {
// this might be a flag, let's see
if let Expr::Call(call) = &expr.expr {
let decl = working_set.get_decl(call.decl_id);
let sig = decl.signature();
let mut output = vec![];
for named in &sig.named {
let mut named = named.long.as_bytes().to_vec();
named.insert(0, b'-');
named.insert(0, b'-');
if named.starts_with(prefix) {
output.push((
reedline::Span {
start: flat.0.start - offset,
end: flat.0.end - offset,
},
String::from_utf8_lossy(&named).to_string(),
));
}
}
return output;
}
}
match &flat.1 {
nu_parser::FlatShape::Custom(custom_completion) => {
let prefix = working_set.get_span_contents(flat.0).to_vec();
let (block, ..) = parse(
&mut working_set,
None,
custom_completion.as_bytes(),
false,
);
let mut stack = Stack::default();
let result = eval_block(
&self.engine_state,
&mut stack,
&block,
PipelineData::new(flat.0),
);
let v: Vec<_> = match result {
Ok(pd) => pd
.into_iter()
.map(move |x| {
let s = x.as_string().expect(
"FIXME: better error handling for custom completions",
);
(
reedline::Span {
start: flat.0.start - offset,
end: flat.0.end - offset,
},
s,
)
})
.filter(|x| x.1.as_bytes().starts_with(&prefix))
.collect(),
_ => vec![],
};
return v;
}
nu_parser::FlatShape::External
| nu_parser::FlatShape::InternalCall
| nu_parser::FlatShape::String => {
let subcommands = self.complete_commands(
&working_set,
Span {
start: expr.span.start,
end: pos,
},
offset,
);
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD")
{
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
}
} else {
"".to_string()
};
let prefix = working_set.get_span_contents(flat.0);
let prefix = String::from_utf8_lossy(prefix).to_string();
return file_path_completion(flat.0, &prefix, &cwd)
.into_iter()
.map(move |x| {
(
reedline::Span {
start: x.0.start - offset,
end: x.0.end - offset,
},
x.1,
)
})
.chain(subcommands.into_iter())
.collect();
}
nu_parser::FlatShape::Filepath
| nu_parser::FlatShape::GlobPattern
| nu_parser::FlatShape::ExternalArg => {
// Check for subcommands
let subcommands = self.complete_commands(
&working_set,
Span {
start: expr.span.start,
end: pos,
},
offset,
);
// Check for args
let prefix = working_set.get_span_contents(flat.0);
let prefix = String::from_utf8_lossy(prefix).to_string();
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD")
{
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
}
} else {
"".to_string()
};
let results = file_path_completion(flat.0, &prefix, &cwd);
return results
.into_iter()
.map(move |x| {
(
reedline::Span {
start: x.0.start - offset,
end: x.0.end - offset,
},
x.1,
)
})
.chain(subcommands.into_iter())
.collect();
}
_ => {
return self.complete_commands(
&working_set,
Span {
start: expr.span.start,
end: pos,
},
offset,
)
}
}
}
// If we get here, let's just check to see if we can complete a subcommand
// Check for subcommands
let subcommands = self.complete_commands(
&working_set,
Span {
start: expr.span.start,
end: pos,
},
offset,
);
if !subcommands.is_empty() {
return subcommands;
}
}
}
}
}
vec![]
}
}
impl Completer for NuCompleter {
fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> {
let mut output = self.completion_helper(line, pos);
output.sort_by(|a, b| a.1.cmp(&b.1));
output
}
}
fn file_path_completion(
span: nu_protocol::Span,
partial: &str,
cwd: &str,
) -> Vec<(nu_protocol::Span, String)> {
use std::path::{is_separator, Path};
let partial = if let Some(s) = partial.strip_prefix('"') {
s
} else {
partial
};
let partial = if let Some(s) = partial.strip_suffix('"') {
s
} else {
partial
};
let (base_dir_name, partial) = {
// If partial is only a word we want to search in the current dir
let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", partial));
// On windows, this standardizes paths to use \
let mut base = base.replace(is_separator, &SEP.to_string());
// rsplit_once removes the separator
base.push(SEP);
(base, rest)
};
let base_dir = nu_path::expand_path_with(&base_dir_name, cwd);
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
// which we don't want in this case (if we did, base_dir would already be ".")
if base_dir == Path::new("") {
return Vec::new();
}
if let Ok(result) = base_dir.read_dir() {
result
.filter_map(|entry| {
entry.ok().and_then(|entry| {
let mut file_name = entry.file_name().to_string_lossy().into_owned();
if matches(partial, &file_name) {
let mut path = format!("{}{}", base_dir_name, file_name);
if entry.path().is_dir() {
path.push(SEP);
file_name.push(SEP);
}
if path.contains(' ') {
path = format!("\"{}\"", path);
}
Some((span, path))
} else {
None
}
})
})
.collect()
} else {
Vec::new()
}
}
fn matches(partial: &str, from: &str) -> bool {
from.to_ascii_lowercase()
.starts_with(&partial.to_ascii_lowercase())
}

View File

@ -0,0 +1,46 @@
use miette::{LabeledSpan, MietteHandler, ReportHandler, Severity, SourceCode};
use nu_protocol::engine::StateWorkingSet;
use thiserror::Error;
/// This error exists so that we can defer SourceCode handling. It simply
/// forwards most methods, except for `.source_code()`, which we provide.
#[derive(Error)]
#[error("{0}")]
pub struct CliError<'src>(
pub &'src (dyn miette::Diagnostic + Send + Sync + 'static),
pub &'src StateWorkingSet<'src>,
);
impl std::fmt::Debug for CliError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
MietteHandler::default().debug(self, f)?;
Ok(())
}
}
impl<'src> miette::Diagnostic for CliError<'src> {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.0.code()
}
fn severity(&self) -> Option<Severity> {
self.0.severity()
}
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.0.help()
}
fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.0.url()
}
fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
self.0.labels()
}
// Finally, we redirect the source_code method to our own source.
fn source_code(&self) -> Option<&dyn SourceCode> {
Some(&self.1)
}
}

View File

@ -1,3 +1,4 @@
<<<<<<< HEAD
pub mod app;
mod cli;
#[cfg(feature = "rustyline-support")]
@ -13,3 +14,18 @@ pub use crate::app::App;
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
pub use nu_command::create_default_context;
=======
mod completions;
mod errors;
mod nu_highlight;
mod prompt;
mod syntax_highlight;
mod validation;
pub use completions::NuCompleter;
pub use errors::CliError;
pub use nu_highlight::NuHighlight;
pub use prompt::NushellPrompt;
pub use syntax_highlight::NuHighlighter;
pub use validation::NuValidator;
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce

View File

@ -0,0 +1,63 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value};
use reedline::Highlighter;
#[derive(Clone)]
pub struct NuHighlight;
impl Command for NuHighlight {
fn name(&self) -> &str {
"nu-highlight"
}
fn signature(&self) -> Signature {
Signature::build("nu-highlight").category(Category::Strings)
}
fn usage(&self) -> &str {
"Syntax highlight the input string."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let config = stack.get_config()?;
let highlighter = crate::NuHighlighter {
engine_state,
config,
};
input.map(
move |x| match x.as_string() {
Ok(line) => {
let highlights = highlighter.highlight(&line);
Value::String {
val: highlights.render_simple(),
span: head,
}
}
Err(err) => Value::Error { error: err },
},
ctrlc,
)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Describe the type of a string",
example: "'let x = 3' | nu-highlight",
result: None,
}]
}
}

143
crates/nu-cli/src/prompt.rs Normal file
View File

@ -0,0 +1,143 @@
use reedline::DefaultPrompt;
use {
reedline::{
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
},
std::borrow::Cow,
};
/// Nushell prompt definition
#[derive(Clone)]
pub struct NushellPrompt {
left_prompt_string: Option<String>,
right_prompt_string: Option<String>,
default_prompt_indicator: String,
default_vi_insert_prompt_indicator: String,
default_vi_normal_prompt_indicator: String,
default_multiline_indicator: String,
}
impl Default for NushellPrompt {
fn default() -> Self {
NushellPrompt::new()
}
}
impl NushellPrompt {
pub fn new() -> NushellPrompt {
NushellPrompt {
left_prompt_string: None,
right_prompt_string: None,
default_prompt_indicator: "".to_string(),
default_vi_insert_prompt_indicator: ": ".to_string(),
default_vi_normal_prompt_indicator: "".to_string(),
default_multiline_indicator: "::: ".to_string(),
}
}
pub fn update_prompt_left(&mut self, prompt_string: Option<String>) {
self.left_prompt_string = prompt_string;
}
pub fn update_prompt_right(&mut self, prompt_string: Option<String>) {
self.right_prompt_string = prompt_string;
}
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: String) {
self.default_prompt_indicator = prompt_indicator_string;
}
pub fn update_prompt_vi_insert(&mut self, prompt_vi_insert_string: String) {
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
}
pub fn update_prompt_vi_normal(&mut self, prompt_vi_normal_string: String) {
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
}
pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: String) {
self.default_multiline_indicator = prompt_multiline_indicator_string;
}
pub fn update_all_prompt_strings(
&mut self,
left_prompt_string: Option<String>,
right_prompt_string: Option<String>,
prompt_indicator_string: String,
prompt_multiline_indicator_string: String,
prompt_vi: (String, String),
) {
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
self.left_prompt_string = left_prompt_string;
self.right_prompt_string = right_prompt_string;
self.default_prompt_indicator = prompt_indicator_string;
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
self.default_multiline_indicator = prompt_multiline_indicator_string;
}
fn default_wrapped_custom_string(&self, str: String) -> String {
format!("({})", str)
}
}
impl Prompt for NushellPrompt {
fn render_prompt_left(&self) -> Cow<str> {
if let Some(prompt_string) = &self.left_prompt_string {
prompt_string.replace("\n", "\r\n").into()
} else {
let default = DefaultPrompt::new();
default
.render_prompt_left()
.to_string()
.replace("\n", "\r\n")
.into()
}
}
fn render_prompt_right(&self) -> Cow<str> {
if let Some(prompt_string) = &self.right_prompt_string {
prompt_string.replace("\n", "\r\n").into()
} else {
let default = DefaultPrompt::new();
default
.render_prompt_right()
.to_string()
.replace("\n", "\r\n")
.into()
}
}
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
match edit_mode {
PromptEditMode::Default => self.default_prompt_indicator.as_str().into(),
PromptEditMode::Emacs => self.default_prompt_indicator.as_str().into(),
PromptEditMode::Vi(vi_mode) => match vi_mode {
PromptViMode::Normal => self.default_vi_normal_prompt_indicator.as_str().into(),
PromptViMode::Insert => self.default_vi_insert_prompt_indicator.as_str().into(),
},
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
}
}
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
Cow::Borrowed(self.default_multiline_indicator.as_str())
}
fn render_prompt_history_search_indicator(
&self,
history_search: PromptHistorySearch,
) -> Cow<str> {
let prefix = match history_search.status {
PromptHistorySearchStatus::Passing => "",
PromptHistorySearchStatus::Failing => "failing ",
};
Cow::Owned(format!(
"({}reverse-search: {})",
prefix, history_search.term
))
}
}

View File

@ -0,0 +1,199 @@
use log::trace;
use nu_ansi_term::Style;
use nu_color_config::get_shape_color;
use nu_parser::{flatten_block, parse, FlatShape};
use nu_protocol::engine::{EngineState, StateWorkingSet};
use nu_protocol::Config;
use reedline::{Highlighter, StyledText};
pub struct NuHighlighter {
pub engine_state: EngineState,
pub config: Config,
}
impl Highlighter for NuHighlighter {
fn highlight(&self, line: &str) -> StyledText {
trace!("highlighting: {}", line);
let (shapes, global_span_offset) = {
let mut working_set = StateWorkingSet::new(&self.engine_state);
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false);
let shapes = flatten_block(&working_set, &block);
(shapes, self.engine_state.next_span_start())
};
let mut output = StyledText::default();
let mut last_seen_span = global_span_offset;
for shape in &shapes {
if shape.0.end <= last_seen_span
|| last_seen_span < global_span_offset
|| shape.0.start < global_span_offset
{
// We've already output something for this span
// so just skip this one
continue;
}
if shape.0.start > last_seen_span {
let gap = line
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
.to_string();
output.push((Style::new(), gap));
}
let next_token = line
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
.to_string();
match shape.1 {
FlatShape::Garbage => output.push((
// nushell Garbage
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Nothing => output.push((
// nushell Nothing
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Bool => {
// nushell ?
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Int => {
// nushell Int
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Float => {
// nushell Decimal
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Range => output.push((
// nushell DotDot ?
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::InternalCall => output.push((
// nushell InternalCommand
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::External => {
// nushell ExternalCommand
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::ExternalArg => {
// nushell ExternalWord
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Literal => {
// nushell ?
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Operator => output.push((
// nushell Operator
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Signature => output.push((
// nushell ?
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::String => {
// nushell String
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::StringInterpolation => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::List => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Table => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Record => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Block => {
// nushell ???
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Filepath => output.push((
// nushell Path
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::GlobPattern => output.push((
// nushell GlobPattern
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Variable => output.push((
// nushell Variable
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::Flag => {
// nushell Flag
output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
))
}
FlatShape::Custom(..) => output.push((
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
}
last_seen_span = shape.0.end;
}
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
if !remainder.is_empty() {
output.push((Style::new(), remainder));
}
output
}
}

View File

@ -0,0 +1,20 @@
use nu_parser::{parse, ParseError};
use nu_protocol::engine::{EngineState, StateWorkingSet};
use reedline::{ValidationResult, Validator};
pub struct NuValidator {
pub engine_state: EngineState,
}
impl Validator for NuValidator {
fn validate(&self, line: &str) -> ValidationResult {
let mut working_set = StateWorkingSet::new(&self.engine_state);
let (_, err) = parse(&mut working_set, None, line.as_bytes(), false);
if matches!(err, Some(ParseError::UnexpectedEof(..))) {
ValidationResult::Incomplete
} else {
ValidationResult::Complete
}
}
}

View File

@ -0,0 +1,13 @@
[package]
name = "nu-color-config"
version = "0.1.0"
edition = "2021"
[dependencies]
nu-protocol = { path = "../nu-protocol" }
# nu-ansi-term = { path = "../nu-ansi-term" }
nu-ansi-term = "0.42.0"
nu-json = { path = "../nu-json" }
nu-table = { path = "../nu-table" }
serde = { version="1.0.123", features=["derive"] }

View File

@ -0,0 +1,411 @@
use crate::nu_style::{color_from_hex, color_string_to_nustyle};
use nu_ansi_term::{Color, Style};
use nu_protocol::Config;
use nu_table::{Alignment, TextStyle};
use std::collections::HashMap;
pub fn lookup_ansi_color_style(s: &str) -> Style {
if s.starts_with('#') {
match color_from_hex(s) {
Ok(c) => match c {
Some(c) => c.normal(),
None => Style::default(),
},
Err(_) => Style::default(),
}
} else if s.starts_with('{') {
color_string_to_nustyle(s.to_string())
} else {
match s {
"g" | "green" => Color::Green.normal(),
"gb" | "green_bold" => Color::Green.bold(),
"gu" | "green_underline" => Color::Green.underline(),
"gi" | "green_italic" => Color::Green.italic(),
"gd" | "green_dimmed" => Color::Green.dimmed(),
"gr" | "green_reverse" => Color::Green.reverse(),
"gbl" | "green_blink" => Color::Green.blink(),
"gst" | "green_strike" => Color::Green.strikethrough(),
"lg" | "light_green" => Color::LightGreen.normal(),
"lgb" | "light_green_bold" => Color::LightGreen.bold(),
"lgu" | "light_green_underline" => Color::LightGreen.underline(),
"lgi" | "light_green_italic" => Color::LightGreen.italic(),
"lgd" | "light_green_dimmed" => Color::LightGreen.dimmed(),
"lgr" | "light_green_reverse" => Color::LightGreen.reverse(),
"lgbl" | "light_green_blink" => Color::LightGreen.blink(),
"lgst" | "light_green_strike" => Color::LightGreen.strikethrough(),
"r" | "red" => Color::Red.normal(),
"rb" | "red_bold" => Color::Red.bold(),
"ru" | "red_underline" => Color::Red.underline(),
"ri" | "red_italic" => Color::Red.italic(),
"rd" | "red_dimmed" => Color::Red.dimmed(),
"rr" | "red_reverse" => Color::Red.reverse(),
"rbl" | "red_blink" => Color::Red.blink(),
"rst" | "red_strike" => Color::Red.strikethrough(),
"lr" | "light_red" => Color::LightRed.normal(),
"lrb" | "light_red_bold" => Color::LightRed.bold(),
"lru" | "light_red_underline" => Color::LightRed.underline(),
"lri" | "light_red_italic" => Color::LightRed.italic(),
"lrd" | "light_red_dimmed" => Color::LightRed.dimmed(),
"lrr" | "light_red_reverse" => Color::LightRed.reverse(),
"lrbl" | "light_red_blink" => Color::LightRed.blink(),
"lrst" | "light_red_strike" => Color::LightRed.strikethrough(),
"u" | "blue" => Color::Blue.normal(),
"ub" | "blue_bold" => Color::Blue.bold(),
"uu" | "blue_underline" => Color::Blue.underline(),
"ui" | "blue_italic" => Color::Blue.italic(),
"ud" | "blue_dimmed" => Color::Blue.dimmed(),
"ur" | "blue_reverse" => Color::Blue.reverse(),
"ubl" | "blue_blink" => Color::Blue.blink(),
"ust" | "blue_strike" => Color::Blue.strikethrough(),
"lu" | "light_blue" => Color::LightBlue.normal(),
"lub" | "light_blue_bold" => Color::LightBlue.bold(),
"luu" | "light_blue_underline" => Color::LightBlue.underline(),
"lui" | "light_blue_italic" => Color::LightBlue.italic(),
"lud" | "light_blue_dimmed" => Color::LightBlue.dimmed(),
"lur" | "light_blue_reverse" => Color::LightBlue.reverse(),
"lubl" | "light_blue_blink" => Color::LightBlue.blink(),
"lust" | "light_blue_strike" => Color::LightBlue.strikethrough(),
"b" | "black" => Color::Black.normal(),
"bb" | "black_bold" => Color::Black.bold(),
"bu" | "black_underline" => Color::Black.underline(),
"bi" | "black_italic" => Color::Black.italic(),
"bd" | "black_dimmed" => Color::Black.dimmed(),
"br" | "black_reverse" => Color::Black.reverse(),
"bbl" | "black_blink" => Color::Black.blink(),
"bst" | "black_strike" => Color::Black.strikethrough(),
"ligr" | "light_gray" => Color::LightGray.normal(),
"ligrb" | "light_gray_bold" => Color::LightGray.bold(),
"ligru" | "light_gray_underline" => Color::LightGray.underline(),
"ligri" | "light_gray_italic" => Color::LightGray.italic(),
"ligrd" | "light_gray_dimmed" => Color::LightGray.dimmed(),
"ligrr" | "light_gray_reverse" => Color::LightGray.reverse(),
"ligrbl" | "light_gray_blink" => Color::LightGray.blink(),
"ligrst" | "light_gray_strike" => Color::LightGray.strikethrough(),
"y" | "yellow" => Color::Yellow.normal(),
"yb" | "yellow_bold" => Color::Yellow.bold(),
"yu" | "yellow_underline" => Color::Yellow.underline(),
"yi" | "yellow_italic" => Color::Yellow.italic(),
"yd" | "yellow_dimmed" => Color::Yellow.dimmed(),
"yr" | "yellow_reverse" => Color::Yellow.reverse(),
"ybl" | "yellow_blink" => Color::Yellow.blink(),
"yst" | "yellow_strike" => Color::Yellow.strikethrough(),
"ly" | "light_yellow" => Color::LightYellow.normal(),
"lyb" | "light_yellow_bold" => Color::LightYellow.bold(),
"lyu" | "light_yellow_underline" => Color::LightYellow.underline(),
"lyi" | "light_yellow_italic" => Color::LightYellow.italic(),
"lyd" | "light_yellow_dimmed" => Color::LightYellow.dimmed(),
"lyr" | "light_yellow_reverse" => Color::LightYellow.reverse(),
"lybl" | "light_yellow_blink" => Color::LightYellow.blink(),
"lyst" | "light_yellow_strike" => Color::LightYellow.strikethrough(),
"p" | "purple" => Color::Purple.normal(),
"pb" | "purple_bold" => Color::Purple.bold(),
"pu" | "purple_underline" => Color::Purple.underline(),
"pi" | "purple_italic" => Color::Purple.italic(),
"pd" | "purple_dimmed" => Color::Purple.dimmed(),
"pr" | "purple_reverse" => Color::Purple.reverse(),
"pbl" | "purple_blink" => Color::Purple.blink(),
"pst" | "purple_strike" => Color::Purple.strikethrough(),
"lp" | "light_purple" => Color::LightPurple.normal(),
"lpb" | "light_purple_bold" => Color::LightPurple.bold(),
"lpu" | "light_purple_underline" => Color::LightPurple.underline(),
"lpi" | "light_purple_italic" => Color::LightPurple.italic(),
"lpd" | "light_purple_dimmed" => Color::LightPurple.dimmed(),
"lpr" | "light_purple_reverse" => Color::LightPurple.reverse(),
"lpbl" | "light_purple_blink" => Color::LightPurple.blink(),
"lpst" | "light_purple_strike" => Color::LightPurple.strikethrough(),
"c" | "cyan" => Color::Cyan.normal(),
"cb" | "cyan_bold" => Color::Cyan.bold(),
"cu" | "cyan_underline" => Color::Cyan.underline(),
"ci" | "cyan_italic" => Color::Cyan.italic(),
"cd" | "cyan_dimmed" => Color::Cyan.dimmed(),
"cr" | "cyan_reverse" => Color::Cyan.reverse(),
"cbl" | "cyan_blink" => Color::Cyan.blink(),
"cst" | "cyan_strike" => Color::Cyan.strikethrough(),
"lc" | "light_cyan" => Color::LightCyan.normal(),
"lcb" | "light_cyan_bold" => Color::LightCyan.bold(),
"lcu" | "light_cyan_underline" => Color::LightCyan.underline(),
"lci" | "light_cyan_italic" => Color::LightCyan.italic(),
"lcd" | "light_cyan_dimmed" => Color::LightCyan.dimmed(),
"lcr" | "light_cyan_reverse" => Color::LightCyan.reverse(),
"lcbl" | "light_cyan_blink" => Color::LightCyan.blink(),
"lcst" | "light_cyan_strike" => Color::LightCyan.strikethrough(),
"w" | "white" => Color::White.normal(),
"wb" | "white_bold" => Color::White.bold(),
"wu" | "white_underline" => Color::White.underline(),
"wi" | "white_italic" => Color::White.italic(),
"wd" | "white_dimmed" => Color::White.dimmed(),
"wr" | "white_reverse" => Color::White.reverse(),
"wbl" | "white_blink" => Color::White.blink(),
"wst" | "white_strike" => Color::White.strikethrough(),
"dgr" | "dark_gray" => Color::DarkGray.normal(),
"dgrb" | "dark_gray_bold" => Color::DarkGray.bold(),
"dgru" | "dark_gray_underline" => Color::DarkGray.underline(),
"dgri" | "dark_gray_italic" => Color::DarkGray.italic(),
"dgrd" | "dark_gray_dimmed" => Color::DarkGray.dimmed(),
"dgrr" | "dark_gray_reverse" => Color::DarkGray.reverse(),
"dgrbl" | "dark_gray_blink" => Color::DarkGray.blink(),
"dgrst" | "dark_gray_strike" => Color::DarkGray.strikethrough(),
_ => Color::White.normal(),
}
}
}
fn update_hashmap(key: &str, val: &str, hm: &mut HashMap<String, Style>) {
// eprintln!("key: {}, val: {}", &key, &val);
let color = lookup_ansi_color_style(val);
if let Some(v) = hm.get_mut(key) {
*v = color;
} else {
hm.insert(key.to_string(), color);
}
}
pub fn get_color_config(config: &Config) -> HashMap<String, Style> {
let config = config;
// create the hashmap
let mut hm: HashMap<String, Style> = HashMap::new();
// set some defaults
// hm.insert("primitive_line".to_string(), Color::White.normal());
// hm.insert("primitive_pattern".to_string(), Color::White.normal());
// hm.insert("primitive_path".to_string(), Color::White.normal());
// hm.insert("separator_color".to_string(), Color::White.normal());
hm.insert(
"leading_trailing_space_bg".to_string(),
Style::default().on(Color::Rgb(128, 128, 128)),
);
hm.insert("header".to_string(), Color::Green.bold());
hm.insert("empty".to_string(), Color::Blue.normal());
hm.insert("bool".to_string(), Color::White.normal());
hm.insert("int".to_string(), Color::White.normal());
hm.insert("filesize".to_string(), Color::White.normal());
hm.insert("duration".to_string(), Color::White.normal());
hm.insert("date".to_string(), Color::White.normal());
hm.insert("range".to_string(), Color::White.normal());
hm.insert("float".to_string(), Color::White.normal());
hm.insert("string".to_string(), Color::White.normal());
hm.insert("nothing".to_string(), Color::White.normal());
hm.insert("binary".to_string(), Color::White.normal());
hm.insert("cellpath".to_string(), Color::White.normal());
hm.insert("row_index".to_string(), Color::Green.bold());
hm.insert("record".to_string(), Color::White.normal());
hm.insert("list".to_string(), Color::White.normal());
hm.insert("block".to_string(), Color::White.normal());
hm.insert("hints".to_string(), Color::DarkGray.normal());
for (key, value) in &config.color_config {
let value = value
.as_string()
.expect("the only values for config color must be strings");
update_hashmap(key, &value, &mut hm);
// eprintln!(
// "config: {}:{}\t\t\thashmap: {}:{:?}",
// &key, &value, &key, &hm[key]
// );
}
hm
}
// This function will assign a text style to a primitive, or really any string that's
// in the hashmap. The hashmap actually contains the style to be applied.
pub fn style_primitive(primitive: &str, color_hm: &HashMap<String, Style>) -> TextStyle {
match primitive {
"bool" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"int" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Right, *s),
None => TextStyle::basic_right(),
}
}
"filesize" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Right, *s),
None => TextStyle::basic_right(),
}
}
"duration" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"date" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"range" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"float" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Right, *s),
None => TextStyle::basic_right(),
}
}
"string" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"record" | "list" | "block" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"nothing" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
// not sure what to do with error
// "error" => {}
"binary" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"cellpath" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Left, *s),
None => TextStyle::basic_left(),
}
}
"row_index" => {
let style = color_hm.get(primitive);
match style {
Some(s) => TextStyle::with_style(Alignment::Right, *s),
None => TextStyle::new()
.alignment(Alignment::Right)
.fg(Color::Green)
.bold(Some(true)),
}
}
// types in nushell but not in engine-q
// "Line" => {
// let style = color_hm.get("Primitive::Line");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
// "GlobPattern" => {
// let style = color_hm.get("Primitive::GlobPattern");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
// "FilePath" => {
// let style = color_hm.get("Primitive::FilePath");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
// "BeginningOfStream" => {
// let style = color_hm.get("Primitive::BeginningOfStream");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
// "EndOfStream" => {
// let style = color_hm.get("Primitive::EndOfStream");
// match style {
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
// None => TextStyle::basic_left(),
// }
// }
_ => TextStyle::basic_left(),
}
}
#[test]
fn test_hm() {
use nu_ansi_term::{Color, Style};
let mut hm: HashMap<String, Style> = HashMap::new();
hm.insert("primitive_int".to_string(), Color::White.normal());
hm.insert("primitive_decimal".to_string(), Color::White.normal());
hm.insert("primitive_filesize".to_string(), Color::White.normal());
hm.insert("primitive_string".to_string(), Color::White.normal());
hm.insert("primitive_line".to_string(), Color::White.normal());
hm.insert("primitive_columnpath".to_string(), Color::White.normal());
hm.insert("primitive_pattern".to_string(), Color::White.normal());
hm.insert("primitive_boolean".to_string(), Color::White.normal());
hm.insert("primitive_date".to_string(), Color::White.normal());
hm.insert("primitive_duration".to_string(), Color::White.normal());
hm.insert("primitive_range".to_string(), Color::White.normal());
hm.insert("primitive_path".to_string(), Color::White.normal());
hm.insert("primitive_binary".to_string(), Color::White.normal());
hm.insert("separator".to_string(), Color::White.normal());
hm.insert("header_align".to_string(), Color::Green.bold());
hm.insert("header".to_string(), Color::Green.bold());
hm.insert("header_style".to_string(), Style::default());
hm.insert("row_index".to_string(), Color::Green.bold());
hm.insert(
"leading_trailing_space_bg".to_string(),
Style::default().on(Color::Rgb(128, 128, 128)),
);
update_hashmap("primitive_int", "green", &mut hm);
assert_eq!(hm["primitive_int"], Color::Green.normal());
}

View File

@ -0,0 +1,7 @@
mod color_config;
mod nu_style;
mod shape_color;
pub use color_config::*;
pub use nu_style::*;
pub use shape_color::*;

View File

@ -0,0 +1,103 @@
use nu_ansi_term::{Color, Style};
use serde::Deserialize;
#[derive(Deserialize, PartialEq, Debug)]
pub struct NuStyle {
pub fg: Option<String>,
pub bg: Option<String>,
pub attr: Option<String>,
}
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
// get the nu_ansi_term::Color foreground color
let fg_color = match nu_style.fg {
Some(fg) => color_from_hex(&fg).expect("error with foreground color"),
_ => None,
};
// get the nu_ansi_term::Color background color
let bg_color = match nu_style.bg {
Some(bg) => color_from_hex(&bg).expect("error with background color"),
_ => None,
};
// get the attributes
let color_attr = match nu_style.attr {
Some(attr) => attr,
_ => "".to_string(),
};
// setup the attributes available in nu_ansi_term::Style
let mut bold = false;
let mut dimmed = false;
let mut italic = false;
let mut underline = false;
let mut blink = false;
let mut reverse = false;
let mut hidden = false;
let mut strikethrough = false;
// since we can combine styles like bold-italic, iterate through the chars
// and set the bools for later use in the nu_ansi_term::Style application
for ch in color_attr.to_lowercase().chars() {
match ch {
'l' => blink = true,
'b' => bold = true,
'd' => dimmed = true,
'h' => hidden = true,
'i' => italic = true,
'r' => reverse = true,
's' => strikethrough = true,
'u' => underline = true,
'n' => (),
_ => (),
}
}
// here's where we build the nu_ansi_term::Style
Style {
foreground: fg_color,
background: bg_color,
is_blink: blink,
is_bold: bold,
is_dimmed: dimmed,
is_hidden: hidden,
is_italic: italic,
is_reverse: reverse,
is_strikethrough: strikethrough,
is_underline: underline,
}
}
pub fn color_string_to_nustyle(color_string: String) -> Style {
// eprintln!("color_string: {}", &color_string);
if color_string.chars().count() < 1 {
Style::default()
} else {
let nu_style = match nu_json::from_str::<NuStyle>(&color_string) {
Ok(s) => s,
Err(_) => NuStyle {
fg: None,
bg: None,
attr: None,
},
};
parse_nustyle(nu_style)
}
}
pub fn color_from_hex(
hex_color: &str,
) -> std::result::Result<Option<Color>, std::num::ParseIntError> {
// right now we only allow hex colors with hashtag and 6 characters
let trimmed = hex_color.trim_matches('#');
if trimmed.len() != 6 {
Ok(None)
} else {
// make a nu_ansi_term::Color::Rgb color by converting hex to decimal
Ok(Some(Color::Rgb(
u8::from_str_radix(&trimmed[..2], 16)?,
u8::from_str_radix(&trimmed[2..4], 16)?,
u8::from_str_radix(&trimmed[4..6], 16)?,
)))
}
}

View File

@ -0,0 +1,38 @@
use crate::color_config::lookup_ansi_color_style;
use nu_ansi_term::{Color, Style};
use nu_protocol::Config;
pub fn get_shape_color(shape: String, conf: &Config) -> Style {
match conf.color_config.get(shape.as_str()) {
Some(int_color) => match int_color.as_string() {
Ok(int_color) => lookup_ansi_color_style(&int_color),
Err(_) => Style::default(),
},
None => match shape.as_ref() {
"flatshape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
"flatshape_bool" => Style::new().fg(Color::LightCyan),
"flatshape_int" => Style::new().fg(Color::Purple).bold(),
"flatshape_float" => Style::new().fg(Color::Purple).bold(),
"flatshape_range" => Style::new().fg(Color::Yellow).bold(),
"flatshape_internalcall" => Style::new().fg(Color::Cyan).bold(),
"flatshape_external" => Style::new().fg(Color::Cyan),
"flatshape_externalarg" => Style::new().fg(Color::Green).bold(),
"flatshape_literal" => Style::new().fg(Color::Blue),
"flatshape_operator" => Style::new().fg(Color::Yellow),
"flatshape_signature" => Style::new().fg(Color::Green).bold(),
"flatshape_string" => Style::new().fg(Color::Green),
"flatshape_string_interpolation" => Style::new().fg(Color::Cyan).bold(),
"flatshape_list" => Style::new().fg(Color::Cyan).bold(),
"flatshape_table" => Style::new().fg(Color::Blue).bold(),
"flatshape_record" => Style::new().fg(Color::Cyan).bold(),
"flatshape_block" => Style::new().fg(Color::Blue).bold(),
"flatshape_filepath" => Style::new().fg(Color::Cyan),
"flatshape_globpattern" => Style::new().fg(Color::Cyan).bold(),
"flatshape_variable" => Style::new().fg(Color::Purple),
"flatshape_flag" => Style::new().fg(Color::Blue).bold(),
"flatshape_custom" => Style::new().bold(),
"flatshape_nothing" => Style::new().fg(Color::LightCyan),
_ => Style::default(),
},
}
}

View File

@ -1,4 +1,5 @@
[package]
<<<<<<< HEAD
authors = ["The Nu Project Contributors"]
build = "build.rs"
description = "Commands for Nushell"
@ -46,10 +47,50 @@ eml-parser = "0.1.0"
encoding_rs = "0.8.28"
filesize = "0.2.0"
futures = { version="0.3.12", features=["compat", "io-compat"] }
=======
name = "nu-command"
version = "0.1.0"
edition = "2021"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-ansi-term = "0.42.0"
nu-color-config = { path = "../nu-color-config" }
nu-engine = { path = "../nu-engine" }
nu-json = { path = "../nu-json" }
nu-parser = { path = "../nu-parser" }
nu-path = { path = "../nu-path" }
nu-pretty-hex = { path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol" }
nu-system = { path = "../nu-system" }
nu-table = { path = "../nu-table" }
nu-term-grid = { path = "../nu-term-grid" }
nu-test-support = { path = "../nu-test-support" }
# Potential dependencies for extras
base64 = "0.13.0"
bytesize = "1.1.0"
calamine = "0.18.0"
chrono = { version = "0.4.19", features = ["serde"] }
chrono-humanize = "0.2.1"
chrono-tz = "0.6.0"
crossterm = "0.22.1"
csv = "1.1.3"
dialoguer = "0.9.0"
digest = "0.10.0"
dtparse = "1.2.0"
eml-parser = "0.1.0"
encoding_rs = "0.8.30"
filesize = "0.2.0"
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
glob = "0.3.0"
htmlescape = "0.3.1"
ical = "0.7.0"
indexmap = { version="1.7", features=["serde-1"] }
<<<<<<< HEAD
itertools = "0.10.0"
lazy_static = "1.*"
log = "0.4.14"
@ -92,21 +133,76 @@ version = "0.17.0"
optional = true
default-features = false
features = ["docs", "zip_with", "csv-file", "temporal", "performant", "pretty_fmt", "dtype-slim", "parquet", "json", "random", "pivot", "strings", "is_in", "cum_agg", "rolling_window"]
=======
Inflector = "0.11"
itertools = "0.10.0"
lazy_static = "1.4.0"
log = "0.4.14"
lscolors = { version = "0.8.0", features = ["crossterm"] }
md5 = { package = "md-5", version = "0.10.0" }
meval = "0.2.0"
mime = "0.3.16"
num = { version = "0.4.0", optional = true }
pathdiff = "0.2.1"
quick-xml = "0.22"
rand = "0.8"
rayon = "1.5.1"
regex = "1.5.4"
reqwest = {version = "0.11", features = ["blocking"] }
roxmltree = "0.14.0"
rust-embed = "6.3.0"
serde = { version="1.0.123", features=["derive"] }
serde_ini = "0.2.0"
serde_urlencoded = "0.7.0"
serde_yaml = "0.8.16"
sha2 = "0.10.0"
shadow-rs = "0.8.1"
strip-ansi-escapes = "0.1.1"
sysinfo = "0.22.2"
terminal_size = "0.1.17"
thiserror = "1.0.29"
titlecase = "1.1.0"
toml = "0.5.8"
trash = { version = "2.0.2", optional = true }
unicode-segmentation = "1.8.0"
url = "2.2.1"
uuid = { version = "0.8.2", features = ["v4"] }
which = { version = "4.2.2", optional = true }
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
zip = { version="0.5.9", optional = true }
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
[target.'cfg(unix)'.dependencies]
umask = "1.0.0"
users = "0.11.0"
<<<<<<< HEAD
# TODO this will be possible with new dependency resolver
# (currently on nightly behind -Zfeatures=itarget):
# https://github.com/rust-lang/cargo/issues/7914
# [target.'cfg(not(windows))'.dependencies]
# num-format = { version = "0.4", features = ["with-system-locale"] }
=======
[dependencies.polars]
version = "0.18.0"
optional = true
features = [
"default", "parquet", "json", "serde", "object",
"checked_arithmetic", "strings", "cum_agg", "is_in",
"rolling_window", "strings", "pivot", "random"
]
[features]
trash-support = ["trash"]
plugin = ["nu-parser/plugin"]
dataframe = ["polars", "num"]
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
[build-dependencies]
shadow-rs = "0.8.1"
[dev-dependencies]
<<<<<<< HEAD
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
hamcrest2 = "0.3.0"
@ -120,3 +216,9 @@ fetch = ["reqwest", "tokio"]
post = ["reqwest", "tokio"]
sys = ["sysinfo"]
ps = ["sysinfo"]
=======
hamcrest2 = "0.3.0"
dirs-next = "2.0.0"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce

View File

@ -0,0 +1,176 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
#[derive(Clone)]
pub struct Fmt;
impl Command for Fmt {
fn name(&self) -> &str {
"fmt"
}
fn usage(&self) -> &str {
"format numbers"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("fmt").category(Category::Conversions)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "format numbers",
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::String {
val: "0b101010".to_string(),
span: Span::test_data(),
},
Value::String {
val: "42".to_string(),
span: Span::test_data(),
},
Value::String {
val: "42".to_string(),
span: Span::test_data(),
},
Value::String {
val: "4.2e1".to_string(),
span: Span::test_data(),
},
Value::String {
val: "0x2a".to_string(),
span: Span::test_data(),
},
Value::String {
val: "0o52".to_string(),
span: Span::test_data(),
},
Value::String {
val: "4.2E1".to_string(),
span: Span::test_data(),
},
Value::String {
val: "0x2A".to_string(),
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
fmt(engine_state, stack, call, input)
}
}
fn fmt(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, 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, head)));
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
}
pub fn action(input: &Value, span: Span) -> Value {
match input {
Value::Int { val, .. } => fmt_it(*val, span),
Value::Filesize { val, .. } => fmt_it(*val, span),
_ => Value::Error {
error: ShellError::UnsupportedInput(
format!("unsupported input type: {:?}", input.get_type()),
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!("{:#b}", num), 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!("{:#e}", num), span));
cols.push("lowerhex".into());
vals.push(Value::string(format!("{:#x}", num), span));
cols.push("octal".into());
vals.push(Value::string(format!("{:#o}", num), span));
// cols.push("pointer".into());
// vals.push(Value::string(format!("{:#p}", &num), span));
cols.push("upperexp".into());
vals.push(Value::string(format!("{:#E}", num), span));
cols.push("upperhex".into());
vals.push(Value::string(format!("{:#X}", num), span));
Value::Record { cols, vals, span }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Fmt {})
}
}

View File

@ -0,0 +1,195 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"into binary"
}
fn signature(&self) -> Signature {
Signature::build("into binary")
.rest(
"rest",
SyntaxShape::CellPath,
"column paths to convert to binary (for table input)",
)
.category(Category::Conversions)
}
fn usage(&self) -> &str {
"Convert value to a binary primitive"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_binary(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "convert string to a nushell binary primitive",
example: "'This is a string that is exactly 52 characters long.' | into binary",
result: Some(Value::Binary {
val: "This is a string that is exactly 52 characters long."
.to_string()
.as_bytes()
.to_vec(),
span: Span::test_data(),
}),
},
Example {
description: "convert a number to a nushell binary primitive",
example: "1 | into binary",
result: Some(Value::Binary {
val: i64::from(1).to_le_bytes().to_vec(),
span: Span::test_data(),
}),
},
Example {
description: "convert a boolean to a nushell binary primitive",
example: "$true | into binary",
result: Some(Value::Binary {
val: i64::from(1).to_le_bytes().to_vec(),
span: Span::test_data(),
}),
},
Example {
description: "convert a filesize to a nushell binary primitive",
example: "ls | where name == LICENSE | get size | into binary",
result: None,
},
Example {
description: "convert a filepath to a nushell binary primitive",
example: "ls | where name == LICENSE | get name | path expand | into binary",
result: None,
},
Example {
description: "convert a decimal to a nushell binary primitive",
example: "1.234 | into binary",
result: Some(Value::Binary {
val: 1.234f64.to_le_bytes().to_vec(),
span: Span::test_data(),
}),
},
]
}
}
fn into_binary(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
match input {
PipelineData::RawStream(stream, ..) => {
// TODO: in the future, we may want this to stream out, converting each to bytes
let output = stream.into_bytes()?;
Ok(Value::Binary {
val: output.item,
span: head,
}
.into_pipeline_data())
}
_ => input.map(
move |v| {
if column_paths.is_empty() {
action(&v, 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, head)),
);
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
),
}
}
fn int_to_endian(n: i64) -> Vec<u8> {
if cfg!(target_endian = "little") {
n.to_le_bytes().to_vec()
} else {
n.to_be_bytes().to_vec()
}
}
fn float_to_endian(n: f64) -> Vec<u8> {
if cfg!(target_endian = "little") {
n.to_le_bytes().to_vec()
} else {
n.to_be_bytes().to_vec()
}
}
pub fn action(input: &Value, span: Span) -> Value {
match input {
Value::Binary { .. } => input.clone(),
Value::Int { val, .. } => Value::Binary {
val: int_to_endian(*val),
span,
},
Value::Float { val, .. } => Value::Binary {
val: float_to_endian(*val),
span,
},
Value::Filesize { val, .. } => Value::Binary {
val: int_to_endian(*val),
span,
},
Value::String { val, .. } => Value::Binary {
val: val.as_bytes().to_vec(),
span,
},
Value::Bool { val, .. } => Value::Binary {
val: int_to_endian(if *val { 1i64 } else { 0 }),
span,
},
Value::Date { val, .. } => Value::Binary {
val: val.format("%c").to_string().as_bytes().to_vec(),
span,
},
_ => Value::Error {
error: ShellError::UnsupportedInput("'into binary' for unsupported type".into(), span),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,183 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"into bool"
}
fn signature(&self) -> Signature {
Signature::build("into bool")
.rest(
"rest",
SyntaxShape::CellPath,
"column paths to convert to boolean (for table input)",
)
.category(Category::Conversions)
}
fn usage(&self) -> &str {
"Convert value to boolean"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_bool(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
let span = Span::test_data();
vec![
Example {
description: "Convert value to boolean in table",
example: "echo [[value]; ['false'] ['1'] [0] [1.0] [$true]] | into bool value",
result: Some(Value::List {
vals: vec![
Value::Record {
cols: vec!["value".to_string()],
vals: vec![Value::boolean(false, span)],
span,
},
Value::Record {
cols: vec!["value".to_string()],
vals: vec![Value::boolean(true, span)],
span,
},
Value::Record {
cols: vec!["value".to_string()],
vals: vec![Value::boolean(false, span)],
span,
},
Value::Record {
cols: vec!["value".to_string()],
vals: vec![Value::boolean(true, span)],
span,
},
Value::Record {
cols: vec!["value".to_string()],
vals: vec![Value::boolean(true, span)],
span,
},
],
span,
}),
},
Example {
description: "Convert bool to boolean",
example: "$true | into bool",
result: Some(Value::boolean(true, span)),
},
Example {
description: "convert decimal to boolean",
example: "1 | into bool",
result: Some(Value::boolean(true, span)),
},
Example {
description: "convert decimal string to boolean",
example: "'0.0' | into bool",
result: Some(Value::boolean(false, span)),
},
Example {
description: "convert string to boolean",
example: "'true' | into bool",
result: Some(Value::boolean(true, span)),
},
]
}
}
fn into_bool(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, 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, head)));
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
}
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
match s.trim().to_lowercase().as_str() {
"true" => Ok(true),
"false" => Ok(false),
o => {
let val = o.parse::<f64>();
match val {
Ok(f) => Ok(f.abs() >= f64::EPSILON),
Err(_) => Err(ShellError::CantConvert(
"boolean".to_string(),
"string".to_string(),
span,
)),
}
}
}
}
fn action(input: &Value, span: Span) -> Value {
match input {
Value::Bool { .. } => input.clone(),
Value::Int { val, .. } => Value::Bool {
val: *val != 0,
span,
},
Value::Float { val, .. } => Value::Bool {
val: val.abs() >= f64::EPSILON,
span,
},
Value::String { val, .. } => match string_to_boolean(val, span) {
Ok(val) => Value::Bool { val, span },
Err(error) => Value::Error { error },
},
_ => Value::Error {
error: ShellError::UnsupportedInput(
"'into bool' does not support this input".into(),
span,
),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,49 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, Signature, Value,
};
#[derive(Clone)]
pub struct Into;
impl Command for Into {
fn name(&self) -> &str {
"into"
}
fn signature(&self) -> Signature {
Signature::build("into").category(Category::Conversions)
}
fn usage(&self) -> &str {
"Apply into function."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String {
val: get_full_help(&Into.signature(), &[], engine_state, stack),
span: call.head,
}
.into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Into {})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,166 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"into decimal"
}
fn signature(&self) -> Signature {
Signature::build("into decimal").rest(
"rest",
SyntaxShape::CellPath,
"optionally convert text into decimal by column paths",
)
}
fn usage(&self) -> &str {
"converts text into decimal"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
operate(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert string to integer in table",
example: "[[num]; ['5.01']] | into decimal num",
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["num".to_string()],
vals: vec![Value::test_float(5.01)],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
Example {
description: "Convert string to integer",
example: "'1.345' | into decimal",
result: Some(Value::test_float(1.345)),
},
Example {
description: "Convert decimal to integer",
example: "'-5.9' | into decimal",
result: Some(Value::test_float(-5.9)),
},
]
}
}
fn operate(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, 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, head)));
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
}
fn action(input: &Value, head: Span) -> Value {
match input {
Value::String { val: s, span } => {
let other = s.trim();
match other.parse::<f64>() {
Ok(x) => Value::Float { val: x, span: head },
Err(reason) => Value::Error {
error: ShellError::CantConvert("float".to_string(), reason.to_string(), *span),
},
}
}
Value::Int { val: v, span } => Value::Float {
val: *v as f64,
span: *span,
},
other => {
let span = other.span();
match span {
Ok(s) => {
let got = format!("Expected a string, got {} instead", other.get_type());
Value::Error {
error: ShellError::UnsupportedInput(got, s),
}
}
Err(e) => Value::Error { error: e },
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use nu_protocol::Type::Error;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
#[test]
#[allow(clippy::approx_constant)]
fn string_to_decimal() {
let word = Value::test_string("3.1415");
let expected = Value::test_float(3.1415);
let actual = action(&word, Span::test_data());
assert_eq!(actual, expected);
}
#[test]
fn communicates_parsing_error_given_an_invalid_decimallike_string() {
let decimal_str = Value::test_string("11.6anra");
let actual = action(&decimal_str, Span::test_data());
assert_eq!(actual.get_type(), Error);
}
#[test]
fn int_to_decimal() {
let decimal_str = Value::test_int(10);
let expected = Value::test_float(10.0);
let actual = action(&decimal_str, Span::test_data());
assert_eq!(actual, expected);
}
}

View File

@ -0,0 +1,165 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"into filesize"
}
fn signature(&self) -> Signature {
Signature::build("into filesize")
.rest(
"rest",
SyntaxShape::CellPath,
"column paths to convert to filesize (for table input)",
)
.category(Category::Conversions)
}
fn usage(&self) -> &str {
"Convert value to filesize"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_filesize(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert string to filesize in table",
example: "[[bytes]; ['5'] [3.2] [4] [2kb]] | into filesize bytes",
result: None,
},
Example {
description: "Convert string to filesize",
example: "'2' | into filesize",
result: Some(Value::Filesize {
val: 2,
span: Span::test_data(),
}),
},
Example {
description: "Convert decimal to filesize",
example: "8.3 | into filesize",
result: Some(Value::Filesize {
val: 8,
span: Span::test_data(),
}),
},
Example {
description: "Convert int to filesize",
example: "5 | into filesize",
result: Some(Value::Filesize {
val: 5,
span: Span::test_data(),
}),
},
Example {
description: "Convert file size to filesize",
example: "4KB | into filesize",
result: Some(Value::Filesize {
val: 4000,
span: Span::test_data(),
}),
},
]
}
}
fn into_filesize(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, 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, head)));
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
}
pub fn action(input: &Value, span: Span) -> Value {
if let Ok(value_span) = input.span() {
match input {
Value::Filesize { .. } => input.clone(),
Value::Int { val, .. } => Value::Filesize {
val: *val,
span: value_span,
},
Value::Float { val, .. } => Value::Filesize {
val: *val as i64,
span: value_span,
},
Value::String { val, .. } => match int_from_string(val, value_span) {
Ok(val) => Value::Filesize {
val,
span: value_span,
},
Err(error) => Value::Error { error },
},
_ => Value::Error {
error: ShellError::UnsupportedInput(
"'into filesize' for unsupported type".into(),
value_span,
),
},
}
} else {
Value::Error {
error: ShellError::UnsupportedInput(
"'into filesize' for unsupported type".into(),
span,
),
}
}
}
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
match a_string.trim().parse::<bytesize::ByteSize>() {
Ok(n) => Ok(n.0 as i64),
Err(_) => Err(ShellError::CantConvert("int".into(), "string".into(), span)),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,302 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
struct Arguments {
radix: Option<Value>,
column_paths: Vec<CellPath>,
}
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"into int"
}
fn signature(&self) -> Signature {
Signature::build("into int")
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
.rest(
"rest",
SyntaxShape::CellPath,
"column paths to convert to int (for table input)",
)
.category(Category::Conversions)
}
fn usage(&self) -> &str {
"Convert value to integer"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
into_int(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Convert string to integer in table",
example: "echo [[num]; ['-5'] [4] [1.5]] | into int num",
result: None,
},
Example {
description: "Convert string to integer",
example: "'2' | into int",
result: Some(Value::test_int(2)),
},
Example {
description: "Convert decimal to integer",
example: "5.9 | into int",
result: Some(Value::test_int(5)),
},
Example {
description: "Convert decimal string to integer",
example: "'5.9' | into int",
result: Some(Value::test_int(5)),
},
Example {
description: "Convert file size to integer",
example: "4KB | into int",
result: Some(Value::Int {
val: 4000,
span: Span::test_data(),
}),
},
Example {
description: "Convert bool to integer",
example: "[$false, $true] | into int",
result: Some(Value::List {
vals: vec![Value::test_int(0), Value::test_int(1)],
span: Span::test_data(),
}),
},
Example {
description: "Convert to integer from binary",
example: "'1101' | into int -r 2",
result: Some(Value::test_int(13)),
},
Example {
description: "Convert to integer from hex",
example: "'FF' | into int -r 16",
result: Some(Value::test_int(255)),
},
]
}
}
fn into_int(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let options = Arguments {
radix: call.get_flag(engine_state, stack, "radix")?,
column_paths: call.rest(engine_state, stack, 0)?,
};
let radix: u32 = match options.radix {
Some(Value::Int { val, .. }) => val as u32,
Some(_) => 10,
None => 10,
};
if let Some(val) = &options.radix {
if !(2..=36).contains(&radix) {
return Err(ShellError::UnsupportedInput(
"Radix must lie in the range [2, 36]".to_string(),
val.span()?,
));
}
}
input.map(
move |v| {
if options.column_paths.is_empty() {
action(&v, head, radix)
} else {
let mut ret = v;
for path in &options.column_paths {
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, head, radix)),
);
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
)
}
pub fn action(input: &Value, span: Span, radix: u32) -> Value {
match input {
Value::Int { val: _, .. } => {
if radix == 10 {
input.clone()
} else {
convert_int(input, span, radix)
}
}
Value::Filesize { val, .. } => Value::Int { val: *val, span },
Value::Float { val, .. } => Value::Int {
val: *val as i64,
span,
},
Value::String { val, .. } => {
if radix == 10 {
match int_from_string(val, span) {
Ok(val) => Value::Int { val, span },
Err(error) => Value::Error { error },
}
} else {
convert_int(input, span, radix)
}
}
Value::Bool { val, .. } => {
if *val {
Value::Int { val: 1, span }
} else {
Value::Int { val: 0, span }
}
}
_ => Value::Error {
error: ShellError::UnsupportedInput("'into int' for unsupported type".into(), span),
},
}
}
fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
let i = match input {
Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => {
if val.starts_with("0x") || val.starts_with("0b") {
match int_from_string(&val.to_string(), head) {
Ok(x) => return Value::Int { val: x, span: head },
Err(e) => return Value::Error { error: e },
}
}
val.to_string()
}
_ => {
return Value::Error {
error: ShellError::UnsupportedInput(
"only strings or integers are supported".to_string(),
head,
),
}
}
};
match i64::from_str_radix(&i, radix) {
Ok(n) => Value::Int { val: n, span: head },
Err(reason) => Value::Error {
error: ShellError::CantConvert("".to_string(), reason.to_string(), head),
},
}
}
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
let trimmed = a_string.trim();
match trimmed {
b if b.starts_with("0b") => {
let num = match i64::from_str_radix(b.trim_start_matches("0b"), 2) {
Ok(n) => n,
Err(reason) => {
return Err(ShellError::CantConvert(
"could not parse as integer".to_string(),
reason.to_string(),
span,
))
}
};
Ok(num)
}
h if h.starts_with("0x") => {
let num = match i64::from_str_radix(h.trim_start_matches("0x"), 16) {
Ok(n) => n,
Err(reason) => {
return Err(ShellError::CantConvert(
"could not parse as int".to_string(),
reason.to_string(),
span,
))
}
};
Ok(num)
}
_ => match a_string.parse::<i64>() {
Ok(n) => Ok(n),
Err(_) => match a_string.parse::<f64>() {
Ok(f) => Ok(f as i64),
_ => Err(ShellError::CantConvert(
"into int".to_string(),
"string".to_string(),
span,
)),
},
},
}
}
#[cfg(test)]
mod test {
use super::Value;
use super::*;
use nu_protocol::Type::Error;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
#[test]
fn turns_to_integer() {
let word = Value::test_string("10");
let expected = Value::test_int(10);
let actual = action(&word, Span::test_data(), 10);
assert_eq!(actual, expected);
}
#[test]
fn turns_binary_to_integer() {
let s = Value::test_string("0b101");
let actual = action(&s, Span::test_data(), 10);
assert_eq!(actual, Value::test_int(5));
}
#[test]
fn turns_hex_to_integer() {
let s = Value::test_string("0xFF");
let actual = action(&s, Span::test_data(), 16);
assert_eq!(actual, Value::test_int(255));
}
#[test]
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
let integer_str = Value::test_string("36anra");
let actual = action(&integer_str, Span::test_data(), 10);
assert_eq!(actual.get_type(), Error)
}
}

View File

@ -0,0 +1,17 @@
mod binary;
mod bool;
mod command;
mod datetime;
mod decimal;
mod filesize;
mod int;
mod string;
pub use self::bool::SubCommand as IntoBool;
pub use self::filesize::SubCommand as IntoFilesize;
pub use binary::SubCommand as IntoBinary;
pub use command::Into;
pub use datetime::SubCommand as IntoDatetime;
pub use decimal::SubCommand as IntoDecimal;
pub use int::SubCommand as IntoInt;
pub use string::SubCommand as IntoString;

View File

@ -0,0 +1,284 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Value,
};
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"into string"
}
fn signature(&self) -> Signature {
Signature::build("into string")
// FIXME - need to support column paths
.rest(
"rest",
SyntaxShape::CellPath,
"column paths to convert to string (for table input)",
)
.named(
"decimals",
SyntaxShape::Int,
"decimal digits to which to round",
Some('d'),
)
.category(Category::Conversions)
}
fn usage(&self) -> &str {
"Convert value to string"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
string_helper(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "convert decimal to string and round to nearest integer",
example: "1.7 | into string -d 0",
result: Some(Value::String {
val: "2".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert decimal to string",
example: "1.7 | into string -d 1",
result: Some(Value::String {
val: "1.7".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert decimal to string and limit to 2 decimals",
example: "1.734 | into string -d 2",
result: Some(Value::String {
val: "1.73".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "try to convert decimal to string and provide negative decimal points",
example: "1.734 | into string -d -2",
result: None,
// FIXME
// result: Some(Value::Error {
// error: ShellError::UnsupportedInput(
// String::from("Cannot accept negative integers for decimals arguments"),
// Span::test_data(),
// ),
// }),
},
Example {
description: "convert decimal to string",
example: "4.3 | into string",
result: Some(Value::String {
val: "4.3".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert string to string",
example: "'1234' | into string",
result: Some(Value::String {
val: "1234".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert boolean to string",
example: "$true | into string",
result: Some(Value::String {
val: "true".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert date to string",
example: "date now | into string",
result: None,
},
Example {
description: "convert filepath to string",
example: "ls Cargo.toml | get name | into string",
result: None,
},
Example {
description: "convert filesize to string",
example: "ls Cargo.toml | get size | into string",
result: None,
},
]
}
}
fn string_helper(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> {
let decimals = call.has_flag("decimals");
let head = call.head;
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let config = stack.get_config().unwrap_or_default();
if let Some(decimal_val) = decimals_value {
if decimals && decimal_val.is_negative() {
return Err(ShellError::UnsupportedInput(
"Cannot accept negative integers for decimals arguments".to_string(),
head,
));
}
}
match input {
PipelineData::RawStream(stream, ..) => {
// TODO: in the future, we may want this to stream out, converting each to bytes
let output = stream.into_string()?;
Ok(Value::String {
val: output.item,
span: head,
}
.into_pipeline_data())
}
_ => input.map(
move |v| {
if column_paths.is_empty() {
action(&v, head, decimals, decimals_value, false, &config)
} else {
let mut ret = v;
for path in &column_paths {
let config = config.clone();
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| {
action(old, head, decimals, decimals_value, false, &config)
}),
);
if let Err(error) = r {
return Value::Error { error };
}
}
ret
}
},
engine_state.ctrlc.clone(),
),
}
}
pub fn action(
input: &Value,
span: Span,
decimals: bool,
digits: Option<i64>,
group_digits: bool,
config: &Config,
) -> Value {
match input {
Value::Int { val, .. } => {
let res = if group_digits {
format_int(*val) // int.to_formatted_string(*locale)
} else {
val.to_string()
};
Value::String { val: res, span }
}
Value::Float { val, .. } => {
if decimals {
let decimal_value = digits.unwrap_or(2) as usize;
Value::String {
val: format!("{:.*}", decimal_value, val),
span,
}
} else {
Value::String {
val: val.to_string(),
span,
}
}
}
Value::Bool { val, .. } => Value::String {
val: val.to_string(),
span,
},
Value::Date { val, .. } => Value::String {
val: val.format("%c").to_string(),
span,
},
Value::String { val, .. } => Value::String {
val: val.to_string(),
span,
},
Value::Filesize { val: _, .. } => Value::String {
val: input.into_string(", ", config),
span,
},
Value::Nothing { .. } => Value::String {
val: "nothing".to_string(),
span,
},
Value::Record {
cols: _,
vals: _,
span: _,
} => Value::Error {
error: ShellError::UnsupportedInput(
"Cannot convert Record into string".to_string(),
span,
),
},
x => Value::Error {
error: ShellError::CantConvert(String::from("string"), x.get_type().to_string(), span),
},
}
}
fn format_int(int: i64) -> String {
int.to_string()
// TODO once platform-specific dependencies are stable (see Cargo.toml)
// #[cfg(windows)]
// {
// int.to_formatted_string(&Locale::en)
// }
// #[cfg(not(windows))]
// {
// match SystemLocale::default() {
// Ok(locale) => int.to_formatted_string(&locale),
// Err(_) => int.to_formatted_string(&Locale::en),
// }
// }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -0,0 +1,5 @@
mod fmt;
pub(crate) mod into;
pub use fmt::Fmt;
pub use into::*;

View File

@ -0,0 +1,37 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape};
#[derive(Clone)]
pub struct Alias;
impl Command for Alias {
fn name(&self) -> &str {
"alias"
}
fn usage(&self) -> &str {
"Alias a command (with optional flags) to a new name"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("alias")
.required("name", SyntaxShape::String, "name of the alias")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
"equals sign followed by value",
)
.category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,71 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value};
#[derive(Clone)]
pub struct Debug;
impl Command for Debug {
fn name(&self) -> &str {
"debug"
}
fn usage(&self) -> &str {
"Debug print the value(s) piped in."
}
fn signature(&self) -> Signature {
Signature::build("debug").category(Category::Core).switch(
"raw",
"Prints the raw value representation",
Some('r'),
)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let config = stack.get_config().unwrap_or_default();
let raw = call.has_flag("raw");
input.map(
move |x| {
if raw {
Value::String {
val: x.debug_value(),
span: head,
}
} else {
Value::String {
val: x.debug_string(", ", &config),
span: head,
}
}
},
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Describe the type of a string",
example: "'hello' | debug",
result: Some(Value::test_string("hello")),
}]
}
}
#[cfg(test)]
mod test {
#[test]
fn test_examples() {
use super::Debug;
use crate::test_examples;
test_examples(Debug {})
}
}

View File

@ -0,0 +1,38 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape};
#[derive(Clone)]
pub struct Def;
impl Command for Def {
fn name(&self) -> &str {
"def"
}
fn usage(&self) -> &str {
"Define a custom command"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("def")
.required("def_name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"body of the definition",
)
.category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,38 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape};
#[derive(Clone)]
pub struct DefEnv;
impl Command for DefEnv {
fn name(&self) -> &str {
"def-env"
}
fn usage(&self) -> &str {
"Define a custom command, which participates in the caller environment"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("def-env")
.required("def_name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"body of the definition",
)
.category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,63 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Value,
};
#[derive(Clone)]
pub struct Describe;
impl Command for Describe {
fn name(&self) -> &str {
"describe"
}
fn usage(&self) -> &str {
"Describe the value(s) piped in."
}
fn signature(&self) -> Signature {
Signature::build("describe").category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
if matches!(input, PipelineData::RawStream(..)) {
Ok(PipelineData::Value(
Value::string("raw input", call.head),
None,
))
} else {
let value = input.into_value(call.head);
Ok(Value::String {
val: value.get_type().to_string(),
span: head,
}
.into_pipeline_data())
}
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Describe the type of a string",
example: "'hello' | describe",
result: Some(Value::test_string("string")),
}]
}
}
#[cfg(test)]
mod test {
#[test]
fn test_examples() {
use super::Describe;
use crate::test_examples;
test_examples(Describe {})
}
}

View File

@ -0,0 +1,98 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape, Value};
#[derive(Clone)]
pub struct Do;
impl Command for Do {
fn name(&self) -> &str {
"do"
}
fn usage(&self) -> &str {
"Run a block"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("do")
.desc(self.usage())
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"the block to run",
)
.switch(
"ignore-errors",
"ignore errors as the block runs",
Some('i'),
)
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
let ignore_errors = call.has_flag("ignore-errors");
let mut stack = stack.captures_to_stack(&block.captures);
let block = engine_state.get_block(block.block_id);
let params: Vec<_> = block
.signature
.required_positional
.iter()
.chain(block.signature.optional_positional.iter())
.collect();
for param in params.iter().zip(&rest) {
if let Some(var_id) = param.0.var_id {
stack.add_var(var_id, param.1.clone())
}
}
if let Some(param) = &block.signature.rest_positional {
if rest.len() > params.len() {
let mut rest_items = vec![];
for r in rest.into_iter().skip(params.len()) {
rest_items.push(r);
}
let span = if let Some(rest_item) = rest_items.first() {
rest_item.span()?
} else {
call.head
};
stack.add_var(
param
.var_id
.expect("Internal error: rest positional parameter lacks var_id"),
Value::List {
vals: rest_items,
span,
},
)
}
}
let result = eval_block(engine_state, &mut stack, block, input);
if ignore_errors {
match result {
Ok(x) => Ok(x),
Err(_) => Ok(PipelineData::new(call.head)),
}
} else {
result
}
}
}

View File

@ -0,0 +1,81 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct Echo;
impl Command for Echo {
fn name(&self) -> &str {
"echo"
}
fn usage(&self) -> &str {
"Echo the arguments back to the user."
}
fn signature(&self) -> Signature {
Signature::build("echo")
.rest("rest", SyntaxShape::Any, "the values to echo")
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
call.rest(engine_state, stack, 0).map(|to_be_echoed| {
let n = to_be_echoed.len();
match n.cmp(&1usize) {
// More than one value is converted in a stream of values
std::cmp::Ordering::Greater => PipelineData::ListStream(
ListStream::from_stream(to_be_echoed.into_iter(), engine_state.ctrlc.clone()),
None,
),
// But a single value can be forwarded as it is
std::cmp::Ordering::Equal => PipelineData::Value(to_be_echoed[0].clone(), None),
// When there are no elements, we echo the empty string
std::cmp::Ordering::Less => PipelineData::Value(
Value::String {
val: "".to_string(),
span: call.head,
},
None,
),
}
})
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Put a hello message in the pipeline",
example: "echo 'hello'",
result: Some(Value::test_string("hello")),
},
Example {
description: "Print the value of the special '$nu' variable",
example: "echo $nu",
result: None,
},
]
}
}
#[cfg(test)]
mod test {
#[test]
fn test_examples() {
use super::Echo;
use crate::test_examples;
test_examples(Echo {})
}
}

View File

@ -0,0 +1,113 @@
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,
Value,
};
#[derive(Clone)]
pub struct ErrorMake;
impl Command for ErrorMake {
fn name(&self) -> &str {
"error make"
}
fn signature(&self) -> Signature {
Signature::build("error make")
.optional("error-struct", SyntaxShape::Record, "the error to create")
.category(Category::Core)
}
fn usage(&self) -> &str {
"Create an error."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let span = call.head;
let ctrlc = engine_state.ctrlc.clone();
let arg: Option<Value> = call.opt(engine_state, stack, 0)?;
if let Some(arg) = arg {
Ok(make_error(&arg)
.map(|err| Value::Error { error: err })
.unwrap_or_else(|| Value::Error {
error: ShellError::SpannedLabeledError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
span,
),
})
.into_pipeline_data())
} else {
input.map(
move |value| {
make_error(&value)
.map(|err| Value::Error { error: err })
.unwrap_or_else(|| Value::Error {
error: ShellError::SpannedLabeledError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
span,
),
})
},
ctrlc,
)
}
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create a custom error for a custom command",
example: r#"def foo [x] {
let span = (metadata $x).span;
error make {msg: "this is fishy", label: {text: "fish right here", start: $span.start, end: $span.end } }
}"#,
result: None,
}]
}
}
fn make_error(value: &Value) -> Option<ShellError> {
if let Value::Record { .. } = &value {
let msg = value.get_data_by_key("msg");
let label = value.get_data_by_key("label");
match (msg, &label) {
(Some(Value::String { val: message, .. }), Some(label)) => {
let label_start = label.get_data_by_key("start");
let label_end = label.get_data_by_key("end");
let label_text = label.get_data_by_key("text");
match (label_start, label_end, label_text) {
(
Some(Value::Int { val: start, .. }),
Some(Value::Int { val: end, .. }),
Some(Value::String {
val: label_text, ..
}),
) => Some(ShellError::SpannedLabeledError(
message,
label_text,
Span {
start: start as usize,
end: end as usize,
},
)),
_ => None,
}
}
_ => None,
}
} else {
None
}
}

View File

@ -0,0 +1,42 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, Signature, Value,
};
#[derive(Clone)]
pub struct ExportCommand;
impl Command for ExportCommand {
fn name(&self) -> &str {
"export"
}
fn signature(&self) -> Signature {
Signature::build("export").category(Category::Core)
}
fn usage(&self) -> &str {
"Export custom commands or environment variables from a module."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String {
val: get_full_help(
&ExportCommand.signature(),
&ExportCommand.examples(),
engine_state,
stack,
),
span: call.head,
}
.into_pipeline_data())
}
}

View File

@ -0,0 +1,38 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape};
#[derive(Clone)]
pub struct ExportDef;
impl Command for ExportDef {
fn name(&self) -> &str {
"export def"
}
fn usage(&self) -> &str {
"Define a custom command and export it from a module"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("export def")
.required("name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"body of the definition",
)
.category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,38 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape};
#[derive(Clone)]
pub struct ExportDefEnv;
impl Command for ExportDefEnv {
fn name(&self) -> &str {
"export def-env"
}
fn usage(&self) -> &str {
"Define a custom command that participates in the environment and export it from a module"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("export def-env")
.required("name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"body of the definition",
)
.category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,42 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape};
#[derive(Clone)]
pub struct ExportEnv;
impl Command for ExportEnv {
fn name(&self) -> &str {
"export env"
}
fn usage(&self) -> &str {
"Export a block from a module that will be evaluated as an environment variable when imported."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("export env")
.required(
"name",
SyntaxShape::String,
"name of the environment variable",
)
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"body of the environment variable definition",
)
.category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
//TODO: Add the env to stack
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,216 @@
use nu_engine::{eval_block_with_redirect, eval_expression, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, Signature, Span, SyntaxShape,
Value,
};
#[derive(Clone)]
pub struct For;
impl Command for For {
fn name(&self) -> &str {
"for"
}
fn usage(&self) -> &str {
"Loop over a range"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("for")
.required(
"var_name",
SyntaxShape::VarWithOptType,
"name of the looping variable",
)
.required(
"range",
SyntaxShape::Keyword(b"in".to_vec(), Box::new(SyntaxShape::Any)),
"range of the loop",
)
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"the block to run",
)
.switch(
"numbered",
"returned a numbered item ($it.index and $it.item)",
Some('n'),
)
.creates_scope()
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let var_id = call.positional[0]
.as_var()
.expect("internal error: missing variable");
let keyword_expr = call.positional[1]
.as_keyword()
.expect("internal error: missing keyword");
let values = eval_expression(engine_state, stack, keyword_expr)?;
let capture_block: CaptureBlock = call.req(engine_state, stack, 2)?;
let numbered = call.has_flag("numbered");
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();
match values {
Value::List { vals, .. } => Ok(vals
.into_iter()
.enumerate()
.map(move |(idx, x)| {
stack.with_env(&orig_env_vars, &orig_env_hidden);
stack.add_var(
var_id,
if numbered {
Value::Record {
cols: vec!["index".into(), "item".into()],
vals: vec![
Value::Int {
val: idx as i64,
span: head,
},
x,
],
span: head,
}
} else {
x
},
);
//let block = engine_state.get_block(block_id);
match eval_block_with_redirect(
&engine_state,
&mut stack,
&block,
PipelineData::new(head),
) {
Ok(pipeline_data) => pipeline_data.into_value(head),
Err(error) => Value::Error { error },
}
})
.into_pipeline_data(ctrlc)),
Value::Range { val, .. } => Ok(val
.into_range_iter()?
.enumerate()
.map(move |(idx, x)| {
stack.with_env(&orig_env_vars, &orig_env_hidden);
stack.add_var(
var_id,
if numbered {
Value::Record {
cols: vec!["index".into(), "item".into()],
vals: vec![
Value::Int {
val: idx as i64,
span: head,
},
x,
],
span: head,
}
} else {
x
},
);
//let block = engine_state.get_block(block_id);
match eval_block_with_redirect(
&engine_state,
&mut stack,
&block,
PipelineData::new(head),
) {
Ok(pipeline_data) => pipeline_data.into_value(head),
Err(error) => Value::Error { error },
}
})
.into_pipeline_data(ctrlc)),
x => {
stack.add_var(var_id, x);
eval_block_with_redirect(&engine_state, &mut stack, &block, PipelineData::new(head))
}
}
}
fn examples(&self) -> Vec<Example> {
let span = Span::test_data();
vec![
Example {
description: "Echo the square of each integer",
example: "for x in [1 2 3] { $x * $x }",
result: Some(Value::List {
vals: vec![
Value::Int { val: 1, span },
Value::Int { val: 4, span },
Value::Int { val: 9, span },
],
span,
}),
},
Example {
description: "Work with elements of a range",
example: "for $x in 1..3 { $x }",
result: Some(Value::List {
vals: vec![
Value::Int { val: 1, span },
Value::Int { val: 2, span },
Value::Int { val: 3, span },
],
span,
}),
},
Example {
description: "Number each item and echo a message",
example: "for $it in ['bob' 'fred'] --numbered { $\"($it.index) is ($it.item)\" }",
result: Some(Value::List {
vals: vec![
Value::String {
val: "0 is bob".into(),
span,
},
Value::String {
val: "1 is fred".into(),
span,
},
],
span,
}),
},
]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(For {})
}
}

View File

@ -0,0 +1,261 @@
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Spanned, SyntaxShape, Value,
};
use nu_engine::{get_full_help, CallExt};
#[derive(Clone)]
pub struct Help;
impl Command for Help {
fn name(&self) -> &str {
"help"
}
fn signature(&self) -> Signature {
Signature::build("help")
.rest(
"rest",
SyntaxShape::String,
"the name of command to get help on",
)
.named(
"find",
SyntaxShape::String,
"string to find in command usage",
Some('f'),
)
.category(Category::Core)
}
fn usage(&self) -> &str {
"Display help information about commands."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
help(engine_state, stack, call)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "show all commands and sub-commands",
example: "help commands",
result: None,
},
Example {
description: "generate documentation",
example: "help generate_docs",
result: None,
},
Example {
description: "show help for single command",
example: "help match",
result: None,
},
Example {
description: "show help for single sub-command",
example: "help str lpad",
result: None,
},
Example {
description: "search for string in command usage",
example: "help --find char",
result: None,
},
]
}
}
fn help(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
let full_commands = engine_state.get_signatures_with_examples(false);
if let Some(f) = find {
let search_string = f.item.to_lowercase();
let mut found_cmds_vec = Vec::new();
for (sig, _, is_plugin, is_custom) in full_commands {
let mut cols = vec![];
let mut vals = vec![];
let key = sig.name.clone();
let c = sig.usage.clone();
let e = sig.extra_usage.clone();
if key.to_lowercase().contains(&search_string)
|| c.to_lowercase().contains(&search_string)
|| e.to_lowercase().contains(&search_string)
{
cols.push("name".into());
vals.push(Value::String {
val: key,
span: head,
});
cols.push("category".into());
vals.push(Value::String {
val: sig.category.to_string(),
span: head,
});
cols.push("is_plugin".into());
vals.push(Value::Bool {
val: is_plugin,
span: head,
});
cols.push("is_custom".into());
vals.push(Value::Bool {
val: is_custom,
span: head,
});
cols.push("usage".into());
vals.push(Value::String { val: c, span: head });
cols.push("extra_usage".into());
vals.push(Value::String { val: e, span: head });
found_cmds_vec.push(Value::Record {
cols,
vals,
span: head,
});
}
}
return Ok(found_cmds_vec
.into_iter()
.into_pipeline_data(engine_state.ctrlc.clone()));
}
if !rest.is_empty() {
let mut found_cmds_vec = Vec::new();
if rest[0].item == "commands" {
for (sig, _, is_plugin, is_custom) in full_commands {
let mut cols = vec![];
let mut vals = vec![];
let key = sig.name.clone();
let c = sig.usage.clone();
let e = sig.extra_usage.clone();
cols.push("name".into());
vals.push(Value::String {
val: key,
span: head,
});
cols.push("category".into());
vals.push(Value::String {
val: sig.category.to_string(),
span: head,
});
cols.push("is_plugin".into());
vals.push(Value::Bool {
val: is_plugin,
span: head,
});
cols.push("is_custom".into());
vals.push(Value::Bool {
val: is_custom,
span: head,
});
cols.push("usage".into());
vals.push(Value::String { val: c, span: head });
cols.push("extra_usage".into());
vals.push(Value::String { val: e, span: head });
found_cmds_vec.push(Value::Record {
cols,
vals,
span: head,
});
}
Ok(found_cmds_vec
.into_iter()
.into_pipeline_data(engine_state.ctrlc.clone()))
} else {
let mut name = String::new();
for r in &rest {
if !name.is_empty() {
name.push(' ');
}
name.push_str(&r.item);
}
let output = full_commands
.iter()
.filter(|(signature, _, _, _)| signature.name == name)
.map(|(signature, examples, _, _)| {
get_full_help(signature, examples, engine_state, stack)
})
.collect::<Vec<String>>();
if !output.is_empty() {
Ok(Value::String {
val: output.join("======================\n\n"),
span: call.head,
}
.into_pipeline_data())
} else {
Err(ShellError::CommandNotFound(span(&[
rest[0].span,
rest[rest.len() - 1].span,
])))
}
}
} else {
let msg = r#"Welcome to Nushell.
Here are some tips to help you get started.
* help commands - list all available commands
* help <command name> - display help about a particular command
* help --find <text to search> - search through all of help
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
Each stage in the pipeline works together to load, parse, and display information to you.
[Examples]
List the files in the current directory, sorted by size:
ls | sort-by size
Get information about the current system:
sys | get host
Get the processes on your system actively using CPU:
ps | where cpu > 0
You can also learn more at https://www.nushell.sh/book/"#;
Ok(Value::String {
val: msg.into(),
span: head,
}
.into_pipeline_data())
}
}

View File

@ -0,0 +1,119 @@
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape};
#[derive(Clone)]
pub struct Hide;
impl Command for Hide {
fn name(&self) -> &str {
"hide"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("hide")
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
.category(Category::Core)
}
fn usage(&self) -> &str {
"Hide definitions in the current scope"
}
fn extra_usage(&self) -> &str {
"If there is a definition and an environment variable with the same name in the current scope, first the definition will be hidden, then the environment variable."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let import_pattern = if let Some(Expression {
expr: Expr::ImportPattern(pat),
..
}) = call.positional.get(0)
{
pat
} else {
return Err(ShellError::SpannedLabeledError(
"Unexpected import".into(),
"import pattern not supported".into(),
call.head,
));
};
let head_name_str = if let Ok(s) = String::from_utf8(import_pattern.head.name.clone()) {
s
} else {
return Err(ShellError::NonUtf8(import_pattern.head.span));
};
if let Some(overlay_id) = engine_state.find_overlay(&import_pattern.head.name) {
// The first word is a module
let overlay = engine_state.get_overlay(overlay_id);
let env_vars_to_hide = if import_pattern.members.is_empty() {
overlay.env_vars_with_head(&import_pattern.head.name)
} else {
match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => overlay.env_vars(),
ImportPatternMember::Name { name, span } => {
let mut output = vec![];
if let Some((name, id)) =
overlay.env_var_with_head(name, &import_pattern.head.name)
{
output.push((name, id));
} else if !overlay.has_decl(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
));
}
output
}
ImportPatternMember::List { names } => {
let mut output = vec![];
for (name, span) in names {
if let Some((name, id)) =
overlay.env_var_with_head(name, &import_pattern.head.name)
{
output.push((name, id));
} else if !overlay.has_decl(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
));
}
}
output
}
}
};
for (name, _) in env_vars_to_hide {
let name = if let Ok(s) = String::from_utf8(name.clone()) {
s
} else {
return Err(ShellError::NonUtf8(import_pattern.span()));
};
if stack.remove_env_var(engine_state, &name).is_none() {
return Err(ShellError::NotFound(call.positional[0].span));
}
}
} else if !import_pattern.hidden.contains(&import_pattern.head.name)
&& stack.remove_env_var(engine_state, &head_name_str).is_none()
{
return Err(ShellError::NotFound(call.positional[0].span));
}
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,71 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value,
};
const NEWLINE_ESCAPE_CODE: &str = "<\\n>";
fn decode_newlines(escaped: &str) -> String {
escaped.replace(NEWLINE_ESCAPE_CODE, "\n")
}
#[derive(Clone)]
pub struct History;
impl Command for History {
fn name(&self) -> &str {
"history"
}
fn usage(&self) -> &str {
"Get the command history"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("history")
.switch("clear", "Clears out the history entries", Some('c'))
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
if let Some(config_path) = nu_path::config_dir() {
let clear = call.has_flag("clear");
let ctrlc = engine_state.ctrlc.clone();
let mut history_path = config_path;
history_path.push("nushell");
history_path.push("history.txt");
if clear {
let _ = std::fs::remove_file(history_path);
Ok(PipelineData::new(head))
} else {
let contents = std::fs::read_to_string(history_path);
if let Ok(contents) = contents {
Ok(contents
.lines()
.map(move |x| Value::String {
val: decode_newlines(x),
span: head,
})
.collect::<Vec<_>>()
.into_iter()
.into_pipeline_data(ctrlc))
} else {
Err(ShellError::FileNotFound(head))
}
}
} else {
Err(ShellError::FileNotFound(head))
}
}
}

View File

@ -0,0 +1,115 @@
use nu_engine::{eval_block, eval_expression, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, FromValue, IntoPipelineData, PipelineData, ShellError, Signature,
SyntaxShape, Value,
};
#[derive(Clone)]
pub struct If;
impl Command for If {
fn name(&self) -> &str {
"if"
}
fn usage(&self) -> &str {
"Conditionally run a block."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("if")
.required("cond", SyntaxShape::Expression, "condition to check")
.required(
"then_block",
SyntaxShape::Block(Some(vec![])),
"block to run if check succeeds",
)
.optional(
"else_expression",
SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)),
"expression or block to run if check fails",
)
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let cond = &call.positional[0];
let then_block: CaptureBlock = call.req(engine_state, stack, 1)?;
let else_case = call.positional.get(2);
let result = eval_expression(engine_state, stack, cond)?;
match &result {
Value::Bool { val, .. } => {
if *val {
let block = engine_state.get_block(then_block.block_id);
let mut stack = stack.captures_to_stack(&then_block.captures);
eval_block(engine_state, &mut stack, block, input)
} else if let Some(else_case) = else_case {
if let Some(else_expr) = else_case.as_keyword() {
if let Some(block_id) = else_expr.as_block() {
let result = eval_expression(engine_state, stack, else_expr)?;
let else_block: CaptureBlock = FromValue::from_value(&result)?;
let mut stack = stack.captures_to_stack(&else_block.captures);
let block = engine_state.get_block(block_id);
eval_block(engine_state, &mut stack, block, input)
} else {
eval_expression(engine_state, stack, else_expr)
.map(|x| x.into_pipeline_data())
}
} else {
eval_expression(engine_state, stack, else_case)
.map(|x| x.into_pipeline_data())
}
} else {
Ok(PipelineData::new(call.head))
}
}
x => Err(ShellError::CantConvert(
"bool".into(),
x.get_type().to_string(),
result.span()?,
)),
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Output a value if a condition matches, otherwise return nothing",
example: "if 2 < 3 { 'yes!' }",
result: Some(Value::test_string("yes!")),
},
Example {
description: "Output a value if a condition matches, else return another value",
example: "if 5 < 3 { 'yes!' } else { 'no!' }",
result: Some(Value::test_string("no!")),
},
Example {
description: "Chain multiple if's together",
example: "if 5 < 3 { 'yes!' } else if 4 < 5 { 'no!' } else { 'okay!' }",
result: Some(Value::test_string("no!")),
},
]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(If {})
}
}

View File

@ -0,0 +1,48 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature};
#[derive(Clone)]
pub struct Ignore;
impl Command for Ignore {
fn name(&self) -> &str {
"ignore"
}
fn usage(&self) -> &str {
"Ignore the output of the previous command in the pipeline"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("ignore").category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::new(call.head))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Ignore the output of an echo command",
example: "echo done | ignore",
result: None,
}]
}
}
#[cfg(test)]
mod test {
#[test]
fn test_examples() {
use super::Ignore;
use crate::test_examples;
test_examples(Ignore {})
}
}

View File

@ -0,0 +1,78 @@
use nu_engine::eval_expression_with_input;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape};
#[derive(Clone)]
pub struct Let;
impl Command for Let {
fn name(&self) -> &str {
"let"
}
fn usage(&self) -> &str {
"Create a variable and give it a value."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("let")
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
"equals sign followed by value",
)
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let var_id = call.positional[0]
.as_var()
.expect("internal error: missing variable");
let keyword_expr = call.positional[1]
.as_keyword()
.expect("internal error: missing keyword");
let rhs = eval_expression_with_input(engine_state, stack, keyword_expr, input, false)?;
//println!("Adding: {:?} to {}", rhs, var_id);
stack.add_var(var_id, rhs.into_value(call.head));
Ok(PipelineData::new(call.head))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Set a variable to a value",
example: "let x = 10",
result: None,
},
Example {
description: "Set a variable to the result of an expression",
example: "let x = 10 + 100",
result: None,
},
]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Let {})
}
}

View File

@ -0,0 +1,169 @@
use nu_engine::CallExt;
use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata, Signature,
Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct Metadata;
impl Command for Metadata {
fn name(&self) -> &str {
"metadata"
}
fn usage(&self) -> &str {
"Get the metadata for items in the stream"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("metadata")
.optional(
"expression",
SyntaxShape::Any,
"the expression you want metadata for",
)
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let arg = call.positional.get(0);
let head = call.head;
match arg {
Some(Expression {
expr: Expr::FullCellPath(full_cell_path),
span,
..
}) => {
if full_cell_path.tail.is_empty() {
match &full_cell_path.head {
Expression {
expr: Expr::Var(var_id),
..
} => {
let origin = stack.get_var_with_origin(*var_id, *span)?;
Ok(build_metadata_record(&origin, &input.metadata(), head)
.into_pipeline_data())
}
_ => {
let val: Value = call.req(engine_state, stack, 0)?;
Ok(build_metadata_record(&val, &input.metadata(), head)
.into_pipeline_data())
}
}
} else {
let val: Value = call.req(engine_state, stack, 0)?;
Ok(build_metadata_record(&val, &input.metadata(), head).into_pipeline_data())
}
}
Some(_) => {
let val: Value = call.req(engine_state, stack, 0)?;
Ok(build_metadata_record(&val, &input.metadata(), head).into_pipeline_data())
}
None => {
let mut cols = vec![];
let mut vals = vec![];
if let Some(x) = &input.metadata() {
match x {
PipelineMetadata {
data_source: DataSource::Ls,
} => {
cols.push("source".into());
vals.push(Value::String {
val: "ls".into(),
span: head,
})
}
}
}
Ok(Value::Record {
cols,
vals,
span: head,
}
.into_pipeline_data())
}
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the metadata of a variable",
example: "metadata $a",
result: None,
},
Example {
description: "Get the metadata of the input",
example: "ls | metadata",
result: None,
},
]
}
}
fn build_metadata_record(arg: &Value, metadata: &Option<PipelineMetadata>, head: Span) -> Value {
let mut cols = vec![];
let mut vals = vec![];
if let Ok(span) = arg.span() {
cols.push("span".into());
vals.push(Value::Record {
cols: vec!["start".into(), "end".into()],
vals: vec![
Value::Int {
val: span.start as i64,
span,
},
Value::Int {
val: span.end as i64,
span,
},
],
span: head,
});
}
if let Some(x) = &metadata {
match x {
PipelineMetadata {
data_source: DataSource::Ls,
} => {
cols.push("source".into());
vals.push(Value::String {
val: "ls".into(),
span: head,
})
}
}
}
Value::Record {
cols,
vals,
span: head,
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Metadata {})
}
}

View File

@ -0,0 +1,56 @@
mod alias;
mod debug;
mod def;
mod def_env;
mod describe;
mod do_;
mod echo;
mod error_make;
mod export;
mod export_def;
mod export_def_env;
mod export_env;
mod for_;
mod help;
mod hide;
mod history;
mod if_;
mod ignore;
mod let_;
mod metadata;
mod module;
mod source;
mod tutor;
mod use_;
mod version;
pub use alias::Alias;
pub use debug::Debug;
pub use def::Def;
pub use def_env::DefEnv;
pub use describe::Describe;
pub use do_::Do;
pub use echo::Echo;
pub use error_make::ErrorMake;
pub use export::ExportCommand;
pub use export_def::ExportDef;
pub use export_def_env::ExportDefEnv;
pub use export_env::ExportEnv;
pub use for_::For;
pub use help::Help;
pub use hide::Hide;
pub use history::History;
pub use if_::If;
pub use ignore::Ignore;
pub use let_::Let;
pub use metadata::Metadata;
pub use module::Module;
pub use source::Source;
pub use tutor::Tutor;
pub use use_::Use;
pub use version::Version;
#[cfg(feature = "plugin")]
mod register;
#[cfg(feature = "plugin")]
pub use register::Register;

View File

@ -0,0 +1,37 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape};
#[derive(Clone)]
pub struct Module;
impl Command for Module {
fn name(&self) -> &str {
"module"
}
fn usage(&self) -> &str {
"Define a custom module"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("module")
.required("module_name", SyntaxShape::String, "module name")
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"body of the module",
)
.category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,53 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape};
#[derive(Clone)]
pub struct Register;
impl Command for Register {
fn name(&self) -> &str {
"register"
}
fn usage(&self) -> &str {
"Register a plugin"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("register")
.required(
"plugin",
SyntaxShape::Filepath,
"path of executable for plugin",
)
.required_named(
"encoding",
SyntaxShape::String,
"Encoding used to communicate with plugin. Options: [capnp, json]",
Some('e'),
)
.optional(
"signature",
SyntaxShape::Any,
"Block with signature description as json object",
)
.named(
"shell",
SyntaxShape::Filepath,
"path of shell used to run plugin (cmd, sh, python, etc)",
Some('s'),
)
.category(Category::Core)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,43 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape};
/// Source a file for environment variables.
#[derive(Clone)]
pub struct Source;
impl Command for Source {
fn name(&self) -> &str {
"source"
}
fn signature(&self) -> Signature {
Signature::build("source")
.required(
"filename",
SyntaxShape::Filepath,
"the filepath to the script file to source",
)
.category(Category::Core)
}
fn usage(&self) -> &str {
"Runs a script file in the current context."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
// Note: this hidden positional is the block_id that corresponded to the 0th position
// it is put here by the parser
let block_id: i64 = call.req(engine_state, stack, 1)?;
let block = engine_state.get_block(block_id as usize).clone();
eval_block(engine_state, stack, &block, input)
}
}

View File

@ -0,0 +1,466 @@
use itertools::Itertools;
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,
Value,
};
#[derive(Clone)]
pub struct Tutor;
impl Command for Tutor {
fn name(&self) -> &str {
"tutor"
}
fn signature(&self) -> Signature {
Signature::build("tutor")
.optional(
"search",
SyntaxShape::String,
"item to search for, or 'list' to list available tutorials",
)
.named(
"find",
SyntaxShape::String,
"Search tutorial for a phrase",
Some('f'),
)
.category(Category::Core)
}
fn usage(&self) -> &str {
"Run the tutorial. To begin, run: tutor"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
tutor(engine_state, stack, call)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Begin the tutorial",
example: "tutor begin",
result: None,
},
Example {
description: "Search a tutorial by phrase",
example: "tutor -f \"$in\"",
result: None,
},
]
}
}
fn tutor(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let search: Option<String> = call.opt(engine_state, stack, 0).unwrap_or(None);
let find: Option<String> = call.get_flag(engine_state, stack, "find")?;
let search_space = [
(vec!["begin"], begin_tutor()),
(
vec!["table", "tables", "row", "rows", "column", "columns"],
table_tutor(),
),
(vec!["cell", "cells"], cell_tutor()),
(
vec![
"expr",
"exprs",
"expressions",
"subexpression",
"subexpressions",
"sub-expression",
"sub-expressions",
],
expression_tutor(),
),
(vec!["echo"], echo_tutor()),
(vec!["each", "iteration", "iter"], each_tutor()),
(
vec!["var", "vars", "variable", "variables"],
variable_tutor(),
),
(vec!["engine-q", "e-q"], engineq_tutor()),
(vec!["block", "blocks"], block_tutor()),
(vec!["shorthand", "shorthands"], shorthand_tutor()),
];
if let Some(find) = find {
let mut results = vec![];
for search_group in search_space {
if search_group.1.contains(&find) {
results.push(search_group.0[0].to_string())
}
}
let message = format!("You can find '{}' in the following topics:\n{}\n\nYou can learn about a topic using `tutor` followed by the name of the topic.\nFor example: `tutor table` to open the table topic.\n\n",
find,
results.into_iter().map(|x| format!("- {}", x)).join("\n")
);
return Ok(display(&message, engine_state, stack, span));
} else if let Some(search) = search {
for search_group in search_space {
if search_group.0.contains(&search.as_str()) {
return Ok(display(search_group.1, engine_state, stack, span));
}
}
}
Ok(display(default_tutor(), engine_state, stack, span))
}
fn default_tutor() -> &'static str {
r#"
Welcome to the Nushell tutorial!
With the `tutor` command, you'll be able to learn a lot about how Nushell
works along with many fun tips and tricks to speed up everyday tasks.
To get started, you can use `tutor begin`.
"#
}
fn begin_tutor() -> &'static str {
r#"
Nushell is a structured shell and programming language. One way to begin
using it is to try a few of the commands.
The first command to try is `ls`. The `ls` command will show you a list
of the files in the current directory. Notice that these files are shown
as a table. Each column of this table not only tells us what is being
shown, but also gives us a way to work with the data.
You can combine the `ls` command with other commands using the pipeline
symbol '|'. This allows data to flow from one command to the next.
For example, if we only wanted the name column, we could do:
```
ls | select name
```
Notice that we still get a table, but this time it only has one column:
the name column.
You can continue to learn more about tables by running:
```
tutor tables
```
If at any point, you'd like to restart this tutorial, you can run:
```
tutor begin
```
"#
}
fn table_tutor() -> &'static str {
r#"
The most common form of data in Nushell is the table. Tables contain rows and
columns of data. In each cell of the table, there is data that you can access
using Nushell commands.
To get the 3rd row in the table, you can use the `nth` command:
```
ls | nth 2
```
This will get the 3rd (note that `nth` is zero-based) row in the table created
by the `ls` command. You can use `nth` on any table created by other commands
as well.
You can also access the column of data in one of two ways. If you want
to keep the column as part of a new table, you can use `select`.
```
ls | select name
```
This runs `ls` and returns only the "name" column of the table.
If, instead, you'd like to get access to the values inside of the column, you
can use the `get` command.
```
ls | get name
```
This allows us to get to the list of strings that are the filenames rather
than having a full table. In some cases, this can make the names easier to
work with.
You can continue to learn more about working with cells of the table by
running:
```
tutor cells
```
"#
}
fn cell_tutor() -> &'static str {
r#"
Working with cells of data in the table is a key part of working with data in
Nushell. Because of this, there is a rich list of commands to work with cells
as well as handy shorthands for accessing cells.
Cells can hold simple values like strings and numbers, or more complex values
like lists and tables.
To reach a cell of data from a table, you can combine a row operation and a
column operation.
```
ls | nth 4 | get name
```
You can combine these operations into one step using a shortcut.
```
(ls).4.name
```
Names/strings represent columns names and numbers represent row numbers.
The `(ls)` is a form of expression. You can continue to learn more about
expressions by running:
```
tutor expressions
```
You can also learn about these cell shorthands by running:
```
tutor shorthands
```
"#
}
fn expression_tutor() -> &'static str {
r#"
Expressions give you the power to mix calls to commands with math. The
simplest expression is a single value like a string or number.
```
3
```
Expressions can also include math operations like addition or division.
```
10 / 2
```
Normally, an expression is one type of operation: math or commands. You can
mix these types by using subexpressions. Subexpressions are just like
expressions, but they're wrapped in parentheses `()`.
```
10 * (3 + 4)
```
Here we use parentheses to create a higher math precedence in the math
expression.
```
echo (2 + 3)
```
You can continue to learn more about the `echo` command by running:
```
tutor echo
```
"#
}
fn echo_tutor() -> &'static str {
r#"
The `echo` command in Nushell is a powerful tool for not only seeing values,
but also for creating new ones.
```
echo "Hello"
```
You can echo output. This output, if it's not redirected using a "|" pipeline
will be displayed to the screen.
```
echo 1..10
```
You can also use echo to work with individual values of a range. In this
example, `echo` will create the values from 1 to 10 as a list.
```
echo 1 2 3 4 5
```
You can also create lists of values by passing `echo` multiple arguments.
This can be helpful if you want to later processes these values.
The `echo` command can pair well with the `each` command which can run
code on each row, or item, of input.
You can continue to learn more about the `each` command by running:
```
tutor each
```
"#
}
fn each_tutor() -> &'static str {
r#"
The `each` command gives us a way of working with each individual row or
element of a list one at a time. It reads these in from the pipeline and
runs a block on each element. A block is a group of pipelines.
```
echo 1 2 3 | each { $it + 10}
```
This example iterates over each element sent by `echo`, giving us three new
values that are the original value + 10. Here, the `$it` is a variable that
is the name given to the block's parameter by default.
You can learn more about blocks by running:
```
tutor blocks
```
You can also learn more about variables by running:
```
tutor variables
```
"#
}
fn variable_tutor() -> &'static str {
r#"
Variables are an important way to store values to be used later. To create a
variable, you can use the `let` keyword. The `let` command will create a
variable and then assign it a value in one step.
```
let $x = 3
```
Once created, we can refer to this variable by name.
```
$x
```
Nushell also comes with built-in variables. The `$nu` variable is a reserved
variable that contains a lot of information about the currently running
instance of Nushell. The `$it` variable is the name given to block parameters
if you don't specify one. And `$in` is the variable that allows you to work
with all of the data coming in from the pipeline in one place.
"#
}
fn block_tutor() -> &'static str {
r#"
Blocks are a special form of expression that hold code to be run at a later
time. Often, you'll see blocks as one of the arguments given to commands
like `each` and `if`.
```
ls | each {|x| $x.name}
```
The above will create a list of the filenames in the directory.
```
if $true { echo "it's true" } { echo "it's not true" }
```
This `if` call will run the first block if the expression is true, or the
second block if the expression is false.
"#
}
fn shorthand_tutor() -> &'static str {
r#"
You can access cells in a table using a shorthand notation sometimes called a
"column path" or "cell path". These paths allow you to go from a table to
rows, columns, or cells inside of the table.
Shorthand paths are made from rows numbers, column names, or both. You can use
them on any variable or subexpression.
```
$nu.cwd
```
The above accesses the built-in `$nu` variable, gets its table, and then uses
the shorthand path to retrieve only the cell data inside the "cwd" column.
```
(ls).name.4
```
This will retrieve the cell data in the "name" column on the 5th row (note:
row numbers are zero-based).
Rows and columns don't need to come in any specific order. You can get the
same value using:
```
(ls).4.name
```
"#
}
fn engineq_tutor() -> &'static str {
r#"
Engine-q is the upcoming engine for Nushell. Build for speed and correctness,
it also comes with a set of changes from Nushell versions prior to 0.60. To
get ready for engine-q look for some of these changes that might impact your
current scripts:
* Engine-q now uses a few new data structures, including a record syntax
that allows you to model key-value pairs similar to JSON objects.
* Environment variables can now contain more than just strings. Structured
values are converted to strings for external commands using converters.
* `if` will now use an `else` keyword before the else block.
* We're moving from "config.toml" to "config.nu". This means startup will
now be a script file.
* `config` and its subcommands are being replaced by a record that you can
update in the shell which contains all the settings under the variable
`$config`.
* bigint/bigdecimal values are now machine i64 and f64 values
* And more, you can read more about upcoming changes in the up-to-date list
at: https://github.com/nushell/engine-q/issues/522
"#
}
fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span) -> PipelineData {
let help = help.split('`');
let mut build = String::new();
let mut code_mode = false;
for item in help {
if code_mode {
code_mode = false;
//TODO: support no-color mode
if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") {
let decl = engine_state.get_decl(highlighter);
if let Ok(output) = decl.run(
engine_state,
stack,
&Call::new(span),
Value::String {
val: item.to_string(),
span: Span { start: 0, end: 0 },
}
.into_pipeline_data(),
) {
let result = output.into_value(Span { start: 0, end: 0 });
match result.as_string() {
Ok(s) => {
build.push_str(&s);
}
_ => {
build.push_str(item);
}
}
}
}
} else {
code_mode = true;
build.push_str(item);
}
}
Value::string(build, span).into_pipeline_data()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Tutor)
}
}

View File

@ -0,0 +1,118 @@
use nu_engine::eval_block;
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape};
#[derive(Clone)]
pub struct Use;
impl Command for Use {
fn name(&self) -> &str {
"use"
}
fn usage(&self) -> &str {
"Use definitions from a module"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("use")
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let import_pattern = if let Some(Expression {
expr: Expr::ImportPattern(pat),
..
}) = call.positional.get(0)
{
pat
} else {
return Err(ShellError::SpannedLabeledError(
"Unexpected import".into(),
"import pattern not supported".into(),
call.head,
));
};
if let Some(overlay_id) = engine_state.find_overlay(&import_pattern.head.name) {
let overlay = engine_state.get_overlay(overlay_id);
let env_vars_to_use = if import_pattern.members.is_empty() {
overlay.env_vars_with_head(&import_pattern.head.name)
} else {
match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => overlay.env_vars(),
ImportPatternMember::Name { name, span } => {
let mut output = vec![];
if let Some(id) = overlay.get_env_var_id(name) {
output.push((name.clone(), id));
} else if !overlay.has_decl(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
));
}
output
}
ImportPatternMember::List { names } => {
let mut output = vec![];
for (name, span) in names {
if let Some(id) = overlay.get_env_var_id(name) {
output.push((name.clone(), id));
} else if !overlay.has_decl(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
));
}
}
output
}
}
};
for (name, block_id) in env_vars_to_use {
let name = if let Ok(s) = String::from_utf8(name.clone()) {
s
} else {
return Err(ShellError::NonUtf8(import_pattern.head.span));
};
let block = engine_state.get_block(block_id);
// TODO: Add string conversions (e.g. int to string)
// TODO: Later expand env to take all Values
let val = eval_block(engine_state, stack, block, PipelineData::new(call.head))?
.into_value(call.head);
stack.add_env_var(name, val);
}
} else {
// TODO: This is a workaround since call.positional[0].span points at 0 for some reason
// when this error is triggered
let bytes = engine_state.get_span_contents(&call.positional[0].span);
return Err(ShellError::SpannedLabeledError(
format!(
"Could not use '{}' import pattern",
String::from_utf8_lossy(bytes)
),
"called here".to_string(),
call.head,
));
}
Ok(PipelineData::new(call.head))
}
}

View File

@ -0,0 +1,352 @@
use indexmap::IndexMap;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Example, IntoPipelineData, PipelineData, ShellError, Signature, Value};
pub mod shadow {
include!(concat!(env!("OUT_DIR"), "/shadow.rs"));
}
#[derive(Clone)]
pub struct Version;
impl Command for Version {
fn name(&self) -> &str {
"version"
}
fn signature(&self) -> Signature {
Signature::build("version")
}
fn usage(&self) -> &str {
"Display Nu version."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
version(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Display Nu version",
example: "version",
result: None,
}]
}
}
pub fn version(
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let tag = call.head;
let mut indexmap = IndexMap::with_capacity(4);
indexmap.insert(
"version".to_string(),
Value::String {
val: env!("CARGO_PKG_VERSION").to_string(),
span: tag,
},
);
let branch: Option<&str> = Some(shadow::BRANCH).filter(|x| !x.is_empty());
if let Some(branch) = branch {
indexmap.insert(
"branch".to_string(),
Value::String {
val: branch.to_string(),
span: call.head,
},
);
}
let short_commit: Option<&str> = Some(shadow::SHORT_COMMIT).filter(|x| !x.is_empty());
if let Some(short_commit) = short_commit {
indexmap.insert(
"short_commit".to_string(),
Value::String {
val: short_commit.to_string(),
span: call.head,
},
);
}
let commit_hash: Option<&str> = Some(shadow::COMMIT_HASH).filter(|x| !x.is_empty());
if let Some(commit_hash) = commit_hash {
indexmap.insert(
"commit_hash".to_string(),
Value::String {
val: commit_hash.to_string(),
span: call.head,
},
);
}
let commit_date: Option<&str> = Some(shadow::COMMIT_DATE).filter(|x| !x.is_empty());
if let Some(commit_date) = commit_date {
indexmap.insert(
"commit_date".to_string(),
Value::String {
val: commit_date.to_string(),
span: call.head,
},
);
}
let build_os: Option<&str> = Some(shadow::BUILD_OS).filter(|x| !x.is_empty());
if let Some(build_os) = build_os {
indexmap.insert(
"build_os".to_string(),
Value::String {
val: build_os.to_string(),
span: call.head,
},
);
}
let rust_version: Option<&str> = Some(shadow::RUST_VERSION).filter(|x| !x.is_empty());
if let Some(rust_version) = rust_version {
indexmap.insert(
"rust_version".to_string(),
Value::String {
val: rust_version.to_string(),
span: call.head,
},
);
}
let rust_channel: Option<&str> = Some(shadow::RUST_CHANNEL).filter(|x| !x.is_empty());
if let Some(rust_channel) = rust_channel {
indexmap.insert(
"rust_channel".to_string(),
Value::String {
val: rust_channel.to_string(),
span: call.head,
},
);
}
let cargo_version: Option<&str> = Some(shadow::CARGO_VERSION).filter(|x| !x.is_empty());
if let Some(cargo_version) = cargo_version {
indexmap.insert(
"cargo_version".to_string(),
Value::String {
val: cargo_version.to_string(),
span: call.head,
},
);
}
let pkg_version: Option<&str> = Some(shadow::PKG_VERSION).filter(|x| !x.is_empty());
if let Some(pkg_version) = pkg_version {
indexmap.insert(
"pkg_version".to_string(),
Value::String {
val: pkg_version.to_string(),
span: call.head,
},
);
}
let build_time: Option<&str> = Some(shadow::BUILD_TIME).filter(|x| !x.is_empty());
if let Some(build_time) = build_time {
indexmap.insert(
"build_time".to_string(),
Value::String {
val: build_time.to_string(),
span: call.head,
},
);
}
let build_rust_channel: Option<&str> =
Some(shadow::BUILD_RUST_CHANNEL).filter(|x| !x.is_empty());
if let Some(build_rust_channel) = build_rust_channel {
indexmap.insert(
"build_rust_channel".to_string(),
Value::String {
val: build_rust_channel.to_string(),
span: call.head,
},
);
}
indexmap.insert(
"features".to_string(),
Value::String {
val: features_enabled().join(", "),
span: call.head,
},
);
// Get a list of command names and check for plugins
let installed_plugins = engine_state
.plugin_decls()
.into_iter()
.filter(|x| x.is_plugin().is_some())
.map(|x| x.name())
.collect::<Vec<_>>();
indexmap.insert(
"installed_plugins".to_string(),
Value::String {
val: installed_plugins.join(", "),
span: call.head,
},
);
let cols = indexmap.keys().cloned().collect::<Vec<_>>();
let vals = indexmap.values().cloned().collect::<Vec<_>>();
// Ok(Value::List {
// vals: vec![Value::Record {
// cols,
// vals,
// span: call.head,
// }],
// span: call.head,
// }
// .into_pipeline_data())
// List looks better than table, imo
Ok(Value::Record {
cols,
vals,
span: call.head,
}
.into_pipeline_data())
}
fn features_enabled() -> Vec<String> {
let mut names = vec!["default".to_string()];
// NOTE: There should be another way to know
// features on.
#[cfg(feature = "ctrlc")]
{
names.push("ctrlc".to_string());
}
// #[cfg(feature = "rich-benchmark")]
// {
// names.push("rich-benchmark".to_string());
// }
#[cfg(feature = "rustyline-support")]
{
names.push("rustyline".to_string());
}
#[cfg(feature = "term")]
{
names.push("term".to_string());
}
#[cfg(feature = "uuid_crate")]
{
names.push("uuid".to_string());
}
#[cfg(feature = "which")]
{
names.push("which".to_string());
}
#[cfg(feature = "zip")]
{
names.push("zip".to_string());
}
#[cfg(feature = "clipboard-cli")]
{
names.push("clipboard-cli".to_string());
}
#[cfg(feature = "trash-support")]
{
names.push("trash".to_string());
}
#[cfg(feature = "dataframe")]
{
names.push("dataframe".to_string());
}
#[cfg(feature = "table-pager")]
{
names.push("table-pager".to_string());
}
// #[cfg(feature = "binaryview")]
// {
// names.push("binaryview".to_string());
// }
// #[cfg(feature = "start")]
// {
// names.push("start".to_string());
// }
// #[cfg(feature = "bson")]
// {
// names.push("bson".to_string());
// }
// #[cfg(feature = "sqlite")]
// {
// names.push("sqlite".to_string());
// }
// #[cfg(feature = "s3")]
// {
// names.push("s3".to_string());
// }
// #[cfg(feature = "chart")]
// {
// names.push("chart".to_string());
// }
// #[cfg(feature = "xpath")]
// {
// names.push("xpath".to_string());
// }
// #[cfg(feature = "selector")]
// {
// names.push("selector".to_string());
// }
// #[cfg(feature = "extra")]
// {
// names.push("extra".to_string());
// }
// #[cfg(feature = "preserve_order")]
// {
// names.push("preserve_order".to_string());
// }
// #[cfg(feature = "wee_alloc")]
// {
// names.push("wee_alloc".to_string());
// }
// #[cfg(feature = "console_error_panic_hook")]
// {
// names.push("console_error_panic_hook".to_string());
// }
names.sort();
names
}

View File

@ -0,0 +1,3 @@
# nu-dataframe
The nu-dataframe crate holds the definitions of the dataframe structures and commands

View File

@ -0,0 +1,375 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
did_you_mean,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use polars::{frame::groupby::GroupBy, prelude::PolarsError};
use crate::dataframe::values::NuGroupBy;
use super::super::values::{Column, NuDataFrame};
enum Operation {
Mean,
Sum,
Min,
Max,
First,
Last,
Nunique,
Quantile(f64),
Median,
Var,
Std,
Count,
}
impl Operation {
fn from_tagged(
name: &Spanned<String>,
quantile: Option<Spanned<f64>>,
) -> Result<Operation, ShellError> {
match name.item.as_ref() {
"mean" => Ok(Operation::Mean),
"sum" => Ok(Operation::Sum),
"min" => Ok(Operation::Min),
"max" => Ok(Operation::Max),
"first" => Ok(Operation::First),
"last" => Ok(Operation::Last),
"nunique" => Ok(Operation::Nunique),
"quantile" => match quantile {
None => Err(ShellError::SpannedLabeledError(
"Quantile value not fount".into(),
"Quantile operation requires quantile value".into(),
name.span,
)),
Some(value) => {
if (value.item < 0.0) | (value.item > 1.0) {
Err(ShellError::SpannedLabeledError(
"Inappropriate quantile".into(),
"Quantile value should be between 0.0 and 1.0".into(),
value.span,
))
} else {
Ok(Operation::Quantile(value.item))
}
}
},
"median" => Ok(Operation::Median),
"var" => Ok(Operation::Var),
"std" => Ok(Operation::Std),
"count" => Ok(Operation::Count),
selection => {
let possibilities = [
"mean".to_string(),
"sum".to_string(),
"min".to_string(),
"max".to_string(),
"first".to_string(),
"last".to_string(),
"nunique".to_string(),
"quantile".to_string(),
"median".to_string(),
"var".to_string(),
"std".to_string(),
"count".to_string(),
];
match did_you_mean(&possibilities, selection) {
Some(suggestion) => Err(ShellError::DidYouMean(suggestion, name.span)),
None => Err(ShellError::SpannedLabeledErrorHelp(
"Operation not fount".into(),
"Operation does not exist".into(),
name.span,
"Perhaps you want: mean, sum, min, max, first, last, nunique, quantile, median, var, std, or count".into(),
))
}
}
}
}
fn to_str(&self) -> &'static str {
match self {
Self::Mean => "mean",
Self::Sum => "sum",
Self::Min => "min",
Self::Max => "max",
Self::First => "first",
Self::Last => "last",
Self::Nunique => "nunique",
Self::Quantile(_) => "quantile",
Self::Median => "median",
Self::Var => "var",
Self::Std => "std",
Self::Count => "count",
}
}
}
#[derive(Clone)]
pub struct Aggregate;
impl Command for Aggregate {
fn name(&self) -> &str {
"dfr aggregate"
}
fn usage(&self) -> &str {
"Performs an aggregation operation on a dataframe and groupby object"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"operation-name",
SyntaxShape::String,
"\n\tDataframes: mean, sum, min, max, quantile, median, var, std
\tGroupBy: mean, sum, min, max, first, last, nunique, quantile, median, var, std, count",
)
.named(
"quantile",
SyntaxShape::Number,
"quantile value for quantile operation",
Some('q'),
)
.switch(
"explicit",
"returns explicit names for groupby aggregations",
Some('e'),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Aggregate sum by grouping by column a and summing on col b",
example:
"[[a b]; [one 1] [one 2]] | dfr to-df | dfr group-by a | dfr aggregate sum",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new("a".to_string(), vec![Value::test_string("one")]),
Column::new("b".to_string(), vec![Value::test_int(3)]),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Aggregate sum in dataframe columns",
example: "[[a b]; [4 1] [5 2]] | dfr to-df | dfr aggregate sum",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new("a".to_string(), vec![Value::test_int(9)]),
Column::new("b".to_string(), vec![Value::test_int(3)]),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Aggregate sum in series",
example: "[4 1 5 6] | dfr to-df | dfr aggregate sum",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0".to_string(),
vec![Value::test_int(16)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let operation: Spanned<String> = call.req(engine_state, stack, 0)?;
let quantile: Option<Spanned<f64>> = call.get_flag(engine_state, stack, "quantile")?;
let op = Operation::from_tagged(&operation, quantile)?;
match input {
PipelineData::Value(Value::CustomValue { val, span }, _) => {
let df = val.as_any().downcast_ref::<NuDataFrame>();
let groupby = val.as_any().downcast_ref::<NuGroupBy>();
match (df, groupby) {
(Some(df), None) => {
let df = df.as_ref();
let res = perform_dataframe_aggregation(df, op, operation.span)?;
Ok(PipelineData::Value(
NuDataFrame::dataframe_into_value(res, span),
None,
))
}
(None, Some(nu_groupby)) => {
let groupby = nu_groupby.to_groupby()?;
let res = perform_groupby_aggregation(
groupby,
op,
operation.span,
call.head,
call.has_flag("explicit"),
)?;
Ok(PipelineData::Value(
NuDataFrame::dataframe_into_value(res, span),
None,
))
}
_ => Err(ShellError::SpannedLabeledError(
"Incorrect datatype".into(),
"no groupby or dataframe found in input stream".into(),
call.head,
)),
}
}
_ => Err(ShellError::SpannedLabeledError(
"Incorrect datatype".into(),
"no groupby or dataframe found in input stream".into(),
call.head,
)),
}
}
fn perform_groupby_aggregation(
groupby: GroupBy,
operation: Operation,
operation_span: Span,
agg_span: Span,
explicit: bool,
) -> Result<polars::prelude::DataFrame, ShellError> {
let mut res = match operation {
Operation::Mean => groupby.mean(),
Operation::Sum => groupby.sum(),
Operation::Min => groupby.min(),
Operation::Max => groupby.max(),
Operation::First => groupby.first(),
Operation::Last => groupby.last(),
Operation::Nunique => groupby.n_unique(),
Operation::Quantile(quantile) => groupby.quantile(quantile),
Operation::Median => groupby.median(),
Operation::Var => groupby.var(),
Operation::Std => groupby.std(),
Operation::Count => groupby.count(),
}
.map_err(|e| {
let span = match &e {
PolarsError::NotFound(_) => agg_span,
_ => operation_span,
};
ShellError::SpannedLabeledError("Error calculating aggregation".into(), e.to_string(), span)
})?;
if !explicit {
let col_names = res
.get_column_names()
.iter()
.map(|name| name.to_string())
.collect::<Vec<String>>();
for col in col_names {
let from = match operation {
Operation::Mean => "_mean",
Operation::Sum => "_sum",
Operation::Min => "_min",
Operation::Max => "_max",
Operation::First => "_first",
Operation::Last => "_last",
Operation::Nunique => "_n_unique",
Operation::Quantile(_) => "_quantile",
Operation::Median => "_median",
Operation::Var => "_agg_var",
Operation::Std => "_agg_std",
Operation::Count => "_count",
};
let new_col = match col.find(from) {
Some(index) => &col[..index],
None => &col[..],
};
res.rename(&col, new_col)
.expect("Column is always there. Looping with known names");
}
}
Ok(res)
}
fn perform_dataframe_aggregation(
dataframe: &polars::prelude::DataFrame,
operation: Operation,
operation_span: Span,
) -> Result<polars::prelude::DataFrame, ShellError> {
match operation {
Operation::Mean => Ok(dataframe.mean()),
Operation::Sum => Ok(dataframe.sum()),
Operation::Min => Ok(dataframe.min()),
Operation::Max => Ok(dataframe.max()),
Operation::Quantile(quantile) => dataframe.quantile(quantile).map_err(|e| {
ShellError::SpannedLabeledError(
"Error calculating quantile".into(),
e.to_string(),
operation_span,
)
}),
Operation::Median => Ok(dataframe.median()),
Operation::Var => Ok(dataframe.var()),
Operation::Std => Ok(dataframe.std()),
operation => {
let possibilities = [
"mean".to_string(),
"sum".to_string(),
"min".to_string(),
"max".to_string(),
"quantile".to_string(),
"median".to_string(),
"var".to_string(),
"std".to_string(),
];
match did_you_mean(&possibilities, operation.to_str()) {
Some(suggestion) => Err(ShellError::DidYouMean(suggestion, operation_span)),
None => Err(ShellError::SpannedLabeledErrorHelp(
"Operation not fount".into(),
"Operation does not exist".into(),
operation_span,
"Perhaps you want: mean, sum, min, max, quantile, median, var, or std".into(),
)),
}
}
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::super::CreateGroupBy;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(Aggregate {}), Box::new(CreateGroupBy {})])
}
}

View File

@ -0,0 +1,130 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use super::super::values::{Axis, Column, NuDataFrame};
#[derive(Clone)]
pub struct AppendDF;
impl Command for AppendDF {
fn name(&self) -> &str {
"dfr append"
}
fn usage(&self) -> &str {
"Appends a new dataframe"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("other", SyntaxShape::Any, "dataframe to be appended")
.switch("col", "appends in col orientation", Some('c'))
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Appends a dataframe as new columns",
example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr to-df);
$a | dfr append $a"#,
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(4)],
),
Column::new(
"a_x".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
),
Column::new(
"b_x".to_string(),
vec![Value::test_int(2), Value::test_int(4)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Appends a dataframe merging at the end of columns",
example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr to-df);
$a | dfr append $a --col"#,
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![
Value::test_int(1),
Value::test_int(3),
Value::test_int(1),
Value::test_int(3),
],
),
Column::new(
"b".to_string(),
vec![
Value::test_int(2),
Value::test_int(4),
Value::test_int(2),
Value::test_int(4),
],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let other: Value = call.req(engine_state, stack, 0)?;
let axis = if call.has_flag("col") {
Axis::Column
} else {
Axis::Row
};
let df_other = NuDataFrame::try_from_value(other)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
df.append_df(&df_other, axis, call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(AppendDF {})])
}
}

View File

@ -0,0 +1,81 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct ColumnDF;
impl Command for ColumnDF {
fn name(&self) -> &str {
"dfr column"
}
fn usage(&self) -> &str {
"Returns the selected column"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("column", SyntaxShape::String, "column name")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns the selected column as series",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr column a",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column: Spanned<String> = call.req(engine_state, stack, 0)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let res = df.as_ref().column(&column.item).map_err(|e| {
ShellError::SpannedLabeledError("Error selecting column".into(), e.to_string(), column.span)
})?;
NuDataFrame::try_from_series(vec![res.clone()], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(ColumnDF {})])
}
}

View File

@ -0,0 +1,42 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, ShellError, Signature, Value,
};
#[derive(Clone)]
pub struct Dataframe;
impl Command for Dataframe {
fn name(&self) -> &str {
"dfr"
}
fn usage(&self) -> &str {
"Dataframe commands"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(Value::String {
val: get_full_help(
&Dataframe.signature(),
&Dataframe.examples(),
engine_state,
stack,
),
span: call.head,
}
.into_pipeline_data())
}
}

View File

@ -0,0 +1,241 @@
use super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
use polars::{
chunked_array::ChunkedArray,
prelude::{
AnyValue, DataFrame, DataType, Float64Type, IntoSeries, NewChunkedArray, Series, Utf8Type,
},
};
#[derive(Clone)]
pub struct DescribeDF;
impl Command for DescribeDF {
fn name(&self) -> &str {
"dfr describe"
}
fn usage(&self) -> &str {
"Describes dataframes numeric columns"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "dataframe description",
example: "[[a b]; [1 1] [1 1]] | dfr to-df | dfr describe",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"descriptor".to_string(),
vec![
Value::test_string("count"),
Value::test_string("sum"),
Value::test_string("mean"),
Value::test_string("median"),
Value::test_string("std"),
Value::test_string("min"),
Value::test_string("25%"),
Value::test_string("50%"),
Value::test_string("75%"),
Value::test_string("max"),
],
),
Column::new(
"a (i64)".to_string(),
vec![
Value::test_float(2.0),
Value::test_float(2.0),
Value::test_float(1.0),
Value::test_float(1.0),
Value::test_float(0.0),
Value::test_float(1.0),
Value::test_float(1.0),
Value::test_float(1.0),
Value::test_float(1.0),
Value::test_float(1.0),
],
),
Column::new(
"b (i64)".to_string(),
vec![
Value::test_float(2.0),
Value::test_float(2.0),
Value::test_float(1.0),
Value::test_float(1.0),
Value::test_float(0.0),
Value::test_float(1.0),
Value::test_float(1.0),
Value::test_float(1.0),
Value::test_float(1.0),
Value::test_float(1.0),
],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let names = ChunkedArray::<Utf8Type>::new_from_opt_slice(
"descriptor",
&[
Some("count"),
Some("sum"),
Some("mean"),
Some("median"),
Some("std"),
Some("min"),
Some("25%"),
Some("50%"),
Some("75%"),
Some("max"),
],
)
.into_series();
let head = std::iter::once(names);
let tail = df
.as_ref()
.get_columns()
.iter()
.filter(|col| col.dtype() != &DataType::Object("object"))
.map(|col| {
let count = col.len() as f64;
let sum = col
.sum_as_series()
.cast(&DataType::Float64)
.ok()
.and_then(|ca| match ca.get(0) {
AnyValue::Float64(v) => Some(v),
_ => None,
});
let mean = match col.mean_as_series().get(0) {
AnyValue::Float64(v) => Some(v),
_ => None,
};
let median = match col.median_as_series().get(0) {
AnyValue::Float64(v) => Some(v),
_ => None,
};
let std = match col.std_as_series().get(0) {
AnyValue::Float64(v) => Some(v),
_ => None,
};
let min = col
.min_as_series()
.cast(&DataType::Float64)
.ok()
.and_then(|ca| match ca.get(0) {
AnyValue::Float64(v) => Some(v),
_ => None,
});
let q_25 = col
.quantile_as_series(0.25)
.ok()
.and_then(|ca| ca.cast(&DataType::Float64).ok())
.and_then(|ca| match ca.get(0) {
AnyValue::Float64(v) => Some(v),
_ => None,
});
let q_50 = col
.quantile_as_series(0.50)
.ok()
.and_then(|ca| ca.cast(&DataType::Float64).ok())
.and_then(|ca| match ca.get(0) {
AnyValue::Float64(v) => Some(v),
_ => None,
});
let q_75 = col
.quantile_as_series(0.75)
.ok()
.and_then(|ca| ca.cast(&DataType::Float64).ok())
.and_then(|ca| match ca.get(0) {
AnyValue::Float64(v) => Some(v),
_ => None,
});
let max = col
.max_as_series()
.cast(&DataType::Float64)
.ok()
.and_then(|ca| match ca.get(0) {
AnyValue::Float64(v) => Some(v),
_ => None,
});
let name = format!("{} ({})", col.name(), col.dtype());
ChunkedArray::<Float64Type>::new_from_opt_slice(
&name,
&[
Some(count),
sum,
mean,
median,
std,
min,
q_25,
q_50,
q_75,
max,
],
)
.into_series()
});
let res = head.chain(tail).collect::<Vec<Series>>();
DataFrame::new(res)
.map_err(|e| {
ShellError::SpannedLabeledError("Dataframe Error".into(), e.to_string(), call.head)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(DescribeDF {})])
}
}

View File

@ -0,0 +1,111 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use super::super::values::utils::convert_columns;
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct DropDF;
impl Command for DropDF {
fn name(&self) -> &str {
"dfr drop"
}
fn usage(&self) -> &str {
"Creates a new dataframe by dropping the selected columns"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.rest("rest", SyntaxShape::Any, "column names to be dropped")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "drop column a",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr drop a",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(4)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let columns: Vec<Value> = call.rest(engine_state, stack, 0)?;
let (col_string, col_span) = convert_columns(columns, call.head)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let new_df = col_string
.get(0)
.ok_or_else(|| {
ShellError::SpannedLabeledError(
"Empty names list".into(),
"No column names where found".into(),
col_span,
)
})
.and_then(|col| {
df.as_ref().drop(&col.item).map_err(|e| {
ShellError::SpannedLabeledError(
"Error dropping column".into(),
e.to_string(),
col.span,
)
})
})?;
// If there are more columns in the drop selection list, these
// are added from the resulting dataframe
col_string
.iter()
.skip(1)
.try_fold(new_df, |new_df, col| {
new_df.drop(&col.item).map_err(|e| {
ShellError::SpannedLabeledError(
"Error dropping column".into(),
e.to_string(),
col.span,
)
})
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(DropDF {})])
}
}

View File

@ -0,0 +1,106 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use super::super::values::utils::convert_columns_string;
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct DropDuplicates;
impl Command for DropDuplicates {
fn name(&self) -> &str {
"dfr drop-duplicates"
}
fn usage(&self) -> &str {
"Drops duplicate values in dataframe"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.optional(
"subset",
SyntaxShape::Table,
"subset of columns to drop duplicates",
)
.switch("maintain", "maintain order", Some('m'))
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "drop duplicates",
example: "[[a b]; [1 2] [3 4] [1 2]] | dfr to-df | dfr drop-duplicates",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(4)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let columns: Option<Vec<Value>> = call.opt(engine_state, stack, 0)?;
let (subset, col_span) = match columns {
Some(cols) => {
let (agg_string, col_span) = convert_columns_string(cols, call.head)?;
(Some(agg_string), col_span)
}
None => (None, call.head),
};
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let subset_slice = subset.as_ref().map(|cols| &cols[..]);
df.as_ref()
.drop_duplicates(call.has_flag("maintain"), subset_slice)
.map_err(|e| {
ShellError::SpannedLabeledError(
"Error dropping duplicates".into(),
e.to_string(),
col_span,
)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(DropDuplicates {})])
}
}

View File

@ -0,0 +1,130 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use super::super::values::utils::convert_columns_string;
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct DropNulls;
impl Command for DropNulls {
fn name(&self) -> &str {
"dfr drop-nulls"
}
fn usage(&self) -> &str {
"Drops null values in dataframe"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.optional(
"subset",
SyntaxShape::Table,
"subset of columns to drop nulls",
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "drop null values in dataframe",
example: r#"let df = ([[a b]; [1 2] [3 0] [1 2]] | dfr to-df);
let res = ($df.b / $df.b);
let a = ($df | dfr with-column $res --name res);
$a | dfr drop-nulls"#,
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(1)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(2)],
),
Column::new(
"res".to_string(),
vec![Value::test_int(1), Value::test_int(1)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "drop null values in dataframe",
example: r#"let s = ([1 2 0 0 3 4] | dfr to-df);
($s / $s) | dfr drop-nulls"#,
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"div_0_0".to_string(),
vec![
Value::test_int(1),
Value::test_int(1),
Value::test_int(1),
Value::test_int(1),
],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let columns: Option<Vec<Value>> = call.opt(engine_state, stack, 0)?;
let (subset, col_span) = match columns {
Some(cols) => {
let (agg_string, col_span) = convert_columns_string(cols, call.head)?;
(Some(agg_string), col_span)
}
None => (None, call.head),
};
let subset_slice = subset.as_ref().map(|cols| &cols[..]);
df.as_ref()
.drop_nulls(subset_slice)
.map_err(|e| {
ShellError::SpannedLabeledError("Error dropping nulls".into(), e.to_string(), col_span)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::super::WithColumn;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(DropNulls {}), Box::new(WithColumn {})])
}
}

View File

@ -0,0 +1,106 @@
use super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
#[derive(Clone)]
pub struct DataTypes;
impl Command for DataTypes {
fn name(&self) -> &str {
"dfr dtypes"
}
fn usage(&self) -> &str {
"Show dataframe data types"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Dataframe dtypes",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr dtypes",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"column".to_string(),
vec![Value::test_string("a"), Value::test_string("b")],
),
Column::new(
"dtype".to_string(),
vec![Value::test_string("i64"), Value::test_string("i64")],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
#[allow(clippy::needless_collect)]
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let mut dtypes: Vec<Value> = Vec::new();
let names: Vec<Value> = df
.as_ref()
.get_column_names()
.iter()
.map(|v| {
let dtype = df
.as_ref()
.column(v)
.expect("using name from list of names from dataframe")
.dtype();
let dtype_str = dtype.to_string();
dtypes.push(Value::String {
val: dtype_str,
span: call.head,
});
Value::String {
val: v.to_string(),
span: call.head,
}
})
.collect();
let names_col = Column::new("column".to_string(), names);
let dtypes_col = Column::new("dtype".to_string(), dtypes);
NuDataFrame::try_from_columns(vec![names_col, dtypes_col])
.map(|df| PipelineData::Value(df.into_value(call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(DataTypes {})])
}
}

View File

@ -0,0 +1,137 @@
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct Dummies;
impl Command for Dummies {
fn name(&self) -> &str {
"dfr to-dummies"
}
fn usage(&self) -> &str {
"Creates a new dataframe with dummy variables"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create new dataframe with dummy variables from a dataframe",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-dummies",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a_1".to_string(),
vec![Value::test_int(1), Value::test_int(0)],
),
Column::new(
"a_3".to_string(),
vec![Value::test_int(0), Value::test_int(1)],
),
Column::new(
"b_2".to_string(),
vec![Value::test_int(1), Value::test_int(0)],
),
Column::new(
"b_4".to_string(),
vec![Value::test_int(0), Value::test_int(1)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Create new dataframe with dummy variables from a series",
example: "[1 2 2 3 3] | dfr to-df | dfr to-dummies",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"0_1".to_string(),
vec![
Value::test_int(1),
Value::test_int(0),
Value::test_int(0),
Value::test_int(0),
Value::test_int(0),
],
),
Column::new(
"0_2".to_string(),
vec![
Value::test_int(0),
Value::test_int(1),
Value::test_int(1),
Value::test_int(0),
Value::test_int(0),
],
),
Column::new(
"0_3".to_string(),
vec![
Value::test_int(0),
Value::test_int(0),
Value::test_int(0),
Value::test_int(1),
Value::test_int(1),
],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
df.as_ref()
.to_dummies()
.map_err(|e| {
ShellError::SpannedLabeledErrorHelp(
"Error calculating dummies".into(),
e.to_string(),
call.head,
"The only allowed column types for dummies are String or Int".into(),
)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(Dummies {})])
}
}

View File

@ -0,0 +1,98 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct FilterWith;
impl Command for FilterWith {
fn name(&self) -> &str {
"dfr filter-with"
}
fn usage(&self) -> &str {
"Filters dataframe using a mask as reference"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("mask", SyntaxShape::Any, "boolean mask used to filter data")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Filter dataframe using a bool mask",
example: r#"let mask = ([$true $false] | dfr to-df);
[[a b]; [1 2] [3 4]] | dfr to-df | dfr filter-with $mask"#,
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new("a".to_string(), vec![Value::test_int(1)]),
Column::new("b".to_string(), vec![Value::test_int(2)]),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mask_value: Value = call.req(engine_state, stack, 0)?;
let mask_span = mask_value.span()?;
let mask = NuDataFrame::try_from_value(mask_value)?.as_series(mask_span)?;
let mask = mask.bool().map_err(|e| {
ShellError::SpannedLabeledErrorHelp(
"Error casting to bool".into(),
e.to_string(),
mask_span,
"Perhaps you want to use a series with booleans as mask".into(),
)
})?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
df.as_ref()
.filter(mask)
.map_err(|e| {
ShellError::SpannedLabeledErrorHelp(
"Error calculating dummies".into(),
e.to_string(),
call.head,
"The only allowed column types for dummies are String or Int".into(),
)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(FilterWith {})])
}
}

View File

@ -0,0 +1,80 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use super::super::values::{utils::DEFAULT_ROWS, Column, NuDataFrame};
#[derive(Clone)]
pub struct FirstDF;
impl Command for FirstDF {
fn name(&self) -> &str {
"dfr first"
}
fn usage(&self) -> &str {
"Creates new dataframe with first rows"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.optional("rows", SyntaxShape::Int, "Number of rows for head")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create new dataframe with head rows",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr first 1",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new("a".to_string(), vec![Value::test_int(1)]),
Column::new("b".to_string(), vec![Value::test_int(2)]),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let rows: Option<usize> = call.opt(engine_state, stack, 0)?;
let rows = rows.unwrap_or(DEFAULT_ROWS);
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let res = df.as_ref().head(Some(rows));
Ok(PipelineData::Value(
NuDataFrame::dataframe_into_value(res, call.head),
None,
))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(FirstDF {})])
}
}

View File

@ -0,0 +1,88 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use crate::dataframe::values::utils::convert_columns_string;
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct GetDF;
impl Command for GetDF {
fn name(&self) -> &str {
"dfr get"
}
fn usage(&self) -> &str {
"Creates dataframe with the selected columns"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.rest("rest", SyntaxShape::Any, "column names to sort dataframe")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Creates dataframe with selected columns",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr get a",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let columns: Vec<Value> = call.rest(engine_state, stack, 0)?;
let (col_string, col_span) = convert_columns_string(columns, call.head)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
df.as_ref()
.select(&col_string)
.map_err(|e| {
ShellError::SpannedLabeledError(
"Error selecting columns".into(),
e.to_string(),
col_span,
)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetDF {})])
}
}

View File

@ -0,0 +1,71 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value,
};
use super::super::values::{utils::convert_columns_string, NuDataFrame, NuGroupBy};
#[derive(Clone)]
pub struct CreateGroupBy;
impl Command for CreateGroupBy {
fn name(&self) -> &str {
"dfr group-by"
}
fn usage(&self) -> &str {
"Creates a groupby object that can be used for other aggregations"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.rest("rest", SyntaxShape::Any, "groupby columns")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Grouping by column a",
example: "[[a b]; [one 1] [one 2]] | dfr to-df | dfr group-by a",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
// Extracting the names of the columns to perform the groupby
let columns: Vec<Value> = call.rest(engine_state, stack, 0)?;
let (col_string, col_span) = convert_columns_string(columns, call.head)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
// This is the expensive part of the groupby; to create the
// groups that will be used for grouping the data in the
// dataframe. Once it has been done these values can be stored
// in a NuGroupBy
let groupby = df.as_ref().groupby(&col_string).map_err(|e| {
ShellError::SpannedLabeledError("Error creating groupby".into(), e.to_string(), col_span)
})?;
let groups = groupby.get_groups().to_vec();
let groupby = NuGroupBy::new(df.as_ref().clone(), col_string, groups);
Ok(PipelineData::Value(groupby.into_value(call.head), None))
}

View File

@ -0,0 +1,226 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use polars::prelude::JoinType;
use crate::dataframe::values::utils::convert_columns_string;
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct JoinDF;
impl Command for JoinDF {
fn name(&self) -> &str {
"dfr join"
}
fn usage(&self) -> &str {
"Joins a dataframe using columns as reference"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("dataframe", SyntaxShape::Any, "right dataframe to join")
.required_named(
"left",
SyntaxShape::Table,
"left column names to perform join",
Some('l'),
)
.required_named(
"right",
SyntaxShape::Table,
"right column names to perform join",
Some('r'),
)
.named(
"type",
SyntaxShape::String,
"type of join. Inner by default",
Some('t'),
)
.named(
"suffix",
SyntaxShape::String,
"suffix for the columns of the right dataframe",
Some('s'),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "inner join dataframe",
example: r#"let right = ([[a b c]; [1 2 5] [3 4 5] [5 6 6]] | dfr to-df);
$right | dfr join $right -l [a b] -r [a b]"#,
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(4), Value::test_int(6)],
),
Column::new(
"c".to_string(),
vec![Value::test_int(5), Value::test_int(5), Value::test_int(6)],
),
Column::new(
"c_right".to_string(),
vec![Value::test_int(5), Value::test_int(5), Value::test_int(6)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let r_df: Value = call.req(engine_state, stack, 0)?;
let l_col: Vec<Value> = call
.get_flag(engine_state, stack, "left")?
.expect("required value in syntax");
let r_col: Vec<Value> = call
.get_flag(engine_state, stack, "right")?
.expect("required value in syntax");
let suffix: Option<String> = call.get_flag(engine_state, stack, "suffix")?;
let join_type_op: Option<Spanned<String>> = call.get_flag(engine_state, stack, "type")?;
let join_type = match join_type_op {
None => JoinType::Inner,
Some(val) => match val.item.as_ref() {
"inner" => JoinType::Inner,
"outer" => JoinType::Outer,
"left" => JoinType::Left,
_ => {
return Err(ShellError::SpannedLabeledErrorHelp(
"Incorrect join type".into(),
"Invalid join type".into(),
val.span,
"Options: inner, outer or left".into(),
))
}
},
};
let (l_col_string, l_col_span) = convert_columns_string(l_col, call.head)?;
let (r_col_string, r_col_span) = convert_columns_string(r_col, call.head)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let r_df = NuDataFrame::try_from_value(r_df)?;
check_column_datatypes(
df.as_ref(),
r_df.as_ref(),
&l_col_string,
l_col_span,
&r_col_string,
r_col_span,
)?;
df.as_ref()
.join(
r_df.as_ref(),
&l_col_string,
&r_col_string,
join_type,
suffix,
)
.map_err(|e| {
ShellError::SpannedLabeledError(
"Error joining dataframes".into(),
e.to_string(),
l_col_span,
)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}
fn check_column_datatypes<T: AsRef<str>>(
df_l: &polars::prelude::DataFrame,
df_r: &polars::prelude::DataFrame,
l_cols: &[T],
l_col_span: Span,
r_cols: &[T],
r_col_span: Span,
) -> Result<(), ShellError> {
if l_cols.len() != r_cols.len() {
return Err(ShellError::SpannedLabeledErrorHelp(
"Mismatched number of column names".into(),
format!(
"found {} left names vs {} right names",
l_cols.len(),
r_cols.len()
),
l_col_span,
"perhaps you need to change the number of columns to join".into(),
));
}
for (l, r) in l_cols.iter().zip(r_cols) {
let l_series = df_l.column(l.as_ref()).map_err(|e| {
ShellError::SpannedLabeledError(
"Error selecting the columns".into(),
e.to_string(),
l_col_span,
)
})?;
let r_series = df_r.column(r.as_ref()).map_err(|e| {
ShellError::SpannedLabeledError(
"Error selecting the columns".into(),
e.to_string(),
r_col_span,
)
})?;
if l_series.dtype() != r_series.dtype() {
return Err(ShellError::SpannedLabeledErrorHelp(
"Mismatched datatypes".into(),
format!(
"left column type '{}' doesn't match '{}' right column match",
l_series.dtype(),
r_series.dtype()
),
l_col_span,
"perhaps you need to select other column to match".into(),
));
}
}
Ok(())
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(JoinDF {})])
}
}

View File

@ -0,0 +1,80 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use super::super::values::{utils::DEFAULT_ROWS, Column, NuDataFrame};
#[derive(Clone)]
pub struct LastDF;
impl Command for LastDF {
fn name(&self) -> &str {
"dfr last"
}
fn usage(&self) -> &str {
"Creates new dataframe with tail rows"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.optional("rows", SyntaxShape::Int, "Number of rows for tail")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create new dataframe with last rows",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr last 1",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new("a".to_string(), vec![Value::test_int(3)]),
Column::new("b".to_string(), vec![Value::test_int(4)]),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let rows: Option<usize> = call.opt(engine_state, stack, 0)?;
let rows = rows.unwrap_or(DEFAULT_ROWS);
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let res = df.as_ref().tail(Some(rows));
Ok(PipelineData::Value(
NuDataFrame::dataframe_into_value(res, call.head),
None,
))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(LastDF {})])
}
}

View File

@ -0,0 +1,243 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use crate::dataframe::values::utils::convert_columns_string;
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct MeltDF;
impl Command for MeltDF {
fn name(&self) -> &str {
"dfr melt"
}
fn usage(&self) -> &str {
"Unpivot a DataFrame from wide to long format"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required_named(
"columns",
SyntaxShape::Table,
"column names for melting",
Some('c'),
)
.required_named(
"values",
SyntaxShape::Table,
"column names used as value columns",
Some('v'),
)
.named(
"variable-name",
SyntaxShape::String,
"optional name for variable column",
Some('r'),
)
.named(
"value-name",
SyntaxShape::String,
"optional name for value column",
Some('l'),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "melt dataframe",
example:
"[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | dfr to-df | dfr melt -c [b c] -v [a d]",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"b".to_string(),
vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
],
),
Column::new(
"c".to_string(),
vec![
Value::test_int(4),
Value::test_int(5),
Value::test_int(6),
Value::test_int(4),
Value::test_int(5),
Value::test_int(6),
],
),
Column::new(
"variable".to_string(),
vec![
Value::test_string("a"),
Value::test_string("a"),
Value::test_string("a"),
Value::test_string("d"),
Value::test_string("d"),
Value::test_string("d"),
],
),
Column::new(
"value".to_string(),
vec![
Value::test_string("x"),
Value::test_string("y"),
Value::test_string("z"),
Value::test_string("a"),
Value::test_string("b"),
Value::test_string("c"),
],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let id_col: Vec<Value> = call
.get_flag(engine_state, stack, "columns")?
.expect("required value");
let val_col: Vec<Value> = call
.get_flag(engine_state, stack, "values")?
.expect("required value");
let value_name: Option<Spanned<String>> = call.get_flag(engine_state, stack, "value-name")?;
let variable_name: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "variable-name")?;
let (id_col_string, id_col_span) = convert_columns_string(id_col, call.head)?;
let (val_col_string, val_col_span) = convert_columns_string(val_col, call.head)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
check_column_datatypes(df.as_ref(), &id_col_string, id_col_span)?;
check_column_datatypes(df.as_ref(), &val_col_string, val_col_span)?;
let mut res = df
.as_ref()
.melt(&id_col_string, &val_col_string)
.map_err(|e| {
ShellError::SpannedLabeledError(
"Error calculating melt".into(),
e.to_string(),
call.head,
)
})?;
if let Some(name) = &variable_name {
res.rename("variable", &name.item).map_err(|e| {
ShellError::SpannedLabeledError(
"Error renaming column".into(),
e.to_string(),
name.span,
)
})?;
}
if let Some(name) = &value_name {
res.rename("value", &name.item).map_err(|e| {
ShellError::SpannedLabeledError(
"Error renaming column".into(),
e.to_string(),
name.span,
)
})?;
}
Ok(PipelineData::Value(
NuDataFrame::dataframe_into_value(res, call.head),
None,
))
}
fn check_column_datatypes<T: AsRef<str>>(
df: &polars::prelude::DataFrame,
cols: &[T],
col_span: Span,
) -> Result<(), ShellError> {
if cols.is_empty() {
return Err(ShellError::SpannedLabeledError(
"Merge error".into(),
"empty column list".into(),
col_span,
));
}
// Checking if they are same type
if cols.len() > 1 {
for w in cols.windows(2) {
let l_series = df.column(w[0].as_ref()).map_err(|e| {
ShellError::SpannedLabeledError(
"Error selecting columns".into(),
e.to_string(),
col_span,
)
})?;
let r_series = df.column(w[1].as_ref()).map_err(|e| {
ShellError::SpannedLabeledError(
"Error selecting columns".into(),
e.to_string(),
col_span,
)
})?;
if l_series.dtype() != r_series.dtype() {
return Err(ShellError::SpannedLabeledErrorHelp(
"Merge error".into(),
"found different column types in list".into(),
col_span,
format!(
"datatypes {} and {} are incompatible",
l_series.dtype(),
r_series.dtype()
),
));
}
}
}
Ok(())
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(MeltDF {})])
}
}

View File

@ -0,0 +1,107 @@
mod aggregate;
mod append;
mod column;
mod command;
mod describe;
mod drop;
mod drop_duplicates;
mod drop_nulls;
mod dtypes;
mod dummies;
mod filter_with;
mod first;
mod get;
mod groupby;
mod join;
mod last;
mod melt;
mod open;
mod pivot;
mod rename;
mod sample;
mod shape;
mod slice;
mod sort;
mod take;
mod to_csv;
mod to_df;
mod to_nu;
mod to_parquet;
mod with_column;
use nu_protocol::engine::StateWorkingSet;
pub use aggregate::Aggregate;
pub use append::AppendDF;
pub use column::ColumnDF;
pub use command::Dataframe;
pub use describe::DescribeDF;
pub use drop::DropDF;
pub use drop_duplicates::DropDuplicates;
pub use drop_nulls::DropNulls;
pub use dtypes::DataTypes;
pub use dummies::Dummies;
pub use filter_with::FilterWith;
pub use first::FirstDF;
pub use get::GetDF;
pub use groupby::CreateGroupBy;
pub use join::JoinDF;
pub use last::LastDF;
pub use melt::MeltDF;
pub use open::OpenDataFrame;
pub use pivot::PivotDF;
pub use rename::RenameDF;
pub use sample::SampleDF;
pub use shape::ShapeDF;
pub use slice::SliceDF;
pub use sort::SortDF;
pub use take::TakeDF;
pub use to_csv::ToCSV;
pub use to_df::ToDataFrame;
pub use to_nu::ToNu;
pub use to_parquet::ToParquet;
pub use with_column::WithColumn;
pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
macro_rules! bind_command {
( $command:expr ) => {
working_set.add_decl(Box::new($command));
};
( $( $command:expr ),* ) => {
$( working_set.add_decl(Box::new($command)); )*
};
}
// Dataframe commands
bind_command!(
Aggregate,
AppendDF,
ColumnDF,
CreateGroupBy,
Dataframe,
DataTypes,
DescribeDF,
DropDF,
DropNulls,
Dummies,
FilterWith,
FirstDF,
GetDF,
JoinDF,
LastDF,
MeltDF,
OpenDataFrame,
PivotDF,
RenameDF,
SampleDF,
ShapeDF,
SliceDF,
SortDF,
TakeDF,
ToCSV,
ToDataFrame,
ToNu,
ToParquet,
WithColumn
);
}

View File

@ -0,0 +1,218 @@
use super::super::values::NuDataFrame;
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
};
use std::{fs::File, path::PathBuf};
use polars::prelude::{CsvEncoding, CsvReader, JsonReader, ParquetReader, SerReader};
#[derive(Clone)]
pub struct OpenDataFrame;
impl Command for OpenDataFrame {
fn name(&self) -> &str {
"dfr open"
}
fn usage(&self) -> &str {
"Opens csv, json or parquet file to create dataframe"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"file",
SyntaxShape::Filepath,
"file path to load values from",
)
.named(
"delimiter",
SyntaxShape::String,
"file delimiter character. CSV file",
Some('d'),
)
.switch(
"no-header",
"Indicates if file doesn't have header. CSV file",
None,
)
.named(
"infer-schema",
SyntaxShape::Number,
"Number of rows to infer the schema of the file. CSV file",
None,
)
.named(
"skip-rows",
SyntaxShape::Number,
"Number of rows to skip from file. CSV file",
None,
)
.named(
"columns",
SyntaxShape::List(Box::new(SyntaxShape::String)),
"Columns to be selected from csv file. CSV and Parquet file",
None,
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Takes a file name and creates a dataframe",
example: "dfr open test.csv",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
match file.item.extension() {
Some(e) => match e.to_str() {
Some("csv") => from_csv(engine_state, stack, call),
Some("parquet") => from_parquet(engine_state, stack, call),
Some("json") => from_json(engine_state, stack, call),
_ => Err(ShellError::FileNotFoundCustom(
"Not a csv, parquet or json file".into(),
file.span,
)),
},
None => Err(ShellError::FileNotFoundCustom(
"File without extension".into(),
file.span,
)),
}
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, span), None))
}
fn from_parquet(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<polars::prelude::DataFrame, ShellError> {
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
let r = File::open(&file.item).map_err(|e| {
ShellError::SpannedLabeledError("Error opening file".into(), e.to_string(), file.span)
})?;
let reader = ParquetReader::new(r);
let reader = match columns {
None => reader,
Some(columns) => reader.with_columns(Some(columns)),
};
reader.finish().map_err(|e| {
ShellError::SpannedLabeledError(
"Parquet reader error".into(),
format!("{:?}", e),
call.head,
)
})
}
fn from_json(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<polars::prelude::DataFrame, ShellError> {
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let r = File::open(&file.item).map_err(|e| {
ShellError::SpannedLabeledError("Error opening file".into(), e.to_string(), file.span)
})?;
let reader = JsonReader::new(r);
reader.finish().map_err(|e| {
ShellError::SpannedLabeledError("Json reader error".into(), format!("{:?}", e), call.head)
})
}
fn from_csv(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<polars::prelude::DataFrame, ShellError> {
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?;
let no_header: bool = call.has_flag("no_header");
let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer_schema")?;
let skip_rows: Option<usize> = call.get_flag(engine_state, stack, "skip_rows")?;
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
let csv_reader = CsvReader::from_path(&file.item)
.map_err(|e| {
ShellError::SpannedLabeledError(
"Error creating CSV reader".into(),
e.to_string(),
file.span,
)
})?
.with_encoding(CsvEncoding::LossyUtf8);
let csv_reader = match delimiter {
None => csv_reader,
Some(d) => {
if d.item.len() != 1 {
return Err(ShellError::SpannedLabeledError(
"Incorrect delimiter".into(),
"Delimiter has to be one character".into(),
d.span,
));
} else {
let delimiter = match d.item.chars().next() {
Some(d) => d as u8,
None => unreachable!(),
};
csv_reader.with_delimiter(delimiter)
}
}
};
let csv_reader = csv_reader.has_header(!no_header);
let csv_reader = match infer_schema {
None => csv_reader,
Some(r) => csv_reader.infer_schema(Some(r)),
};
let csv_reader = match skip_rows {
None => csv_reader,
Some(r) => csv_reader.with_skip_rows(r),
};
let csv_reader = match columns {
None => csv_reader,
Some(columns) => csv_reader.with_columns(Some(columns)),
};
csv_reader.finish().map_err(|e| {
ShellError::SpannedLabeledError(
"Parquet reader error".into(),
format!("{:?}", e),
call.head,
)
})
}

View File

@ -0,0 +1,175 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
};
use polars::prelude::DataType;
use crate::dataframe::values::NuGroupBy;
use super::super::values::NuDataFrame;
enum Operation {
First,
Sum,
Min,
Max,
Mean,
Median,
}
impl Operation {
fn from_tagged(name: Spanned<String>) -> Result<Operation, ShellError> {
match name.item.as_ref() {
"first" => Ok(Operation::First),
"sum" => Ok(Operation::Sum),
"min" => Ok(Operation::Min),
"max" => Ok(Operation::Max),
"mean" => Ok(Operation::Mean),
"median" => Ok(Operation::Median),
_ => Err(ShellError::SpannedLabeledErrorHelp(
"Operation not fount".into(),
"Operation does not exist for pivot".into(),
name.span,
"Options: first, sum, min, max, mean, median".into(),
)),
}
}
}
#[derive(Clone)]
pub struct PivotDF;
impl Command for PivotDF {
fn name(&self) -> &str {
"dfr pivot"
}
fn usage(&self) -> &str {
"Performs a pivot operation on a groupby object"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"pivot-column",
SyntaxShape::String,
"pivot column to perform pivot",
)
.required(
"value-column",
SyntaxShape::String,
"value column to perform pivot",
)
.required("operation", SyntaxShape::String, "aggregate operation")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Pivot a dataframe on b and aggregation on col c",
example:
"[[a b c]; [one x 1] [two y 2]] | dfr to-df | dfr group-by a | dfr pivot b c sum",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pivot_col: Spanned<String> = call.req(engine_state, stack, 0)?;
let value_col: Spanned<String> = call.req(engine_state, stack, 1)?;
let operation: Spanned<String> = call.req(engine_state, stack, 2)?;
let op = Operation::from_tagged(operation)?;
let nu_groupby = NuGroupBy::try_from_pipeline(input, call.head)?;
let df_ref = nu_groupby.as_ref();
check_pivot_column(df_ref, &pivot_col)?;
check_value_column(df_ref, &value_col)?;
let mut groupby = nu_groupby.to_groupby()?;
let pivot = groupby.pivot(&pivot_col.item, &value_col.item);
match op {
Operation::Mean => pivot.mean(),
Operation::Sum => pivot.sum(),
Operation::Min => pivot.min(),
Operation::Max => pivot.max(),
Operation::First => pivot.first(),
Operation::Median => pivot.median(),
}
.map_err(|e| {
ShellError::SpannedLabeledError("Error creating pivot".into(), e.to_string(), call.head)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}
fn check_pivot_column(
df: &polars::prelude::DataFrame,
col: &Spanned<String>,
) -> Result<(), ShellError> {
let series = df.column(&col.item).map_err(|e| {
ShellError::SpannedLabeledError("Column not found".into(), e.to_string(), col.span)
})?;
match series.dtype() {
DataType::UInt8
| DataType::UInt16
| DataType::UInt32
| DataType::UInt64
| DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::Utf8 => Ok(()),
_ => Err(ShellError::SpannedLabeledError(
"Pivot error".into(),
format!("Unsupported datatype {}", series.dtype()),
col.span,
)),
}
}
fn check_value_column(
df: &polars::prelude::DataFrame,
col: &Spanned<String>,
) -> Result<(), ShellError> {
let series = df.column(&col.item).map_err(|e| {
ShellError::SpannedLabeledError("Column not found".into(), e.to_string(), col.span)
})?;
match series.dtype() {
DataType::UInt8
| DataType::UInt16
| DataType::UInt32
| DataType::UInt64
| DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::Float32
| DataType::Float64 => Ok(()),
_ => Err(ShellError::SpannedLabeledError(
"Pivot error".into(),
format!("Unsupported datatype {}", series.dtype()),
col.span,
)),
}
}

View File

@ -0,0 +1,94 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct RenameDF;
impl Command for RenameDF {
fn name(&self) -> &str {
"dfr rename-col"
}
fn usage(&self) -> &str {
"rename a dataframe column"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("from", SyntaxShape::String, "column name to be renamed")
.required("to", SyntaxShape::String, "new column name")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Renames a dataframe column",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr rename-col a a_new",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a_new".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(4)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let from: String = call.req(engine_state, stack, 0)?;
let to: String = call.req(engine_state, stack, 1)?;
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
df.as_mut()
.rename(&from, &to)
.map_err(|e| {
ShellError::SpannedLabeledError("Error renaming".into(), e.to_string(), call.head)
})
.map(|df| {
PipelineData::Value(
NuDataFrame::dataframe_into_value(df.clone(), call.head),
None,
)
})
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(RenameDF {})])
}
}

View File

@ -0,0 +1,106 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
};
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct SampleDF;
impl Command for SampleDF {
fn name(&self) -> &str {
"dfr sample"
}
fn usage(&self) -> &str {
"Create sample dataframe"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named(
"n-rows",
SyntaxShape::Int,
"number of rows to be taken from dataframe",
Some('n'),
)
.named(
"fraction",
SyntaxShape::Number,
"fraction of dataframe to be taken",
Some('f'),
)
.switch("replace", "sample with replace", Some('e'))
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Sample rows from dataframe",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr sample -n 1",
result: None, // No expected value because sampling is random
},
Example {
description: "Shows sample row using fraction and replace",
example: "[[a b]; [1 2] [3 4] [5 6]] | dfr to-df | dfr sample -f 0.5 -e",
result: None, // No expected value because sampling is random
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let rows: Option<Spanned<usize>> = call.get_flag(engine_state, stack, "n-rows")?;
let fraction: Option<Spanned<f64>> = call.get_flag(engine_state, stack, "fraction")?;
let replace: bool = call.has_flag("replace");
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
match (rows, fraction) {
(Some(rows), None) => df.as_ref().sample_n(rows.item, replace).map_err(|e| {
ShellError::SpannedLabeledError(
"Error creating sample".into(),
e.to_string(),
rows.span,
)
}),
(None, Some(frac)) => df.as_ref().sample_frac(frac.item, replace).map_err(|e| {
ShellError::SpannedLabeledError(
"Error creating sample".into(),
e.to_string(),
frac.span,
)
}),
(Some(_), Some(_)) => Err(ShellError::SpannedLabeledError(
"Incompatible flags".into(),
"Only one selection criterion allowed".into(),
call.head,
)),
(None, None) => Err(ShellError::SpannedLabeledErrorHelp(
"No selection".into(),
"No selection criterion was found".into(),
call.head,
"Perhaps you want to use the flag -n or -f".into(),
)),
}
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
}

View File

@ -0,0 +1,87 @@
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
use crate::dataframe::values::Column;
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct ShapeDF;
impl Command for ShapeDF {
fn name(&self) -> &str {
"dfr shape"
}
fn usage(&self) -> &str {
"Shows column and row size for a dataframe"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Shows row and column shape",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr shape",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new("rows".to_string(), vec![Value::test_int(2)]),
Column::new("columns".to_string(), vec![Value::test_int(2)]),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let rows = Value::Int {
val: df.as_ref().height() as i64,
span: call.head,
};
let cols = Value::Int {
val: df.as_ref().width() as i64,
span: call.head,
};
let rows_col = Column::new("rows".to_string(), vec![rows]);
let cols_col = Column::new("columns".to_string(), vec![cols]);
NuDataFrame::try_from_columns(vec![rows_col, cols_col])
.map(|df| PipelineData::Value(df.into_value(call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(ShapeDF {})])
}
}

View File

@ -0,0 +1,85 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use crate::dataframe::values::Column;
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct SliceDF;
impl Command for SliceDF {
fn name(&self) -> &str {
"dfr slice"
}
fn usage(&self) -> &str {
"Creates new dataframe from a slice of rows"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("offset", SyntaxShape::Int, "start of slice")
.required("size", SyntaxShape::Int, "size of slice")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create new dataframe from a slice of the rows",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr slice 0 1",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new("a".to_string(), vec![Value::test_int(1)]),
Column::new("b".to_string(), vec![Value::test_int(2)]),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let offset: i64 = call.req(engine_state, stack, 0)?;
let size: usize = call.req(engine_state, stack, 1)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let res = df.as_ref().slice(offset, size);
Ok(PipelineData::Value(
NuDataFrame::dataframe_into_value(res, call.head),
None,
))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(SliceDF {})])
}
}

View File

@ -0,0 +1,142 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use crate::dataframe::values::{utils::convert_columns_string, Column};
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct SortDF;
impl Command for SortDF {
fn name(&self) -> &str {
"dfr sort"
}
fn usage(&self) -> &str {
"Creates new sorted dataframe or series"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.switch("reverse", "invert sort", Some('r'))
.rest("rest", SyntaxShape::Any, "column names to sort dataframe")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create new sorted dataframe",
example: "[[a b]; [3 4] [1 2]] | dfr to-df | dfr sort a",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(4)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Create new sorted series",
example: "[3 4 1 2] | dfr to-df | dfr sort",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0".to_string(),
vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
Value::test_int(4),
],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let reverse = call.has_flag("reverse");
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
if df.is_series() {
let columns = df.as_ref().get_column_names();
df.as_ref()
.sort(columns, reverse)
.map_err(|e| {
ShellError::SpannedLabeledError(
"Error sorting dataframe".into(),
e.to_string(),
call.head,
)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
} else {
let columns: Vec<Value> = call.rest(engine_state, stack, 0)?;
if !columns.is_empty() {
let (col_string, col_span) = convert_columns_string(columns, call.head)?;
df.as_ref()
.sort(&col_string, reverse)
.map_err(|e| {
ShellError::SpannedLabeledError(
"Error sorting dataframe".into(),
e.to_string(),
col_span,
)
})
.map(|df| {
PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)
})
} else {
Err(ShellError::SpannedLabeledError(
"Missing columns".into(),
"missing column name to perform sort".into(),
call.head,
))
}
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(SortDF {})])
}
}

View File

@ -0,0 +1,144 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use polars::prelude::DataType;
use crate::dataframe::values::Column;
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct TakeDF;
impl Command for TakeDF {
fn name(&self) -> &str {
"dfr take"
}
fn usage(&self) -> &str {
"Creates new dataframe using the given indices"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"indices",
SyntaxShape::Any,
"list of indices used to take data",
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Takes selected rows from dataframe",
example: r#"let df = ([[a b]; [4 1] [5 2] [4 3]] | dfr to-df);
let indices = ([0 2] | dfr to-df);
$df | dfr take $indices"#,
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_int(4), Value::test_int(4)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Takes selected rows from series",
example: r#"let series = ([4 1 5 2 4 3] | dfr to-df);
let indices = ([0 2] | dfr to-df);
$series | dfr take $indices"#,
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0".to_string(),
vec![Value::test_int(4), Value::test_int(5)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let index_value: Value = call.req(engine_state, stack, 0)?;
let index_span = index_value.span()?;
let index = NuDataFrame::try_from_value(index_value)?.as_series(index_span)?;
let casted = match index.dtype() {
DataType::UInt32 | DataType::UInt64 | DataType::Int32 | DataType::Int64 => {
index.cast(&DataType::UInt32).map_err(|e| {
ShellError::SpannedLabeledError(
"Error casting index list".into(),
e.to_string(),
index_span,
)
})
}
_ => Err(ShellError::SpannedLabeledErrorHelp(
"Incorrect type".into(),
"Series with incorrect type".into(),
call.head,
"Consider using a Series with type int type".into(),
)),
}?;
let indices = casted.u32().map_err(|e| {
ShellError::SpannedLabeledError(
"Error casting index list".into(),
e.to_string(),
index_span,
)
})?;
NuDataFrame::try_from_pipeline(input, call.head).and_then(|df| {
df.as_ref()
.take(indices)
.map_err(|e| {
ShellError::SpannedLabeledError(
"Error taking values".into(),
e.to_string(),
call.head,
)
})
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
})
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(TakeDF {})])
}
}

View File

@ -0,0 +1,132 @@
use std::{fs::File, path::PathBuf};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
};
use polars::prelude::{CsvWriter, SerWriter};
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct ToCSV;
impl Command for ToCSV {
fn name(&self) -> &str {
"dfr to-csv"
}
fn usage(&self) -> &str {
"Saves dataframe to csv file"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
.named(
"delimiter",
SyntaxShape::String,
"file delimiter character",
Some('d'),
)
.switch("no-header", "Indicates if file doesn't have header", None)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Saves dataframe to csv file",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-csv test.csv",
result: None,
},
Example {
description: "Saves dataframe to csv file using other delimiter",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-csv test.csv -d '|'",
result: None,
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?;
let no_header: bool = call.has_flag("no_header");
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let mut file = File::create(&file_name.item).map_err(|e| {
ShellError::SpannedLabeledError(
"Error with file name".into(),
e.to_string(),
file_name.span,
)
})?;
let writer = CsvWriter::new(&mut file);
let writer = if no_header {
writer.has_header(false)
} else {
writer.has_header(true)
};
let writer = match delimiter {
None => writer,
Some(d) => {
if d.item.len() != 1 {
return Err(ShellError::SpannedLabeledError(
"Incorrect delimiter".into(),
"Delimiter has to be one char".into(),
d.span,
));
} else {
let delimiter = match d.item.chars().next() {
Some(d) => d as u8,
None => unreachable!(),
};
writer.with_delimiter(delimiter)
}
}
};
writer.finish(df.as_ref()).map_err(|e| {
ShellError::SpannedLabeledError(
"Error writing to file".into(),
e.to_string(),
file_name.span,
)
})?;
let file_value = Value::String {
val: format!("saved {:?}", &file_name.item),
span: file_name.span,
};
Ok(PipelineData::Value(
Value::List {
vals: vec![file_value],
span: call.head,
},
None,
))
}

View File

@ -0,0 +1,127 @@
use super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
#[derive(Clone)]
pub struct ToDataFrame;
impl Command for ToDataFrame {
fn name(&self) -> &str {
"dfr to-df"
}
fn usage(&self) -> &str {
"Converts a List, Table or Dictionary into a dataframe"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Takes a dictionary and creates a dataframe",
example: "[[a b];[1 2] [3 4]] | dfr to-df",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(4)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Takes a list of tables and creates a dataframe",
example: "[[1 2 a] [3 4 b] [5 6 c]] | dfr to-df",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"0".to_string(),
vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)],
),
Column::new(
"1".to_string(),
vec![Value::test_int(2), Value::test_int(4), Value::test_int(6)],
),
Column::new(
"2".to_string(),
vec![
Value::test_string("a"),
Value::test_string("b"),
Value::test_string("c"),
],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Takes a list and creates a dataframe",
example: "[a b c] | dfr to-df",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0".to_string(),
vec![
Value::test_string("a"),
Value::test_string("b"),
Value::test_string("c"),
],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Takes a list of booleans and creates a dataframe",
example: "[$true $true $false] | dfr to-df",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0".to_string(),
vec![
Value::test_bool(true),
Value::test_bool(true),
Value::test_bool(false),
],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
NuDataFrame::try_from_iter(input.into_iter())
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(ToDataFrame {})])
}
}

View File

@ -0,0 +1,83 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value,
};
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct ToNu;
impl Command for ToNu {
fn name(&self) -> &str {
"dfr to-nu"
}
fn usage(&self) -> &str {
"Converts a section of the dataframe to Nushell Table"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named(
"n-rows",
SyntaxShape::Number,
"number of rows to be shown",
Some('n'),
)
.switch("tail", "shows tail rows", Some('t'))
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Shows head rows from dataframe",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-nu",
result: None,
},
Example {
description: "Shows tail rows from dataframe",
example: "[[a b]; [1 2] [3 4] [5 6]] | dfr to-df | dfr to-nu -t -n 1",
result: None,
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let rows: Option<usize> = call.get_flag(engine_state, stack, "n-rows")?;
let tail: bool = call.has_flag("tail");
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let values = if tail {
df.tail(rows, call.head)?
} else {
df.head(rows, call.head)?
};
let value = Value::List {
vals: values,
span: call.head,
};
Ok(PipelineData::Value(value, None))
}

View File

@ -0,0 +1,84 @@
use std::{fs::File, path::PathBuf};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
};
use polars::prelude::ParquetWriter;
use super::super::values::NuDataFrame;
#[derive(Clone)]
pub struct ToParquet;
impl Command for ToParquet {
fn name(&self) -> &str {
"dfr to-parquet"
}
fn usage(&self) -> &str {
"Saves dataframe to parquet file"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Saves dataframe to csv file",
example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-parquet test.parquet",
result: None,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let file = File::create(&file_name.item).map_err(|e| {
ShellError::SpannedLabeledError(
"Error with file name".into(),
e.to_string(),
file_name.span,
)
})?;
ParquetWriter::new(file).finish(df.as_ref()).map_err(|e| {
ShellError::SpannedLabeledError("Error saving file".into(), e.to_string(), file_name.span)
})?;
let file_value = Value::String {
val: format!("saved {:?}", &file_name.item),
span: file_name.span,
};
Ok(PipelineData::Value(
Value::List {
vals: vec![file_value],
span: call.head,
},
None,
))
}

View File

@ -0,0 +1,109 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use super::super::values::{Column, NuDataFrame};
#[derive(Clone)]
pub struct WithColumn;
impl Command for WithColumn {
fn name(&self) -> &str {
"dfr with-column"
}
fn usage(&self) -> &str {
"Adds a series to the dataframe"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("series", SyntaxShape::Any, "series to be added")
.required_named("name", SyntaxShape::String, "column name", Some('n'))
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Adds a series to the dataframe",
example:
"[[a b]; [1 2] [3 4]] | dfr to-df | dfr with-column ([5 6] | dfr to-df) --name c",
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_int(1), Value::test_int(3)],
),
Column::new(
"b".to_string(),
vec![Value::test_int(2), Value::test_int(4)],
),
Column::new(
"c".to_string(),
vec![Value::test_int(5), Value::test_int(6)],
),
])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let name: Spanned<String> = call
.get_flag(engine_state, stack, "name")?
.expect("required named value");
let other_value: Value = call.req(engine_state, stack, 0)?;
let other_span = other_value.span()?;
let mut other = NuDataFrame::try_from_value(other_value)?.as_series(other_span)?;
let series = other.rename(&name.item).clone();
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
df.as_mut()
.with_column(series)
.map_err(|e| {
ShellError::SpannedLabeledError(
"Error adding column to dataframe".into(),
e.to_string(),
other_span,
)
})
.map(|df| {
PipelineData::Value(
NuDataFrame::dataframe_into_value(df.clone(), call.head),
None,
)
})
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(WithColumn {})])
}
}

View File

@ -0,0 +1,16 @@
mod eager;
mod series;
mod values;
pub use eager::add_eager_decls;
pub use series::add_series_decls;
use nu_protocol::engine::StateWorkingSet;
pub fn add_dataframe_decls(working_set: &mut StateWorkingSet) {
add_series_decls(working_set);
add_eager_decls(working_set);
}
#[cfg(test)]
mod test_dataframe;

View File

@ -0,0 +1,102 @@
use super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
#[derive(Clone)]
pub struct AllFalse;
impl Command for AllFalse {
fn name(&self) -> &str {
"dfr all-false"
}
fn usage(&self) -> &str {
"Returns true if all values are false"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Returns true if all values are false",
example: "[$false $false $false] | dfr to-df | dfr all-false",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"all_false".to_string(),
vec![Value::test_bool(true)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Checks the result from a comparison",
example: r#"let s = ([5 6 2 10] | dfr to-df);
let res = ($s > 9);
$res | dfr all-false"#,
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"all_false".to_string(),
vec![Value::test_bool(false)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let bool = series.bool().map_err(|_| {
ShellError::SpannedLabeledError(
"Error converting to bool".into(),
"all-false only works with series of type bool".into(),
call.head,
)
})?;
let value = Value::Bool {
val: bool.all_false(),
span: call.head,
};
NuDataFrame::try_from_columns(vec![Column::new("all_false".to_string(), vec![value])])
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(AllFalse {})])
}
}

View File

@ -0,0 +1,102 @@
use super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
#[derive(Clone)]
pub struct AllTrue;
impl Command for AllTrue {
fn name(&self) -> &str {
"dfr all-true"
}
fn usage(&self) -> &str {
"Returns true if all values are true"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Returns true if all values are true",
example: "[$true $true $true] | dfr to-df | dfr all-true",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"all_true".to_string(),
vec![Value::test_bool(true)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Checks the result from a comparison",
example: r#"let s = ([5 6 2 8] | dfr to-df);
let res = ($s > 9);
$res | dfr all-true"#,
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"all_true".to_string(),
vec![Value::test_bool(false)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let bool = series.bool().map_err(|_| {
ShellError::SpannedLabeledError(
"Error converting to bool".into(),
"all-false only works with series of type bool".into(),
call.head,
)
})?;
let value = Value::Bool {
val: bool.all_true(),
span: call.head,
};
NuDataFrame::try_from_columns(vec![Column::new("all_true".to_string(), vec![value])])
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(AllTrue {})])
}
}

View File

@ -0,0 +1,81 @@
use super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
use polars::prelude::{IntoSeries, NewChunkedArray, UInt32Chunked};
#[derive(Clone)]
pub struct ArgMax;
impl Command for ArgMax {
fn name(&self) -> &str {
"dfr arg-max"
}
fn usage(&self) -> &str {
"Return index for max value in series"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns index for max value",
example: "[1 3 2] | dfr to-df | dfr arg-max",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"arg_max".to_string(),
vec![Value::test_int(1)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let res = series.arg_max();
let chunked = match res {
Some(index) => UInt32Chunked::new_from_slice("arg_max", &[index as u32]),
None => UInt32Chunked::new_from_slice("arg_max", &[]),
};
let res = chunked.into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(ArgMax {})])
}
}

View File

@ -0,0 +1,81 @@
use super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
use polars::prelude::{IntoSeries, NewChunkedArray, UInt32Chunked};
#[derive(Clone)]
pub struct ArgMin;
impl Command for ArgMin {
fn name(&self) -> &str {
"dfr arg-min"
}
fn usage(&self) -> &str {
"Return index for min value in series"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns index for min value",
example: "[1 3 2] | dfr to-df | dfr arg-min",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"arg_min".to_string(),
vec![Value::test_int(0)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let res = series.arg_min();
let chunked = match res {
Some(index) => UInt32Chunked::new_from_slice("arg_min", &[index as u32]),
None => UInt32Chunked::new_from_slice("arg_min", &[]),
};
let res = chunked.into_series();
NuDataFrame::try_from_series(vec![res], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(ArgMin {})])
}
}

View File

@ -0,0 +1,135 @@
use super::super::values::{Column, NuDataFrame};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use polars::prelude::{DataType, IntoSeries};
enum CumType {
Min,
Max,
Sum,
}
impl CumType {
fn from_str(roll_type: &str, span: Span) -> Result<Self, ShellError> {
match roll_type {
"min" => Ok(Self::Min),
"max" => Ok(Self::Max),
"sum" => Ok(Self::Sum),
_ => Err(ShellError::SpannedLabeledErrorHelp(
"Wrong operation".into(),
"Operation not valid for cumulative".into(),
span,
"Allowed values: max, min, sum".into(),
)),
}
}
fn to_str(&self) -> &'static str {
match self {
CumType::Min => "cumulative_min",
CumType::Max => "cumulative_max",
CumType::Sum => "cumulative_sum",
}
}
}
#[derive(Clone)]
pub struct Cumulative;
impl Command for Cumulative {
fn name(&self) -> &str {
"dfr cumulative"
}
fn usage(&self) -> &str {
"Cumulative calculation for a series"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("type", SyntaxShape::String, "rolling operation")
.switch("reverse", "Reverse cumulative calculation", Some('r'))
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Cumulative sum for a series",
example: "[1 2 3 4 5] | dfr to-df | dfr cumulative sum",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0_cumulative_sum".to_string(),
vec![
Value::test_int(1),
Value::test_int(3),
Value::test_int(6),
Value::test_int(10),
Value::test_int(15),
],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cum_type: Spanned<String> = call.req(engine_state, stack, 0)?;
let reverse = call.has_flag("reverse");
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
if let DataType::Object(_) = series.dtype() {
return Err(ShellError::SpannedLabeledError(
"Found object series".into(),
"Series of type object cannot be used for cumulative operation".into(),
call.head,
));
}
let cum_type = CumType::from_str(&cum_type.item, cum_type.span)?;
let mut res = match cum_type {
CumType::Max => series.cummax(reverse),
CumType::Min => series.cummin(reverse),
CumType::Sum => series.cumsum(reverse),
};
let name = format!("{}_{}", series.name(), cum_type.to_str());
res.rename(&name);
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(Cumulative {})])
}
}

View File

@ -0,0 +1,87 @@
use super::super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
use polars::prelude::IntoSeries;
#[derive(Clone)]
pub struct GetDay;
impl Command for GetDay {
fn name(&self) -> &str {
"dfr get-day"
}
fn usage(&self) -> &str {
"Gets day from date"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns day from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
let df = ([$dt $dt] | dfr to-df);
$df | dfr get-day"#,
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0".to_string(),
vec![Value::test_int(4), Value::test_int(4)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| {
ShellError::SpannedLabeledError(
"Error casting to datetime type".into(),
e.to_string(),
call.head,
)
})?;
let res = casted.day().into_series();
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetDay {}), Box::new(IntoDatetime {})])
}
}

View File

@ -0,0 +1,87 @@
use super::super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
use polars::prelude::IntoSeries;
#[derive(Clone)]
pub struct GetHour;
impl Command for GetHour {
fn name(&self) -> &str {
"dfr get-hour"
}
fn usage(&self) -> &str {
"Gets hour from date"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns hour from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
let df = ([$dt $dt] | dfr to-df);
$df | dfr get-hour"#,
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0".to_string(),
vec![Value::test_int(16), Value::test_int(16)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| {
ShellError::SpannedLabeledError(
"Error casting to datetime type".into(),
e.to_string(),
call.head,
)
})?;
let res = casted.hour().into_series();
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetHour {}), Box::new(IntoDatetime {})])
}
}

View File

@ -0,0 +1,87 @@
use super::super::super::values::{Column, NuDataFrame};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Value,
};
use polars::prelude::IntoSeries;
#[derive(Clone)]
pub struct GetMinute;
impl Command for GetMinute {
fn name(&self) -> &str {
"dfr get-minute"
}
fn usage(&self) -> &str {
"Gets minute from date"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Returns minute from a date",
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
let df = ([$dt $dt] | dfr to-df);
$df | dfr get-minute"#,
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"0".to_string(),
vec![Value::test_int(39), Value::test_int(39)],
)])
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
command(engine_state, stack, call, input)
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?;
let casted = series.datetime().map_err(|e| {
ShellError::SpannedLabeledError(
"Error casting to datetime type".into(),
e.to_string(),
call.head,
)
})?;
let res = casted.minute().into_series();
NuDataFrame::try_from_series(vec![res.into_series()], call.head)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
}
#[cfg(test)]
mod test {
use super::super::super::super::super::IntoDatetime;
use super::super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(GetMinute {}), Box::new(IntoDatetime {})])
}
}

Some files were not shown because too many files have changed in this diff Show More