mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 08:32:16 +01:00
114 lines
2.7 KiB
Go
114 lines
2.7 KiB
Go
package gotenberg
|
|
|
|
import (
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
)
|
|
|
|
type numberLoc int
|
|
|
|
const (
|
|
numberNone numberLoc = iota
|
|
numberPrefix
|
|
numberExtSuffix // number right before extension.
|
|
numberSuffix // trailing number with no extension.
|
|
)
|
|
|
|
// AlphanumericSort implements sort.Interface and helps to sort strings
|
|
// alphanumerically by either a numeric prefix or, if missing, a numeric
|
|
// suffix.
|
|
//
|
|
// See:
|
|
// https://github.com/gotenberg/gotenberg/issues/805.
|
|
// https://github.com/gotenberg/gotenberg/issues/1287.
|
|
type AlphanumericSort []string
|
|
|
|
func (s AlphanumericSort) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s AlphanumericSort) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
func (s AlphanumericSort) Less(i, j int) bool {
|
|
numI, restI, locI := extractNumber(s[i])
|
|
numJ, restJ, locJ := extractNumber(s[j])
|
|
|
|
// Both have a number.
|
|
if numI != -1 && numJ != -1 {
|
|
// Both prefix numbers: numeric first, then rest.
|
|
if locI == numberPrefix && locJ == numberPrefix {
|
|
if numI != numJ {
|
|
return numI < numJ
|
|
}
|
|
return restI < restJ
|
|
}
|
|
|
|
// Both are suffix-ish (right-before-ext or trailing): rest first, then
|
|
// number.
|
|
if locI != numberPrefix && locJ != numberPrefix {
|
|
if restI != restJ {
|
|
return restI < restJ
|
|
}
|
|
if numI != numJ {
|
|
return numI < numJ
|
|
}
|
|
return s[i] < s[j]
|
|
}
|
|
|
|
// Mixed: one prefix, one not.
|
|
if restI != restJ {
|
|
return restI < restJ
|
|
}
|
|
return locI == numberPrefix
|
|
}
|
|
|
|
// One has a number: it comes first.
|
|
if numI != -1 {
|
|
return true
|
|
}
|
|
if numJ != -1 {
|
|
return false
|
|
}
|
|
|
|
// Neither has a number: plain lexicographic.
|
|
return s[i] < s[j]
|
|
}
|
|
|
|
func extractNumber(str string) (int, string, numberLoc) {
|
|
str = filepath.Base(str)
|
|
|
|
if matches := prefixRegexp.FindStringSubmatch(str); len(matches) > 2 {
|
|
if num, err := strconv.Atoi(matches[1]); err == nil {
|
|
return num, matches[2], numberPrefix
|
|
}
|
|
}
|
|
if matches := extensionSuffixRegexp.FindStringSubmatch(str); len(matches) > 3 {
|
|
if num, err := strconv.Atoi(matches[2]); err == nil {
|
|
return num, matches[1] + matches[3], numberExtSuffix
|
|
}
|
|
}
|
|
if matches := suffixRegexp.FindStringSubmatch(str); len(matches) > 2 {
|
|
if num, err := strconv.Atoi(matches[2]); err == nil {
|
|
return num, matches[1], numberSuffix
|
|
}
|
|
}
|
|
return -1, str, numberNone
|
|
}
|
|
|
|
// Regular expressions used by extractNumber.
|
|
var (
|
|
// Matches a numeric prefix: one or more digits at the start.
|
|
prefixRegexp = regexp.MustCompile(`^(\d+)(.*)$`)
|
|
// Matches a numeric block immediately before a file extension.
|
|
extensionSuffixRegexp = regexp.MustCompile(`^(.*?)(\d+)(\.[^.]+)$`)
|
|
// Matches a trailing numeric sequence when there is no extension.
|
|
suffixRegexp = regexp.MustCompile(`^(.*?)(\d+)$`)
|
|
)
|
|
|
|
// Interface guard.
|
|
var _ sort.Interface = (*AlphanumericSort)(nil)
|