Skip to main content

nu_command/misc/
tutor.rs

1use itertools::Itertools;
2use nu_engine::command_prelude::*;
3
4#[derive(Clone)]
5pub struct Tutor;
6
7impl Command for Tutor {
8    fn name(&self) -> &str {
9        "tutor"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("tutor")
14            .input_output_types(vec![(Type::Nothing, Type::String)])
15            .allow_variants_without_examples(true)
16            .optional(
17                "search",
18                SyntaxShape::String,
19                "Item to search for, or 'list' to list available tutorials.",
20            )
21            .named(
22                "find",
23                SyntaxShape::String,
24                "Search tutorial for a phrase.",
25                Some('f'),
26            )
27            .category(Category::Misc)
28    }
29
30    fn description(&self) -> &str {
31        "Run the tutorial. To begin, run: tutor."
32    }
33
34    fn search_terms(&self) -> Vec<&str> {
35        vec!["help", "learn", "tutorial"]
36    }
37
38    fn run(
39        &self,
40        engine_state: &EngineState,
41        stack: &mut Stack,
42        call: &Call,
43        _input: PipelineData,
44    ) -> Result<PipelineData, ShellError> {
45        tutor(engine_state, stack, call)
46    }
47
48    fn examples(&self) -> Vec<Example<'_>> {
49        vec![
50            Example {
51                description: "Begin the tutorial.",
52                example: "tutor begin",
53                result: None,
54            },
55            Example {
56                description: "Search a tutorial by phrase.",
57                example: "tutor --find \"$in\"",
58                result: None,
59            },
60        ]
61    }
62}
63
64fn tutor(
65    engine_state: &EngineState,
66    stack: &mut Stack,
67    call: &Call,
68) -> Result<PipelineData, ShellError> {
69    let span = call.head;
70
71    let search: Option<String> = call.opt(engine_state, stack, 0).unwrap_or(None);
72    let find: Option<String> = call.get_flag(engine_state, stack, "find")?;
73    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";
74
75    let search_space = [
76        (vec!["begin"], begin_tutor()),
77        (
78            vec!["table", "tables", "row", "rows", "column", "columns"],
79            table_tutor(),
80        ),
81        (vec!["cell", "cells"], cell_tutor()),
82        (
83            vec![
84                "expr",
85                "exprs",
86                "expressions",
87                "subexpression",
88                "subexpressions",
89                "sub-expression",
90                "sub-expressions",
91            ],
92            expression_tutor(),
93        ),
94        (vec!["echo"], echo_tutor()),
95        (vec!["each", "iteration", "iter"], each_tutor()),
96        (
97            vec!["var", "vars", "variable", "variables"],
98            variable_tutor(),
99        ),
100        (vec!["block", "blocks"], block_tutor()),
101        (vec!["closure", "closures"], closure_tutor()),
102        (vec!["shorthand", "shorthands"], shorthand_tutor()),
103    ];
104
105    if let Some(find) = find {
106        let mut results = vec![];
107        for search_group in search_space {
108            if search_group.1.contains(&find) {
109                results.push(search_group.0[0].to_string())
110            }
111        }
112
113        let message = format!(
114            "You can find '{find}' in the following topics:\n\n{}\n\n{notes}",
115            results.into_iter().map(|x| format!("- {x}")).join("\n")
116        );
117
118        return Ok(display(&message, engine_state, stack, span));
119    } else if let Some(search) = search {
120        if search == "list" {
121            let results = search_space.map(|s| s.0[0].to_string());
122            let message = format!(
123                "This tutorial contains the following topics:\n\n{}\n\n{notes}",
124                results.map(|x| format!("- {x}")).join("\n")
125            );
126            return Ok(display(&message, engine_state, stack, span));
127        }
128
129        for search_group in search_space {
130            if search_group.0.contains(&search.as_str()) {
131                return Ok(display(search_group.1, engine_state, stack, span));
132            }
133        }
134    }
135    Ok(display(default_tutor(), engine_state, stack, span))
136}
137
138fn default_tutor() -> &'static str {
139    r#"
140Welcome to the Nushell tutorial!
141
142With the `tutor` command, you'll be able to learn a lot about how Nushell
143works along with many fun tips and tricks to speed up everyday tasks.
144
145To get started, you can use `tutor begin`, and to see all the available
146tutorials just run `tutor list`.
147
148"#
149}
150
151fn begin_tutor() -> &'static str {
152    r#"
153Nushell is a structured shell and programming language. One way to begin
154using it is to try a few of the commands.
155
156The first command to try is `ls`. The `ls` command will show you a list
157of the files in the current directory. Notice that these files are shown
158as a table. Each column of this table not only tells us what is being
159shown, but also gives us a way to work with the data.
160
161You can combine the `ls` command with other commands using the pipeline
162symbol '|'. This allows data to flow from one command to the next.
163
164For example, if we only wanted the name column, we could do:
165```
166ls | select name
167```
168Notice that we still get a table, but this time it only has one column:
169the name column.
170
171You can continue to learn more about tables by running:
172```
173tutor tables
174```
175If at any point, you'd like to restart this tutorial, you can run:
176```
177tutor begin
178```
179"#
180}
181
182fn table_tutor() -> &'static str {
183    r#"
184The most common form of data in Nushell is the table. Tables contain rows and
185columns of data. In each cell of the table, there is data that you can access
186using Nushell commands.
187
188To get the 3rd row in the table, you can use the `select` command:
189```
190ls | select 2
191```
192This will get the 3rd (note that `select` is zero-based) row in the table created
193by the `ls` command. You can use `select` on any table created by other commands
194as well.
195
196You can also access the column of data in one of two ways. If you want
197to keep the column as part of a new table, you can use `select`.
198```
199ls | select name
200```
201This runs `ls` and returns only the "name" column of the table.
202
203If, instead, you'd like to get access to the values inside of the column, you
204can use the `get` command.
205```
206ls | get name
207```
208This allows us to get to the list of strings that are the filenames rather
209than having a full table. In some cases, this can make the names easier to
210work with.
211
212You can continue to learn more about working with cells of the table by
213running:
214```
215tutor cells
216```
217"#
218}
219
220fn cell_tutor() -> &'static str {
221    r#"
222Working with cells of data in the table is a key part of working with data in
223Nushell. Because of this, there is a rich list of commands to work with cells
224as well as handy shorthands for accessing cells.
225
226Cells can hold simple values like strings and numbers, or more complex values
227like lists and tables.
228
229To reach a cell of data from a table, you can combine a row operation and a
230column operation.
231```
232ls | select 4 | get name
233```
234You can combine these operations into one step using a shortcut.
235```
236(ls).4.name
237```
238Names/strings represent columns names and numbers represent row numbers.
239
240The `(ls)` is a form of expression. You can continue to learn more about
241expressions by running:
242```
243tutor expressions
244```
245You can also learn about these cell shorthands by running:
246```
247tutor shorthands
248```
249"#
250}
251
252fn expression_tutor() -> &'static str {
253    r#"
254Expressions give you the power to mix calls to commands with math. The
255simplest expression is a single value like a string or number.
256```
2573
258```
259Expressions can also include math operations like addition or division.
260```
26110 / 2
262```
263Normally, an expression is one type of operation: math or commands. You can
264mix these types by using subexpressions. Subexpressions are just like
265expressions, but they're wrapped in parentheses `()`.
266```
26710 * (3 + 4)
268```
269Here we use parentheses to create a higher math precedence in the math
270expression.
271```
272echo (2 + 3)
273```
274You can continue to learn more about the `echo` command by running:
275```
276tutor echo
277```
278"#
279}
280
281fn echo_tutor() -> &'static str {
282    r#"
283The `echo` command in Nushell is a powerful tool for not only seeing values,
284but also for creating new ones.
285```
286echo "Hello"
287```
288You can echo output. This output, if it's not redirected using a "|" pipeline
289will be displayed to the screen.
290```
291echo 1..10
292```
293You can also use echo to work with individual values of a range. In this
294example, `echo` will create the values from 1 to 10 as a list.
295```
296echo 1 2 3 4 5
297```
298You can also create lists of values by passing `echo` multiple arguments.
299This can be helpful if you want to later processes these values.
300
301The `echo` command can pair well with the `each` command which can run
302code on each row, or item, of input.
303
304You can continue to learn more about the `each` command by running:
305```
306tutor each
307```
308"#
309}
310
311fn each_tutor() -> &'static str {
312    r#"
313The `each` command gives us a way of working with the individual elements
314(sometimes called 'rows') of a list one at a time. It reads these in from
315the pipeline and runs a closure on each one.
316```
317echo 1 2 3 | each { |it| $it + 10}
318```
319This example iterates over each element sent by `echo`, giving us three new
320values that are the original value + 10. Here, `|it|` declares a closure
321parameter named `$it` that receives each element.
322
323You can learn more about closures by running:
324```
325tutor closures
326```
327You can also learn more about variables by running:
328```
329tutor variables
330```
331"#
332}
333
334fn variable_tutor() -> &'static str {
335    r#"
336Variables are an important way to store values to be used later. To create a
337variable, you can use the `let` keyword. The `let` command will create a
338variable and then assign it a value in one step.
339```
340let x = 3
341```
342Once created, we can refer to this variable by name.
343```
344$x
345```
346Nushell also comes with built-in variables. The `$nu` variable is a reserved
347variable that contains a lot of information about the currently running
348instance of Nushell. The `$it` variable is the name given to closure parameters
349if you don't specify one. And `$in` is the variable that allows you to work
350with all of the data coming in from the pipeline in one place.
351
352"#
353}
354
355fn block_tutor() -> &'static str {
356    r#"
357Blocks are pieces of code wrapped in curly braces `{}` that are used with
358control flow commands. Unlike closures, blocks don't have parameters and
359can't be passed as values. (You can learn more about closures by running
360`tutor closures`)
361
362You'll most commonly see blocks used with `if`:
363```
364if true { print "it's true" } else { print "it's not true" }
365```
366This runs the first block if the expression is true, or the second block
367if the expression is false.
368
369Blocks can also be used with loops like `while` and `for`:
370```
371mut x = 0
372while $x < 5 {
373    $x += 1
374}
375```
376One special feature of blocks is that they can modify mutable variables
377from the parent scope. For example, the `while` loop above mutates `$x`
378even though it was declared outside the block.
379"#
380}
381
382fn closure_tutor() -> &'static str {
383    r#"
384Closures are blocks of code you can pass to commands. They can accept
385parameters and are commonly used with commands like `each`, `where`, and
386`reduce`.
387
388The parameter list is written between `|` symbols. Inside the closure, you
389refer to the parameter with a `$` prefix:
390```
391[1 2 3] | each { |x| $x + 10 }
392```
393
394Closures can also use values from the surrounding scope:
395```
396let multiplier = 3
397[1 2 3] | each { |x| $x * $multiplier }
398```
399
400Many commands also make the incoming pipeline value available as `$in` inside the closure.
401
402"#
403}
404
405fn shorthand_tutor() -> &'static str {
406    r#"
407You can access data in a structure via a shorthand notation called a "cell path",
408sometimes called a "column path". These paths allow you to go from a structure to
409rows, columns, or cells inside of the structure.
410
411Shorthand paths are made from rows numbers, column names, or both. You can use
412them on any variable or subexpression.
413```
414$env.PWD
415```
416The above accesses the built-in `$env` variable, gets its table, and then uses
417the shorthand path to retrieve only the cell data inside the "PWD" column.
418```
419(ls).name.4
420```
421This will retrieve the cell data in the "name" column on the 5th row (note:
422row numbers are zero-based).
423
424For tables, rows and columns don't need to come in any specific order. You can
425produce the same value using:
426```
427(ls).4.name
428```
429"#
430}
431
432fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span) -> PipelineData {
433    let help = help.split('`');
434
435    let mut build = String::new();
436    let mut code_mode = false;
437
438    for item in help {
439        if code_mode {
440            code_mode = false;
441
442            //TODO: support no-color mode
443            if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) {
444                let decl = engine_state.get_decl(highlighter);
445                let result = decl.run(
446                    engine_state,
447                    stack,
448                    &Call::new(span),
449                    Value::string(item, Span::unknown()).into_pipeline_data(),
450                );
451
452                if let Ok(value) = result.and_then(|data| data.into_value(Span::unknown())) {
453                    match value.coerce_into_string() {
454                        Ok(s) => {
455                            build.push_str(&s);
456                        }
457                        _ => {
458                            build.push_str(item);
459                        }
460                    }
461                }
462            }
463        } else {
464            code_mode = true;
465            build.push_str(item);
466        }
467    }
468
469    Value::string(build, span).into_pipeline_data()
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475
476    #[test]
477    fn test_examples() {
478        use crate::test_examples;
479
480        test_examples(Tutor)
481    }
482}