package web import ( "sort" "strings" "gitea.kazu634.com/kazu634/tver/internal/store" ) func sortJobsNaturally(jobs []store.Job) { sort.SliceStable(jobs, func(i, j int) bool { if comparison := compareNaturalTitle(jobs[i].Title, jobs[j].Title); comparison != 0 { return comparison < 0 } left, right := jobs[i].FinishedAt, jobs[j].FinishedAt if left == nil || right == nil { if left != right { return left == nil } } else if !left.Equal(*right) { return left.Before(*right) } return jobs[i].ID < jobs[j].ID }) } func compareNaturalTitle(left, right string) int { a, b := []rune(left), []rune(right) ai, bi := 0, 0 for ai < len(a) && bi < len(b) { _, aIsDigit := decimalDigit(a[ai]) _, bIsDigit := decimalDigit(b[bi]) if aIsDigit && bIsDigit { var an, bn strings.Builder for ai < len(a) { digit, ok := decimalDigit(a[ai]) if !ok { break } an.WriteByte(byte('0' + digit)) ai++ } for bi < len(b) { digit, ok := decimalDigit(b[bi]) if !ok { break } bn.WriteByte(byte('0' + digit)) bi++ } if comparison := compareDecimal(an.String(), bn.String()); comparison != 0 { return comparison } continue } if a[ai] < b[bi] { return -1 } if a[ai] > b[bi] { return 1 } ai++ bi++ } if ai == len(a) && bi < len(b) { return -1 } if bi == len(b) && ai < len(a) { return 1 } return 0 } func decimalDigit(r rune) (int, bool) { switch { case '0' <= r && r <= '9': return int(r - '0'), true case '0' <= r && r <= '9': return int(r - '0'), true default: return 0, false } } func compareDecimal(left, right string) int { left = strings.TrimLeft(left, "0") right = strings.TrimLeft(right, "0") if left == "" { left = "0" } if right == "" { right = "0" } if len(left) < len(right) { return -1 } if len(left) > len(right) { return 1 } return strings.Compare(left, right) }