Build a well-formatted nftables rule based on kwargs.
    A `table` and `chain` are not required, unless `full` is True.

    If `full` is `True`, then `table`, `chain` and `command` are required.
    `command` may be specified as either insert, append, or delete.
    This will return the nftables command, exactly as it would
    be used from the command line.

    If a position is required (as with `insert` or `delete`), it may be specified as
    `position`. This will only be useful if `full` is True.

    If `connstate` is passed in, it will automatically be changed to `state`.

    CLI Examples:

    .. code-block:: bash

        salt '*' nftables.build_rule match=state \\
            connstate=RELATED,ESTABLISHED jump=ACCEPT
        salt '*' nftables.build_rule filter input command=insert position=3 \\
            full=True match=state state=related,established jump=accept

        IPv6:
        salt '*' nftables.build_rule match=state \\
            connstate=related,established jump=accept \\
            family=ipv6
        salt '*' nftables.build_rule filter input command=insert position=3 \\
            full=True match=state state=related,established jump=accept \\
            family=ipv6

def build_rule(table=None, chain=None, command=None, position='', full=None, family='ipv4',
               **kwargs):
    '''
    Build a well-formatted nftables rule based on kwargs.
    A `table` and `chain` are not required, unless `full` is True.

    If `full` is `True`, then `table`, `chain` and `command` are required.
    `command` may be specified as either insert, append, or delete.
    This will return the nftables command, exactly as it would
    be used from the command line.

    If a position is required (as with `insert` or `delete`), it may be specified as
    `position`. This will only be useful if `full` is True.

    If `connstate` is passed in, it will automatically be changed to `state`.

    CLI Examples:

    .. code-block:: bash

        salt '*' nftables.build_rule match=state \\
            connstate=RELATED,ESTABLISHED jump=ACCEPT
        salt '*' nftables.build_rule filter input command=insert position=3 \\
            full=True match=state state=related,established jump=accept

        IPv6:
        salt '*' nftables.build_rule match=state \\
            connstate=related,established jump=accept \\
            family=ipv6
        salt '*' nftables.build_rule filter input command=insert position=3 \\
            full=True match=state state=related,established jump=accept \\
            family=ipv6

    '''
    ret = {'comment': '',
           'rule': '',
           'result': False}

    if 'target' in kwargs:
        kwargs['jump'] = kwargs['target']
        del kwargs['target']

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

    rule = ''
    proto = ''

    nft_family = _NFTABLES_FAMILIES[family]

    if 'if' in kwargs:
        rule += 'meta iifname {0} '.format(kwargs['if'])
        del kwargs['if']

    if 'of' in kwargs:
        rule += 'meta oifname {0} '.format(kwargs['of'])
        del kwargs['of']

    if 'proto' in kwargs:
        proto = kwargs['proto']

    if 'state' in kwargs:
        del kwargs['state']

    if 'connstate' in kwargs:
        rule += 'ct state {{ {0}}} '.format(kwargs['connstate'])
        del kwargs['connstate']

    if 'dport' in kwargs:
        kwargs['dport'] = six.text_type(kwargs['dport'])
        if ':' in kwargs['dport']:
            kwargs['dport'] = kwargs['dport'].replace(':', '-')
        rule += 'dport {{ {0} }} '.format(kwargs['dport'])
        del kwargs['dport']

    if 'sport' in kwargs:
        kwargs['sport'] = six.text_type(kwargs['sport'])
        if ':' in kwargs['sport']:
            kwargs['sport'] = kwargs['sport'].replace(':', '-')
        rule += 'sport {{ {0} }} '.format(kwargs['sport'])
        del kwargs['sport']

    if 'dports' in kwargs:
        # nftables reverse sorts the ports from
        # high to low, create rule like this
        # so that the check will work
        _dports = kwargs['dports'].split(',')
        _dports = [int(x) for x in _dports]
        _dports.sort(reverse=True)
        kwargs['dports'] = ', '.join(six.text_type(x) for x in _dports)

        rule += 'dport {{ {0} }} '.format(kwargs['dports'])
        del kwargs['dports']

    if 'sports' in kwargs:
        # nftables reverse sorts the ports from
        # high to low, create rule like this
        # so that the check will work
        _sports = kwargs['sports'].split(',')
        _sports = [int(x) for x in _sports]
        _sports.sort(reverse=True)
        kwargs['sports'] = ', '.join(six.text_type(x) for x in _sports)

        rule += 'sport {{ {0} }} '.format(kwargs['sports'])
        del kwargs['sports']

    # Jumps should appear last, except for any arguments that are passed to
    # jumps, which of course need to follow.
    after_jump = []

    if 'jump' in kwargs:
        after_jump.append('{0} '.format(kwargs['jump']))
        del kwargs['jump']

    if 'j' in kwargs:
        after_jump.append('{0} '.format(kwargs['j']))
        del kwargs['j']

    if 'to-port' in kwargs:
        after_jump.append('--to-port {0} '.format(kwargs['to-port']))
        del kwargs['to-port']

    if 'to-ports' in kwargs:
        after_jump.append('--to-ports {0} '.format(kwargs['to-ports']))
        del kwargs['to-ports']

    if 'to-destination' in kwargs:
        after_jump.append('--to-destination {0} '.format(kwargs['to-destination']))
        del kwargs['to-destination']

    if 'reject-with' in kwargs:
        after_jump.append('--reject-with {0} '.format(kwargs['reject-with']))
        del kwargs['reject-with']

    for item in after_jump:
        rule += item

    # Strip trailing spaces off rule
    rule = rule.strip()

    # Insert the protocol prior to dport or sport
    rule = rule.replace('dport', '{0} dport'.format(proto))
    rule = rule.replace('sport', '{0} sport'.format(proto))

    ret['rule'] = rule

    if full in ['True', 'true']:

        if not table:
            ret['comment'] = 'Table needs to be specified'
            return ret

        if not chain:
            ret['comment'] = 'Chain needs to be specified'
            return ret

        if not command:
            ret['comment'] = 'Command needs to be specified'
            return ret

        if command in ['Insert', 'insert', 'INSERT']:
            if position:
                ret['rule'] = '{0} insert rule {1} {2} {3} ' \
                              'position {4} {5}'.format(_nftables_cmd(),
                                                        nft_family,
                                                        table,
                                                        chain,
                                                        position,
                                                        rule)
            else:
                ret['rule'] = '{0} insert rule ' \
                              '{1} {2} {3} {4}'.format(_nftables_cmd(),
                                                       nft_family,
                                                       table,
                                                       chain,
                                                       rule)
        else:
            ret['rule'] = '{0} {1} rule {2} {3} {4} {5}'.format(_nftables_cmd(),
                                                                command,
                                                                nft_family,
                                                                table,
                                                                chain,
                                                                rule)

    if ret['rule']:
        ret['comment'] = 'Successfully built rule'
    ret['result'] = True
    return ret