diff --git a/crates/nu-command/src/commands/core_commands/mod.rs b/crates/nu-command/src/commands/core_commands/mod.rs index ea48e353b1..0becc5231d 100644 --- a/crates/nu-command/src/commands/core_commands/mod.rs +++ b/crates/nu-command/src/commands/core_commands/mod.rs @@ -13,6 +13,7 @@ mod nu_plugin; mod nu_signature; mod source; mod tags; +mod tutor; mod unalias; mod version; @@ -33,5 +34,6 @@ pub use ignore::Ignore; pub use let_::Let; pub use source::Source; pub use tags::Tags; +pub use tutor::Tutor; pub use unalias::Unalias; pub use version::{version, Version}; diff --git a/crates/nu-command/src/commands/core_commands/tutor.rs b/crates/nu-command/src/commands/core_commands/tutor.rs new file mode 100644 index 0000000000..2538ccbdb8 --- /dev/null +++ b/crates/nu-command/src/commands/core_commands/tutor.rs @@ -0,0 +1,407 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Signature, SyntaxShape, UntaggedValue}; + +pub struct Tutor; + +impl WholeStreamCommand 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'), + ) + } + + fn usage(&self) -> &str { + "Run the tutorial. To begin, run: tutor" + } + + fn run(&self, args: CommandArgs) -> Result { + tutor(args) + } + + fn examples(&self) -> Vec { + 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(args: CommandArgs) -> Result { + let tag = args.name_tag(); + let scope = args.scope().clone(); + + let search: Option = args.opt(0).unwrap_or(None); + let find: Option = args.get_flag("find")?; + + let search_space = vec![ + (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!["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.as_str()) { + 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(tag, &scope, &message)); + } else if let Some(search) = search { + for search_group in search_space { + if search_group.0.contains(&search.as_str()) { + return Ok(display(tag, &scope, search_group.1)); + } + } + } + Ok(display(tag, &scope, default_tutor())) +} + +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 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 `echo` 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 display(tag: Tag, scope: &Scope, help: &str) -> OutputStream { + let help = help.split('`'); + + let mut build = String::new(); + let mut code_mode = false; + let palette = nu_engine::DefaultPalette {}; + + for item in help { + if code_mode { + code_mode = false; + + //TODO: support no-color mode + let colored_example = nu_engine::Painter::paint_string(item, scope, &palette); + build.push_str(&format!("{}", colored_example)); + } else { + code_mode = true; + build.push_str(item); + } + } + + OutputStream::one(UntaggedValue::string(build).into_value(tag)) +} + +#[cfg(test)] +mod tests { + use super::ShellError; + use super::Tutor; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + test_examples(Tutor {}) + } +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a13c3f96ab..b6c7ff38c2 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -20,6 +20,7 @@ pub fn create_default_context(interactive: bool) -> Result