summaryrefslogtreecommitdiffstats
path: root/modules/base
diff options
context:
space:
mode:
Diffstat (limited to 'modules/base')
-rw-r--r--modules/base/natural_sort.go89
-rw-r--r--modules/base/natural_sort_test.go24
2 files changed, 113 insertions, 0 deletions
diff --git a/modules/base/natural_sort.go b/modules/base/natural_sort.go
new file mode 100644
index 0000000000..60db363df0
--- /dev/null
+++ b/modules/base/natural_sort.go
@@ -0,0 +1,89 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "math/big"
+ "unicode/utf8"
+)
+
+// NaturalSortLess compares two strings so that they could be sorted in natural order
+func NaturalSortLess(s1, s2 string) bool {
+ var i1, i2 int
+ for {
+ rune1, j1, end1 := getNextRune(s1, i1)
+ rune2, j2, end2 := getNextRune(s2, i2)
+ if end1 || end2 {
+ return end1 != end2 && end1
+ }
+ dec1 := isDecimal(rune1)
+ dec2 := isDecimal(rune2)
+ var less, equal bool
+ if dec1 && dec2 {
+ i1, i2, less, equal = compareByNumbers(s1, i1, s2, i2)
+ } else if !dec1 && !dec2 {
+ equal = rune1 == rune2
+ less = rune1 < rune2
+ i1 = j1
+ i2 = j2
+ } else {
+ return rune1 < rune2
+ }
+ if !equal {
+ return less
+ }
+ }
+}
+
+func getNextRune(str string, pos int) (rune, int, bool) {
+ if pos < len(str) {
+ r, w := utf8.DecodeRuneInString(str[pos:])
+ // Fallback to ascii
+ if r == utf8.RuneError {
+ r = rune(str[pos])
+ w = 1
+ }
+ return r, pos + w, false
+ }
+ return 0, pos, true
+}
+
+func isDecimal(r rune) bool {
+ return '0' <= r && r <= '9'
+}
+
+func compareByNumbers(str1 string, pos1 int, str2 string, pos2 int) (i1, i2 int, less, equal bool) {
+ var d1, d2 bool = true, true
+ var dec1, dec2 string
+ for d1 || d2 {
+ if d1 {
+ r, j, end := getNextRune(str1, pos1)
+ if !end && isDecimal(r) {
+ dec1 += string(r)
+ pos1 = j
+ } else {
+ d1 = false
+ }
+ }
+ if d2 {
+ r, j, end := getNextRune(str2, pos2)
+ if !end && isDecimal(r) {
+ dec2 += string(r)
+ pos2 = j
+ } else {
+ d2 = false
+ }
+ }
+ }
+ less, equal = compareBigNumbers(dec1, dec2)
+ return pos1, pos2, less, equal
+}
+
+func compareBigNumbers(dec1, dec2 string) (less, equal bool) {
+ d1, _ := big.NewInt(0).SetString(dec1, 10)
+ d2, _ := big.NewInt(0).SetString(dec2, 10)
+ cmp := d1.Cmp(d2)
+ return cmp < 0, cmp == 0
+}
diff --git a/modules/base/natural_sort_test.go b/modules/base/natural_sort_test.go
new file mode 100644
index 0000000000..b8d45e4ec0
--- /dev/null
+++ b/modules/base/natural_sort_test.go
@@ -0,0 +1,24 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNaturalSortLess(t *testing.T) {
+ test := func(s1, s2 string, less bool) {
+ assert.Equal(t, less, NaturalSortLess(s1, s2))
+ }
+ test("v1.20.0", "v1.2.0", false)
+ test("v1.20.0", "v1.29.0", true)
+ test("v1.20.0", "v1.20.0", false)
+ test("abc", "bcd", "abc" < "bcd")
+ test("a-1-a", "a-1-b", true)
+ test("2", "12", true)
+ test("a", "ab", "a" < "ab")
+}