diff --git a/packages/script/README.md b/packages/script/README.md index 38fde1d4..60bfd209 100644 --- a/packages/script/README.md +++ b/packages/script/README.md @@ -256,7 +256,7 @@ they're interned values available everywhere: |--------|---------| | `:redo` | Re-enter the current multiplexer (after reading more data) | | `:skip` | Skip the current item, continue to next | -| `:else` | Fallback key in multiplexers — fires when no other key matches | +| `:else` | Fallback key in multiplexers — fires when no other key matches and no more output arrives within `fast` seconds; its value acts like any key's (`:redo` keeps listening, `null` moves on) | | `:this` | In a multiplexer value, return the matched text itself | | `:pure` | Raw mode — no line terminator appended to sends | diff --git a/packages/script/script.rip b/packages/script/script.rip index efc7ec77..34daede6 100644 --- a/packages/script/script.rip +++ b/packages/script/script.rip @@ -199,15 +199,16 @@ class Engine else if typeof val is 'function' back = invoke! val, back if Array.isArray(back) - if back.length > 0 and back[0] is :pure + reply = back + if reply.length > 0 and reply[0] is :pure savedLine = @line @line = '' - back = @chat!(null, ...back.slice(1)) unless back.length is 1 + back = @chat!(null, ...reply.slice(1)) unless reply.length is 1 @line = savedLine - back = :redo if back.length <= 2 + back = :redo if reply.length <= 2 else - back = @chat!(null, ...back) unless back.length is 0 - back = :redo if back.length <= 1 + back = @chat!(null, ...reply) unless reply.length is 0 + back = :redo if reply.length <= 1 back else if val instanceof Map or (typeof val is 'object' and val isnt null and not Array.isArray(val)) @chat! val @@ -330,10 +331,9 @@ class Engine matched = true talk = false back = null - else if fast and elseVal? + else if fast and hasElse matched = true - back = :else - @dispatchVal! elseVal, back + back = @dispatchVal! elseVal, :else else for [key, val] in entries continue if key is :else or key is 'else' diff --git a/packages/script/test.rip b/packages/script/test.rip index b5f782ab..9e938bfc 100644 --- a/packages/script/test.rip +++ b/packages/script/test.rip @@ -420,7 +420,44 @@ test! ":else fires when nothing matches within the fast window", -> } ] eq hit, 'else' - ok back is :else + eq back, true + +# The shell commands below spell their prompts through printf's %s, so +# the PTY's echo of the command line never contains the prompt text. +TWO_QUESTIONS = "printf 'Q%s? ' 1; read a; SLEEP printf 'Q%s? ' 2; read b; printf '[%s|%s]%s%s\\n' \"$a\" \"$b\" EN D" + +test! "a callback's one-item reply re-enters the multiplexer, whatever its length", -> + withChat! (chat) -> + back = chat! [ + /\$\s*/, TWO_QUESTIONS.replace('SLEEP ', '') + { + 'Q1? ': -> ['abc'] + 'Q2? ': -> [:pure, "xyz\n"] + ']END': :this + } + ] + ok back.endsWith('[abc|xyz]END') + +test! ":else: :redo keeps the multiplexer listening through a pause", -> + withChat! (chat) -> + back = chat! [ + /\$\s*/, TWO_QUESTIONS.replace('SLEEP', 'sleep 1;') + *{ + 'Q1? ': ['abc'] + 'Q2? ': ['xyz'] + ']END': :this + :else: :redo + } + ] + ok back.endsWith('[abc|xyz]END') + +test! ":else: null leaves the multiplexer when nothing matches", -> + withChat! (chat) -> + back = chat! [ + /\$\s*/, 'echo ZZZ' + *{ NOMATCH: 'x', :else: null } + ] + eq back, null test! ":this returns the matched text itself", -> withChat! (chat) ->