use itertools::Itertools;
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct Tutor;
impl Command for Tutor {
fn name(&self) -> &str {
"tutor"
}
fn signature(&self) -> Signature {
Signature::build("tutor")
.input_output_types(vec![(Type::Nothing, Type::String)])
.allow_variants_without_examples(true)
.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::Misc)
}
fn description(&self) -> &str {
"Run the tutorial. To begin, run: tutor."
}
fn search_terms(&self) -> Vec<&str> {
vec!["help", "learn", "tutorial"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, 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 --find \"$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 notes = "You 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";
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!["block", "blocks"], block_tutor()),
(vec!["closure", "closures"], closure_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 '{find}' in the following topics:\n\n{}\n\n{notes}",
results.into_iter().map(|x| format!("- {x}")).join("\n")
);
return Ok(display(&message, engine_state, stack, span));
} else if let Some(search) = search {
if search == "list" {
let results = search_space.map(|s| s.0[0].to_string());
let message = format!(
"This tutorial contains the following topics:\n\n{}\n\n{notes}",
results.map(|x| format!("- {x}")).join("\n")
);
return Ok(display(&message, engine_state, stack, span));
}
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 {
"
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`, and to see all the available
tutorials just run `tutor list`.
"
}
fn begin_tutor() -> &'static str {
"
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 `select` command:
```
ls | select 2
```
This will get the 3rd (note that `select` is zero-based) row in the table created
by the `ls` command. You can use `select` 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 {
"
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 | select 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 {
"
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 {
"
The `each` command gives us a way of working with the individual elements
(sometimes called 'rows') of a list one at a time. It reads these in from
the pipeline and runs a closure on each one.
```
echo 1 2 3 | each { |it| $it + 10}
```
This example iterates over each element sent by `echo`, giving us three new
values that are the original value + 10. Here, `|it|` declares a closure
parameter named `$it` that receives each element.
You can learn more about closures by running:
```
tutor closures
```
You can also learn more about variables by running:
```
tutor variables
```
"
}
fn variable_tutor() -> &'static str {
"
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 closure 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 pieces of code wrapped in curly braces `{}` that are used with
control flow commands. Unlike closures, blocks don't have parameters and
can't be passed as values. (You can learn more about closures by running
`tutor closures`)
You'll most commonly see blocks used with `if`:
```
if true { print "it's true" } else { print "it's not true" }
```
This runs the first block if the expression is true, or the second block
if the expression is false.
Blocks can also be used with loops like `while` and `for`:
```
mut x = 0
while $x < 5 {
$x += 1
}
```
One special feature of blocks is that they can modify mutable variables
from the parent scope. For example, the `while` loop above mutates `$x`
even though it was declared outside the block.
"#
}
fn closure_tutor() -> &'static str {
"
Closures are blocks of code you can pass to commands. They can accept
parameters and are commonly used with commands like `each`, `where`, and
`reduce`.
The parameter list is written between `|` symbols. Inside the closure, you
refer to the parameter with a `$` prefix:
```
[1 2 3] | each { |x| $x + 10 }
```
Closures can also use values from the surrounding scope:
```
let multiplier = 3
[1 2 3] | each { |x| $x * $multiplier }
```
Many commands also make the incoming pipeline value available as `$in` inside the closure.
"
}
fn shorthand_tutor() -> &'static str {
r#"
You can access data in a structure via a shorthand notation called a "cell path",
sometimes called a "column path". These paths allow you to go from a structure to
rows, columns, or cells inside of the structure.
Shorthand paths are made from rows numbers, column names, or both. You can use
them on any variable or subexpression.
```
$env.PWD
```
The above accesses the built-in `$env` variable, gets its table, and then uses
the shorthand path to retrieve only the cell data inside the "PWD" column.
```
(ls).name.4
```
This will retrieve the cell data in the "name" column on the 5th row (note:
row numbers are zero-based).
For tables, rows and columns don't need to come in any specific order. You can
produce the same value using:
```
(ls).4.name
```
"#
}
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;
if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) {
let decl = engine_state.get_decl(highlighter);
let result = decl.run(
engine_state,
stack,
&Call::new(span),
Value::string(item, span).into_pipeline_data(),
);
if let Ok(value) = result.and_then(|data| data.into_value(span)) {
match value.coerce_into_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() -> nu_test_support::Result {
nu_test_support::test().examples(Tutor)
}
}