nu_command/path/
relative_to.rs

1use 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        // This doesn't match explicit nulls
65        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        // This doesn't match explicit nulls
86        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}