.. $Id: special.txt 4670da845e46 2010-03-05 mtnyogi $
.. 
.. Copyright © 2007-2008 Bruce Frederiksen
.. 
.. Permission is hereby granted, free of charge, to any person obtaining a copy
.. of this software and associated documentation files (the "Software"), to deal
.. in the Software without restriction, including without limitation the rights
.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
.. copies of the Software, and to permit persons to whom the Software is
.. furnished to do so, subject to the following conditions:
.. 
.. The above copyright notice and this permission notice shall be included in
.. all copies or substantial portions of the Software.
.. 
.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
.. THE SOFTWARE.

restindex
    crumb: Special
    page-description:
        Explanation of the ``special`` knowledge base.
    /description
    format: rest
    encoding: utf8
    output-encoding: utf8
    include: yes
    initialheaderlevel: 2
/restindex

uservalues
    filedate: $Id: special.txt 4670da845e46 2010-03-05 mtnyogi $
/uservalues

=======
Special
=======

There is only one instance of this 'special' knowledge base, called
``special``.

The ``special`` knowledge base is a collection of miscellaneous helper
*knowledge entities* that determine whether a statement is true or not in
various interesting ways.

Thus, each entity in this `knowledge base`_ is a Python function that does
something "special" when run.

The ``special`` functions are:

  - claim_goal_
  - check_command_
  - command_
  - general_command_

Claim_goal
==========

The ``claim_goal`` function has no arguments::

    special.claim_goal()

This acts like the Prolog_ `cut operator`_.

In general there are multiple rules_ that might be used to try to prove_ any
goal_.  They are each tried in the order that they appear in the `.krb file`_.
If one rule fails, the next rule is tried.  The goal itself doesn't fail
until all of the rules for it have failed.

Example
-------

Suppose I want to translate a number, N, into the phrase "N dogs".  I could
use the following rules::

    one_dog
        use n_dogs(1, '1 dog')

    n_dogs
        use n_dogs($n, $phrase)
        when
            $phrase = "%d dogs" % $n

The problem here is that both rules might be used when ``n`` is 1, but the
second rule isn't appropriate in this case.  ``Special.claim_goal()`` may
be used to fix this, as follows::

    one_dog
        use n_dogs(1, '1 dog')
        when
            special.claim_goal()

    n_dogs
        use n_dogs($n, $phrase)
        when
            $phrase = "%d dogs" % $n

The ``special.claim_goal()`` prevents the second rule from being used when
``n`` is 1.

Explanation
-----------

When a rule_ executes ``special.claim_goal()`` in its ``when``
clause, none of the rest of the rules will be tried for that goal_.
Thus, when ``special.claim_goal()`` is backtracked_ over, the goal fails
immediately without trying any more rules for it.

This ends up acting like an "else".  You place it in the ``when`` clause 
after the premises_ that show that this rule **must** be the correct one
to use.  Then the subsequent rules will only be tried if these premises
fail, such that ``special.claim_goal()`` is never executed.

This means that you don't need to add extra premises in each subsequent rule
to make sure that these premises have **not** occurred.

Without the ``special.claim_goal()`` in the prior example, you would have to
write::

    one_dog
        use n_dogs(1, '1 dog')

    n_dogs
        use n_dogs($n, $phrase)
        when
            check $n != 1
            $phrase = "%d dogs" % $n

This is a simple example where it is easy to add the check in the second
rule.  But in general, it can be difficult to check for prior conditions,
especially when many rules are involved that each has its own condition.

Running Commands
================

The remaining three functions deal with running programs (commands) on the
host computer that is running your Pyke program.  Their difference is in what
kind of output they provide from the command.

These functions all use the `subprocess.Popen`_ function from the standard
Python library.

