mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2024-11-14 21:31:16 +08:00
feat: support convert mrs
format back to text
format
This commit is contained in:
parent
1db3e4583b
commit
c830b8aaf7
@ -57,6 +57,16 @@ func (set *IpCidrSet) Merge() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (set *IpCidrSet) Foreach(f func(prefix netip.Prefix) bool) {
|
||||||
|
for _, r := range set.rr {
|
||||||
|
for _, prefix := range r.Prefixes() {
|
||||||
|
if !f(prefix) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ToIPSet not safe convert to *netipx.IPSet
|
// ToIPSet not safe convert to *netipx.IPSet
|
||||||
// be careful, must be used after Merge
|
// be careful, must be used after Merge
|
||||||
func (set *IpCidrSet) ToIPSet() *netipx.IPSet {
|
func (set *IpCidrSet) ToIPSet() *netipx.IPSet {
|
||||||
|
@ -123,16 +123,18 @@ func (t *DomainTrie[T]) Optimize() {
|
|||||||
t.root.optimize()
|
t.root.optimize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DomainTrie[T]) Foreach(print func(domain string, data T)) {
|
func (t *DomainTrie[T]) Foreach(fn func(domain string, data T) bool) {
|
||||||
for key, data := range t.root.getChildren() {
|
for key, data := range t.root.getChildren() {
|
||||||
recursion([]string{key}, data, print)
|
recursion([]string{key}, data, fn)
|
||||||
if data != nil && data.inited {
|
if data != nil && data.inited {
|
||||||
print(joinDomain([]string{key}), data.data)
|
if !fn(joinDomain([]string{key}), data.data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func recursion[T any](items []string, node *Node[T], fn func(domain string, data T)) {
|
func recursion[T any](items []string, node *Node[T], fn func(domain string, data T) bool) bool {
|
||||||
for key, data := range node.getChildren() {
|
for key, data := range node.getChildren() {
|
||||||
newItems := append([]string{key}, items...)
|
newItems := append([]string{key}, items...)
|
||||||
if data != nil && data.inited {
|
if data != nil && data.inited {
|
||||||
@ -140,10 +142,15 @@ func recursion[T any](items []string, node *Node[T], fn func(domain string, data
|
|||||||
if domain[0] == domainStepByte {
|
if domain[0] == domainStepByte {
|
||||||
domain = complexWildcard + domain
|
domain = complexWildcard + domain
|
||||||
}
|
}
|
||||||
fn(domain, data.Data())
|
if !fn(domain, data.Data()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !recursion(newItems, data, fn) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
recursion(newItems, data, fn)
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinDomain(items []string) string {
|
func joinDomain(items []string) string {
|
||||||
|
@ -28,8 +28,9 @@ type qElt struct{ s, e, col int }
|
|||||||
// NewDomainSet creates a new *DomainSet struct, from a DomainTrie.
|
// NewDomainSet creates a new *DomainSet struct, from a DomainTrie.
|
||||||
func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
|
func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
|
||||||
reserveDomains := make([]string, 0)
|
reserveDomains := make([]string, 0)
|
||||||
t.Foreach(func(domain string, data T) {
|
t.Foreach(func(domain string, data T) bool {
|
||||||
reserveDomains = append(reserveDomains, utils.Reverse(domain))
|
reserveDomains = append(reserveDomains, utils.Reverse(domain))
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
// ensure that the same prefix is continuous
|
// ensure that the same prefix is continuous
|
||||||
// and according to the ascending sequence of length
|
// and according to the ascending sequence of length
|
||||||
@ -136,6 +137,41 @@ func (ss *DomainSet) Has(key string) bool {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ss *DomainSet) keys(f func(key string) bool) {
|
||||||
|
var currentKey []byte
|
||||||
|
var traverse func(int, int) bool
|
||||||
|
traverse = func(nodeId, bmIdx int) bool {
|
||||||
|
if getBit(ss.leaves, nodeId) != 0 {
|
||||||
|
if !f(string(currentKey)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; ; bmIdx++ {
|
||||||
|
if getBit(ss.labelBitmap, bmIdx) != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
nextLabel := ss.labels[bmIdx-nodeId]
|
||||||
|
currentKey = append(currentKey, nextLabel)
|
||||||
|
nextNodeId := countZeros(ss.labelBitmap, ss.ranks, bmIdx+1)
|
||||||
|
nextBmIdx := selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nextNodeId-1) + 1
|
||||||
|
if !traverse(nextNodeId, nextBmIdx) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
currentKey = currentKey[:len(currentKey)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(0, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *DomainSet) Foreach(f func(key string) bool) {
|
||||||
|
ss.keys(func(key string) bool {
|
||||||
|
return f(utils.Reverse(key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func setBit(bm *[]uint64, i int, v int) {
|
func setBit(bm *[]uint64, i int, v int) {
|
||||||
for i>>6 >= len(*bm) {
|
for i>>6 >= len(*bm) {
|
||||||
*bm = append(*bm, 0)
|
*bm = append(*bm, 0)
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
package trie_test
|
package trie_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/trie"
|
"github.com/metacubex/mihomo/component/trie"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func testDump(t *testing.T, tree *trie.DomainTrie[struct{}], set *trie.DomainSet) {
|
||||||
|
var dataSrc []string
|
||||||
|
tree.Foreach(func(domain string, data struct{}) bool {
|
||||||
|
dataSrc = append(dataSrc, domain)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
slices.Sort(dataSrc)
|
||||||
|
var dataSet []string
|
||||||
|
set.Foreach(func(key string) bool {
|
||||||
|
dataSet = append(dataSet, key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
slices.Sort(dataSet)
|
||||||
|
assert.Equal(t, dataSrc, dataSet)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDomainSet(t *testing.T) {
|
func TestDomainSet(t *testing.T) {
|
||||||
tree := trie.New[struct{}]()
|
tree := trie.New[struct{}]()
|
||||||
domainSet := []string{
|
domainSet := []string{
|
||||||
@ -33,6 +50,7 @@ func TestDomainSet(t *testing.T) {
|
|||||||
assert.True(t, set.Has("google.com"))
|
assert.True(t, set.Has("google.com"))
|
||||||
assert.False(t, set.Has("qq.com"))
|
assert.False(t, set.Has("qq.com"))
|
||||||
assert.False(t, set.Has("www.baidu.com"))
|
assert.False(t, set.Has("www.baidu.com"))
|
||||||
|
testDump(t, tree, set)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDomainSetComplexWildcard(t *testing.T) {
|
func TestDomainSetComplexWildcard(t *testing.T) {
|
||||||
@ -55,6 +73,7 @@ func TestDomainSetComplexWildcard(t *testing.T) {
|
|||||||
assert.False(t, set.Has("google.com"))
|
assert.False(t, set.Has("google.com"))
|
||||||
assert.True(t, set.Has("www.baidu.com"))
|
assert.True(t, set.Has("www.baidu.com"))
|
||||||
assert.True(t, set.Has("test.test.baidu.com"))
|
assert.True(t, set.Has("test.test.baidu.com"))
|
||||||
|
testDump(t, tree, set)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDomainSetWildcard(t *testing.T) {
|
func TestDomainSetWildcard(t *testing.T) {
|
||||||
@ -82,4 +101,5 @@ func TestDomainSetWildcard(t *testing.T) {
|
|||||||
assert.False(t, set.Has("a.www.google.com"))
|
assert.False(t, set.Has("a.www.google.com"))
|
||||||
assert.False(t, set.Has("test.qq.com"))
|
assert.False(t, set.Has("test.qq.com"))
|
||||||
assert.False(t, set.Has("test.test.test.qq.com"))
|
assert.False(t, set.Has("test.test.test.qq.com"))
|
||||||
|
testDump(t, tree, set)
|
||||||
}
|
}
|
||||||
|
@ -121,8 +121,9 @@ func TestTrie_Foreach(t *testing.T) {
|
|||||||
assert.NoError(t, tree.Insert(domain, localIP))
|
assert.NoError(t, tree.Insert(domain, localIP))
|
||||||
}
|
}
|
||||||
count := 0
|
count := 0
|
||||||
tree.Foreach(func(domain string, data netip.Addr) {
|
tree.Foreach(func(domain string, data netip.Addr) bool {
|
||||||
count++
|
count++
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
assert.Equal(t, 7, count)
|
assert.Equal(t, 7, count)
|
||||||
}
|
}
|
||||||
|
@ -944,10 +944,17 @@ rule-providers:
|
|||||||
type: file
|
type: file
|
||||||
rule3:
|
rule3:
|
||||||
# mrs类型ruleset,目前仅支持domain和ipcidr(即不支持classical),
|
# mrs类型ruleset,目前仅支持domain和ipcidr(即不支持classical),
|
||||||
# behavior=domain,format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换得到
|
#
|
||||||
# behavior=domain,format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换得到
|
# 对于behavior=domain:
|
||||||
# behavior=ipcidr,format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换得到
|
# - format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换到mrs格式
|
||||||
# behavior=ipcidr,format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换得到
|
# - format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换到mrs格式
|
||||||
|
# - XXX.mrs 可以通过"mihomo convert-ruleset domain mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式)
|
||||||
|
#
|
||||||
|
# 对于behavior=ipcidr:
|
||||||
|
# - format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换到mrs格式
|
||||||
|
# - format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换到mrs格式
|
||||||
|
# - XXX.mrs 可以通过"mihomo convert-ruleset ipcidr mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式)
|
||||||
|
#
|
||||||
type: http
|
type: http
|
||||||
url: "url"
|
url: "url"
|
||||||
format: mrs
|
format: mrs
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
P "github.com/metacubex/mihomo/constant/provider"
|
P "github.com/metacubex/mihomo/constant/provider"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type domainStrategy struct {
|
type domainStrategy struct {
|
||||||
@ -78,6 +80,26 @@ func (d *domainStrategy) WriteMrs(w io.Writer) error {
|
|||||||
return d.domainSet.WriteBin(w)
|
return d.domainSet.WriteBin(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *domainStrategy) DumpMrs(f func(key string) bool) {
|
||||||
|
if d.domainSet != nil {
|
||||||
|
var keys []string
|
||||||
|
d.domainSet.Foreach(func(key string) bool {
|
||||||
|
keys = append(keys, key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
slices.Sort(keys)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
if _, ok := slices.BinarySearch(keys, "+."+key); ok {
|
||||||
|
continue // ignore the rules added by trie internal processing
|
||||||
|
}
|
||||||
|
if !f(key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var _ mrsRuleStrategy = (*domainStrategy)(nil)
|
var _ mrsRuleStrategy = (*domainStrategy)(nil)
|
||||||
|
|
||||||
func NewDomainStrategy() *domainStrategy {
|
func NewDomainStrategy() *domainStrategy {
|
||||||
|
@ -3,6 +3,7 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/cidr"
|
"github.com/metacubex/mihomo/component/cidr"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
@ -82,6 +83,14 @@ func (i *ipcidrStrategy) WriteMrs(w io.Writer) error {
|
|||||||
return i.cidrSet.WriteBin(w)
|
return i.cidrSet.WriteBin(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *ipcidrStrategy) DumpMrs(f func(key string) bool) {
|
||||||
|
if i.cidrSet != nil {
|
||||||
|
i.cidrSet.Foreach(func(prefix netip.Prefix) bool {
|
||||||
|
return f(prefix.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i *ipcidrStrategy) ToIpCidr() *netipx.IPSet {
|
func (i *ipcidrStrategy) ToIpCidr() *netipx.IPSet {
|
||||||
return i.cidrSet.ToIPSet()
|
return i.cidrSet.ToIPSet()
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@ -21,6 +22,17 @@ func ConvertToMrs(buf []byte, behavior P.RuleBehavior, format P.RuleFormat, w io
|
|||||||
return errors.New("empty rule")
|
return errors.New("empty rule")
|
||||||
}
|
}
|
||||||
if _strategy, ok := strategy.(mrsRuleStrategy); ok {
|
if _strategy, ok := strategy.(mrsRuleStrategy); ok {
|
||||||
|
if format == P.MrsRule { // export to TextRule
|
||||||
|
_strategy.DumpMrs(func(key string) bool {
|
||||||
|
_, err = fmt.Fprintln(w, key)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var encoder *zstd.Encoder
|
var encoder *zstd.Encoder
|
||||||
encoder, err = zstd.NewWriter(w)
|
encoder, err = zstd.NewWriter(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,6 +58,7 @@ type mrsRuleStrategy interface {
|
|||||||
ruleStrategy
|
ruleStrategy
|
||||||
FromMrs(r io.Reader, count int) error
|
FromMrs(r io.Reader, count int) error
|
||||||
WriteMrs(w io.Writer) error
|
WriteMrs(w io.Writer) error
|
||||||
|
DumpMrs(f func(key string) bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *ruleSetProvider) Type() P.ProviderType {
|
func (rp *ruleSetProvider) Type() P.ProviderType {
|
||||||
|
Loading…
Reference in New Issue
Block a user