1use super::PathSubcommandArguments;
2use nu_engine::command_prelude::*;
3use nu_path::AbsolutePathBuf;
4use nu_protocol::{engine::StateWorkingSet, shell_error::io::IoError};
5use std::{io, path::Path};
6
7struct Arguments {
8 pwd: AbsolutePathBuf,
9}
10
11impl PathSubcommandArguments for Arguments {}
12
13#[derive(Clone)]
14pub struct PathType;
15
16impl Command for PathType {
17 fn name(&self) -> &str {
18 "path type"
19 }
20
21 fn signature(&self) -> Signature {
22 Signature::build("path type")
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 .allow_variants_without_examples(true)
31 .category(Category::Path)
32 }
33
34 fn description(&self) -> &str {
35 "Get the type of the object a path refers to (e.g., file, dir, symlink)."
36 }
37
38 fn extra_description(&self) -> &str {
39 r#"This checks the file system to confirm the path's object type.
40If the path does not exist, null will be returned."#
41 }
42
43 fn is_const(&self) -> bool {
44 true
45 }
46
47 fn run(
48 &self,
49 engine_state: &EngineState,
50 stack: &mut Stack,
51 call: &Call,
52 input: PipelineData,
53 ) -> Result<PipelineData, ShellError> {
54 let head = call.head;
55 let args = Arguments {
56 pwd: engine_state.cwd(Some(stack))?,
57 };
58
59 if matches!(input, PipelineData::Empty) {
61 return Err(ShellError::PipelineEmpty { dst_span: head });
62 }
63 input.map(
64 move |value| super::operate(&path_type, &args, value, head),
65 engine_state.signals(),
66 )
67 }
68
69 fn run_const(
70 &self,
71 working_set: &StateWorkingSet,
72 call: &Call,
73 input: PipelineData,
74 ) -> Result<PipelineData, ShellError> {
75 let head = call.head;
76 let args = Arguments {
77 pwd: working_set.permanent().cwd(None)?,
78 };
79
80 if matches!(input, PipelineData::Empty) {
82 return Err(ShellError::PipelineEmpty { dst_span: head });
83 }
84 input.map(
85 move |value| super::operate(&path_type, &args, value, head),
86 working_set.permanent().signals(),
87 )
88 }
89
90 fn examples(&self) -> Vec<Example> {
91 vec![
92 Example {
93 description: "Show type of a filepath",
94 example: "'.' | path type",
95 result: Some(Value::test_string("dir")),
96 },
97 Example {
98 description: "Show type of a filepaths in a list",
99 example: "ls | get name | path type",
100 result: None,
101 },
102 ]
103 }
104}
105
106fn path_type(path: &Path, span: Span, args: &Arguments) -> Value {
107 let path = nu_path::expand_path_with(path, &args.pwd, true);
108 match path.symlink_metadata() {
109 Ok(metadata) => Value::string(get_file_type(&metadata), span),
110 Err(err) if err.kind() == io::ErrorKind::NotFound => Value::nothing(span),
111 Err(err) => Value::error(IoError::new(err, span, None).into(), span),
112 }
113}
114
115fn get_file_type(md: &std::fs::Metadata) -> &str {
116 let ft = md.file_type();
117 let mut file_type = "unknown";
118 if ft.is_dir() {
119 file_type = "dir";
120 } else if ft.is_file() {
121 file_type = "file";
122 } else if ft.is_symlink() {
123 file_type = "symlink";
124 } else {
125 #[cfg(unix)]
126 {
127 use std::os::unix::fs::FileTypeExt;
128 if ft.is_block_device() {
129 file_type = "block device";
130 } else if ft.is_char_device() {
131 file_type = "char device";
132 } else if ft.is_fifo() {
133 file_type = "pipe";
134 } else if ft.is_socket() {
135 file_type = "socket";
136 }
137 }
138 }
139 file_type
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_examples() {
148 use crate::test_examples;
149
150 test_examples(PathType {})
151 }
152}