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!["shorthand", "shorthands"], shorthand_tutor()),
102    ];
103
104    if let Some(find) = find {
105        let mut results = vec![];
106        for search_group in search_space {
107            if search_group.1.contains(&find) {
108                results.push(search_group.0[0].to_string())
109            }
110        }
111
112        let message = format!(
113            "You can find '{find}' in the following topics:\n\n{}\n\n{notes}",
114            results.into_iter().map(|x| format!("- {x}")).join("\n")
115        );
116
117        return Ok(display(&message, engine_state, stack, span));
118    } else if let Some(search) = search {
119        if search == "list" {
120            let results = search_space.map(|s| s.0[0].to_string());
121            let message = format!(
122                "This tutorial contains the following topics:\n\n{}\n\n{notes}",
123                results.map(|x| format!("- {x}")).join("\n")
124            );
125            return Ok(display(&message, engine_state, stack, span));
126        }
127
128        for search_group in search_space {
129            if search_group.0.contains(&search.as_str()) {
130                return Ok(display(search_group.1, engine_state, stack, span));
131            }
132        }
133    }
134    Ok(display(default_tutor(), engine_state, stack, span))
135}
136
137fn default_tutor() -> &'static str {
138    r#"
139Welcome to the Nushell tutorial!
140
141With the `tutor` command, you'll be able to learn a lot about how Nushell
142works along with many fun tips and tricks to speed up everyday tasks.
143
144To get started, you can use `tutor begin`, and to see all the available
145tutorials just run `tutor list`.
146
147"#
148}
149
150fn begin_tutor() -> &'static str {
151    r#"
152Nushell is a structured shell and programming language. One way to begin
153using it is to try a few of the commands.
154
155The first command to try is `ls`. The `ls` command will show you a list
156of the files in the current directory. Notice that these files are shown
157as a table. Each column of this table not only tells us what is being
158shown, but also gives us a way to work with the data.
159
160You can combine the `ls` command with other commands using the pipeline
161symbol '|'. This allows data to flow from one command to the next.
162
163For example, if we only wanted the name column, we could do:
164```
165ls | select name
166```
167Notice that we still get a table, but this time it only has one column:
168the name column.
169
170You can continue to learn more about tables by running:
171```
172tutor tables
173```
174If at any point, you'd like to restart this tutorial, you can run:
175```
176tutor begin
177```
178"#
179}
180
181fn table_tutor() -> &'static str {
182    r#"
183The most common form of data in Nushell is the table. Tables contain rows and
184columns of data. In each cell of the table, there is data that you can access
185using Nushell commands.
186
187To get the 3rd row in the table, you can use the `select` command:
188```
189ls | select 2
190```
191This will get the 3rd (note that `select` is zero-based) row in the table created
192by the `ls` command. You can use `select` on any table created by other commands
193as well.
194
195You can also access the column of data in one of two ways. If you want
196to keep the column as part of a new table, you can use `select`.
197```
198ls | select name
199```
200This runs `ls` and returns only the "name" column of the table.
201
202If, instead, you'd like to get access to the values inside of the column, you
203can use the `get` command.
204```
205ls | get name
206```
207This allows us to get to the list of strings that are the filenames rather
208than having a full table. In some cases, this can make the names easier to
209work with.
210
211You can continue to learn more about working with cells of the table by
212running:
213```
214tutor cells
215```
216"#
217}
218
219fn cell_tutor() -> &'static str {
220    r#"
221Working with cells of data in the table is a key part of working with data in
222Nushell. Because of this, there is a rich list of commands to work with cells
223as well as handy shorthands for accessing cells.
224
225Cells can hold simple values like strings and numbers, or more complex values
226like lists and tables.
227
228To reach a cell of data from a table, you can combine a row operation and a
229column operation.
230```
231ls | select 4 | get name
232```
233You can combine these operations into one step using a shortcut.
234```
235(ls).4.name
236```
237Names/strings represent columns names and numbers represent row numbers.
238
239The `(ls)` is a form of expression. You can continue to learn more about
240expressions by running:
241```
242tutor expressions
243```
244You can also learn about these cell shorthands by running:
245```
246tutor shorthands
247```
248"#
249}
250
251fn expression_tutor() -> &'static str {
252    r#"
253Expressions give you the power to mix calls to commands with math. The
254simplest expression is a single value like a string or number.
255```
2563
257```
258Expressions can also include math operations like addition or division.
259```
26010 / 2
261```
262Normally, an expression is one type of operation: math or commands. You can
263mix these types by using subexpressions. Subexpressions are just like
264expressions, but they're wrapped in parentheses `()`.
265```
26610 * (3 + 4)
267```
268Here we use parentheses to create a higher math precedence in the math
269expression.
270```
271echo (2 + 3)
272```
273You can continue to learn more about the `echo` command by running:
274```
275tutor echo
276```
277"#
278}
279
280fn echo_tutor() -> &'static str {
281    r#"
282The `echo` command in Nushell is a powerful tool for not only seeing values,
283but also for creating new ones.
284```
285echo "Hello"
286```
287You can echo output. This output, if it's not redirected using a "|" pipeline
288will be displayed to the screen.
289```
290echo 1..10
291```
292You can also use echo to work with individual values of a range. In this
293example, `echo` will create the values from 1 to 10 as a list.
294```
295echo 1 2 3 4 5
296```
297You can also create lists of values by passing `echo` multiple arguments.
298This can be helpful if you want to later processes these values.
299
300The `echo` command can pair well with the `each` command which can run
301code on each row, or item, of input.
302
303You can continue to learn more about the `each` command by running:
304```
305tutor each
306```
307"#
308}
309
310fn each_tutor() -> &'static str {
311    r#"
312The `each` command gives us a way of working with the individual elements
313(sometimes called 'rows') of a list one at a time. It reads these in from
314the pipeline and runs a block on each one. A block is a group of pipelines.
315```
316echo 1 2 3 | each { |it| $it + 10}
317```
318This example iterates over each element sent by `echo`, giving us three new
319values that are the original value + 10. Here, the `$it` is a variable that
320is the name given to the block's parameter by default.
321
322You can learn more about blocks by running:
323```
324tutor blocks
325```
326You can also learn more about variables by running:
327```
328tutor variables
329```
330"#
331}
332
333fn variable_tutor() -> &'static str {
334    r#"
335Variables are an important way to store values to be used later. To create a
336variable, you can use the `let` keyword. The `let` command will create a
337variable and then assign it a value in one step.
338```
339let $x = 3
340```
341Once created, we can refer to this variable by name.
342```
343$x
344```
345Nushell also comes with built-in variables. The `$nu` variable is a reserved
346variable that contains a lot of information about the currently running
347instance of Nushell. The `$it` variable is the name given to block parameters
348if you don't specify one. And `$in` is the variable that allows you to work
349with all of the data coming in from the pipeline in one place.
350
351"#
352}
353
354fn block_tutor() -> &'static str {
355    r#"
356Blocks are a special form of expression that hold code to be run at a later
357time. Often, you'll see blocks as one of the arguments given to commands
358like `each` and `if`.
359```
360ls | each {|x| $x.name}
361```
362The above will create a list of the filenames in the directory.
363```
364if true { echo "it's true" } else { echo "it's not true" }
365```
366This `if` call will run the first block if the expression is true, or the
367second block if the expression is false.
368
369"#
370}
371
372fn shorthand_tutor() -> &'static str {
373    r#"
374You can access data in a structure via a shorthand notation called a "cell path",
375sometimes called a "column path". These paths allow you to go from a structure to
376rows, columns, or cells inside of the structure.
377
378Shorthand paths are made from rows numbers, column names, or both. You can use
379them on any variable or subexpression.
380```
381$env.PWD
382```
383The above accesses the built-in `$env` variable, gets its table, and then uses
384the shorthand path to retrieve only the cell data inside the "PWD" column.
385```
386(ls).name.4
387```
388This will retrieve the cell data in the "name" column on the 5th row (note:
389row numbers are zero-based).
390
391For tables, rows and columns don't need to come in any specific order. You can
392produce the same value using:
393```
394(ls).4.name
395```
396"#
397}
398
399fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span) -> PipelineData {
400    let help = help.split('`');
401
402    let mut build = String::new();
403    let mut code_mode = false;
404
405    for item in help {
406        if code_mode {
407            code_mode = false;
408
409            //TODO: support no-color mode
410            if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) {
411                let decl = engine_state.get_decl(highlighter);
412                let result = decl.run(
413                    engine_state,
414                    stack,
415                    &Call::new(span),
416                    Value::string(item, Span::unknown()).into_pipeline_data(),
417                );
418
419                if let Ok(value) = result.and_then(|data| data.into_value(Span::unknown())) {
420                    match value.coerce_into_string() {
421                        Ok(s) => {
422                            build.push_str(&s);
423                        }
424                        _ => {
425                            build.push_str(item);
426                        }
427                    }
428                }
429            }
430        } else {
431            code_mode = true;
432            build.push_str(item);
433        }
434    }
435
436    Value::string(build, span).into_pipeline_data()
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    #[test]
444    fn test_examples() {
445        use crate::test_examples;
446
447        test_examples(Tutor)
448    }
449}