diff --git a/builtin/bracket_osh.py b/builtin/bracket_osh.py index c95b89a5b..4500dca1a 100644 --- a/builtin/bracket_osh.py +++ b/builtin/bracket_osh.py @@ -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) diff --git a/doc/ref/chap-builtin-cmd.md b/doc/ref/chap-builtin-cmd.md index fa0915c1a..d37cfdaba 100644 --- a/doc/ref/chap-builtin-cmd.md +++ b/doc/ref/chap-builtin-cmd.md @@ -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. @@ -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: @@ -1168,12 +1192,9 @@ these are discouraged. -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 diff --git a/doc/ref/toc-ysh.md b/doc/ref/toc-ysh.md index 3425a1e21..ba121ef74 100644 --- a/doc/ref/toc-ysh.md +++ b/doc/ref/toc-ysh.md @@ -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 diff --git a/doc/ysh-faq.md b/doc/ysh-faq.md index 39cccd650..ad167343d 100644 --- a/doc/ysh-faq.md +++ b/doc/ysh-faq.md @@ -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`? diff --git a/frontend/id_kind_def.py b/frontend/id_kind_def.py index 21a9a8e52..02726b76d 100755 --- a/frontend/id_kind_def.py +++ b/frontend/id_kind_def.py @@ -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: @@ -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', '='), @@ -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) diff --git a/osh/sh_expr_eval.py b/osh/sh_expr_eval.py index be93ac5b8..1ccbfac23 100644 --- a/osh/sh_expr_eval.py +++ b/osh/sh_expr_eval.py @@ -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 diff --git a/spec/ysh-builtins.test.sh b/spec/ysh-builtins.test.sh index 7511cc5b2..1fbfc32b6 100644 --- a/spec/ysh-builtins.test.sh +++ b/spec/ysh-builtins.test.sh @@ -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