mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 07:46:01 +02:00
merge w/ upstream
This commit is contained in:
@ -6,6 +6,17 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol" }
|
||||
nu-engine = { path = "../nu-engine" }
|
||||
nu-parser = {path = "../nu-parser"}
|
||||
nu-json = { path = "../nu-json" }
|
||||
nu-protocol = { path = "../nu-protocol" }
|
||||
<<<<<<< HEAD
|
||||
nu-engine = { path = "../nu-engine" }
|
||||
nu-parser = {path = "../nu-parser"}
|
||||
=======
|
||||
nu-table = { path = "../nu-table" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
glob = "0.3.0"
|
||||
thiserror = "1.0.29"
|
||||
sysinfo = "0.20.4"
|
||||
>>>>>>> 3567bbbf32302dbc3cbf97a39b03efa3bd3e8bb5
|
||||
|
@ -17,7 +17,11 @@ impl Command for Def {
|
||||
Signature::build("def")
|
||||
.required("def_name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.required("block", SyntaxShape::Block, "body of the definition")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"body of the definition",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
@ -15,7 +15,11 @@ impl Command for Do {
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("do").required("block", SyntaxShape::Block, "the block to run")
|
||||
Signature::build("do").required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"the block to run",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
406
crates/nu-command/src/core_commands/help.rs
Normal file
406
crates/nu-command/src/core_commands/help.rs
Normal file
@ -0,0 +1,406 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EvaluationContext},
|
||||
Example, ShellError, Signature, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
use nu_engine::CallExt;
|
||||
|
||||
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'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display help information about commands."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
help(context, 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(context: &EvaluationContext, call: &Call) -> Result<Value, ShellError> {
|
||||
let span = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(context, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(context, 0)?;
|
||||
|
||||
let full_commands = context.get_commands_info();
|
||||
|
||||
if let Some(f) = find {
|
||||
let search_string = f.item;
|
||||
let mut found_cmds_vec = Vec::new();
|
||||
|
||||
for cmd in full_commands {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let key = cmd.name.clone();
|
||||
let c = cmd.usage.clone();
|
||||
let e = cmd.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 });
|
||||
|
||||
cols.push("usage".into());
|
||||
vals.push(Value::String { val: c, span });
|
||||
|
||||
cols.push("extra_usage".into());
|
||||
vals.push(Value::String { val: e, span });
|
||||
|
||||
found_cmds_vec.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Value::List {
|
||||
vals: found_cmds_vec,
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
if !rest.is_empty() {
|
||||
let mut found_cmds_vec = Vec::new();
|
||||
|
||||
if rest[0].item == "commands" {
|
||||
for cmd in full_commands {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let key = cmd.name.clone();
|
||||
let c = cmd.usage.clone();
|
||||
let e = cmd.extra_usage.clone();
|
||||
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String { val: key, span });
|
||||
|
||||
cols.push("usage".into());
|
||||
vals.push(Value::String { val: c, span });
|
||||
|
||||
cols.push("extra_usage".into());
|
||||
vals.push(Value::String { val: e, span });
|
||||
|
||||
found_cmds_vec.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
} else {
|
||||
let mut name = String::new();
|
||||
|
||||
for r in rest {
|
||||
if !name.is_empty() {
|
||||
name.push(' ');
|
||||
}
|
||||
name.push_str(&r.item);
|
||||
}
|
||||
|
||||
for cmd in full_commands {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let key = cmd.name.clone();
|
||||
let c = cmd.usage.clone();
|
||||
let e = cmd.extra_usage.clone();
|
||||
|
||||
if key.starts_with(&name) {
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String { val: key, span });
|
||||
|
||||
cols.push("usage".into());
|
||||
vals.push(Value::String { val: c, span });
|
||||
|
||||
cols.push("extra_usage".into());
|
||||
vals.push(Value::String { val: e, span });
|
||||
|
||||
found_cmds_vec.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Value::List {
|
||||
vals: found_cmds_vec,
|
||||
span,
|
||||
})
|
||||
|
||||
// FIXME: the fancy help stuff needs to be reimplemented
|
||||
/*
|
||||
if rest[0].item == "commands" {
|
||||
let mut sorted_names = scope.get_command_names();
|
||||
sorted_names.sort();
|
||||
|
||||
let (mut subcommand_names, command_names) = sorted_names
|
||||
.into_iter()
|
||||
// private only commands shouldn't be displayed
|
||||
.filter(|cmd_name| {
|
||||
scope
|
||||
.get_command(cmd_name)
|
||||
.filter(|command| !command.is_private())
|
||||
.is_some()
|
||||
})
|
||||
.partition::<Vec<_>, _>(|cmd_name| cmd_name.contains(' '));
|
||||
|
||||
fn process_name(
|
||||
dict: &mut TaggedDictBuilder,
|
||||
cmd_name: &str,
|
||||
scope: Scope,
|
||||
rest: Vec<Tagged<String>>,
|
||||
name: Tag,
|
||||
) -> Result<(), ShellError> {
|
||||
let document_tag = rest[0].tag.clone();
|
||||
let value = command_dict(
|
||||
scope.get_command(cmd_name).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd_name),
|
||||
"could not load command",
|
||||
document_tag,
|
||||
)
|
||||
})?,
|
||||
name,
|
||||
);
|
||||
|
||||
dict.insert_untagged("name", cmd_name);
|
||||
dict.insert_untagged(
|
||||
"description",
|
||||
value
|
||||
.get_data_by_key("usage".spanned_unknown())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a usage key",
|
||||
"expected a 'usage' key",
|
||||
&value.tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_subcommands_table(
|
||||
subcommand_names: &mut Vec<String>,
|
||||
cmd_name: &str,
|
||||
scope: Scope,
|
||||
rest: Vec<Tagged<String>>,
|
||||
name: Tag,
|
||||
) -> Result<Value, ShellError> {
|
||||
let (matching, not_matching) =
|
||||
subcommand_names.drain(..).partition(|subcommand_name| {
|
||||
subcommand_name.starts_with(&format!("{} ", cmd_name))
|
||||
});
|
||||
*subcommand_names = not_matching;
|
||||
Ok(if !matching.is_empty() {
|
||||
UntaggedValue::table(
|
||||
&(matching
|
||||
.into_iter()
|
||||
.map(|cmd_name: String| -> Result<_, ShellError> {
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
process_name(
|
||||
&mut short_desc,
|
||||
&cmd_name,
|
||||
scope.clone(),
|
||||
rest.clone(),
|
||||
name.clone(),
|
||||
)?;
|
||||
Ok(short_desc.into_value())
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?[..]),
|
||||
)
|
||||
.into_value(name)
|
||||
} else {
|
||||
UntaggedValue::nothing().into_value(name)
|
||||
})
|
||||
}
|
||||
|
||||
let iterator =
|
||||
command_names
|
||||
.into_iter()
|
||||
.map(move |cmd_name| -> Result<_, ShellError> {
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
process_name(
|
||||
&mut short_desc,
|
||||
&cmd_name,
|
||||
scope.clone(),
|
||||
rest.clone(),
|
||||
name.clone(),
|
||||
)?;
|
||||
short_desc.insert_value(
|
||||
"subcommands",
|
||||
make_subcommands_table(
|
||||
&mut subcommand_names,
|
||||
&cmd_name,
|
||||
scope.clone(),
|
||||
rest.clone(),
|
||||
name.clone(),
|
||||
)?,
|
||||
);
|
||||
ReturnSuccess::value(short_desc.into_value())
|
||||
});
|
||||
|
||||
Ok(iterator.into_action_stream())
|
||||
} else if rest[0].item == "generate_docs" {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(generate_docs(
|
||||
&scope,
|
||||
))))
|
||||
} else if rest.len() == 2 {
|
||||
// Check for a subcommand
|
||||
let command_name = format!("{} {}", rest[0].item, rest[1].item);
|
||||
if let Some(command) = scope.get_command(&command_name) {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_full_help(command.stream_command(), &scope))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
Ok(ActionStream::empty())
|
||||
}
|
||||
} else if let Some(command) = scope.get_command(&rest[0].item) {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_full_help(command.stream_command(), &scope))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Can't find command (use 'help commands' for full list)",
|
||||
"can't find command",
|
||||
rest[0].tag.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
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn for_spec(name: &str, ty: &str, required: bool, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut spec = TaggedDictBuilder::new(tag);
|
||||
|
||||
spec.insert_untagged("name", UntaggedValue::string(name));
|
||||
spec.insert_untagged("type", UntaggedValue::string(ty));
|
||||
spec.insert_untagged(
|
||||
"required",
|
||||
UntaggedValue::string(if required { "yes" } else { "no" }),
|
||||
);
|
||||
|
||||
spec.into_value()
|
||||
}
|
||||
|
||||
pub fn signature_dict(signature: Signature, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
let mut sig = TaggedListBuilder::new(&tag);
|
||||
|
||||
for arg in &signature.positional {
|
||||
let is_required = matches!(arg.0, PositionalType::Mandatory(_, _));
|
||||
|
||||
sig.push_value(for_spec(arg.0.name(), "argument", is_required, &tag));
|
||||
}
|
||||
|
||||
if signature.rest_positional.is_some() {
|
||||
let is_required = false;
|
||||
sig.push_value(for_spec("rest", "argument", is_required, &tag));
|
||||
}
|
||||
|
||||
for (name, ty) in &signature.named {
|
||||
match ty.0 {
|
||||
NamedType::Mandatory(_, _) => sig.push_value(for_spec(name, "flag", true, &tag)),
|
||||
NamedType::Optional(_, _) => sig.push_value(for_spec(name, "flag", false, &tag)),
|
||||
NamedType::Switch(_) => sig.push_value(for_spec(name, "switch", false, &tag)),
|
||||
}
|
||||
}
|
||||
|
||||
sig.into_value()
|
||||
}
|
||||
|
||||
fn command_dict(command: Command, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut cmd_dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
cmd_dict.insert_untagged("name", UntaggedValue::string(command.name()));
|
||||
|
||||
cmd_dict.insert_untagged("type", UntaggedValue::string("Command"));
|
||||
|
||||
cmd_dict.insert_value("signature", signature_dict(command.signature(), tag));
|
||||
cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage()));
|
||||
|
||||
cmd_dict.into_value()
|
||||
}
|
||||
|
||||
*/
|
@ -11,13 +11,13 @@ impl Command for If {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create a variable and give it a value."
|
||||
"Conditionally run a block."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("if")
|
||||
.required("cond", SyntaxShape::Expression, "condition")
|
||||
.required("then_block", SyntaxShape::Block, "then block")
|
||||
.required("then_block", SyntaxShape::Block(Some(vec![])), "then block")
|
||||
.optional(
|
||||
"else",
|
||||
SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)),
|
17
crates/nu-command/src/core_commands/mod.rs
Normal file
17
crates/nu-command/src/core_commands/mod.rs
Normal file
@ -0,0 +1,17 @@
|
||||
mod alias;
|
||||
mod def;
|
||||
mod do_;
|
||||
mod help;
|
||||
mod if_;
|
||||
mod let_;
|
||||
mod module;
|
||||
mod use_;
|
||||
|
||||
pub use alias::Alias;
|
||||
pub use def::Def;
|
||||
pub use do_::Do;
|
||||
pub use help::Help;
|
||||
pub use if_::If;
|
||||
pub use let_::Let;
|
||||
pub use module::Module;
|
||||
pub use use_::Use;
|
34
crates/nu-command/src/core_commands/module.rs
Normal file
34
crates/nu-command/src/core_commands/module.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
Ok(Value::Nothing { span: call.head })
|
||||
}
|
||||
}
|
28
crates/nu-command/src/core_commands/use_.rs
Normal file
28
crates/nu-command/src/core_commands/use_.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
|
||||
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("module_name", SyntaxShape::String, "module name")
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
Ok(Value::Nothing { span: call.head })
|
||||
}
|
||||
}
|
@ -2,10 +2,17 @@ use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
Signature, SyntaxShape,
|
||||
Signature,
|
||||
};
|
||||
|
||||
<<<<<<< HEAD
|
||||
use crate::{Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, Source};
|
||||
=======
|
||||
use crate::{
|
||||
Alias, Benchmark, BuildString, Def, Do, Each, External, For, From, FromJson, Git, GitCheckout,
|
||||
Help, If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Ps, Sys, Table, Use, Where,
|
||||
};
|
||||
>>>>>>> 3567bbbf32302dbc3cbf97a39b03efa3bd3e8bb5
|
||||
|
||||
pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
||||
let engine_state = Rc::new(RefCell::new(EngineState::new()));
|
||||
@ -13,31 +20,34 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
||||
let engine_state = engine_state.borrow();
|
||||
let mut working_set = StateWorkingSet::new(&*engine_state);
|
||||
|
||||
let sig =
|
||||
Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition");
|
||||
working_set.add_decl(sig.predeclare());
|
||||
|
||||
working_set.add_decl(Box::new(If));
|
||||
|
||||
working_set.add_decl(Box::new(Let));
|
||||
|
||||
working_set.add_decl(Box::new(LetEnv));
|
||||
|
||||
working_set.add_decl(Box::new(Alias));
|
||||
|
||||
working_set.add_decl(Box::new(BuildString));
|
||||
|
||||
working_set.add_decl(Box::new(Def));
|
||||
|
||||
working_set.add_decl(Box::new(For));
|
||||
|
||||
working_set.add_decl(Box::new(Each));
|
||||
|
||||
working_set.add_decl(Box::new(Do));
|
||||
|
||||
working_set.add_decl(Box::new(Benchmark));
|
||||
|
||||
working_set.add_decl(Box::new(BuildString));
|
||||
working_set.add_decl(Box::new(Def));
|
||||
working_set.add_decl(Box::new(Do));
|
||||
working_set.add_decl(Box::new(Each));
|
||||
working_set.add_decl(Box::new(External));
|
||||
working_set.add_decl(Box::new(For));
|
||||
working_set.add_decl(Box::new(From));
|
||||
working_set.add_decl(Box::new(FromJson));
|
||||
working_set.add_decl(Box::new(Help));
|
||||
working_set.add_decl(Box::new(If));
|
||||
working_set.add_decl(Box::new(Length));
|
||||
working_set.add_decl(Box::new(Let));
|
||||
working_set.add_decl(Box::new(LetEnv));
|
||||
working_set.add_decl(Box::new(Lines));
|
||||
working_set.add_decl(Box::new(Ls));
|
||||
working_set.add_decl(Box::new(Module));
|
||||
working_set.add_decl(Box::new(Ps));
|
||||
working_set.add_decl(Box::new(Sys));
|
||||
working_set.add_decl(Box::new(Table));
|
||||
working_set.add_decl(Box::new(Use));
|
||||
working_set.add_decl(Box::new(Where));
|
||||
|
||||
// This is a WIP proof of concept
|
||||
working_set.add_decl(Box::new(ListGitBranches));
|
||||
working_set.add_decl(Box::new(Git));
|
||||
working_set.add_decl(Box::new(GitCheckout));
|
||||
|
||||
working_set.add_decl(Box::new(Source));
|
||||
|
||||
@ -51,6 +61,8 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
||||
working_set.add_decl(sig.predeclare());
|
||||
let sig = Signature::build("stack");
|
||||
working_set.add_decl(sig.predeclare());
|
||||
let sig = Signature::build("contents");
|
||||
working_set.add_decl(sig.predeclare());
|
||||
|
||||
working_set.render()
|
||||
};
|
||||
|
3
crates/nu-command/src/env/mod.rs
vendored
Normal file
3
crates/nu-command/src/env/mod.rs
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
mod let_env;
|
||||
|
||||
pub use let_env::LetEnv;
|
51
crates/nu-command/src/experimental/git.rs
Normal file
51
crates/nu-command/src/experimental/git.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{Signature, Value};
|
||||
|
||||
pub struct Git;
|
||||
|
||||
impl Command for Git {
|
||||
fn name(&self) -> &str {
|
||||
"git"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("git")
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
use std::process::Command as ProcessCommand;
|
||||
use std::process::Stdio;
|
||||
|
||||
let proc = ProcessCommand::new("git").stdout(Stdio::piped()).spawn();
|
||||
|
||||
match proc {
|
||||
Ok(child) => {
|
||||
match child.wait_with_output() {
|
||||
Ok(val) => {
|
||||
let result = val.stdout;
|
||||
|
||||
Ok(Value::string(&String::from_utf8_lossy(&result), call.head))
|
||||
}
|
||||
Err(_err) => {
|
||||
// FIXME
|
||||
Ok(Value::nothing())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
// FIXME
|
||||
Ok(Value::nothing())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
crates/nu-command/src/experimental/git_checkout.rs
Normal file
66
crates/nu-command/src/experimental/git_checkout.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use nu_engine::eval_expression;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct GitCheckout;
|
||||
|
||||
impl Command for GitCheckout {
|
||||
fn name(&self) -> &str {
|
||||
"git checkout"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Checkout a git revision"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("git checkout").required(
|
||||
"branch",
|
||||
SyntaxShape::Custom(Box::new(SyntaxShape::String), "list-git-branches".into()),
|
||||
"the branch to checkout",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
use std::process::Command as ProcessCommand;
|
||||
use std::process::Stdio;
|
||||
|
||||
let block = &call.positional[0];
|
||||
|
||||
let out = eval_expression(context, block)?;
|
||||
|
||||
let out = out.as_string()?;
|
||||
|
||||
let proc = ProcessCommand::new("git")
|
||||
.arg("checkout")
|
||||
.arg(out)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn();
|
||||
|
||||
match proc {
|
||||
Ok(child) => {
|
||||
match child.wait_with_output() {
|
||||
Ok(val) => {
|
||||
let result = val.stdout;
|
||||
|
||||
Ok(Value::string(&String::from_utf8_lossy(&result), call.head))
|
||||
}
|
||||
Err(_err) => {
|
||||
// FIXME
|
||||
Ok(Value::nothing())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
// FIXME
|
||||
Ok(Value::nothing())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
crates/nu-command/src/experimental/list_git_branches.rs
Normal file
69
crates/nu-command/src/experimental/list_git_branches.rs
Normal file
@ -0,0 +1,69 @@
|
||||
// Note: this is a temporary command that later will be converted into a pipeline
|
||||
|
||||
use std::process::Command as ProcessCommand;
|
||||
use std::process::Stdio;
|
||||
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{Signature, Value};
|
||||
|
||||
pub struct ListGitBranches;
|
||||
|
||||
//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one.
|
||||
impl Command for ListGitBranches {
|
||||
fn name(&self) -> &str {
|
||||
"list-git-branches"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"List the git branches of the current directory."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("list-git-branches")
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
let list_branches = ProcessCommand::new("git")
|
||||
.arg("branch")
|
||||
.stdout(Stdio::piped())
|
||||
.spawn();
|
||||
|
||||
if let Ok(child) = list_branches {
|
||||
if let Ok(output) = child.wait_with_output() {
|
||||
let val = output.stdout;
|
||||
|
||||
let s = String::from_utf8_lossy(&val).to_string();
|
||||
|
||||
let lines: Vec<_> = s
|
||||
.lines()
|
||||
.filter_map(|x| {
|
||||
if x.starts_with("* ") {
|
||||
None
|
||||
} else {
|
||||
Some(x.trim())
|
||||
}
|
||||
})
|
||||
.map(|x| Value::String {
|
||||
val: x.into(),
|
||||
span: call.head,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Value::List {
|
||||
vals: lines,
|
||||
span: call.head,
|
||||
})
|
||||
} else {
|
||||
Ok(Value::Nothing { span: call.head })
|
||||
}
|
||||
} else {
|
||||
Ok(Value::Nothing { span: call.head })
|
||||
}
|
||||
}
|
||||
}
|
7
crates/nu-command/src/experimental/mod.rs
Normal file
7
crates/nu-command/src/experimental/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod git;
|
||||
mod git_checkout;
|
||||
mod list_git_branches;
|
||||
|
||||
pub use git::Git;
|
||||
pub use git_checkout::GitCheckout;
|
||||
pub use list_git_branches::ListGitBranches;
|
93
crates/nu-command/src/filesystem/ls.rs
Normal file
93
crates/nu-command/src/filesystem/ls.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use nu_engine::eval_expression;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct Ls;
|
||||
|
||||
//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one.
|
||||
impl Command for Ls {
|
||||
fn name(&self) -> &str {
|
||||
"ls"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"List the files in a directory."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("ls").optional(
|
||||
"pattern",
|
||||
SyntaxShape::GlobPattern,
|
||||
"the glob pattern to use",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
let pattern = if let Some(expr) = call.positional.get(0) {
|
||||
let result = eval_expression(context, expr)?;
|
||||
result.as_string()?
|
||||
} else {
|
||||
"*".into()
|
||||
};
|
||||
|
||||
let call_span = call.head;
|
||||
let glob = glob::glob(&pattern).unwrap();
|
||||
|
||||
Ok(Value::Stream {
|
||||
stream: glob
|
||||
.into_iter()
|
||||
.map(move |x| match x {
|
||||
Ok(path) => match std::fs::symlink_metadata(&path) {
|
||||
Ok(metadata) => {
|
||||
let is_file = metadata.is_file();
|
||||
let is_dir = metadata.is_dir();
|
||||
let filesize = metadata.len();
|
||||
|
||||
Value::Record {
|
||||
cols: vec!["name".into(), "type".into(), "size".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: path.to_string_lossy().to_string(),
|
||||
span: call_span,
|
||||
},
|
||||
if is_file {
|
||||
Value::string("file", call_span)
|
||||
} else if is_dir {
|
||||
Value::string("dir", call_span)
|
||||
} else {
|
||||
Value::Nothing { span: call_span }
|
||||
},
|
||||
Value::Int {
|
||||
val: filesize as i64,
|
||||
span: call_span,
|
||||
},
|
||||
],
|
||||
span: call_span,
|
||||
}
|
||||
}
|
||||
Err(_) => Value::Record {
|
||||
cols: vec!["name".into(), "type".into(), "size".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: path.to_string_lossy().to_string(),
|
||||
span: call_span,
|
||||
},
|
||||
Value::Nothing { span: call_span },
|
||||
Value::Nothing { span: call_span },
|
||||
],
|
||||
span: call_span,
|
||||
},
|
||||
},
|
||||
_ => Value::Nothing { span: call_span },
|
||||
})
|
||||
.into_value_stream(),
|
||||
span: call_span,
|
||||
})
|
||||
}
|
||||
}
|
3
crates/nu-command/src/filesystem/mod.rs
Normal file
3
crates/nu-command/src/filesystem/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod ls;
|
||||
|
||||
pub use ls::Ls;
|
@ -15,7 +15,13 @@ impl Command for Each {
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("each").required("block", SyntaxShape::Block, "the block to run")
|
||||
Signature::build("each")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![SyntaxShape::Any])),
|
||||
"the block to run",
|
||||
)
|
||||
.switch("numbered", "iterate with an index", Some('n'))
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -27,20 +33,42 @@ impl Command for Each {
|
||||
let block_id = call.positional[0]
|
||||
.as_block()
|
||||
.expect("internal error: expected block");
|
||||
|
||||
let numbered = call.has_flag("numbered");
|
||||
let context = context.clone();
|
||||
let span = call.head;
|
||||
|
||||
match input {
|
||||
Value::Range { val, .. } => Ok(Value::Stream {
|
||||
stream: val
|
||||
.into_iter()
|
||||
.map(move |x| {
|
||||
.enumerate()
|
||||
.map(move |(idx, x)| {
|
||||
let engine_state = context.engine_state.borrow();
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let state = context.enter_scope();
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
state.add_var(*var_id, x);
|
||||
if numbered {
|
||||
state.add_var(
|
||||
*var_id,
|
||||
Value::Record {
|
||||
cols: vec!["index".into(), "item".into()],
|
||||
vals: vec![
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
x,
|
||||
],
|
||||
span,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
state.add_var(*var_id, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,14 +83,32 @@ impl Command for Each {
|
||||
Value::List { vals: val, .. } => Ok(Value::Stream {
|
||||
stream: val
|
||||
.into_iter()
|
||||
.map(move |x| {
|
||||
.enumerate()
|
||||
.map(move |(idx, x)| {
|
||||
let engine_state = context.engine_state.borrow();
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let state = context.enter_scope();
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
state.add_var(*var_id, x);
|
||||
if numbered {
|
||||
state.add_var(
|
||||
*var_id,
|
||||
Value::Record {
|
||||
cols: vec!["index".into(), "item".into()],
|
||||
vals: vec![
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
x,
|
||||
],
|
||||
span,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
state.add_var(*var_id, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,14 +122,32 @@ impl Command for Each {
|
||||
}),
|
||||
Value::Stream { stream, .. } => Ok(Value::Stream {
|
||||
stream: stream
|
||||
.map(move |x| {
|
||||
.enumerate()
|
||||
.map(move |(idx, x)| {
|
||||
let engine_state = context.engine_state.borrow();
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let state = context.enter_scope();
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
state.add_var(*var_id, x);
|
||||
if numbered {
|
||||
state.add_var(
|
||||
*var_id,
|
||||
Value::Record {
|
||||
cols: vec!["index".into(), "item".into()],
|
||||
vals: vec![
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
x,
|
||||
],
|
||||
span,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
state.add_var(*var_id, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_engine::{eval_block, eval_expression};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{Example, IntoValueStream, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
pub struct For;
|
||||
|
||||
@ -29,7 +29,11 @@ impl Command for For {
|
||||
),
|
||||
"range of the loop",
|
||||
)
|
||||
.required("block", SyntaxShape::Block, "the block to run")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"the block to run",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -87,4 +91,42 @@ impl Command for For {
|
||||
_ => Ok(Value::nothing()),
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
let span = Span::unknown();
|
||||
vec![
|
||||
Example {
|
||||
description: "Echo the square of each integer",
|
||||
example: "for x in [1 2 3] { $x * $x }",
|
||||
result: Some(vec![
|
||||
Value::Int { val: 1, span },
|
||||
Value::Int { val: 4, span },
|
||||
Value::Int { val: 9, span },
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Work with elements of a range",
|
||||
example: "for $x in 1..3 { $x }",
|
||||
result: Some(vec![
|
||||
Value::Int { val: 1, span },
|
||||
Value::Int { val: 2, span },
|
||||
Value::Int { val: 3, span },
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Number each item and echo a message",
|
||||
example: "for $it in ['bob' 'fred'] --numbered { $\"($it.index) is ($it.item)\" }",
|
||||
result: Some(vec![
|
||||
Value::String {
|
||||
val: "0 is bob".into(),
|
||||
span,
|
||||
},
|
||||
Value::String {
|
||||
val: "0 is fred".into(),
|
||||
span,
|
||||
},
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
92
crates/nu-command/src/filters/lines.rs
Normal file
92
crates/nu-command/src/filters/lines.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{ShellError, Signature, Span, Value, ValueStream};
|
||||
|
||||
pub struct Lines;
|
||||
|
||||
const SPLIT_CHAR: char = '\n';
|
||||
|
||||
impl Command for Lines {
|
||||
fn name(&self) -> &str {
|
||||
"lines"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Converts input to lines"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("lines")
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
call: &Call,
|
||||
input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
match input {
|
||||
#[allow(clippy::needless_collect)]
|
||||
// Collect is needed because the string may not live long enough for
|
||||
// the Rc structure to continue using it. If split could take ownership
|
||||
// of the split values, then this wouldn't be needed
|
||||
Value::String { val, span } => {
|
||||
let lines = val
|
||||
.split(SPLIT_CHAR)
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let iter = lines.into_iter().filter_map(move |s| {
|
||||
if !s.is_empty() {
|
||||
Some(Value::String { val: s, span })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Value::Stream {
|
||||
stream: ValueStream(Rc::new(RefCell::new(iter))),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
Value::Stream { stream, span: _ } => {
|
||||
let iter = stream
|
||||
.into_iter()
|
||||
.filter_map(|value| {
|
||||
if let Value::String { val, span } = value {
|
||||
let inner = val
|
||||
.split(SPLIT_CHAR)
|
||||
.filter_map(|s| {
|
||||
if !s.is_empty() {
|
||||
Some(Value::String {
|
||||
val: s.trim().into(),
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Value>>();
|
||||
|
||||
Some(inner)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten();
|
||||
|
||||
Ok(Value::Stream {
|
||||
stream: ValueStream(Rc::new(RefCell::new(iter))),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
val => Err(ShellError::UnsupportedInput(
|
||||
format!("Not supported input: {}", val.as_string()?),
|
||||
call.head,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
11
crates/nu-command/src/filters/mod.rs
Normal file
11
crates/nu-command/src/filters/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
mod each;
|
||||
mod for_;
|
||||
mod length;
|
||||
mod lines;
|
||||
mod where_;
|
||||
|
||||
pub use each::Each;
|
||||
pub use for_::For;
|
||||
pub use length::Length;
|
||||
pub use lines::Lines;
|
||||
pub use where_::Where;
|
92
crates/nu-command/src/filters/where_.rs
Normal file
92
crates/nu-command/src/filters/where_.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use nu_engine::eval_expression;
|
||||
use nu_protocol::ast::{Call, Expr, Expression};
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{IntoValueStream, ShellError, Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct Where;
|
||||
|
||||
impl Command for Where {
|
||||
fn name(&self) -> &str {
|
||||
"where"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Filter values based on a condition."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition")
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
context: &EvaluationContext,
|
||||
call: &Call,
|
||||
input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
let cond = call.positional[0].clone();
|
||||
|
||||
let context = context.enter_scope();
|
||||
|
||||
let (var_id, cond) = match cond {
|
||||
Expression {
|
||||
expr: Expr::RowCondition(var_id, expr),
|
||||
..
|
||||
} => (var_id, expr),
|
||||
_ => return Err(ShellError::InternalError("Expected row condition".into())),
|
||||
};
|
||||
|
||||
match input {
|
||||
Value::Stream { stream, span } => {
|
||||
let output_stream = stream
|
||||
.filter(move |value| {
|
||||
context.add_var(var_id, value.clone());
|
||||
|
||||
let result = eval_expression(&context, &cond);
|
||||
|
||||
match result {
|
||||
Ok(result) => result.is_true(),
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.into_value_stream();
|
||||
|
||||
Ok(Value::Stream {
|
||||
stream: output_stream,
|
||||
span,
|
||||
})
|
||||
}
|
||||
Value::List { vals, span } => {
|
||||
let output_stream = vals
|
||||
.into_iter()
|
||||
.filter(move |value| {
|
||||
context.add_var(var_id, value.clone());
|
||||
|
||||
let result = eval_expression(&context, &cond);
|
||||
|
||||
match result {
|
||||
Ok(result) => result.is_true(),
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.into_value_stream();
|
||||
|
||||
Ok(Value::Stream {
|
||||
stream: output_stream,
|
||||
span,
|
||||
})
|
||||
}
|
||||
x => {
|
||||
context.add_var(var_id, x.clone());
|
||||
|
||||
let result = eval_expression(&context, &cond)?;
|
||||
|
||||
if result.is_true() {
|
||||
Ok(x)
|
||||
} else {
|
||||
Ok(Value::Nothing { span: call.head })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
crates/nu-command/src/formats/from/command.rs
Normal file
28
crates/nu-command/src/formats/from/command.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{ShellError, Signature, Value};
|
||||
|
||||
pub struct From;
|
||||
|
||||
impl Command for From {
|
||||
fn name(&self) -> &str {
|
||||
"from"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse a string or binary data into structured data"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("from")
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
_call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, ShellError> {
|
||||
Ok(Value::nothing())
|
||||
}
|
||||
}
|
111
crates/nu-command/src/formats/from/json.rs
Normal file
111
crates/nu-command/src/formats/from/json.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{IntoValueStream, ShellError, Signature, Span, Value};
|
||||
|
||||
pub struct FromJson;
|
||||
|
||||
impl Command for FromJson {
|
||||
fn name(&self) -> &str {
|
||||
"from json"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert from json to structured data"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("from json").switch(
|
||||
"objects",
|
||||
"treat each line as a separate value",
|
||||
Some('o'),
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
call: &Call,
|
||||
input: Value,
|
||||
) -> Result<nu_protocol::Value, ShellError> {
|
||||
let span = input.span();
|
||||
let mut string_input = input.collect_string();
|
||||
string_input.push('\n');
|
||||
|
||||
// TODO: turn this into a structured underline of the nu_json error
|
||||
if call.has_flag("objects") {
|
||||
#[allow(clippy::needless_collect)]
|
||||
let lines: Vec<String> = string_input.lines().map(|x| x.to_string()).collect();
|
||||
Ok(Value::Stream {
|
||||
stream: lines
|
||||
.into_iter()
|
||||
.map(move |mut x| {
|
||||
x.push('\n');
|
||||
match convert_string_to_value(x, span) {
|
||||
Ok(v) => v,
|
||||
Err(error) => Value::Error { error },
|
||||
}
|
||||
})
|
||||
.into_value_stream(),
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
convert_string_to_value(string_input, span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_nujson_to_value(value: &nu_json::Value, span: Span) -> Value {
|
||||
match value {
|
||||
nu_json::Value::Array(array) => {
|
||||
let v: Vec<Value> = array
|
||||
.iter()
|
||||
.map(|x| convert_nujson_to_value(x, span))
|
||||
.collect();
|
||||
|
||||
Value::List { vals: v, span }
|
||||
}
|
||||
nu_json::Value::Bool(b) => Value::Bool { val: *b, span },
|
||||
nu_json::Value::F64(f) => Value::Float { val: *f, span },
|
||||
nu_json::Value::I64(i) => Value::Int { val: *i, span },
|
||||
nu_json::Value::Null => Value::Nothing { span },
|
||||
nu_json::Value::Object(k) => {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
for item in k {
|
||||
cols.push(item.0.clone());
|
||||
vals.push(convert_nujson_to_value(item.1, span));
|
||||
}
|
||||
|
||||
Value::Record { cols, vals, span }
|
||||
}
|
||||
nu_json::Value::U64(u) => {
|
||||
if *u > i64::MAX as u64 {
|
||||
Value::Error {
|
||||
error: ShellError::CantConvert("i64 sized integer".into(), span),
|
||||
}
|
||||
} else {
|
||||
Value::Int {
|
||||
val: *u as i64,
|
||||
span,
|
||||
}
|
||||
}
|
||||
}
|
||||
nu_json::Value::String(s) => Value::String {
|
||||
val: s.clone(),
|
||||
span,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_string_to_value(string_input: String, span: Span) -> Result<Value, ShellError> {
|
||||
let result: Result<nu_json::Value, nu_json::Error> = nu_json::from_str(&string_input);
|
||||
match result {
|
||||
Ok(value) => Ok(convert_nujson_to_value(&value, span)),
|
||||
|
||||
Err(_x) => Err(ShellError::CantConvert(
|
||||
"structured data from json".into(),
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
5
crates/nu-command/src/formats/from/mod.rs
Normal file
5
crates/nu-command/src/formats/from/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod command;
|
||||
mod json;
|
||||
|
||||
pub use command::From;
|
||||
pub use json::FromJson;
|
3
crates/nu-command/src/formats/mod.rs
Normal file
3
crates/nu-command/src/formats/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod from;
|
||||
|
||||
pub use from::*;
|
@ -1,8 +1,6 @@
|
||||
mod alias;
|
||||
mod benchmark;
|
||||
mod build_string;
|
||||
mod def;
|
||||
mod core_commands;
|
||||
mod default_context;
|
||||
<<<<<<< HEAD
|
||||
mod do_;
|
||||
mod each;
|
||||
mod for_;
|
||||
@ -25,3 +23,24 @@ pub use length::Length;
|
||||
pub use let_::Let;
|
||||
pub use let_env::LetEnv;
|
||||
pub use source::Source;
|
||||
=======
|
||||
mod env;
|
||||
mod experimental;
|
||||
mod filesystem;
|
||||
mod filters;
|
||||
mod formats;
|
||||
mod strings;
|
||||
mod system;
|
||||
mod viewers;
|
||||
|
||||
pub use core_commands::*;
|
||||
pub use default_context::*;
|
||||
pub use env::*;
|
||||
pub use experimental::*;
|
||||
pub use filesystem::*;
|
||||
pub use filters::*;
|
||||
pub use formats::*;
|
||||
pub use strings::*;
|
||||
pub use system::*;
|
||||
pub use viewers::*;
|
||||
>>>>>>> 3567bbbf32302dbc3cbf97a39b03efa3bd3e8bb5
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_engine::eval_expression;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{ShellError, Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct BuildString;
|
||||
|
||||
@ -24,13 +24,12 @@ impl Command for BuildString {
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
let mut output = vec![];
|
||||
let output = call
|
||||
.positional
|
||||
.iter()
|
||||
.map(|expr| eval_expression(context, expr).map(|val| val.into_string()))
|
||||
.collect::<Result<Vec<String>, ShellError>>()?;
|
||||
|
||||
for expr in &call.positional {
|
||||
let val = eval_expression(context, expr)?;
|
||||
|
||||
output.push(val.into_string());
|
||||
}
|
||||
Ok(Value::String {
|
||||
val: output.join(""),
|
||||
span: call.head,
|
3
crates/nu-command/src/strings/mod.rs
Normal file
3
crates/nu-command/src/strings/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod build_string;
|
||||
|
||||
pub use build_string::BuildString;
|
@ -17,7 +17,11 @@ impl Command for Benchmark {
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run")
|
||||
Signature::build("benchmark").required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"the block to run",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
9
crates/nu-command/src/system/mod.rs
Normal file
9
crates/nu-command/src/system/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
mod benchmark;
|
||||
mod ps;
|
||||
mod run_external;
|
||||
mod sys;
|
||||
|
||||
pub use benchmark::Benchmark;
|
||||
pub use ps::Ps;
|
||||
pub use run_external::{External, ExternalCommand};
|
||||
pub use sys::Sys;
|
128
crates/nu-command/src/system/ps.rs
Normal file
128
crates/nu-command/src/system/ps.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EvaluationContext},
|
||||
Example, ShellError, Signature, Value,
|
||||
};
|
||||
use sysinfo::{ProcessExt, System, SystemExt};
|
||||
|
||||
pub struct Ps;
|
||||
|
||||
impl Command for Ps {
|
||||
fn name(&self) -> &str {
|
||||
"ps"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ps")
|
||||
.desc("View information about system processes.")
|
||||
.switch(
|
||||
"long",
|
||||
"list all available columns for each entry",
|
||||
Some('l'),
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View information about system processes."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
run_ps(call)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "List the system processes",
|
||||
example: "ps",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn run_ps(call: &Call) -> Result<Value, ShellError> {
|
||||
let span = call.head;
|
||||
let long = call.has_flag("long");
|
||||
let mut sys = System::new_all();
|
||||
sys.refresh_all();
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
let result: Vec<_> = sys.processes().iter().map(|x| *x.0).collect();
|
||||
|
||||
for pid in result {
|
||||
if let Some(result) = sys.process(pid) {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
cols.push("pid".into());
|
||||
vals.push(Value::Int {
|
||||
val: pid as i64,
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String {
|
||||
val: result.name().into(),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("status".into());
|
||||
vals.push(Value::String {
|
||||
val: format!("{:?}", result.status()),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("cpu".into());
|
||||
vals.push(Value::Float {
|
||||
val: result.cpu_usage() as f64,
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("mem".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: result.memory() * 1000,
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("virtual".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: result.virtual_memory() * 1000,
|
||||
span,
|
||||
});
|
||||
|
||||
if long {
|
||||
cols.push("parent".into());
|
||||
if let Some(parent) = result.parent() {
|
||||
vals.push(Value::Int {
|
||||
val: parent as i64,
|
||||
span,
|
||||
});
|
||||
} else {
|
||||
vals.push(Value::Nothing { span });
|
||||
}
|
||||
|
||||
cols.push("exe".into());
|
||||
vals.push(Value::String {
|
||||
val: result.exe().to_string_lossy().to_string(),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("command".into());
|
||||
vals.push(Value::String {
|
||||
val: result.cmd().join(" "),
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
output.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::List { vals: output, span })
|
||||
}
|
281
crates/nu-command/src/system/run_external.rs
Normal file
281
crates/nu-command/src/system/run_external.rs
Normal file
@ -0,0 +1,281 @@
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{ChildStdin, Command as CommandSys, Stdio};
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use nu_protocol::{
|
||||
ast::{Call, Expression},
|
||||
engine::{Command, EvaluationContext},
|
||||
ShellError, Signature, SyntaxShape, Value,
|
||||
};
|
||||
use nu_protocol::{Span, ValueStream};
|
||||
|
||||
use nu_engine::eval_expression;
|
||||
|
||||
const OUTPUT_BUFFER_SIZE: usize = 8192;
|
||||
|
||||
pub struct External;
|
||||
|
||||
impl Command for External {
|
||||
fn name(&self) -> &str {
|
||||
"run_external"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs external command"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("run_external")
|
||||
.switch("last_expression", "last_expression", None)
|
||||
.rest("rest", SyntaxShape::Any, "external command to run")
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
context: &EvaluationContext,
|
||||
call: &Call,
|
||||
input: Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
let command = ExternalCommand::try_new(call, context)?;
|
||||
command.run_with_input(input)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExternalCommand<'call, 'contex> {
|
||||
pub name: &'call Expression,
|
||||
pub args: &'call [Expression],
|
||||
pub context: &'contex EvaluationContext,
|
||||
pub last_expression: bool,
|
||||
}
|
||||
|
||||
impl<'call, 'contex> ExternalCommand<'call, 'contex> {
|
||||
pub fn try_new(
|
||||
call: &'call Call,
|
||||
context: &'contex EvaluationContext,
|
||||
) -> Result<Self, ShellError> {
|
||||
if call.positional.is_empty() {
|
||||
return Err(ShellError::ExternalNotSupported(call.head));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
name: &call.positional[0],
|
||||
args: &call.positional[1..],
|
||||
context,
|
||||
last_expression: call.has_flag("last_expression"),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> Result<String, ShellError> {
|
||||
let value = eval_expression(self.context, self.name)?;
|
||||
value.as_string()
|
||||
}
|
||||
|
||||
pub fn get_args(&self) -> Vec<String> {
|
||||
self.args
|
||||
.iter()
|
||||
.filter_map(|expr| eval_expression(self.context, expr).ok())
|
||||
.filter_map(|value| value.as_string().ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn run_with_input(&self, input: Value) -> Result<Value, ShellError> {
|
||||
let mut process = self.create_command();
|
||||
|
||||
// TODO. We don't have a way to know the current directory
|
||||
// This should be information from the EvaluationContex or EngineState
|
||||
let path = env::current_dir().unwrap();
|
||||
process.current_dir(path);
|
||||
|
||||
let envs = self.context.stack.get_env_vars();
|
||||
process.envs(envs);
|
||||
|
||||
// If the external is not the last command, its output will get piped
|
||||
// either as a string or binary
|
||||
if !self.last_expression {
|
||||
process.stdout(Stdio::piped());
|
||||
}
|
||||
|
||||
// If there is an input from the pipeline. The stdin from the process
|
||||
// is piped so it can be used to send the input information
|
||||
if let Value::String { .. } = input {
|
||||
process.stdin(Stdio::piped());
|
||||
}
|
||||
|
||||
if let Value::Stream { .. } = input {
|
||||
process.stdin(Stdio::piped());
|
||||
}
|
||||
|
||||
match process.spawn() {
|
||||
Err(err) => Err(ShellError::ExternalCommand(
|
||||
format!("{}", err),
|
||||
self.name.span,
|
||||
)),
|
||||
Ok(mut child) => {
|
||||
// if there is a string or a stream, that is sent to the pipe std
|
||||
match input {
|
||||
Value::String { val, span: _ } => {
|
||||
if let Some(mut stdin_write) = child.stdin.take() {
|
||||
self.write_to_stdin(&mut stdin_write, val.as_bytes())?
|
||||
}
|
||||
}
|
||||
Value::Binary { val, span: _ } => {
|
||||
if let Some(mut stdin_write) = child.stdin.take() {
|
||||
self.write_to_stdin(&mut stdin_write, &val)?
|
||||
}
|
||||
}
|
||||
Value::Stream { stream, span: _ } => {
|
||||
if let Some(mut stdin_write) = child.stdin.take() {
|
||||
for value in stream {
|
||||
match value {
|
||||
Value::String { val, span: _ } => {
|
||||
self.write_to_stdin(&mut stdin_write, val.as_bytes())?
|
||||
}
|
||||
Value::Binary { val, span: _ } => {
|
||||
self.write_to_stdin(&mut stdin_write, &val)?
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// If this external is not the last expression, then its output is piped to a channel
|
||||
// and we create a ValueStream that can be consumed
|
||||
let value = if !self.last_expression {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let stdout = child.stdout.take().ok_or_else(|| {
|
||||
ShellError::ExternalCommand(
|
||||
"Error taking stdout from external".to_string(),
|
||||
self.name.span,
|
||||
)
|
||||
})?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// Stdout is read using the Buffer reader. It will do so until there is an
|
||||
// error or there are no more bytes to read
|
||||
let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, stdout);
|
||||
while let Ok(bytes) = buf_read.fill_buf() {
|
||||
if bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// The Cow generated from the function represents the conversion
|
||||
// from bytes to String. If no replacements are required, then the
|
||||
// borrowed value is a proper UTF-8 string. The Owned option represents
|
||||
// a string where the values had to be replaced, thus marking it as bytes
|
||||
let data = match String::from_utf8_lossy(bytes) {
|
||||
Cow::Borrowed(s) => Data::String(s.into()),
|
||||
Cow::Owned(_) => Data::Bytes(bytes.to_vec()),
|
||||
};
|
||||
|
||||
let length = bytes.len();
|
||||
buf_read.consume(length);
|
||||
|
||||
match tx.send(data) {
|
||||
Ok(_) => continue,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// The ValueStream is consumed by the next expression in the pipeline
|
||||
Value::Stream {
|
||||
stream: ValueStream(Rc::new(RefCell::new(ChannelReceiver::new(rx)))),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
} else {
|
||||
Value::nothing()
|
||||
};
|
||||
|
||||
match child.wait() {
|
||||
Err(err) => Err(ShellError::ExternalCommand(
|
||||
format!("{}", err),
|
||||
self.name.span,
|
||||
)),
|
||||
Ok(_) => Ok(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_command(&self) -> CommandSys {
|
||||
// in all the other cases shell out
|
||||
if cfg!(windows) {
|
||||
//TODO. This should be modifiable from the config file.
|
||||
// We could give the option to call from powershell
|
||||
// for minimal builds cwd is unused
|
||||
let mut process = CommandSys::new("cmd");
|
||||
process.arg("/c");
|
||||
process.arg(&self.get_name().unwrap());
|
||||
for arg in self.get_args() {
|
||||
// Clean the args before we use them:
|
||||
// https://stackoverflow.com/questions/1200235/how-to-pass-a-quoted-pipe-character-to-cmd-exe
|
||||
// cmd.exe needs to have a caret to escape a pipe
|
||||
let arg = arg.replace("|", "^|");
|
||||
process.arg(&arg);
|
||||
}
|
||||
process
|
||||
} else {
|
||||
let cmd_with_args = vec![self.get_name().unwrap(), self.get_args().join(" ")].join(" ");
|
||||
let mut process = CommandSys::new("sh");
|
||||
process.arg("-c").arg(cmd_with_args);
|
||||
process
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_stdin(&self, stdin_write: &mut ChildStdin, val: &[u8]) -> Result<(), ShellError> {
|
||||
if stdin_write.write(val).is_err() {
|
||||
Err(ShellError::ExternalCommand(
|
||||
"Error writing input to stdin".to_string(),
|
||||
self.name.span,
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The piped data from stdout from the external command can be either String
|
||||
// or binary. We use this enum to pass the data from the spawned process
|
||||
enum Data {
|
||||
String(String),
|
||||
Bytes(Vec<u8>),
|
||||
}
|
||||
|
||||
// Receiver used for the ValueStream
|
||||
// It implements iterator so it can be used as a ValueStream
|
||||
struct ChannelReceiver {
|
||||
rx: mpsc::Receiver<Data>,
|
||||
}
|
||||
|
||||
impl ChannelReceiver {
|
||||
pub fn new(rx: mpsc::Receiver<Data>) -> Self {
|
||||
Self { rx }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ChannelReceiver {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.rx.recv() {
|
||||
Ok(v) => match v {
|
||||
Data::String(s) => Some(Value::String {
|
||||
val: s,
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
Data::Bytes(b) => Some(Value::Binary {
|
||||
val: b,
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
356
crates/nu-command/src/system/sys.rs
Normal file
356
crates/nu-command/src/system/sys.rs
Normal file
@ -0,0 +1,356 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EvaluationContext},
|
||||
Example, ShellError, Signature, Span, Value,
|
||||
};
|
||||
use sysinfo::{ComponentExt, DiskExt, NetworkExt, ProcessorExt, System, SystemExt, UserExt};
|
||||
|
||||
pub struct Sys;
|
||||
|
||||
impl Command for Sys {
|
||||
fn name(&self) -> &str {
|
||||
"sys"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("sys")
|
||||
.desc("View information about the current system.")
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View information about the system."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
run_sys(call)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Show info about the system",
|
||||
example: "sys",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn run_sys(call: &Call) -> Result<Value, ShellError> {
|
||||
let span = call.head;
|
||||
let mut sys = System::new();
|
||||
|
||||
let mut headers = vec![];
|
||||
let mut values = vec![];
|
||||
|
||||
if let Some(value) = host(&mut sys, span) {
|
||||
headers.push("host".into());
|
||||
values.push(value);
|
||||
}
|
||||
if let Some(value) = cpu(&mut sys, span) {
|
||||
headers.push("cpu".into());
|
||||
values.push(value);
|
||||
}
|
||||
if let Some(value) = disks(&mut sys, span) {
|
||||
headers.push("disks".into());
|
||||
values.push(value);
|
||||
}
|
||||
if let Some(value) = mem(&mut sys, span) {
|
||||
headers.push("mem".into());
|
||||
values.push(value);
|
||||
}
|
||||
if let Some(value) = temp(&mut sys, span) {
|
||||
headers.push("temp".into());
|
||||
values.push(value);
|
||||
}
|
||||
if let Some(value) = net(&mut sys, span) {
|
||||
headers.push("net".into());
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
Ok(Value::Record {
|
||||
cols: headers,
|
||||
vals: values,
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn trim_cstyle_null(s: String) -> String {
|
||||
s.trim_matches(char::from(0)).to_string()
|
||||
}
|
||||
|
||||
pub fn disks(sys: &mut System, span: Span) -> Option<Value> {
|
||||
sys.refresh_disks();
|
||||
sys.refresh_disks_list();
|
||||
|
||||
let mut output = vec![];
|
||||
for disk in sys.disks() {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
cols.push("device".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(disk.name().to_string_lossy().to_string()),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("type".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(String::from_utf8_lossy(disk.file_system()).to_string()),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("mount".into());
|
||||
vals.push(Value::String {
|
||||
val: disk.mount_point().to_string_lossy().to_string(),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("total".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: disk.total_space(),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("free".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: disk.available_space(),
|
||||
span,
|
||||
});
|
||||
|
||||
output.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
if !output.is_empty() {
|
||||
Some(Value::List { vals: output, span })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn net(sys: &mut System, span: Span) -> Option<Value> {
|
||||
sys.refresh_networks();
|
||||
sys.refresh_networks_list();
|
||||
|
||||
let mut output = vec![];
|
||||
for (iface, data) in sys.networks() {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(iface.to_string()),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("sent".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: data.total_transmitted(),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("recv".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: data.total_received(),
|
||||
span,
|
||||
});
|
||||
|
||||
output.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
if !output.is_empty() {
|
||||
Some(Value::List { vals: output, span })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cpu(sys: &mut System, span: Span) -> Option<Value> {
|
||||
sys.refresh_cpu();
|
||||
|
||||
let mut output = vec![];
|
||||
for cpu in sys.processors() {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(cpu.name().to_string()),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("brand".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(cpu.brand().to_string()),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("freq".into());
|
||||
vals.push(Value::Int {
|
||||
val: cpu.frequency() as i64,
|
||||
span,
|
||||
});
|
||||
|
||||
output.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
if !output.is_empty() {
|
||||
Some(Value::List { vals: output, span })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mem(sys: &mut System, span: Span) -> Option<Value> {
|
||||
sys.refresh_memory();
|
||||
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let total_mem = sys.total_memory();
|
||||
let free_mem = sys.free_memory();
|
||||
let total_swap = sys.total_swap();
|
||||
let free_swap = sys.free_swap();
|
||||
|
||||
cols.push("total".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: total_mem * 1000,
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("free".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: free_mem * 1000,
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("swap total".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: total_swap * 1000,
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("swap free".into());
|
||||
vals.push(Value::Filesize {
|
||||
val: free_swap * 1000,
|
||||
span,
|
||||
});
|
||||
|
||||
Some(Value::Record { cols, vals, span })
|
||||
}
|
||||
|
||||
pub fn host(sys: &mut System, span: Span) -> Option<Value> {
|
||||
sys.refresh_users_list();
|
||||
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
if let Some(name) = sys.name() {
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(name),
|
||||
span,
|
||||
});
|
||||
}
|
||||
if let Some(version) = sys.os_version() {
|
||||
cols.push("os version".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(version),
|
||||
span,
|
||||
});
|
||||
}
|
||||
if let Some(version) = sys.kernel_version() {
|
||||
cols.push("kernel version".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(version),
|
||||
span,
|
||||
});
|
||||
}
|
||||
if let Some(hostname) = sys.host_name() {
|
||||
cols.push("hostname".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(hostname),
|
||||
span,
|
||||
});
|
||||
}
|
||||
// dict.insert_untagged(
|
||||
// "uptime",
|
||||
// UntaggedValue::duration(1000000000 * sys.uptime() as i64),
|
||||
// );
|
||||
|
||||
let mut users = vec![];
|
||||
for user in sys.users() {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String {
|
||||
val: trim_cstyle_null(user.name().to_string()),
|
||||
span,
|
||||
});
|
||||
|
||||
let mut groups = vec![];
|
||||
for group in user.groups() {
|
||||
groups.push(Value::String {
|
||||
val: trim_cstyle_null(group.to_string()),
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
cols.push("groups".into());
|
||||
vals.push(Value::List { vals: groups, span });
|
||||
|
||||
users.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
if !users.is_empty() {
|
||||
cols.push("sessions".into());
|
||||
vals.push(Value::List { vals: users, span });
|
||||
}
|
||||
|
||||
Some(Value::Record { cols, vals, span })
|
||||
}
|
||||
|
||||
pub fn temp(sys: &mut System, span: Span) -> Option<Value> {
|
||||
sys.refresh_components();
|
||||
sys.refresh_components_list();
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
for component in sys.components() {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
cols.push("unit".into());
|
||||
vals.push(Value::String {
|
||||
val: component.label().to_string(),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("temp".into());
|
||||
vals.push(Value::Float {
|
||||
val: component.temperature() as f64,
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("high".into());
|
||||
vals.push(Value::Float {
|
||||
val: component.max() as f64,
|
||||
span,
|
||||
});
|
||||
|
||||
if let Some(critical) = component.critical() {
|
||||
cols.push("critical".into());
|
||||
vals.push(Value::Float {
|
||||
val: critical as f64,
|
||||
span,
|
||||
});
|
||||
}
|
||||
output.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
if !output.is_empty() {
|
||||
Some(Value::List { vals: output, span })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
3
crates/nu-command/src/viewers/mod.rs
Normal file
3
crates/nu-command/src/viewers/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod table;
|
||||
|
||||
pub use table::Table;
|
166
crates/nu-command/src/viewers/table.rs
Normal file
166
crates/nu-command/src/viewers/table.rs
Normal file
@ -0,0 +1,166 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nu_protocol::ast::{Call, PathMember};
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{Signature, Span, Value};
|
||||
use nu_table::StyledString;
|
||||
|
||||
pub struct Table;
|
||||
|
||||
//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one.
|
||||
impl Command for Table {
|
||||
fn name(&self) -> &str {
|
||||
"table"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Render the table."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("table")
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
call: &Call,
|
||||
input: Value,
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
match input {
|
||||
Value::List { vals, .. } => {
|
||||
let table = convert_to_table(vals);
|
||||
|
||||
if let Some(table) = table {
|
||||
let result = nu_table::draw_table(&table, 80, &HashMap::new());
|
||||
|
||||
Ok(Value::String {
|
||||
val: result,
|
||||
span: call.head,
|
||||
})
|
||||
} else {
|
||||
Ok(Value::Nothing { span: call.head })
|
||||
}
|
||||
}
|
||||
Value::Stream { stream, .. } => {
|
||||
let table = convert_to_table(stream);
|
||||
|
||||
if let Some(table) = table {
|
||||
let result = nu_table::draw_table(&table, 80, &HashMap::new());
|
||||
|
||||
Ok(Value::String {
|
||||
val: result,
|
||||
span: call.head,
|
||||
})
|
||||
} else {
|
||||
Ok(Value::Nothing { span: call.head })
|
||||
}
|
||||
}
|
||||
Value::Record { cols, vals, .. } => {
|
||||
let mut output = vec![];
|
||||
|
||||
for (c, v) in cols.into_iter().zip(vals.into_iter()) {
|
||||
output.push(vec![
|
||||
StyledString {
|
||||
contents: c,
|
||||
style: nu_table::TextStyle::default_header(),
|
||||
},
|
||||
StyledString {
|
||||
contents: v.into_string(),
|
||||
style: nu_table::TextStyle::default(),
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
let table = nu_table::Table {
|
||||
headers: vec![],
|
||||
data: output,
|
||||
theme: nu_table::Theme::rounded(),
|
||||
};
|
||||
|
||||
let result = nu_table::draw_table(&table, 80, &HashMap::new());
|
||||
|
||||
Ok(Value::String {
|
||||
val: result,
|
||||
span: call.head,
|
||||
})
|
||||
}
|
||||
x => Ok(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_table(iter: impl IntoIterator<Item = Value>) -> Option<nu_table::Table> {
|
||||
let mut iter = iter.into_iter().peekable();
|
||||
|
||||
if let Some(first) = iter.peek() {
|
||||
let mut headers = first.columns();
|
||||
|
||||
if !headers.is_empty() {
|
||||
headers.insert(0, "#".into());
|
||||
}
|
||||
|
||||
let mut data = vec![];
|
||||
|
||||
for (row_num, item) in iter.enumerate() {
|
||||
let mut row = vec![row_num.to_string()];
|
||||
|
||||
if headers.is_empty() {
|
||||
row.push(item.into_string())
|
||||
} else {
|
||||
for header in headers.iter().skip(1) {
|
||||
let result = match item {
|
||||
Value::Record { .. } => {
|
||||
item.clone().follow_cell_path(&[PathMember::String {
|
||||
val: header.into(),
|
||||
span: Span::unknown(),
|
||||
}])
|
||||
}
|
||||
_ => Ok(item.clone()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(value) => row.push(value.into_string()),
|
||||
Err(_) => row.push(String::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.push(row);
|
||||
}
|
||||
|
||||
Some(nu_table::Table {
|
||||
headers: headers
|
||||
.into_iter()
|
||||
.map(|x| StyledString {
|
||||
contents: x,
|
||||
style: nu_table::TextStyle::default_header(),
|
||||
})
|
||||
.collect(),
|
||||
data: data
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
x.into_iter()
|
||||
.enumerate()
|
||||
.map(|(col, y)| {
|
||||
if col == 0 {
|
||||
StyledString {
|
||||
contents: y,
|
||||
style: nu_table::TextStyle::default_header(),
|
||||
}
|
||||
} else {
|
||||
StyledString {
|
||||
contents: y,
|
||||
style: nu_table::TextStyle::basic_left(),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<StyledString>>()
|
||||
})
|
||||
.collect(),
|
||||
theme: nu_table::Theme::rounded(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user