Apply a command on the specified table.

    table:
        Name of the table.

    command:
        Command to apply to the table. Supported commands are:

        - add
        - delete
        - expire
        - flush
        - kill
        - replace
        - show
        - test
        - zero

        Please refer to the OpenBSD `pfctl(8) <https://man.openbsd.org/pfctl#T>`_
        documentation for a detailed explanation of each command.

    CLI example:

    .. code-block:: bash

        salt '*' pf.table expire table=spam_hosts number=300
        salt '*' pf.table add table=local_hosts addresses='["127.0.0.1", "::1"]'

def table(command, table, **kwargs):
    '''
    Apply a command on the specified table.

    table:
        Name of the table.

    command:
        Command to apply to the table. Supported commands are:

        - add
        - delete
        - expire
        - flush
        - kill
        - replace
        - show
        - test
        - zero

        Please refer to the OpenBSD `pfctl(8) <https://man.openbsd.org/pfctl#T>`_
        documentation for a detailed explanation of each command.

    CLI example:

    .. code-block:: bash

        salt '*' pf.table expire table=spam_hosts number=300
        salt '*' pf.table add table=local_hosts addresses='["127.0.0.1", "::1"]'
    '''
    ret = {}

    all_commands = ['kill', 'flush', 'add', 'delete', 'expire', 'replace', 'show', 'test', 'zero']
    if command not in all_commands:
        raise SaltInvocationError('Unknown table command: {0}'.format(command))

    cmd = ['pfctl', '-t', table, '-T', command]

    if command in ['add', 'delete', 'replace', 'test']:
        cmd += kwargs.get('addresses', [])
    elif command == 'expire':
        number = kwargs.get('number', None)
        if not number:
            raise SaltInvocationError('need expire_number argument for expire command')
        else:
            cmd.append(number)

    result = __salt__['cmd.run_all'](cmd,
                                     output_level='trace',
                                     python_shell=False)

    if result['retcode'] == 0:
        if command == 'show':
            ret = {'comment': result['stdout'].split()}
        elif command == 'test':
            ret = {'comment': result['stderr'], 'matches': True}
        else:
            if re.match(r'^(0.*|no changes)', result['stderr']):
                ret['changes'] = False
            else:
                ret['changes'] = True

            ret['comment'] = result['stderr']
    else:
        # 'test' returns a non-zero code if the address didn't match, even if
        # the command itself ran fine; also set 'matches' to False since not
        # everything matched.
        if command == 'test' and re.match(r'^\d+/\d+ addresses match.$', result['stderr']):
            ret = {'comment': result['stderr'], 'matches': False}
        else:
            raise CommandExecutionError(
                'Could not apply {0} on table {1}'.format(command, table),
                info={'errors': [result['stderr']], 'changes': False}
            )

    return ret