use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, }; use wax::{Glob as WaxGlob, WalkBehavior}; #[derive(Clone)] pub struct Glob; impl Command for Glob { fn name(&self) -> &str { "glob" } fn signature(&self) -> Signature { Signature::build("glob") .required("glob", SyntaxShape::String, "the glob expression") .named( "depth", SyntaxShape::Int, "directory depth to search", Some('d'), ) .category(Category::FileSystem) } fn usage(&self) -> &str { "Creates a list of files and/or folders based on the glob pattern provided." } fn search_terms(&self) -> Vec<&str> { vec!["pattern", "files", "folders", "list", "ls"] } fn examples(&self) -> Vec { vec![ Example { description: "Search for *.rs files", example: "glob *.rs", result: None, }, Example { description: "Search for *.rs and *.toml files recursively up to 2 folders deep", example: "glob **/*.{rs,toml} --depth 2", result: None, }, Example { description: "Search for files and folders that begin with uppercase C and lowercase c", example: r#"glob "[Cc]*""#, result: None, }, Example { description: "Search for files and folders like abc or xyz substituting a character for ?", example: r#"glob "{a?c,x?z}""#, result: None, }, Example { description: "A case-insensitive search for files and folders that begin with c", example: r#"glob "(?i)c*""#, result: None, }, Example { description: "Search for files for folders that do not begin with c, C, b, M, or s", example: r#"glob "[!cCbMs]*""#, result: None, }, Example { description: "Search for files or folders with 3 a's in a row in the name", example: "glob ", result: None, }, Example { description: "Search for files or folders with only a, b, c, or d in the file name between 1 and 10 times", example: "glob <[a-d]:1,10>", result: None, }, ] } fn extra_usage(&self) -> &str { r#"For more glob pattern help please refer to https://github.com/olson-sean-k/wax"# } fn run( &self, engine_state: &EngineState, stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { let span = call.head; let path = current_dir(engine_state, stack)?; let glob_pattern: Spanned = call.req(engine_state, stack, 0)?; let depth = call.get_flag(engine_state, stack, "depth")?; if glob_pattern.item.is_empty() { return Err(ShellError::GenericError( "glob pattern must not be empty".to_string(), "".to_string(), Some(glob_pattern.span), Some("add characters to the glob pattern".to_string()), Vec::new(), )); } let folder_depth = if let Some(depth) = depth { depth } else { usize::MAX }; let glob = match WaxGlob::new(&glob_pattern.item) { Ok(p) => p, Err(e) => { return Err(ShellError::GenericError( "error with glob pattern".to_string(), "".to_string(), None, Some(format!("{}", e)), Vec::new(), )) } }; #[allow(clippy::needless_collect)] let glob_results: Vec = glob .walk_with_behavior( path, WalkBehavior { depth: folder_depth, ..Default::default() }, ) .flatten() .map(|entry| Value::String { val: entry.into_path().to_string_lossy().to_string(), span, }) .collect(); Ok(glob_results .into_iter() .into_pipeline_data(engine_state.ctrlc.clone())) } }