Skip to content

Commit

Permalink
[builtin/test] Implement test --true; test --false
Browse files Browse the repository at this point in the history
The other part of issue #2094.  Based on feedback from Will Clardy.

Also add a FAQ entry on this, since Julian also ran into it.
  • Loading branch information
Andy C committed Oct 16, 2024
1 parent 11f8dff commit a2cd18a
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 15 deletions.
4 changes: 4 additions & 0 deletions builtin/bracket_osh.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ def _TwoArgs(w_parser):
unary_id = Id.BoolUnary_f
elif s0 == '--symlink':
unary_id = Id.BoolUnary_L
elif s0 == '--true':
unary_id = Id.BoolUnary_true
elif s0 == '--false':
unary_id = Id.BoolUnary_false

if unary_id == Id.Undefined_Tok:
unary_id = match.BracketUnary(w0.s)
Expand Down
35 changes: 28 additions & 7 deletions doc/ref/chap-builtin-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,30 @@ See the [YSH FAQ][echo-en] for details.
[simple_echo]: chap-option.html#ysh:all
[echo-en]: ../ysh-faq.html#how-do-i-write-the-equivalent-of-echo-e-or-echo-n

### ysh-test

The YSH [test](#test) builtin supports these long flags:

--dir same as -d
--exists same as -e
--file same as -f
--symlink same as -L

--true Is the argument equal to the string "true"?
--false Is the argument equal to the string "false"?

The `--true` and `--false` flags can be used to combine commands and
expressions:

if test --file a && test --true $[bool(mydict)] {
echo ok
}

This works because the boolean `true` *stringifies* to `"true"`, and likewise
with `false`.

That is, `$[true] === "true"` and `$[false] === "false"`.

### write

write fixes problems with shell's `echo` builtin.
Expand Down Expand Up @@ -1108,8 +1132,8 @@ JOB:

Evaluates a conditional expression and returns 0 (true) or 1 (false).

Note that [ is the name of a builtin, not an operator in the language. Use
'test' to avoid this confusion.
Note that `[` is the name of a builtin, not an operator in the language. Use
`test` to avoid this confusion.

String expressions:

Expand Down Expand Up @@ -1168,12 +1192,9 @@ these are discouraged.

<!-- -R VAR True if the variable VAR has been set and is a nameref variable. -->

Oils supports these long flags:
---

--dir same as -d
--exists same as -e
--file same as -f
--symlink same as -L
See [ysh-test](#ysh-test) for log flags like `--file` and `--true`.

### getopts

Expand Down
1 change: 1 addition & 0 deletions doc/ref/toc-ysh.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ X [Wok] _field()
use create a module Obj from a source file
[I/O] ysh-read flags --all, -0
ysh-echo no -e -n with simple_echo
ysh-test --file --true etc.
write Like echo, with --, --sep, --end
fork forkwait Replace & and (), and takes a block
fopen Open multiple streams, takes a block
Expand Down
17 changes: 14 additions & 3 deletions doc/ysh-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,22 @@ not `${}`.
-->

## How do I combine conditional commands and expressions: `if (myvar)` versus `if test`?
## How do I combine conditional commands and expressions: `if (myvar)` and `if test -f`?

TODO: `test --true --false`
You can use the `--true` and `--false` flags to the [YSH test][ysh-test]
builtin:

if test --true $[myvar] && test --file x {
echo ok
}

They test if their argument is literally the string `"true"` or `"false"`.

This works because the boolean `true` *stringifies* to `"true"`, and likewise
with `false`.

[ysh-test]: ref/chap-builtin-cmd.html#ysh-test

This happens in `while` too.

## Why do I lose the value of `p` in `myproc (&p) | grep foo`?

Expand Down
18 changes: 13 additions & 5 deletions frontend/id_kind_def.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ def AddBoolKind(
):
# type: (...) -> None
"""
Args:
kind_name: string
arg_type_pairs: dictionary of bool_arg_type_e -> []
"""
Args:
kind_name: string
arg_type_pairs: dictionary of bool_arg_type_e -> []
"""
lexer_pairs = []
num_tokens = 0
for arg_type, pairs in arg_type_pairs:
Expand Down Expand Up @@ -733,6 +733,15 @@ def AddBoolKinds(spec):
(bool_arg_type_e.Path, _Dash(list(_UNARY_PATH_CHARS))),
])

Id = spec.id_str2int

# test --true and test --false have no single letter flags. They need no
# lexing.
for long_flag in ('true', 'false'):
id_name = 'BoolUnary_%s' % long_flag
spec._AddId(id_name)
spec.AddBoolOp(Id[id_name], bool_arg_type_e.Str)

spec.AddBoolKind('BoolBinary', [
(bool_arg_type_e.Str, [
('GlobEqual', '='),
Expand All @@ -744,7 +753,6 @@ def AddBoolKinds(spec):
(bool_arg_type_e.Int, _Dash(_BINARY_INT)),
])

Id = spec.id_str2int
# logical, arity, arg_type
spec.AddBoolOp(Id['Op_DAmp'], bool_arg_type_e.Undefined)
spec.AddBoolOp(Id['Op_DPipe'], bool_arg_type_e.Undefined)
Expand Down
4 changes: 4 additions & 0 deletions osh/sh_expr_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,10 @@ def EvalB(self, node):
return not bool(s)
if op_id == Id.BoolUnary_n:
return bool(s)
if op_id == Id.BoolUnary_true:
return s == 'true'
if op_id == Id.BoolUnary_false:
return s == 'false'

raise AssertionError(op_id) # should never happen

Expand Down
86 changes: 86 additions & 0 deletions spec/ysh-builtins.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,92 @@ status=1
status=2
## END

#### test --true; test --false
shopt --set ysh:upgrade

for expr in (true, false, '', 'other') {
pp test_ (expr)

try {
test --true $[expr]
}
echo true=$[_error.code]

try {
test --false $[expr]
}
echo false=$[_error.code]
echo
}

## STDOUT:
(Bool) true
true=0
false=1

(Bool) false
true=1
false=0

(Str) ""
true=1
false=1

(Str) "other"
true=1
false=1

## END

#### More test --true --false
shopt --set ysh:upgrade

var d = {}

try {
test --true $[bool(d)]
}
echo dict=$[_error.code]

setvar d.key = 'val'

try {
test --true $[bool(d)]
}
echo dict=$[_error.code]

echo

if test --true $[bool(d)] && ! test -f / {
echo AndOr
}

## STDOUT:
dict=1
dict=0

AndOr
## END


#### Make sure [[ is not affected by --true --false

set +o errexit

$SH +o ysh:all -c '[[ --true ]]; echo dbracket=$?'
$SH +o ysh:all -c '[[ --false ]]; echo dbracket=$?'

$SH +o ysh:all -c '[[ --true true ]]; echo dbracket=$?'
echo "parse error $?"
$SH +o ysh:all -c '[[ --false false ]]; echo dbracket=$?'
echo "parse error $?"

## STDOUT:
dbracket=0
dbracket=0
parse error 2
parse error 2
## END

#### push-registers
shopt --set ysh:upgrade
Expand Down

0 comments on commit a2cd18a

Please sign in to comment.