# 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")

Copyright © 2007-2009 Bruce Frederiksen

Page last modified Fri, Mar 05 2010.