def build_rule(table='filter', chain=None, command=None, position='', full=None, family='ipv4',
               **kwargs):
    if 'target' in kwargs:
        kwargs['jump'] = kwargs.pop('target')

    # Ignore name and state for this function
    kwargs.pop('name', None)
    kwargs.pop('state', None)

    for ignore in list(_STATE_INTERNAL_KEYWORDS) + ['chain', 'save', 'table']:
        if ignore in kwargs:
            del kwargs[ignore]

    rule = []
    proto = False
    bang_not_pat = re.compile(r'(!|not)\s?')

    def maybe_add_negation(arg):
        '''
        Will check if the defined argument is intended to be negated,
        (i.e. prefixed with '!' or 'not'), and add a '! ' to the rule.

        The prefix will be removed from the value in the kwargs dict.
        '''
        value = str(kwargs[arg])
        if value.startswith('!') or value.startswith('not'):
            kwargs[arg] = re.sub(bang_not_pat, '', value)
            return '! '
        return ''

    if 'if' in kwargs:
        rule.append('{0}-i {1}'.format(maybe_add_negation('if'), kwargs['if']))
        del kwargs['if']

    if 'of' in kwargs:
        rule.append('{0}-o {1}'.format(maybe_add_negation('of'), kwargs['of']))
        del kwargs['of']

    for proto_arg in ('protocol', 'proto'):
        if proto_arg in kwargs:
            if not proto:
                rule.append('{0}-p {1}'.format(maybe_add_negation(proto_arg), kwargs[proto_arg]))
                proto = True
            del kwargs[proto_arg]

    if 'match' in kwargs:
        match_value = kwargs['match']
        if not isinstance(match_value, list):
            match_value = match_value.split(',')
        for match in match_value:
            rule.append('-m {0}'.format(match))
            if 'name' in kwargs and match.strip() in ('pknock', 'quota2', 'recent'):
                rule.append('--name {0}'.format(kwargs['name']))
        del kwargs['match']

    if 'match-set' in kwargs:
        if isinstance(kwargs['match-set'], six.string_types):
            kwargs['match-set'] = [kwargs['match-set']]
        for match_set in kwargs['match-set']:
            negative_match_set = ''
            if match_set.startswith('!') or match_set.startswith('not'):
                negative_match_set = '! '
                match_set = re.sub(bang_not_pat, '', match_set)
            rule.append('-m set {0}--match-set {1}'.format(negative_match_set, match_set))
        del kwargs['match-set']

    if 'connstate' in kwargs:
        if '-m state' not in rule:
            rule.append('-m state')

        rule.append('{0}--state {1}'.format(maybe_add_negation('connstate'), kwargs['connstate']))

        del kwargs['connstate']

    if 'dport' in kwargs:
        rule.append('{0}--dport {1}'.format(maybe_add_negation('dport'), kwargs['dport']))
        del kwargs['dport']

    if 'sport' in kwargs:
        rule.append('{0}--sport {1}'.format(maybe_add_negation('sport'), kwargs['sport']))
        del kwargs['sport']

    for multiport_arg in ('dports', 'sports'):
        if multiport_arg in kwargs:
            if '-m multiport' not in rule:
                rule.append('-m multiport')
                if not proto:
                    return 'Error: proto must be specified'

            mp_value = kwargs[multiport_arg]
            if isinstance(mp_value, list):
                if any(i for i in mp_value if str(i).startswith('!') or str(i).startswith('not')):
                    mp_value = [re.sub(bang_not_pat, '', str(item)) for item in mp_value]
                    rule.append('!')
                dports = ','.join(str(i) for i in mp_value)
            else:
                if str(mp_value).startswith('!') or str(mp_value).startswith('not'):
                    dports = re.sub(bang_not_pat, '', mp_value)
                    rule.append('!')
                else:
                    dports = mp_value

            rule.append('--{0} {1}'.format(multiport_arg, dports))
            del kwargs[multiport_arg]

    if 'comment' in kwargs:
        if '-m comment' not in rule:
            rule.append('-m comment')

        rule.append('--comment "{0}"'.format(kwargs['comment']))
        del kwargs['comment']

    # --set in ipset is deprecated, works but returns error.
    # rewrite to --match-set if not empty, otherwise treat as recent option
    if 'set' in kwargs and kwargs['set']:
        rule.append('{0}--match-set {1}'.format(maybe_add_negation('set'), kwargs['set']))
        del kwargs['set']

    # Jumps should appear last, except for any arguments that are passed to
    # jumps, which of course need to follow.
    after_jump = []
    # All jump arguments as extracted from man iptables-extensions, man iptables,
    # man xtables-addons and http://www.iptables.info/en/iptables-targets-and-jumps.html
    after_jump_arguments = (
        'j',  # j and jump needs to be first
        'jump',

        # IPTABLES
        'add-set',
        'and-mark',
        'and-tos',
        'checksum-fill',
        'clamp-mss-to-pmtu',
        'clustermac',
        'ctevents',
        'ctmask',
        'del-set',
        'ecn-tcp-remove',
        'exist',
        'expevents',
        'gateway',
        'hash-init',
        'hashmode',
        'helper',
        'label',
        'local-node',
        'log-ip-options',
        'log-level',
        'log-prefix',
        'log-tcp-options',
        'log-tcp-sequence',
        'log-uid',
        'mask',
        'new',
        'nfmask',
        'nflog-group',
        'nflog-prefix',
        'nflog-range',
        'nflog-threshold',
        'nodst',
        'notrack',
        'on-ip',
        'on-port',
        'or-mark',
        'or-tos',
        'persistent',
        'queue-balance',
        'queue-bypass',
        'queue-num',
        'random',
        'rateest-ewmalog',
        'rateest-interval',
        'rateest-name',
        'reject-with',
        'restore',
        'restore-mark',
        #'save',  # no arg, problematic name: How do we avoid collision with this?
        'save-mark',
        'selctx',
        'set-class',
        'set-dscp',
        'set-dscp-class',
        'set-mark',
        'set-mss',
        'set-tos',
        'set-xmark',
        'strip-options',
        'timeout',
        'to',
        'to-destination',
        'to-ports',
        'to-source',
        'total-nodes',
        'tproxy-mark',
        'ttl-dec',
        'ttl-inc',
        'ttl-set',
        'type',
        'ulog-cprange',
        'ulog-nlgroup',
        'ulog-prefix',
        'ulog-qthreshold',
        'xor-mark',
        'xor-tos',
        'zone',

        # IPTABLES-EXTENSIONS
        'dst-pfx',
        'hl-dec',
        'hl-inc',
        'hl-set',
        'hmark-dport-mask',
        'hmark-dst-prefix',
        'hmark-mod',
        'hmark-offset',
        'hmark-proto-mask',
        'hmark-rnd',
        'hmark-spi-mask',
        'hmark-sport-mask',
        'hmark-src-prefix',
        'hmark-tuple',
        'led-always-blink',
        'led-delay',
        'led-trigger-id',
        'queue-cpu-fanout',
        'src-pfx',

        # WEB
        'to-port',

        # XTABLES
        'addr',
        'and-mask',
        'delude',
        'honeypot',
        'or-mask',
        'prefix',
        'reset',
        'reuse',
        'set-mac',
        'shift',
        'static',
        'tarpit',
        'tname',
        'ttl',
    )
    for after_jump_argument in after_jump_arguments:
        if after_jump_argument in kwargs:
            value = kwargs[after_jump_argument]
            if any(ws_char in str(value) for ws_char in string.whitespace):
                after_jump.append('--{0} "{1}"'.format(after_jump_argument, value))
            else:
                after_jump.append('--{0} {1}'.format(after_jump_argument, value))
            del kwargs[after_jump_argument]

    for item in kwargs:
        rule.append(maybe_add_negation(item))
        if len(item) == 1:
            rule.append('-{0} {1}'.format(item, kwargs[item]))
        else:
            rule.append('--{0} {1}'.format(item, kwargs[item]))

    rule += after_jump

    if full in ['True', 'true']:
        if not table:
            return 'Error: Table needs to be specified'
        if not chain:
            return 'Error: Chain needs to be specified'
        if not command:
            return 'Error: Command needs to be specified'

        if command in 'ACDIRLSFZNXPE':
            flag = '-'
        else:
            flag = '--'

        wait = '--wait' if _has_option('--wait', family) else ''

        return '{0} {1} -t {2} {3}{4} {5} {6} {7}'.format(_iptables_cmd(family),
               wait, table, flag, command, chain, position, ' '.join(rule))

    return ' '.join(rule)
@todo: docstring
dataset/ETHPy150Open saltstack/salt/salt/modules/iptables.py/build_rule--crossover with--dataset/ETHPy150Open sahana/eden/modules/s3db/survey.py/survey_MatrixElement.nextX