nu_command/path/
relative_to.rs1use super::PathSubcommandArguments;
2use nu_engine::command_prelude::*;
3use nu_path::expand_to_real_path;
4use nu_protocol::engine::StateWorkingSet;
5use std::path::Path;
6
7struct Arguments {
8 path: Spanned<String>,
9}
10
11impl PathSubcommandArguments for Arguments {}
12
13#[derive(Clone)]
14pub struct PathRelativeTo;
15
16impl Command for PathRelativeTo {
17 fn name(&self) -> &str {
18 "path relative-to"
19 }
20
21 fn signature(&self) -> Signature {
22 Signature::build("path relative-to")
23 .input_output_types(vec![
24 (Type::String, Type::String),
25 (
26 Type::List(Box::new(Type::String)),
27 Type::List(Box::new(Type::String)),
28 ),
29 ])
30 .required(
31 "path",
32 SyntaxShape::String,
33 "Parent shared with the input path.",
34 )
35 .category(Category::Path)
36 }
37
38 fn description(&self) -> &str {
39 "Express a path as relative to another path."
40 }
41
42 fn extra_description(&self) -> &str {
43 r#"Can be used only when the input and the argument paths are either both
44absolute or both relative. The argument path needs to be a parent of the input
45path."#
46 }
47
48 fn is_const(&self) -> bool {
49 true
50 }
51
52 fn run(
53 &self,
54 engine_state: &EngineState,
55 stack: &mut Stack,
56 call: &Call,
57 input: PipelineData,
58 ) -> Result<PipelineData, ShellError> {
59 let head = call.head;
60 let args = Arguments {
61 path: call.req(engine_state, stack, 0)?,
62 };
63
64 if matches!(input, PipelineData::Empty) {
66 return Err(ShellError::PipelineEmpty { dst_span: head });
67 }
68 input.map(
69 move |value| super::operate(&relative_to, &args, value, head),
70 engine_state.signals(),
71 )
72 }
73
74 fn run_const(
75 &self,
76 working_set: &StateWorkingSet,
77 call: &Call,
78 input: PipelineData,
79 ) -> Result<PipelineData, ShellError> {
80 let head = call.head;
81 let args = Arguments {
82 path: call.req_const(working_set, 0)?,
83 };
84
85 if matches!(input, PipelineData::Empty) {
87 return Err(ShellError::PipelineEmpty { dst_span: head });
88 }
89 input.map(
90 move |value| super::operate(&relative_to, &args, value, head),
91 working_set.permanent().signals(),
92 )
93 }
94
95 #[cfg(windows)]
96 fn examples(&self) -> Vec<Example> {
97 vec![
98 Example {
99 description: "Find a relative path from two absolute paths",
100 example: r"'C:\Users\viking' | path relative-to 'C:\Users'",
101 result: Some(Value::test_string(r"viking")),
102 },
103 Example {
104 description: "Find a relative path from absolute paths in list",
105 example: r"[ C:\Users\viking, C:\Users\spam ] | path relative-to C:\Users",
106 result: Some(Value::test_list(vec![
107 Value::test_string("viking"),
108 Value::test_string("spam"),
109 ])),
110 },
111 Example {
112 description: "Find a relative path from two relative paths",
113 example: r"'eggs\bacon\sausage\spam' | path relative-to 'eggs\bacon\sausage'",
114 result: Some(Value::test_string(r"spam")),
115 },
116 ]
117 }
118
119 #[cfg(not(windows))]
120 fn examples(&self) -> Vec<Example> {
121 vec![
122 Example {
123 description: "Find a relative path from two absolute paths",
124 example: r"'/home/viking' | path relative-to '/home'",
125 result: Some(Value::test_string(r"viking")),
126 },
127 Example {
128 description: "Find a relative path from absolute paths in list",
129 example: r"[ /home/viking, /home/spam ] | path relative-to '/home'",
130 result: Some(Value::test_list(vec![
131 Value::test_string("viking"),
132 Value::test_string("spam"),
133 ])),
134 },
135 Example {
136 description: "Find a relative path from two relative paths",
137 example: r"'eggs/bacon/sausage/spam' | path relative-to 'eggs/bacon/sausage'",
138 result: Some(Value::test_string(r"spam")),
139 },
140 ]
141 }
142}
143
144fn relative_to(path: &Path, span: Span, args: &Arguments) -> Value {
145 let lhs = expand_to_real_path(path);
146 let rhs = expand_to_real_path(&args.path.item);
147 match lhs.strip_prefix(&rhs) {
148 Ok(p) => Value::string(p.to_string_lossy(), span),
149 Err(e) => Value::error(
150 ShellError::CantConvert {
151 to_type: e.to_string(),
152 from_type: "string".into(),
153 span,
154 help: None,
155 },
156 span,
157 ),
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn test_examples() {
167 use crate::test_examples;
168
169 test_examples(PathRelativeTo {})
170 }
171}