Thus, each of these functions accept these three parameters that are passed
on to subprocess.Popen:

    - The ``$command`` parameter (required).
    
      - This is a tuple indicating the program to run along with its command
        line arguments, such as ``(ls, '-l')``.

    - The ``$cwd`` parameter (optional).
    
      - This specifies the *current working directory* to run the command in.
      - If omitted or ``None`` the current working directory is not changed.

    - The ``$stdin`` parameter (optional).
    
      - This is a string that is fed to the command as its stdin.

        - If the command expects multiple lines of input, this string must
          include embedded newlines (e.g., ``'line 1\nline 2\n'``).

      - If omitted or ``None``, no stdin is provided to the command.

All of these functions fail on backtracking_.

Check_command
-------------

::

    special.check_command($command [, $cwd [, $stdin]])

Succeeds if the command returns a zero exit status.  Fails otherwise.  Any
output from the command to stdout or stderr is unavailable.

    >>> from pyke import knowledge_engine
    >>> engine = knowledge_engine.engine()
    >>> engine.prove_1_goal('special.check_command((true))')
    ({}, None)
    >>> engine.prove_1_goal('special.check_command((false))')
    Traceback (most recent call last):
        ...
    CanNotProve: Can not prove special.check_command((false))

Command
-------

::

    special.command($stdout, $command [, $cwd [, $stdin]])

This just looks at the stdout of the command.  Any output from the command
to stderr is unavailable.

The ``$stdout`` is a tuple of lines with the trailing newlines removed.

This raises `subprocess.CalledProcessError`_ if the command returns a non-zero
exit status.

    >>> from __future__ import with_statement
    >>> from pyke import pattern, contexts
    >>> def run_command(entity, command, cwd=None, stdin=None):
    ...     with engine.prove_goal(
    ...            'special.%s($output, $command, $cwd, $stdin)' % entity,
    ...            command=command,
    ...            cwd=cwd,
    ...            stdin=stdin) \
    ...       as gen:
    ...         for vars, no_plan in gen:
    ...             print vars['output']
    >>> run_command('command', ('echo', 'hi', 'mom'))
    ('hi mom',)
    >>> run_command('command', ('ls',))   # doctest: +NORMALIZE_WHITESPACE
    ('fact_bases.txt', 'index.txt', 'links', 'question_bases.txt',
     'rule_bases.txt', 'special.txt')
    >>> run_command('command', ('ls', '-l', 'links')) # doctest: +ELLIPSIS
    ('-rw-r--r-- 1 ... links',)
    >>> run_command('command', ('tail', '-n', '5', 'template.txt', '-'),
    ...             '..',   # cwd (doc/source)
    ...             'stdin: line 1\nstdin: line 2\nstdin: line 3\n')
    ...     # doctest: +NORMALIZE_WHITESPACE
    ('==> template.txt <==',
     '   } catch(err) {}',
     '  </script>',
     '</body>',
     '</html>',
     '',
     '',
     '==> standard input <==',
     'stdin: line 1',
     'stdin: line 2',
     'stdin: line 3')
    >>> run_command('command', ('false',))
    Traceback (most recent call last):
        ...
    CalledProcessError: Command 'false' returned non-zero exit status 1

General_command
---------------

::

    special.general_command($output, $command [, $cwd [, $stdin]])

This is the fully general form that gives you all output from the command.

The ``$output`` is a three tuple: (exit_status, stdout, stderr).  Both stdout
and stderr are single strings (with embedded newlines).

    >>> run_command('general_command', ('echo', 'hi', 'mom'))
    (0, 'hi mom\n', '')
    >>> run_command('general_command', ('cat', 'foobar'))
    (1, '', 'cat: foobar: No such file or directory\n')
    >>> run_command('general_command', ('tail', '-n', '5', '../../r2w.ini', 'foobar'))
    ...     # doctest: +NORMALIZE_WHITESPACE
    (1,
     "==> ../../r2w.ini <==\ntarget_directory = 'html'\nmacros =
         ''\n\n[uservalues]\nversion = '0.2'\n",
     "tail: cannot open `foobar' for reading: No such file or directory\n")


