diff --git a/examples/exists.ion b/examples/exists.ion new file mode 100644 index 0000000000000000000000000000000000000000..bc8cff4d9f7796fe184f06e8596e7b88c36668c9 --- /dev/null +++ b/examples/exists.ion @@ -0,0 +1,96 @@ +# Same tests as in src/builtins/exists.rs:test_evaluate_arguments() +# TODO: find a better way than to write "echo $?" between each test cases +exists +echo $? +exists foo bar +echo $? + +exists --help +echo $? +exists --help unused params +echo $? + + +exists "" +echo $? +exists string +echo $? +exists "string with spaces" +echo $? +exists "-startswithdash" +echo $? + + +exists -a +echo $? +let emptyarray = [] +echo @emptyarray +exists -a emptyarray +echo $? +let array = [ "element" ] +echo @array +exists -a array +echo $? +exists -a array +echo $? + + +exists -b +echo $? +let OLDPATH = $PATH +let PATH = testing/ +echo "PATH = $PATH" +ls -1 $PATH +exists -b executable_file +echo $? +exists -b empty_file +echo $? +exists -b file_does_not_exist +echo $? +let PATH = $OLDPATH +echo "Reset PATH to old path" + +exists -d +echo $? +exists -d testing/ +echo $? +exists -d testing/empty_file +echo $? +exists -d does/not/exist +echo $? + + +exists -f +echo $? +exists -f testing/ +echo $? +exists -f testing/empty_file +echo $? +exists -f does-not-exist +echo $? + +fn testFunc a + echo $a +end +exists --fn testFunc +echo $? + +exists -s +echo $? +let emptyvar = "" +echo "emptyvar = $emptyvar" +exists -s emptyvar +echo $? +let testvar = "foobar" +echo "testvar = $testvar" +exists -s testvar +echo $? +drop testvar +echo "testvar = $testvar" +exists -s testvar +echo $? + +exists --foo +echo $? +exists -x +echo $? diff --git a/examples/exists.out b/examples/exists.out new file mode 100644 index 0000000000000000000000000000000000000000..6b1d71c50b0f4c1b92165c3dba5902969b7bf592 --- /dev/null +++ b/examples/exists.out @@ -0,0 +1,152 @@ +1 +0 +NAME + exists - check whether items exist + +SYNOPSIS + exists [EXPRESSION] + +DESCRIPTION + Checks whether the given item exists and returns an exit status of 0 if it does, else 1. + +OPTIONS + -a ARRAY + array var is not empty + + -b BINARY + binary is in PATH + + -d PATH + path is a directory + This is the same as test -d + + -f PATH + path is a file + This is the same as test -f + + --fn FUNCTION + function is defined + + -s STRING + string var is not empty + + STRING + string is not empty + This is the same as test -n + +EXAMPLES + Test if the file exists: + exists -f FILE && echo "The FILE exists" || echo "The FILE does not exist" + + Test if some-command exists in the path and is executable: + exists -b some-command && echo "some-command exists" || echo "some-command does not exist" + + Test if variable exists AND is not empty + exists -s myVar && echo "myVar exists: $myVar" || echo "myVar does not exist or is empty" + NOTE: Don't use the '$' sigil, but only the name of the variable to check + + Test if array exists and is not empty + exists -a myArr && echo "myArr exists: @myArr" || echo "myArr does not exist or is empty" + NOTE: Don't use the '@' sigil, but only the name of the array to check + + Test if a function named 'myFunc' exists + exists --fn myFunc && myFunc || echo "No function with name myFunc found" + +AUTHOR + Written by Fabian Würfl. + Heavily based on implementation of the test builtin, which was written by Michael Murph. +0 +NAME + exists - check whether items exist + +SYNOPSIS + exists [EXPRESSION] + +DESCRIPTION + Checks whether the given item exists and returns an exit status of 0 if it does, else 1. + +OPTIONS + -a ARRAY + array var is not empty + + -b BINARY + binary is in PATH + + -d PATH + path is a directory + This is the same as test -d + + -f PATH + path is a file + This is the same as test -f + + --fn FUNCTION + function is defined + + -s STRING + string var is not empty + + STRING + string is not empty + This is the same as test -n + +EXAMPLES + Test if the file exists: + exists -f FILE && echo "The FILE exists" || echo "The FILE does not exist" + + Test if some-command exists in the path and is executable: + exists -b some-command && echo "some-command exists" || echo "some-command does not exist" + + Test if variable exists AND is not empty + exists -s myVar && echo "myVar exists: $myVar" || echo "myVar does not exist or is empty" + NOTE: Don't use the '$' sigil, but only the name of the variable to check + + Test if array exists and is not empty + exists -a myArr && echo "myArr exists: @myArr" || echo "myArr does not exist or is empty" + NOTE: Don't use the '@' sigil, but only the name of the array to check + + Test if a function named 'myFunc' exists + exists --fn myFunc && myFunc || echo "No function with name myFunc found" + +AUTHOR + Written by Fabian Würfl. + Heavily based on implementation of the test builtin, which was written by Michael Murph. +0 +1 +0 +0 +0 +0 + +1 +element +0 +0 +0 +PATH = testing/ +empty_file +executable_file +file_with_text +symlink +0 +1 +1 +Reset PATH to old path +0 +0 +1 +1 +0 +1 +0 +1 +0 +0 +emptyvar = +1 +testvar = foobar +0 +testvar = +1 +0 +0 diff --git a/examples/glob.out b/examples/glob.out index af89d3abca8976e5719e584583fa532f31d756cd..17ff9ff8f470f2080235da7d36dc718b858c7f64 100644 --- a/examples/glob.out +++ b/examples/glob.out @@ -5,7 +5,7 @@ Cargo.toml Cargo.lock Cargo.toml Cargo.toml Cargo.toml -examples/else_if.ion examples/fail.ion examples/fibonacci.ion examples/fn.ion examples/for.ion examples/function_piping.ion +examples/else_if.ion examples/exists.ion examples/fail.ion examples/fibonacci.ion examples/fn.ion examples/for.ion examples/function_piping.ion one three two three two three two diff --git a/src/builtins/exists.rs b/src/builtins/exists.rs new file mode 100644 index 0000000000000000000000000000000000000000..9dd1931978d8059e791e5422a42043cddce871f7 --- /dev/null +++ b/src/builtins/exists.rs @@ -0,0 +1,472 @@ + +use std::error::Error; +use std::fs; +use std::io::{self, BufWriter}; +use std::os::unix::fs::{PermissionsExt}; +#[cfg(test)] +use smallstring::SmallString; +#[cfg(test)] +use smallvec::SmallVec; + +#[cfg(test)] +use builtins::Builtin; +#[cfg(test)] +use shell::flow_control::{Function, FunctionArgument, Statement}; +use shell::Shell; + +const MAN_PAGE: &'static str = r#"NAME + exists - check whether items exist + +SYNOPSIS + exists [EXPRESSION] + +DESCRIPTION + Checks whether the given item exists and returns an exit status of 0 if it does, else 1. + +OPTIONS + -a ARRAY + array var is not empty + + -b BINARY + binary is in PATH + + -d PATH + path is a directory + This is the same as test -d + + -f PATH + path is a file + This is the same as test -f + + --fn FUNCTION + function is defined + + -s STRING + string var is not empty + + STRING + string is not empty + This is the same as test -n + +EXAMPLES + Test if the file exists: + exists -f FILE && echo "The FILE exists" || echo "The FILE does not exist" + + Test if some-command exists in the path and is executable: + exists -b some-command && echo "some-command exists" || echo "some-command does not exist" + + Test if variable exists AND is not empty + exists -s myVar && echo "myVar exists: $myVar" || echo "myVar does not exist or is empty" + NOTE: Don't use the '$' sigil, but only the name of the variable to check + + Test if array exists and is not empty + exists -a myArr && echo "myArr exists: @myArr" || echo "myArr does not exist or is empty" + NOTE: Don't use the '@' sigil, but only the name of the array to check + + Test if a function named 'myFunc' exists + exists --fn myFunc && myFunc || echo "No function with name myFunc found" + +AUTHOR + Written by Fabian Würfl. + Heavily based on implementation of the test builtin, which was written by Michael Murph. +"#; /* @MANEND */ + +pub fn exists(args: &[&str], shell: &Shell) -> Result<bool, String> { + let stdout = io::stdout(); + let mut buffer = BufWriter::new(stdout.lock()); + + let arguments = &args[1..]; + evaluate_arguments(arguments, &mut buffer, shell) +} + +fn evaluate_arguments<W: io::Write>(arguments: &[&str], buffer: &mut W, shell: &Shell) -> Result<bool, String> { + match arguments.first() { + Some(&"--help") => { + // not handled by the second case, so that we don't have to pass the buffer around + buffer.write_all(MAN_PAGE.as_bytes()).map_err(|x| { + x.description().to_owned() + })?; + buffer.flush().map_err(|x| x.description().to_owned())?; + Ok(true) + } + Some(&s) if s.starts_with("--") => { + let (_, option) = s.split_at(2); + // If no argument was given, return `SUCCESS`, as this means a string starting + // with a dash was given + arguments.get(1).map_or(Ok(true), { + |arg| + // Match the correct function to the associated flag + Ok(match_option_argument(option, arg, shell)) + }) + } + Some(&s) if s.starts_with("-") => { + // Access the second character in the flag string: this will be type of the flag. + // If no flag was given, return `SUCCESS`, as this means a string with value "-" was + // checked. + s.chars().nth(1).map_or(Ok(true), |flag| { + // If no argument was given, return `SUCCESS`, as this means a string starting + // with a dash was given + arguments.get(1).map_or(Ok(true), { + |arg| + // Match the correct function to the associated flag + Ok(match_flag_argument(flag, arg, shell)) + }) + }) + } + Some(string) => { + Ok(string_is_nonzero(string)) + } + None => Ok(false), + } +} + +/// Matches flag arguments to their respective functionaity when the `-` character is detected. +fn match_flag_argument(flag: char, argument: &str, shell: &Shell) -> bool { + match flag { + 'a' => array_var_is_not_empty(argument, shell), + 'b' => binary_is_in_path(argument, shell), + 'd' => path_is_directory(argument), + 'f' => path_is_file(argument), + 's' => string_var_is_not_empty(argument, shell), + _ => false, + } +} + +// Matches option arguments to their respective functionality +fn match_option_argument(option: &str, argument: &str, shell: &Shell) -> bool { + match option { + "fn" => function_is_defined(argument, &shell), + _ => false, + } +} + +/// Returns true if the file is a regular file +fn path_is_file(filepath: &str) -> bool { + fs::metadata(filepath).ok().map_or(false, |metadata| { + metadata.file_type().is_file() + }) +} + +/// Returns true if the file is a directory +fn path_is_directory(filepath: &str) -> bool { + fs::metadata(filepath).ok().map_or(false, |metadata| { + metadata.file_type().is_dir() + }) +} + +/// Returns true if the binary is found in path (and is executable) +fn binary_is_in_path(binaryname: &str, shell: &Shell) -> bool { + // TODO: Maybe this function should reflect the logic for spawning new processes + // TODO: Right now they use an entirely different logic which means that it *might* be possible + // TODO: that `exists` reports a binary to be in the path, while the shell cannot find it or + // TODO: vice-versa + if let Some(path) = shell.variables.get_var("PATH") { + for dir in path.split(":") { + let fname = format!("{}/{}", dir, binaryname); + if let Ok(metadata) = fs::metadata(&fname) { + if metadata.is_file() && file_has_execute_permission(&fname) { + return true; + } + } + } + }; + + false +} + +/// Returns true if the file has execute permissions. This function is rather low level because +/// Rust currently does not have a higher level abstraction for obtaining non-standard file modes. +/// To extract the permissions from the mode, the bitwise AND operator will be used and compared +/// with the respective execute bits. +/// Note: This function is 1:1 the same as src/builtins/test.rs:file_has_execute_permission +/// If you change the following function, please also update the one in src/builtins/test.rs +fn file_has_execute_permission(filepath: &str) -> bool { + const USER: u32 = 0b1000000; + const GROUP: u32 = 0b1000; + const GUEST: u32 = 0b1; + + // Collect the mode of permissions for the file + fs::metadata(filepath).map(|metadata| metadata.permissions().mode()).ok() + // If the mode is equal to any of the above, return `SUCCESS` + .map_or(false, |mode| mode & (USER + GROUP + GUEST) != 0) +} + +/// Returns true if the string is not empty +fn string_is_nonzero(string: &str) -> bool { !string.is_empty() } + +/// Returns true if the variable is an array and the array is not empty +fn array_var_is_not_empty(arrayvar: &str, shell: &Shell) -> bool { + match shell.variables.get_array(arrayvar) { + Some(array) => !array.is_empty(), + None => false + } +} + +/// Returns true if the variable is a string and the string is not empty +fn string_var_is_not_empty(stringvar: &str, shell: &Shell) -> bool { + match shell.variables.get_var(stringvar) { + Some(string) => !string.is_empty(), + None => false + } +} + +/// Returns true if a function with the given name is defined +fn function_is_defined(function: &str, shell: &Shell) -> bool { + match shell.functions.get(function) { + Some(_) => true, + None => false + } +} + +#[test] +fn test_evaluate_arguments() { + let builtins = Builtin::map(); + let mut shell = Shell::new(&builtins); + let mut sink = BufWriter::new(io::sink()); + + // assert_eq!(evaluate_arguments(&[], &mut sink, &shell), Ok(false)); + // no parameters + assert_eq!(evaluate_arguments(&[], &mut sink, &shell), Ok(false)); + // multiple arguments + // ignores all but the first argument + assert_eq!(evaluate_arguments(&["foo", "bar"], &mut sink, &shell), Ok(true)); + + // check whether --help returns SUCCESS + assert_eq!(evaluate_arguments(&["--help"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["--help", "unused", "params"], &mut sink, &shell), Ok(true)); + + // check `exists STRING` + assert_eq!(evaluate_arguments(&[""], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["string"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["string with space"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-startswithdash"], &mut sink, &shell), Ok(true)); + + // check `exists -a` + // no argument means we treat it as a string + assert_eq!(evaluate_arguments(&["-a"], &mut sink, &shell), Ok(true)); + shell.variables.set_array("emptyarray", SmallVec::from_vec(Vec::new())); + assert_eq!(evaluate_arguments(&["-a", "emptyarray"], &mut sink, &shell), Ok(false)); + let mut vec = Vec::new(); + vec.push("element".to_owned()); + shell.variables.set_array("array", SmallVec::from_vec(vec)); + assert_eq!(evaluate_arguments(&["-a", "array"], &mut sink, &shell), Ok(true)); + shell.variables.unset_array("array"); + assert_eq!(evaluate_arguments(&["-a", "array"], &mut sink, &shell), Ok(false)); + + // check `exists -b` + // TODO: see test_binary_is_in_path() + // no argument means we treat it as a string + assert_eq!(evaluate_arguments(&["-b"], &mut sink, &shell), Ok(true)); + let oldpath = shell.variables.get_var("PATH").unwrap_or("/usr/bin".to_owned()); + shell.variables.set_var("PATH", "testing/"); + + assert_eq!(evaluate_arguments(&["-b", "executable_file"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-b", "empty_file"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-b", "file_does_not_exist"], &mut sink, &shell), Ok(false)); + + // restore original PATH. Not necessary for the currently defined test cases but this might + // change in the future? Better safe than sorry! + shell.variables.set_var("PATH", &oldpath); + + // check `exists -d` + // no argument means we treat it as a string + assert_eq!(evaluate_arguments(&["-d"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-d", "testing/"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-d", "testing/empty_file"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-d", "does/not/exist/"], &mut sink, &shell), Ok(false)); + + // check `exists -f` + // no argument means we treat it as a string + assert_eq!(evaluate_arguments(&["-f"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-f", "testing/"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-f", "testing/empty_file"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-f", "does-not-exist"], &mut sink, &shell), Ok(false)); + + // check `exists -s` + // no argument means we treat it as a string + assert_eq!(evaluate_arguments(&["-s"], &mut sink, &shell), Ok(true)); + shell.variables.set_var("emptyvar", ""); + assert_eq!(evaluate_arguments(&["-s", "emptyvar"], &mut sink, &shell), Ok(false)); + shell.variables.set_var("testvar", "foobar"); + assert_eq!(evaluate_arguments(&["-s", "testvar"], &mut sink, &shell), Ok(true)); + shell.variables.unset_var("testvar"); + assert_eq!(evaluate_arguments(&["-s", "testvar"], &mut sink, &shell), Ok(false)); + // also check that it doesn't trigger on arrays + let mut vec = Vec::new(); + vec.push("element".to_owned()); + shell.variables.unset_var("array"); + shell.variables.set_array("array", SmallVec::from_vec(vec)); + assert_eq!(evaluate_arguments(&["-s", "array"], &mut sink, &shell), Ok(false)); + + // check `exists --fn` + let name_str = "test_function"; + let name = SmallString::from_str(name_str); + let mut args = Vec::new(); + args.push(FunctionArgument::Untyped("testy".to_owned())); + let mut statements = Vec::new(); + statements.push(Statement::End); + let description = "description".to_owned(); + + shell.functions.insert( + name.clone(), + Function { + name: name, + args: args, + statements: statements, + description: description, + }, + ); + + assert_eq!(evaluate_arguments(&["--fn", name_str], &mut sink, &shell), Ok(true)); + shell.functions.remove(name_str); + assert_eq!(evaluate_arguments(&["--fn", name_str], &mut sink, &shell), Ok(false)); + + // check invalid flags / parameters (should all be treated as strings and therefore succeed) + assert_eq!(evaluate_arguments(&["--foo"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-x"], &mut sink, &shell), Ok(true)); +} + +#[test] +fn test_match_flag_argument() { + let builtins = Builtin::map(); + let shell = Shell::new(&builtins); + + // we don't really care about the passed values, as long as both sited return the same value + assert_eq!(match_flag_argument('a', "ARRAY", &shell), array_var_is_not_empty("ARRAY", &shell)); + assert_eq!(match_flag_argument('b', "binary", &shell), binary_is_in_path("binary", &shell)); + assert_eq!(match_flag_argument('d', "path", &shell), path_is_directory("path")); + assert_eq!(match_flag_argument('f', "file", &shell), path_is_file("file")); + assert_eq!(match_flag_argument('s', "STR", &shell), string_var_is_not_empty("STR", &shell)); + + // Any flag which is not implemented + assert_eq!(match_flag_argument('x', "ARG", &shell), false); +} + +#[test] +fn test_match_option_argument() { + let builtins = Builtin::map(); + let shell = Shell::new(&builtins); + + // we don't really care about the passed values, as long as both sited return the same value + assert_eq!(match_option_argument("fn", "FUN", &shell), array_var_is_not_empty("FUN", &shell)); + + // Any option which is not implemented + assert_eq!(match_option_argument("foo", "ARG", &shell), false); +} + +#[test] +fn test_path_is_file() { + assert_eq!(path_is_file("testing/empty_file"), true); + assert_eq!(path_is_file("this-does-not-exist"), false); +} + +#[test] +fn test_path_is_directory() { + assert_eq!(path_is_directory("testing"), true); + assert_eq!(path_is_directory("testing/empty_file"), false); +} + +#[test] +fn test_binary_is_in_path() { + let builtins = Builtin::map(); + let mut shell = Shell::new(&builtins); + + // TODO: We should probably also test with more complex PATH-variables: + // TODO: multiple/:directories/ + // TODO: PATH containing directories which do not exist + // TODO: PATH containing directories without read permission (for user) + // TODO: PATH containing directories without execute ("enter") permission (for user) + // TODO: empty PATH? + shell.variables.set_var("PATH", "testing/"); + + assert_eq!(binary_is_in_path("executable_file", &shell), true); + assert_eq!(binary_is_in_path("empty_file", &shell), false); + assert_eq!(binary_is_in_path("file_does_not_exist", &shell), false); +} + +#[test] +fn test_file_has_execute_permission() { + assert_eq!(file_has_execute_permission("testing/executable_file"), true); + assert_eq!(file_has_execute_permission("testing"), true); + assert_eq!(file_has_execute_permission("testing/empty_file"), false); + assert_eq!(file_has_execute_permission("this-does-not-exist"), false); +} + +#[test] +fn test_string_is_nonzero() { + assert_eq!(string_is_nonzero("NOT ZERO"), true); + assert_eq!(string_is_nonzero(""), false); +} + +#[test] +fn test_array_var_is_not_empty() { + let builtins = Builtin::map(); + let mut shell = Shell::new(&builtins); + + shell.variables.set_array("EMPTY_ARRAY", SmallVec::from_vec(Vec::new())); + assert_eq!(array_var_is_not_empty("EMPTY_ARRAY", &shell), false); + + let mut not_empty_vec = Vec::new(); + not_empty_vec.push("array not empty".to_owned()); + shell.variables.set_array("NOT_EMPTY_ARRAY", SmallVec::from_vec(not_empty_vec)); + assert_eq!(array_var_is_not_empty("NOT_EMPTY_ARRAY", &shell), true); + + // test for array which does not even exist + shell.variables.unset_array("NOT_EMPTY_ARRAY"); + assert_eq!(array_var_is_not_empty("NOT_EMPTY_ARRAY", &shell), false); + + // array_var_is_not_empty should NOT match for non-array variables with the same name + shell.variables.set_var("VARIABLE", "notempty-variable"); + assert_eq!(array_var_is_not_empty("VARIABLE", &shell), false); +} + +#[test] +fn test_string_var_is_not_empty() { + let builtins = Builtin::map(); + let mut shell = Shell::new(&builtins); + + shell.variables.set_var("EMPTY", ""); + assert_eq!(string_var_is_not_empty("EMPTY", &shell), false); + + shell.variables.set_var("NOT_EMPTY", "notempty"); + assert_eq!(string_var_is_not_empty("NOT_EMPTY", &shell), true); + + // string_var_is_not_empty should NOT match for arrays with the same name + let mut vec = Vec::new(); + vec.push("not-empty".to_owned()); + shell.variables.set_array("ARRAY_NOT_EMPTY", SmallVec::from_vec(vec) ); + assert_eq!(string_var_is_not_empty("ARRAY_NOT_EMPTY", &shell), false); + + // test for a variable which does not even exist + shell.variables.unset_var("NOT_EMPTY"); + assert_eq!(string_var_is_not_empty("NOT_EMPTY", &shell), false); +} + +#[test] +fn test_function_is_defined() { + let builtins = Builtin::map(); + let mut shell = Shell::new(&builtins); + + // create a simple dummy function + let name_str = "test_function"; + let name = SmallString::from_str(name_str); + let mut args = Vec::new(); + args.push(FunctionArgument::Untyped("testy".to_owned())); + let mut statements = Vec::new(); + statements.push(Statement::End); + let description = "description".to_owned(); + + shell.functions.insert( + name.clone(), + Function { + name: name, + args: args, + statements: statements, + description: description, + }, + ); + + assert_eq!(function_is_defined(name_str, &shell), true); + shell.functions.remove(name_str); + assert_eq!(function_is_defined(name_str, &shell), false); +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index eb261e7bfcbf9f048f17dd224c803d84f065ba86..3ec8115d4bbcb8d7842ff799f09b851d05e595b4 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -9,6 +9,7 @@ mod test; mod time; mod echo; mod set; +mod exists; use self::conditionals::{contains, ends_with, starts_with}; use self::echo::echo; @@ -16,6 +17,7 @@ use self::functions::fn_; use self::source::source; use self::test::test; use self::variables::{alias, drop_alias, drop_array, drop_variable}; +use self::exists::exists; use fnv::FnvHashMap; use std::error::Error; @@ -152,6 +154,7 @@ impl Builtin { contains, "Evaluates if the supplied argument contains a given string" ); + insert_builtin!("exists", builtin_exists, "Performs tests on files and text"); commands } @@ -414,3 +417,16 @@ fn builtin_or(args: &[&str], shell: &mut Shell) -> i32 { _ => shell.previous_status, } } + +fn builtin_exists(args: &[&str], shell: &mut Shell) -> i32 { + match exists(args, shell) { + Ok(true) => SUCCESS, + Ok(false) => FAILURE, + Err(why) => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "{}", why); + FAILURE + } + } +} diff --git a/src/builtins/test.rs b/src/builtins/test.rs index 45b02373dbc6d06fd59bec51ac4ae6b2a1bf8e83..3ceacd7684710eea49fad97a0349baf1a0c1979a 100644 --- a/src/builtins/test.rs +++ b/src/builtins/test.rs @@ -304,6 +304,8 @@ fn file_has_write_permission(filepath: &str) -> bool { /// Rust currently does not have a higher level abstraction for obtaining non-standard file modes. /// To extract the permissions from the mode, the bitwise AND operator will be used and compared /// with the respective execute bits. +/// Note: This function is 1:1 the same as src/builtins/exists.rs:file_has_execute_permission +/// If you change the following function, please also update the one in src/builtins/exists.rs fn file_has_execute_permission(filepath: &str) -> bool { const USER: u32 = 0b1000000; const GROUP: u32 = 0b1000;