diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml index c5378d2..f023f61 100644 --- a/.github/workflows/debug.yml +++ b/.github/workflows/debug.yml @@ -30,7 +30,7 @@ jobs: uses: actions/setup-go@v2 with: go-version: ${{ steps.version.outputs.go_version }} - - name: Build + - name: Add cache to Go proxy run: | version=`git rev-parse HEAD` mkdir build @@ -38,4 +38,9 @@ jobs: go mod init build go get -v github.com/sagernet/sing@$version popd - go build -v ./... \ No newline at end of file + continue-on-error: true + - name: Build + run: | + make test + make lint_install + make lint \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index b81d894..5c439f7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,14 +3,14 @@ linters: enable: - gofumpt - govet - - gci +# - gci - staticcheck linters-settings: - gci: - sections: - - standard - - prefix(github.com/sagernet/) - - default +# gci: +# sections: +# - standard +# - prefix(github.com/sagernet/) +# - default staticcheck: - go: '1.18' + go: '1.19' diff --git a/Makefile b/Makefile index a2950e2..6f7c6f1 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ fmt: - gofumpt -l -w . - gofmt -s -w . - gci write -s "standard,prefix(github.com/sagernet/),default" . + @gofumpt -l -w . + @gofmt -s -w . + @gci write -s "standard,prefix(github.com/sagernet/),default" . -lint_install: - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +fmt_install: + go install -v mvdan.cc/gofumpt@latest + go install -v github.com/daixiang0/gci@v0.4.0 lint: GOOS=linux golangci-lint run ./... @@ -12,10 +13,8 @@ lint: GOOS=darwin golangci-lint run ./... GOOS=freebsd golangci-lint run ./... -test: - go test -v . +lint_install: + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest -update: - git fetch - git reset FETCH_HEAD --hard - git clean -fdx \ No newline at end of file +test: + go test -v ./... \ No newline at end of file diff --git a/common/ranges/range.go b/common/ranges/range.go new file mode 100644 index 0000000..28fdf24 --- /dev/null +++ b/common/ranges/range.go @@ -0,0 +1,112 @@ +package ranges + +import ( + "sort" + + "github.com/sagernet/sing/common/x/constraints" +) + +type Range[N comparable] struct { + Start N + End N +} + +func New[N constraints.Integer](start N, end N) Range[N] { + return Range[N]{start, end} +} + +func NewSingle[N constraints.Integer](index N) Range[N] { + return Range[N]{index, index} +} + +func Merge[N constraints.Integer](ranges []Range[N]) (mergedRanges []Range[N]) { + if len(ranges) == 0 { + return + } + sort.Slice(ranges, func(i, j int) bool { + return ranges[i].Start < ranges[j].Start + }) + mergedRanges = ranges[:1] + var rangeIndex N + for _, r := range ranges[1:] { + if r.Start > mergedRanges[rangeIndex].End+1 { + mergedRanges = append(mergedRanges, r) + rangeIndex++ + } else if r.End > mergedRanges[rangeIndex].End { + mergedRanges[rangeIndex].End = r.End + } + } + return +} + +func Revert[N constraints.Integer](start, end N, ranges []Range[N]) (revertedRanges []Range[N]) { + if len(ranges) == 0 { + return + } + ranges = Merge(ranges) + if ranges[0].Start > start { + revertedRanges = append(revertedRanges, Range[N]{start, ranges[0].Start - 1}) + } + rangeEnd := ranges[0].End + for _, r := range ranges[1:] { + if r.Start > rangeEnd+1 { + revertedRanges = append(revertedRanges, Range[N]{rangeEnd + 1, r.Start - 1}) + } + rangeEnd = r.End + } + if end > rangeEnd { + revertedRanges = append(revertedRanges, Range[N]{rangeEnd + 1, end}) + } + return +} + +func Exclude[N constraints.Integer](ranges []Range[N], targetRanges []Range[N]) []Range[N] { + ranges = Merge(ranges) + if len(ranges) == 0 { + return nil + } + targetRanges = Merge(targetRanges) + if len(targetRanges) == 0 { + return ranges + } + var mergedRanges []Range[N] + rangeStart := ranges[0].Start + rangeEnd := ranges[0].End + rangeIndex := rangeStart + ranges = ranges[1:] + targetStart := targetRanges[0].Start + targetEnd := targetRanges[0].End + targetRanges = targetRanges[1:] + for { + if targetStart > rangeEnd { + if rangeIndex <= rangeEnd { + mergedRanges = append(mergedRanges, Range[N]{rangeIndex, rangeEnd}) + } + if len(ranges) == 0 { + break + } + rangeStart = ranges[0].Start + rangeEnd = ranges[0].End + rangeIndex = rangeStart + ranges = ranges[1:] + continue + } + if targetStart > rangeIndex { + mergedRanges = append(mergedRanges, Range[N]{rangeIndex, targetStart - 1}) + rangeIndex = targetStart + 1 + } + if targetEnd <= rangeEnd { + rangeIndex = targetEnd + 1 + if len(targetRanges) == 0 { + break + } + targetStart = targetRanges[0].Start + targetEnd = targetRanges[0].End + targetRanges = targetRanges[1:] + } + } + if rangeIndex <= rangeEnd { + mergedRanges = append(mergedRanges, Range[N]{rangeIndex, rangeEnd}) + } + return Merge(append(mergedRanges, ranges...)) +} diff --git a/common/ranges/range_test.go b/common/ranges/range_test.go new file mode 100644 index 0000000..d4dad35 --- /dev/null +++ b/common/ranges/range_test.go @@ -0,0 +1,194 @@ +package ranges + +import ( + "reflect" + "testing" +) + +func TestRevertRanges(t *testing.T) { + for _, testRange := range []struct { + start, end int + ranges []Range[int] + expected []Range[int] + }{ + { + start: 0, + end: 10, + ranges: []Range[int]{ + {0, 1}, + }, + expected: []Range[int]{ + {2, 10}, + }, + }, + { + start: 0, + end: 10, + ranges: []Range[int]{ + {9, 10}, + }, + expected: []Range[int]{ + {0, 8}, + }, + }, + { + start: 0, + end: 10, + ranges: []Range[int]{ + {0, 1}, + {9, 10}, + }, + expected: []Range[int]{ + {2, 8}, + }, + }, + { + start: 0, + end: 10, + ranges: []Range[int]{ + {2, 4}, + {6, 8}, + }, + expected: []Range[int]{ + {0, 1}, + {5, 5}, + {9, 10}, + }, + }, + { + start: 0, + end: 10, + ranges: []Range[int]{ + {2, 4}, + {8, 9}, + }, + expected: []Range[int]{ + {0, 1}, + {5, 7}, + {10, 10}, + }, + }, + } { + result := Revert(testRange.start, testRange.end, testRange.ranges) + if !reflect.DeepEqual(result, testRange.expected) { + t.Fatal("expected", testRange.expected, "\ngot", result) + } + } +} + +func TestMergeRanges(t *testing.T) { + for _, testRange := range []struct { + ranges []Range[int] + expected []Range[int] + }{ + { + ranges: []Range[int]{ + {0, 1}, + {1, 2}, + }, + expected: []Range[int]{ + {0, 2}, + }, + }, + { + ranges: []Range[int]{ + {0, 3}, + {5, 7}, + {8, 9}, + {10, 10}, + }, + expected: []Range[int]{ + {0, 3}, + {5, 10}, + }, + }, + { + ranges: []Range[int]{ + {1, 3}, + {2, 6}, + {8, 10}, + {15, 18}, + }, + expected: []Range[int]{ + {1, 6}, + {8, 10}, + {15, 18}, + }, + }, + { + ranges: []Range[int]{ + {1, 3}, + {2, 7}, + {2, 6}, + }, + expected: []Range[int]{ + {1, 7}, + }, + }, + { + ranges: []Range[int]{ + {1, 3}, + {2, 6}, + {2, 7}, + }, + expected: []Range[int]{ + {1, 7}, + }, + }, + } { + result := Merge(testRange.ranges) + if !reflect.DeepEqual(result, testRange.expected) { + t.Fatal("input", testRange.ranges, "\nexpected", testRange.expected, "\ngot", result) + } + } +} + +func TestExcludeRanges(t *testing.T) { + for _, testRange := range []struct { + ranges []Range[int] + exclude []Range[int] + expected []Range[int] + }{ + { + ranges: []Range[int]{ + {0, 100}, + }, + exclude: []Range[int]{ + {0, 10}, + {20, 30}, + {55, 55}, + }, + expected: []Range[int]{ + {11, 19}, + {31, 54}, + {56, 100}, + }, + }, + { + ranges: []Range[int]{ + {0, 100}, + {200, 300}, + }, + exclude: []Range[int]{ + {0, 10}, + {20, 30}, + {55, 55}, + {250, 250}, + {299, 299}, + }, + expected: []Range[int]{ + {11, 19}, + {31, 54}, + {56, 100}, + {200, 249}, + {251, 298}, + {300, 300}, + }, + }, + } { + result := Exclude(testRange.ranges, testRange.exclude) + if !reflect.DeepEqual(result, testRange.expected) { + t.Fatal("input", testRange.ranges, testRange.exclude, "\nexpected", testRange.expected, "\ngot", result) + } + } +}