Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
redox-os
ion
Commits
abdb02af
Commit
abdb02af
authored
Aug 17, 2017
by
Fabian Würfl
Committed by
Michael Aaron Murphy
Aug 17, 2017
Browse files
Implement Exists Builtin Command (#504)
parent
5d35de2d
Changes
6
Hide whitespace changes
Inline
Side-by-side
examples/exists.ion
0 → 100644
View file @
abdb02af
# 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 $?
examples/exists.out
0 → 100644
View file @
abdb02af
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
examples/glob.out
View file @
abdb02af
...
...
@@ -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
src/builtins/exists.rs
0 → 100644
View file @
abdb02af
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]