nu_command/filesystem/
cd.rs1use std::path::PathBuf;
2
3use nu_engine::command_prelude::*;
4use nu_protocol::shell_error::{self, io::IoError};
5use nu_utils::filesystem::{have_permission, PermissionResult};
6
7#[derive(Clone)]
8pub struct Cd;
9
10impl Command for Cd {
11 fn name(&self) -> &str {
12 "cd"
13 }
14
15 fn description(&self) -> &str {
16 "Change directory."
17 }
18
19 fn search_terms(&self) -> Vec<&str> {
20 vec!["change", "directory", "dir", "folder", "switch"]
21 }
22
23 fn signature(&self) -> nu_protocol::Signature {
24 Signature::build("cd")
25 .input_output_types(vec![(Type::Nothing, Type::Nothing)])
26 .switch("physical", "use the physical directory structure; resolve symbolic links before processing instances of ..", Some('P'))
27 .optional("path", SyntaxShape::Directory, "The path to change to.")
28 .allow_variants_without_examples(true)
29 .category(Category::FileSystem)
30 }
31
32 fn run(
33 &self,
34 engine_state: &EngineState,
35 stack: &mut Stack,
36 call: &Call,
37 _input: PipelineData,
38 ) -> Result<PipelineData, ShellError> {
39 let physical = call.has_flag(engine_state, stack, "physical")?;
40 let path_val: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
41
42 let cwd = engine_state
45 .cwd(Some(stack))
46 .ok()
47 .or_else(nu_path::home_dir)
48 .map(|path| path.into_std_path_buf())
49 .unwrap_or_default();
50
51 let path_val = {
52 if let Some(path) = path_val {
53 Some(Spanned {
54 item: nu_utils::strip_ansi_string_unlikely(path.item),
55 span: path.span,
56 })
57 } else {
58 path_val
59 }
60 };
61
62 let path = match path_val {
63 Some(v) => {
64 if v.item == "-" {
65 if let Some(oldpwd) = stack.get_env_var(engine_state, "OLDPWD") {
66 oldpwd.to_path()?
67 } else {
68 cwd
69 }
70 } else {
71 let path_no_whitespace =
73 &v.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
74
75 if physical {
77 if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) {
78 if !path.is_dir() {
79 return Err(shell_error::io::IoError::new(
80 shell_error::io::ErrorKind::Std(
81 std::io::ErrorKind::NotADirectory,
82 ),
83 v.span,
84 None,
85 )
86 .into());
87 };
88 path
89 } else {
90 return Err(shell_error::io::IoError::new(
91 ErrorKind::DirectoryNotFound,
92 v.span,
93 PathBuf::from(path_no_whitespace),
94 )
95 .into());
96 }
97 } else {
98 let path = nu_path::expand_path_with(path_no_whitespace, &cwd, true);
99 if !path.exists() {
100 return Err(shell_error::io::IoError::new(
101 ErrorKind::DirectoryNotFound,
102 v.span,
103 PathBuf::from(path_no_whitespace),
104 )
105 .into());
106 };
107 if !path.is_dir() {
108 return Err(shell_error::io::IoError::new(
109 shell_error::io::ErrorKind::Std(std::io::ErrorKind::NotADirectory),
110 v.span,
111 path,
112 )
113 .into());
114 };
115 path
116 }
117 }
118 }
119 None => nu_path::expand_tilde("~"),
120 };
121
122 if let Some(oldpwd) = stack.get_env_var(engine_state, "PWD") {
125 stack.add_env_var("OLDPWD".into(), oldpwd.clone())
126 }
127
128 match have_permission(&path) {
129 PermissionResult::PermissionOk => {
132 stack.set_cwd(path)?;
133 Ok(PipelineData::empty())
134 }
135 PermissionResult::PermissionDenied(_) => {
136 Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into())
137 }
138 }
139 }
140
141 fn examples(&self) -> Vec<Example> {
142 vec![
143 Example {
144 description: "Change to your home directory",
145 example: r#"cd ~"#,
146 result: None,
147 },
148 Example {
149 description: r#"Change to the previous working directory (same as "cd $env.OLDPWD")"#,
150 example: r#"cd -"#,
151 result: None,
152 },
153 Example {
154 description: "Changing directory with a custom command requires 'def --env'",
155 example: r#"def --env gohome [] { cd ~ }"#,
156 result: None,
157 },
158 Example {
159 description: "Move two directories up in the tree (the parent directory's parent). Additional dots can be added for additional levels.",
160 example: r#"cd ..."#,
161 result: None,
162 },
163 Example {
164 description: "The cd command itself is often optional. Simply entering a path to a directory will cd to it.",
165 example: r#"/home"#,
166 result: None,
167 },
168 ]
169 }
170}