Skip to main content

nu_cmd_lang/core_commands/overlay/
use_.rs

1use nu_engine::{
2    command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block, redirect_env,
3};
4use nu_parser::trim_quotes_str;
5use nu_protocol::{ModuleId, ast::Expr, engine::CommandType};
6
7use std::path::Path;
8
9#[derive(Clone)]
10pub struct OverlayUse;
11
12impl Command for OverlayUse {
13    fn name(&self) -> &str {
14        "overlay use"
15    }
16
17    fn description(&self) -> &str {
18        "Use definitions from a module as an overlay."
19    }
20
21    fn signature(&self) -> nu_protocol::Signature {
22        Signature::build("overlay use")
23            .input_output_types(vec![(Type::Nothing, Type::Nothing)])
24            .allow_variants_without_examples(true)
25            .param(Parameter::Required(
26                PositionalArg::new(
27                    "name",
28                    SyntaxShape::OneOf(vec![SyntaxShape::String, SyntaxShape::Nothing]),
29                )
30                .desc("Module name to use overlay for (`null` for no-op).")
31                .completion(Completion::Builtin(BuiltinCompletion::NuFile {
32                    std_virtual_path: true,
33                })),
34            ))
35            .optional(
36                "as",
37                SyntaxShape::Keyword(b"as".to_vec(), Box::new(SyntaxShape::String)),
38                "`as` keyword followed by a new name.",
39            )
40            .switch(
41                "prefix",
42                "Prepend module name to the imported commands and aliases.",
43                Some('p'),
44            )
45            .switch(
46                "reload",
47                "If the overlay already exists, reload its definitions and environment.",
48                Some('r'),
49            )
50            .category(Category::Core)
51    }
52
53    fn extra_description(&self) -> &str {
54        "This command is a parser keyword. For details, check:
55  https://www.nushell.sh/book/thinking_in_nu.html"
56    }
57
58    fn command_type(&self) -> CommandType {
59        CommandType::Keyword
60    }
61
62    fn run(
63        &self,
64        engine_state: &EngineState,
65        caller_stack: &mut Stack,
66        call: &Call,
67        input: PipelineData,
68    ) -> Result<PipelineData, ShellError> {
69        let noop = call.get_parser_info(caller_stack, "noop");
70        if noop.is_some() {
71            return Ok(PipelineData::empty());
72        }
73
74        let name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
75        let name_arg_item = trim_quotes_str(&name_arg.item);
76
77        let maybe_origin_module_id: Option<ModuleId> =
78            if let Some(overlay_expr) = call.get_parser_info(caller_stack, "overlay_expr") {
79                if let Expr::Overlay(module_id) = &overlay_expr.expr {
80                    *module_id
81                } else {
82                    return Err(ShellError::NushellFailedSpanned {
83                        msg: "Not an overlay".to_string(),
84                        label: "requires an overlay (path or a string)".to_string(),
85                        span: overlay_expr.span,
86                    });
87                }
88            } else {
89                return Err(ShellError::NushellFailedSpanned {
90                    msg: "Missing positional".to_string(),
91                    label: "missing required overlay".to_string(),
92                    span: call.head,
93                });
94            };
95
96        let overlay_name = if let Some(name) = call.opt(engine_state, caller_stack, 1)? {
97            name
98        } else if engine_state
99            .find_overlay(name_arg_item.as_bytes())
100            .is_some()
101        {
102            name_arg_item.to_string()
103        } else if let Some(os_str) = Path::new(name_arg_item).file_stem() {
104            if let Some(name) = os_str.to_str() {
105                name.to_string()
106            } else {
107                return Err(ShellError::NonUtf8 {
108                    span: name_arg.span,
109                });
110            }
111        } else {
112            return Err(ShellError::OverlayNotFoundAtRuntime {
113                overlay_name: (name_arg_item.to_string()),
114                span: name_arg.span,
115            });
116        };
117
118        if let Some(module_id) = maybe_origin_module_id {
119            // Add environment variables only if (determined by parser):
120            // a) adding a new overlay
121            // b) refreshing an active overlay (the origin module changed)
122
123            let module = engine_state.get_module(module_id);
124            // in such case, should also make sure that PWD is not restored in old overlays.
125            let cwd = caller_stack.get_env_var(engine_state, "PWD").cloned();
126
127            // Evaluate the export-env block (if any) and keep its environment
128            if let Some(block_id) = module.env_block {
129                let maybe_file_path_or_dir = find_in_dirs_env(
130                    name_arg_item,
131                    engine_state,
132                    caller_stack,
133                    get_dirs_var_from_call(caller_stack, call),
134                )?;
135                let block = engine_state.get_block(block_id);
136                let mut callee_stack = caller_stack
137                    .gather_captures(engine_state, &block.captures)
138                    .reset_pipes();
139
140                if let Some(path) = &maybe_file_path_or_dir {
141                    // Set the currently evaluated directory, if the argument is a valid path
142                    let parent = if path.is_dir() {
143                        path.clone()
144                    } else {
145                        let mut parent = path.clone();
146                        parent.pop();
147                        parent
148                    };
149                    let file_pwd = Value::string(parent.to_string_lossy(), call.head);
150
151                    callee_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
152                }
153
154                if let Some(path) = &maybe_file_path_or_dir {
155                    let module_file_path = if path.is_dir() {
156                        // the existence of `mod.nu` is verified in parsing time
157                        // so it's safe to use it here.
158                        Value::string(path.join("mod.nu").to_string_lossy(), call.head)
159                    } else {
160                        Value::string(path.to_string_lossy(), call.head)
161                    };
162                    callee_stack.add_env_var("CURRENT_FILE".to_string(), module_file_path);
163                }
164
165                let eval_block = get_eval_block(engine_state);
166                let _ = eval_block(engine_state, &mut callee_stack, block, input)?;
167
168                // The export-env block should see the env vars *before* activating this overlay
169                caller_stack.add_overlay(overlay_name);
170                // make sure that PWD is not restored in old overlays.
171                if let Some(cwd) = cwd {
172                    caller_stack.add_env_var("PWD".to_string(), cwd);
173                }
174
175                // Merge the block's environment to the current stack
176                redirect_env(engine_state, caller_stack, &callee_stack);
177            } else {
178                caller_stack.add_overlay(overlay_name);
179                // make sure that PWD is not restored in old overlays.
180                if let Some(cwd) = cwd {
181                    caller_stack.add_env_var("PWD".to_string(), cwd);
182                }
183            }
184        } else {
185            caller_stack.add_overlay(overlay_name);
186            caller_stack.update_config(engine_state)?;
187        }
188
189        Ok(PipelineData::empty())
190    }
191
192    fn examples(&self) -> Vec<Example<'_>> {
193        vec![
194            Example {
195                description: "Create an overlay from a module.",
196                example: r#"module spam { export def foo [] { "foo" } }
197    overlay use spam
198    foo"#,
199                result: None,
200            },
201            Example {
202                description: "Create an overlay from a module and rename it.",
203                example: r#"module spam { export def foo [] { "foo" } }
204    overlay use spam as spam_new
205    foo"#,
206                result: None,
207            },
208            Example {
209                description: "Create an overlay with a prefix.",
210                example: r#"'export def foo { "foo" }'
211    overlay use --prefix spam
212    spam foo"#,
213                result: None,
214            },
215            Example {
216                description: "Create an overlay from a file.",
217                example: r#"'export-env { $env.FOO = "foo" }' | save spam.nu
218    overlay use spam.nu
219    $env.FOO"#,
220                result: None,
221            },
222        ]
223    }
224}
225
226#[cfg(test)]
227mod test {
228    use super::*;
229
230    #[test]
231    #[ignore = "examples do not run every line separately in test"]
232    fn test_examples() -> nu_test_support::Result {
233        nu_test_support::test().examples(OverlayUse)
234    }
235}