.. include:: ../global.rst .. idio:currentmodule:: expect expect Functions ---------------- .. _`expect/spawn`: .. idio:function:: expect/spawn [argv] Spawn a process defined by `argv` :param argv: command and arguments :type argv: list :return: :ref:`struct-spawn ` :rtype: struct-instance :raises ^system-error: ``spawn`` will set and return :ref:`spawn-id `. .. tip:: ``spawn-id`` defaults to ``#f`` however if you set it to ``#n`` or a list of spawn-id(s) then ``spawn`` will prepend any new value to the list. .. _`expect/exp-case`: .. idio:function:: expect/exp-case [clauses] define-template: (exp-case & clauses) (x e) ``exp-case`` looks to find the first match from the clauses: * passed to :ref:`exp-case-before `, if any * passed to ``exp-case`` itself, if any * passed to :ref:`exp-case-after `, if any ``exp-case`` operates on the current value of :ref:`spawn-id ` (no such value can be passed directly as ``exp-case`` is a template). Each clause in `clauses` takes one of the forms :samp:`([:str-kw]* {string} {body})` :samp:`(:term-kw {body})` Here, :samp:`:str-kw` is one of the string keywords: * ``:re`` indicating that :samp:`{string}` should be used for a regular expression match * ``:gl`` indicating that :samp:`{string}` should be used for a glob-style pattern match (the default) -- see :ref:`regex-pattern-string ` for how the string is modified * ``:ex`` indicating that :samp:`{string}` should be used for an exact match -- see :ref:`regex-exact-string ` for how the string is modified * ``:icase`` indicates that a case-insensitive match should be used :samp:`:term-kw` is one of the terminal keywords: * ``:eof`` for matching the End of File indicator .. note:: If ``spawn-id`` is a list then :samp:`{body}` will be called for each spawn-id for which the condition is true. If ``spawn-id`` is a list then it is inadvisable to call ``(exp-continue)`` in the :samp:`{body}` of an ``:eof`` clause as that will prevent the invocation of any other extant End of File notifications. See also the End of File note below. * ``:timeout`` for matching a time out .. note:: ``:timeout`` will be true for all spawn-ids if `spawn-id` is a list. The :samp:`{body}` will be called with the value of `spawn-id` (which could be a list or a struct instance). * ``:all`` to match everything in the buffer, ie. in effect, to empty the buffer .. note:: ``:all`` always matches and therefore will not read any more data from the spawned process. If ``spawn-id`` is a list then for each spawn-id in the list the entire buffer will be matched and the :samp:`{body}` called. :samp:`{body}` will be invoked as though the body of a function with the following arguments: * for a successful string match, including ``:all``, the arguments are ``spawn-id``, ``r``, the result of the match as per :ref:`regexec `, and ``prefix``, the contents of the `spawn-id`'s buffer before the match * for a successful terminal match, excluding ``:all``, the argument is ``spawn-id`` :samp:`{body}` can invoke the function ``(exp-continue)`` to loop around again. :samp:`{body}` can invoke the function :samp:`(exp-break [{value}])` to exit the loop immediately. Passing no clauses will still attempt to match using any existing clauses from :ref:`exp-case-before ` or :ref:`exp-case-after `. If a clause matches, ``exp-case`` returns the value from :samp:`{body}`. If no clauses match or all spawn-ids have indicated End of File, or a timeout has occurred and there is no ``:timeout`` clause ``exp-case`` returns ``#f``. .. admonition:: End of File If the spawned process indicates End of File then the master file descriptor is generally closed although this is not guaranteed. Call :ref:`exp-wait ` to clean up both file descriptors and spawned processes. .. note:: All (supported) operating systems can use :manpage:`poll(2)`. However, some tested operating systems (Mac OS 10.5.8) return ``POLLNVAL`` for (pseudo-terminal) devices. In this case, the code reverts to the uses of :manpage:`select(2)` with any associated limits (notably, ``FD_SETSIZE``). :Example: From the top of the :lname:`Idio` distribution you might try: .. code-block:: idio import expect spawn ls -1 ;; minus one ! (expect-case (:re "NG[.]" { printf ":re '%s' => %s\n" prefix r (exp-continue) }) ("doc?" { printf ":gl '%s' => %s\n" prefix r (exp-continue) }) (:icase "EXT?" { printf ":gl '%s' => %s\n" prefix r (exp-continue) }) (:ex "NSE." { printf ":ex '%s' => %s\n" prefix r (exp-continue) }) (:eof { printf ":eof\n" }) (:timeout { printf ":timeout\n" })) to get: .. code-block:: console :re 'bin CONTRIBUTI' => #[ ("NG." 15 18) ] :gl 'md ' => #[ ("doc\r" 4 8) ] :gl ' ' => #[ ("ext\r" 1 5) ] :ex ' lib LICENSE LICE' => #[ ("NSE." 19 23) ] :eof .. _`expect/exp-continue`: .. idio:function:: expect/exp-continue [value] define-template: (exp-continue & args) (x e) ``exp-continue`` stops processing the current :ref:`exp-case ` `clauses` and starts the next iteration of the loop. `value` is ignored and set to ``#`` if not supplied. .. warning:: ``exp-continue`` ignores any protection blocks set up by :ref:`unwind-protect ` or :ref:`dynamic-wind `. .. _`expect/exp-break`: .. idio:function:: expect/exp-break [value] define-template: (exp-break & args) (x e) ``exp-break`` returns `value` from the enclosing :ref:`exp-case ` or ``#`` if no `value` is supplied. .. warning:: ``exp-break`` ignores any protection blocks set up by :ref:`unwind-protect ` or :ref:`dynamic-wind `. .. _`expect/exp-case-before`: .. idio:function:: expect/exp-case-before [clauses] define-template: (exp-case-before & clauses) (x e) ``exp-case-before`` establishes match clauses identically to :ref:`exp-case ` except the `clauses` are tested *before* those passed in ``exp-case``. Invoking ``exp-case-before`` doesn't actually perform any matching, you must still call ``exp-case``. Passing no clauses effectively unsets this behaviour. :Example: .. code-block:: idio import expect spawn echo foo (exp-case-before ("foo" { 'before })) (exp-case ("foo" { 'normally })) to have ``exp-case`` return ``before``. .. _`expect/exp-case-after`: .. idio:function:: expect/exp-case-after [clauses] define-template: (exp-case-after & clauses) (x e) ``exp-case-after`` establishes match clauses identically to :ref:`exp-case ` except the `clauses` are tested *after* those passed in ``exp-case``. Invoking ``exp-case-after`` doesn't actually perform any matching, you must still call ``exp-case``. Passing no clauses effectively unsets this behaviour. :Example: .. code-block:: idio import expect spawn echo foo (exp-case-after ("foo" { 'after })) (exp-case ("bar" { 'bar })) to have ``exp-case`` return ``after``. .. _`expect/exp-close`: .. idio:function:: expect/exp-close (:spawn-id spawn-id) close the master file descriptor to the spawned process :keyword :spawn-id: the spawn-id(s) to use, defaults to :ref:`spawn-id ` :type :spawn-id: :ref:`expect/struct-spawn `, optional :return: ``#`` ``exp-close`` will also set the `mfd` structure element to ``#f`` `spawn-id` can be a :ref:`struct-spawn ` or a list of such ``struct-spawn`` s. .. _`expect/exp-log-file`: .. idio:function:: expect/exp-log-file file (:spawn-id spawn-id) (:append #t) Log the send/expect dialog for `spawn-id` to `file` or not :param file: the file to log to :type file: see below :keyword :spawn-id: the spawn-id(s) to use, defaults to :ref:`spawn-id ` :type :spawn-id: :ref:`expect/struct-spawn `, optional :keyword :append: to append or not, defaults to ``#t`` :type :append: boolean, optional :return: ``#`` `file` can be: * ``#f`` to disable logging to a file If the existing log file was passed as a string, ie. it was opened by this module, then the associated log file handle will be closed. If this log file was shared with other spawn-ids then this closure will affect all of them. Both `log-file` and `lfh` will be set to ``#f`` in all cases. * a string, indicating the filename to log to * an output handle * a file descriptor (open for output) `spawn-id` can be a :ref:`struct-spawn ` or a list of such ``struct-spawn`` s. .. _`expect/exp-log-user`: .. idio:function:: expect/exp-log-user on? Log the send/expect dialog to the user (ie. `stdout`) or not :param on?: to log or not :type on?: boolean :return: ``#`` The default is to have logging to the user enabled. .. _`expect/exp-send`: .. idio:function:: expect/exp-send msg (:slow #f) (:human #f) (:spawn-id spawn-id) (:cr #f) send `msg` to the spawned process :param msg: the string to send :type msg: string :keyword :slow: send `msg` slowly, defaults to ``#f`` :type :slow: boolean, optional :keyword :human: send `msg` humanly, defaults to ``#f`` :type :human: boolean, optional :keyword :spawn-id: the spawn-id to use, defaults to :ref:`spawn-id ` :type :spawn-id: :ref:`expect/struct-spawn `, optional :keyword :cr: to send a 'carriage return' or not, defaults to ``#f`` :type :cr: boolean, optional :return: ``#`` `:slow` says to use the value of :ref:`exp-slow ` which is a tuple of the number of code points in a burst prefixed by a delay of a number of milliseconds. The default is ``(600 50)`` -- 600 code points and a 50 ms delay -- roughly equivalent to a 115200 baud modem (if the code points were ASCII). `:human` says to use the value of :ref:`exp-human ` which is a tuple of: * the average gap between in-word code points * the average gap transitioning from an in-word code point to a non-word code point * a moderating factor, K: "tiredness" might be represented by a K < 1 and preternaturally consistent typing by a K > 1 * the minimum inter-code point gap * the maximum inter-code point gap The default is ``(180 240 1 45 360)`` roughly equivalent to a 60 wpm typist. The algorithm used to calculate the inter-code point gap is the inverse cumulative distribution function of the Weibull distribution. The same as Don Libes' :manpage:`expect(1)`. `:human` is preferred to `:slow` if both are supplied. `:spawn-id` can be passed a C/int representing an open (for output) file descriptor which will be used instead. For example, to send output to the user pass ``libc/STDOUT_FILENO``. :Example: Suppose we wanted to observe a professional typist attempting a well-known English `pangram `_: .. code-block:: idio exp-send "The quick brown fox jumps over the lazy dog" :spawn-id libc/STDOUT_FILENO :human #t .. _`expect/exp-send-human`: .. idio:function:: expect/exp-send-human fd msg send `msg` slowly as if a human was typing :param fd: file descriptor :type fd: C/int :param msg: message to send :type msg: string :return: ``#`` ``exp-send-human`` uses a similar algorithm to :manpage:`expect(1)` .. seealso:: :ref:`exp-send ` for details .. _`expect/exp-wait`: .. idio:function:: expect/exp-wait (:spawn-id spawn-id) (:close #t) wait for the spawned process :keyword :spawn-id: the spawn-id(s) to use, defaults to :ref:`spawn-id ` :type :spawn-id: :ref:`expect/struct-spawn ` :keyword :close: close the `mfd`, defaults to ``#t`` :type :close: boolean :return: see below :rtype: list ``exp-wait`` will call :ref:`exp-close ` if `:close` is `true` and ``exp-close`` has not already been called. Otherwise ``exp-wait`` will read data from the spawned process until End of File (or some ^system-error) is indicated. .. warning:: Neither option is ideal if the spawned process has not completed its processing * pro-actively closing the `mfd` of a running program will probably have the operating system send the spawned process a hangup signal This is most likely to result in a failed process state (and presumes the spawned process does not ignore or otherwise handle a ``SIGHUP``). * on the other hand, *not* closing the `mfd` might have the spawned process block waiting for full output buffers to be emptied, hence reading from the spawned process until End of File is indicated However, if the spawned process is waiting for some input before writing any final output then the spawned process will also block. ``exp-wait`` will return a list of the result from :ref:`waitpid ` plus a decoding of the status, eg. ``(exit 0)`` or ``(killed 1)`` (see tip). `spawn-id` can be a :ref:`struct-spawn ` or a list of such ``struct-spawn`` s. If `spawn-id` is a list the value returned will be a list of individual result lists. .. tip:: The per-`spawn-id` result is also stored in the ``struct-spawn`` structure as the `status` field. .. _`expect/exp-spawn-sync`: .. idio:function:: expect/exp-spawn-sync [spawn-id [timeout]] wait for the spawned processes to be "ready" :param spawn-id: the spawn-id(s) to use, defaults to :ref:`spawn-id ` :type spawn-id: a list of :ref:`expect/struct-spawn ` :param timeout: timeout per spawn-id (in seconds), defaults to :ref:`exp-timeout ` :type timeout: integer :return: unspecified :rtype: unspecified If you spawn multiple processes on a transiently/mechanically under-resourced system where a subsequent :ref:`exp-case ` is intended to test patterns against all processes it is quite possible that one or more of the spawned processes will not (yet) have been scheduled to run by the operating system before the ``exp-case`` starts. :manpage:`poll(2)`/:manpage:`select(2)` will quite happily return with what is available from those processes that have been scheduled leaving the possibility of missing data from not yet ready processes. ``exp-spawn-sync`` will test each of the spawn-ids in `spawn-id` in turn for a response which will give the operating system an opportunity to schedule the process in. The response could be that the is output available to be read, there is no output or an error has occurred -- however, at least a response is ready. There should be no need to use ``exp-spawn-sync`` for single-instance spawn-ids as the following ``exp-case`` provides the same functionality implicitly. ``exp-spawn-sync`` is only of use where multiple spawn-ids are expected to be tested against simultaneously. .. _`expect/exp-set-winsize`: .. idio:function:: expect/exp-set-winsize [spawn-id [lines [columns]]] set the terminal's window size :param spawn-id: the spawn-id to be set, default :ref:`spawn-id ` :type spawn-id: :ref:`struct-spawn `, optional :param lines: terminal lines, default that of Idio's terminal, if available :type lines: fixnum|C/int :param columns: terminal columns, default that of Idio's terminal, if available :type columns: fixnum|C/int :return: ``#`` .. include:: ../commit.rst